Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fixedpoint/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

const Fix64Scale = 8
const Fix64Factor = 100_000_000
const Fix64FactorSqrt = 10_000

// Fix64

Expand Down
44 changes: 44 additions & 0 deletions runtime/interpreter/big.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"math/big"

"github.com/onflow/cadence/runtime/errors"
"github.com/onflow/cadence/runtime/sema"
)

func SignedBigIntToBigEndianBytes(bigInt *big.Int) []byte {
Expand Down Expand Up @@ -140,3 +141,46 @@ func BigEndianBytesToSignedBigInt(b []byte) *big.Int {
func BigEndianBytesToUnsignedBigInt(b []byte) *big.Int {
return new(big.Int).SetBytes(b)
}

func BigIntSqrt(interpreter *Interpreter, value *big.Int, locationRange LocationRange) UFix64Value {
if value.Sign() < 0 {
panic(UnderflowError{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the overflow case where the result can't fit into the target type, this case corresponds to an "undefined" case rather than an underflow. The square root being non defined for negative inputs (similar to the division by zero error where dividing by zero isn't mathematically defined).

There doesn't seem to be an "undefined" error available so I see why underflow was used, but I wanted to mention this.

LocationRange: locationRange,
})
}

if value.Cmp(sema.MaxSquareIntegerBig) == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When calling this check in the case of Fix64Value and UFix64Value, I think we would exclude some values because value is larger than the max, but in reality it represents a number less than max (because value is multiplied by the factor). Actually, I think all Fix64 and UFix64 values are less than the max, so the result (square root) can always fit.

If I didn't miss anything and my comment makes sense, it may be better to have a specific function BigIntSqrtFloat to process the case of UFix64 and Fix64 skipping the overflow (also to add the optimization from my previous comment)

panic(OverflowError{
LocationRange: locationRange,
})
}

// Once we reach here, Cadence integer values are guaranteed to fit into
// floating-point values with 256 bit precision _without_ truncation.
// This is because of the above check with sema.MaxSquareIntegerBig.
valueFloat := new(big.Float).SetPrec(256).SetInt(value)
res := new(big.Float).SetPrec(256).SetMode(big.ToZero).Sqrt(valueFloat)
res.Mul(res, new(big.Float).SetPrec(256).SetInt(sema.Fix64FactorBig))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new(big.Float).SetPrec(256).SetInt(Fix64FactorBig) could be a global constant defined like Fix64FactorBig.


// Converting the result to a fixed-point number, we are conceptually converting it to an integer
// IEEE 754 specifies different rounding modes https: //en.wikipedia.org/wiki/IEEE_754#Rounding_rules
// We follow the "Rationale for International Standard -- Programming Languages -- C", Revision 5.10, April-2003:
// > Section 6.3.1.5 Real floating types:
// > When a finite value of real floating type is converted to an integer type other than Bool,
// > the fractional part is discarded (i.e., the value is truncated toward zero). If the value
// > of the integral part cannot be represented by the integer type, the behavior is undefined.
// For details, see
// https: //wiki.sei.cmu.edu/confluence/display/c/FLP34-C.+Ensure+that+floating-point+conversions+are+within+range+of+the+new+type
resInt := new(big.Int)
res.Int(resInt)
if !resInt.IsUint64() {
// We checked for overflow above, so we shouldn't hit this.
panic(errors.NewUnreachableError())
}

valueGetter := func() uint64 {
return resInt.Uint64()
}

return NewUFix64Value(interpreter, valueGetter)
}
126 changes: 126 additions & 0 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/rivo/uniseg"
"golang.org/x/text/unicode/norm"

