Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Sqrt on NumberType #3055

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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 @@
"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 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)
darkdrag00nv2 marked this conversation as resolved.
Show resolved Hide resolved
res := new(big.Float).SetPrec(256).SetMode(big.ToZero).Sqrt(valueFloat)
darkdrag00nv2 marked this conversation as resolved.
Show resolved Hide resolved
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())

Check warning on line 178 in runtime/interpreter/big.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/big.go#L177-L178

Added lines #L177 - L178 were not covered by tests
}

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 @@
"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 @@
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 @@
return v.Div(interpreter, other, locationRange)
}

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

Check warning on line 3849 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L3849

Added line #L3849 was not covered by tests

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 @@
return NewInt8Value(interpreter, valueGetter)
}

func (v Int8Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 4508 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L4508

Added line #L4508 was not covered by tests
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 @@
return NewInt16Value(interpreter, valueGetter)
}

func (v Int16Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 5154 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L5154

Added line #L5154 was not covered by tests
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 @@
return NewInt32Value(interpreter, valueGetter)
}

func (v Int32Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 5803 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L5803

Added line #L5803 was not covered by tests
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 @@
return NewInt64Value(interpreter, valueGetter)
}

func (v Int64Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 6451 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L6451

Added line #L6451 was not covered by tests
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 @@
return NewInt128ValueFromBigInt(interpreter, valueGetter)
}

func (v Int128Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 7154 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L7154

Added line #L7154 was not covered by tests
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 @@
return NewInt256ValueFromBigInt(interpreter, valueGetter)
}

func (v Int256Value) Sqrt(interpreter *Interpreter, locationRange LocationRange) UFix64Value {

Check warning on line 7901 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L7901

Added line #L7901 was not covered by tests
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 @@
return v.Div(interpreter, other, locationRange)
}

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

Check warning on line 8561 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L8561

Added line #L8561 was not covered by tests

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 @@
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)

Check warning on line 9130 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L9130

Added line #L9130 was not covered by tests
}

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 @@
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)

Check warning on line 9727 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L9727

Added line #L9727 was not covered by tests
}

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 @@
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)

Check warning on line 10275 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L10275

Added line #L10275 was not covered by tests
}

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 @@
return v.Div(interpreter, other, locationRange)
}

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

Check warning on line 10852 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L10852

Added line #L10852 was not covered by tests

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 @@
return v.Div(interpreter, other, locationRange)
}

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

Check warning on line 11475 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L11475

Added line #L11475 was not covered by tests

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 @@
return v.Div(interpreter, other, locationRange)
}

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

Check warning on line 12157 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L12157

Added line #L12157 was not covered by tests

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 @@
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)

Check warning on line 12666 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L12665-L12666

Added lines #L12665 - L12666 were not covered by tests
}

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 @@
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)

Check warning on line 13108 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L13107-L13108

Added lines #L13107 - L13108 were not covered by tests
}

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 @@
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)

Check warning on line 13553 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L13552-L13553

Added lines #L13552 - L13553 were not covered by tests
}

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 @@
panic(errors.NewUnreachableError())
}

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

Check warning on line 14024 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L14023-L14024

Added lines #L14023 - L14024 were not covered by tests

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 @@
panic(errors.NewUnreachableError())
}

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

Check warning on line 14546 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L14545-L14546

Added lines #L14545 - L14546 were not covered by tests

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 @@
panic(errors.NewUnreachableError())
}

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

Check warning on line 15131 in runtime/interpreter/value.go

View check run for this annotation

Codecov / codecov/patch

runtime/interpreter/value.go#L15130-L15131

Added lines #L15130 - L15131 were not covered by tests

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) 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) 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
Loading