"github.com/onflow/cadence/fixedpoint"
"github.com/onflow/cadence/runtime/ast"
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/common/orderedmap"
Expand Down Expand Up @@ -3405,6 +3406,7 @@ type NumberValue interface {
Div(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue
SaturatingDiv(interpreter *Interpreter, other NumberValue, locationRange LocationRange) NumberValue
ToBigEndianBytes() []byte
Sqrt(*Interpreter, LocationRange) UFix64Value
}

func getNumberValueMember(interpreter *Interpreter, v NumberValue, name string, typ sema.Type, locationRange LocationRange) Value {
Expand Down Expand Up @@ -3841,6 +3843,11 @@ func (v IntValue) SaturatingDiv(interpreter *Interpreter, other NumberValue, loc
return v.Div(interpreter, other, locationRange)
}

func (v IntValue) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v IntValue) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(IntValue)
if !ok {
Expand Down Expand Up @@ -4498,6 +4505,12 @@ func (v Int8Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, lo
return NewInt8Value(interpreter, valueGetter)
}

func (v Int8Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetInt64(int64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int8Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int8Value)
if !ok {
Expand Down Expand Up @@ -5138,6 +5151,12 @@ func (v Int16Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, l
return NewInt16Value(interpreter, valueGetter)
}

func (v Int16Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetInt64(int64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int16Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int16Value)
if !ok {
Expand Down Expand Up @@ -5781,6 +5800,12 @@ func (v Int32Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, l
return NewInt32Value(interpreter, valueGetter)
}

func (v Int32Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetInt64(int64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int32Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int32Value)
if !ok {
Expand Down Expand Up @@ -6423,6 +6448,12 @@ func (v Int64Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, l
return NewInt64Value(interpreter, valueGetter)
}

func (v Int64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetInt64(int64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int64Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int64Value)
if !ok {
Expand Down Expand Up @@ -7120,6 +7151,11 @@ func (v Int128Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return NewInt128ValueFromBigInt(interpreter, valueGetter)
}

func (v Int128Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int128Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int128Value)
if !ok {
Expand Down Expand Up @@ -7862,6 +7898,11 @@ func (v Int256Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return NewInt256ValueFromBigInt(interpreter, valueGetter)
}

func (v Int256Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Int256Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Int256Value)
if !ok {
Expand Down Expand Up @@ -8514,6 +8555,11 @@ func (v UIntValue) SaturatingDiv(interpreter *Interpreter, other NumberValue, lo
return v.Div(interpreter, other, locationRange)
}

func (v UIntValue) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UIntValue) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UIntValue)
if !ok {
Expand Down Expand Up @@ -9078,6 +9124,12 @@ func (v UInt8Value) SaturatingDiv(interpreter *Interpreter, other NumberValue, l
return v.Div(interpreter, other, locationRange)
}

func (v UInt8Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt8Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt8Value)
if !ok {
Expand Down Expand Up @@ -9669,6 +9721,12 @@ func (v UInt16Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return v.Div(interpreter, other, locationRange)
}

func (v UInt16Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt16Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt16Value)
if !ok {
Expand Down Expand Up @@ -10211,6 +10269,12 @@ func (v UInt32Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return v.Div(interpreter, other, locationRange)
}

func (v UInt32Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt32Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt32Value)
if !ok {
Expand Down Expand Up @@ -10782,6 +10846,11 @@ func (v UInt64Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return v.Div(interpreter, other, locationRange)
}

func (v UInt64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt64Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt64Value)
if !ok {
Expand Down Expand Up @@ -11400,6 +11469,11 @@ func (v UInt128Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return v.Div(interpreter, other, locationRange)
}

func (v UInt128Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt128Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt128Value)
if !ok {
Expand Down Expand Up @@ -12077,6 +12151,11 @@ func (v UInt256Value) SaturatingDiv(interpreter *Interpreter, other NumberValue,
return v.Div(interpreter, other, locationRange)
}

func (v UInt256Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v UInt256Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UInt256Value)
if !ok {
Expand Down Expand Up @@ -12581,6 +12660,12 @@ func (v Word8Value) SaturatingDiv(*Interpreter, NumberValue, LocationRange) Numb
panic(errors.NewUnreachableError())
}

func (v Word8Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word8Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word8Value)
if !ok {
Expand Down Expand Up @@ -13017,6 +13102,12 @@ func (v Word16Value) SaturatingDiv(*Interpreter, NumberValue, LocationRange) Num
panic(errors.NewUnreachableError())
}

func (v Word16Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word16Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word16Value)
if !ok {
Expand Down Expand Up @@ -13456,6 +13547,12 @@ func (v Word32Value) SaturatingDiv(*Interpreter, NumberValue, LocationRange) Num
panic(errors.NewUnreachableError())
}

func (v Word32Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int)
val.SetUint64(uint64(v))
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word32Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word32Value)
if !ok {
Expand Down Expand Up @@ -13921,6 +14018,11 @@ func (v Word64Value) SaturatingDiv(*Interpreter, NumberValue, LocationRange) Num
panic(errors.NewUnreachableError())
}

func (v Word64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word64Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word64Value)
if !ok {
Expand Down Expand Up @@ -14438,6 +14540,11 @@ func (v Word128Value) SaturatingDiv(_ *Interpreter, _ NumberValue, _ LocationRan
panic(errors.NewUnreachableError())
}

func (v Word128Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word128Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word128Value)
if !ok {
Expand Down Expand Up @@ -15018,6 +15125,11 @@ func (v Word256Value) SaturatingDiv(_ *Interpreter, _ NumberValue, _ LocationRan
panic(errors.NewUnreachableError())
}

func (v Word256Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := v.ToBigInt(interpreter)
return BigIntSqrt(interpreter, val, locationRange)
}

func (v Word256Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Word256Value)
if !ok {
Expand Down Expand Up @@ -15705,6 +15817,13 @@ func (v Fix64Value) Mod(interpreter *Interpreter, other NumberValue, locationRan
)
}

func (v Fix64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int).SetUint64(uint64(v))
sqrtWithFix64FactorSqrt := BigIntSqrt(interpreter, val, locationRange)
sqrt := sqrtWithFix64FactorSqrt / fixedpoint.Fix64FactorSqrt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possible optimization: I think we can save this division if in the function BigIntSqrt (L163), we multiply by Fix64FactorSqrt instead of Fix64Factor, only in the case of Fix64Value and UFix64Value. For the rest of the types, we can keep multiplying by Fix64Factor.

return sqrt
}

func (v Fix64Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(Fix64Value)
if !ok {
Expand Down Expand Up @@ -16228,6 +16347,13 @@ func (v UFix64Value) Mod(interpreter *Interpreter, other NumberValue, locationRa
)
}

func (v UFix64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {
val := new(big.Int).SetUint64(uint64(v))
sqrtWithFix64FactorSqrt := BigIntSqrt(interpreter, val, locationRange)
sqrt := sqrtWithFix64FactorSqrt / fixedpoint.Fix64FactorSqrt
return sqrt
}

func (v UFix64Value) Less(interpreter *Interpreter, other ComparableValue, locationRange LocationRange) BoolValue {
o, ok := other.(UFix64Value)
if !ok {
Expand Down
Loading