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
15 changes: 15 additions & 0 deletions bbq/vm/builtin_globals.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ func init() {
registerBuiltinCommonTypeBoundFunctions()

registerBuiltinSaturatingArithmeticFunctions()

registerBuiltinFixedPointPowFunctions()
}

func registerBuiltinCommonTypeBoundFunctions() {
Expand Down Expand Up @@ -421,6 +423,19 @@ func registerBuiltinTypeSaturatingArithmeticFunctions(t sema.SaturatingArithmeti
}
}

func registerBuiltinFixedPointPowFunctions() {
for baseType, funcType := range sema.FixedPointPowFunctionTypes { //nolint:maprange
Comment thread
turbolent marked this conversation as resolved.
registerBuiltinTypeBoundFunction(
commons.TypeQualifier(baseType),
NewNativeFunctionValue(
sema.FixedPointNumericTypePowFunctionName,
funcType,
interpreter.NativeFixedPointPowFunction,
),
)
}
}

func newFromStringFunction(typedParser interpreter.TypedStringValueParser) *NativeFunctionValue {
functionType := sema.FromStringFunctionType(typedParser.ReceiverType)
parser := typedParser.Parser
Expand Down
177 changes: 177 additions & 0 deletions interpreter/fixedpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,183 @@ import (
. "github.com/onflow/cadence/test_utils/sema_utils"
)

func TestInterpretFixedPointPow(t *testing.T) {

t.Parallel()

t.Run("UFix64", func(t *testing.T) {

t.Parallel()

type testCase struct {
base string
exponent string
expected uint64
expectedError bool
}

// Expected values were pre-computed using the fixed-point library's UFix64.Pow(Fix64).
testCases := []testCase{
// Edge cases
{base: "0.00000000", exponent: "0.00000000", expected: 100000000}, // 0^0 = 1
{base: "0.00000000", exponent: "2.00000000", expected: 0}, // 0^2 = 0
{base: "1.00000000", exponent: "0.00000000", expected: 100000000}, // 1^0 = 1
{base: "1.00000000", exponent: "5.00000000", expected: 100000000}, // 1^5 = 1
{base: "2.00000000", exponent: "0.00000000", expected: 100000000}, // 2^0 = 1
{base: "2.00000000", exponent: "1.00000000", expected: 200000000}, // 2^1 = 2

// Integer exponents
{base: "2.00000000", exponent: "3.00000000", expected: 800000000}, // 2^3 = 8
{base: "5.00000000", exponent: "2.00000000", expected: 2500000000}, // 5^2 = 25
{base: "10.00000000", exponent: "3.00000000", expected: 100000000000}, // 10^3 = 1000

// Negative exponents
{base: "2.00000000", exponent: "-1.00000000", expected: 50000000}, // 2^(-1) = 0.5
{base: "4.00000000", exponent: "-1.00000000", expected: 25000000}, // 4^(-1) = 0.25
{base: "10.00000000", exponent: "-2.00000000", expected: 1000000}, // 10^(-2) = 0.01

// Fractional bases
{base: "0.50000000", exponent: "2.00000000", expected: 25000000}, // 0.5^2 = 0.25
{base: "1.50000000", exponent: "2.00000000", expected: 225000000}, // 1.5^2 = 2.25
{base: "0.25000000", exponent: "3.00000000", expected: 1562500}, // 0.25^3 = 0.015625

// Fractional exponents
{base: "4.00000000", exponent: "0.50000000", expected: 200000000}, // 4^0.5 = 2
{base: "9.00000000", exponent: "0.50000000", expected: 300000000}, // 9^0.5 = 3
{base: "8.00000000", exponent: "0.33333333", expected: 199999999}, // 8^(1/3) ≈ 2

// Values from library test data
{base: "0.11111111", exponent: "2.00000000", expected: 1234568}, // (1/9)^2
{base: "0.33333333", exponent: "3.00000000", expected: 3703704}, // (1/3)^3
{base: "2.71828183", exponent: "1.00000000", expected: 271828183}, // e^1
{base: "3.14159265", exponent: "-0.50000000", expected: 56418958}, // pi^(-0.5)
{base: "0.14285714", exponent: "2.00000000", expected: 2040816}, // (1/7)^2
{base: "123.45678901", exponent: "0.50000000", expected: 1111111106}, // 123.45678901^0.5

// Repeating decimal bases with negative exponents
{base: "0.66666666", exponent: "-1.00000000", expected: 150000002}, // (2/3)^(-1)
{base: "0.50000000", exponent: "-2.00000000", expected: 400000000}, // 0.5^(-2) = 4

// Overflow
{base: "429496.72960000", exponent: "2.00000000", expectedError: true}, // sqrt(MaxUFix64)^2 overflows
{base: "10.00000000", exponent: "20.00000000", expectedError: true}, // 10^20 overflows

// Underflow (truncated to 0 by handleFixedpointError)
{base: "0.00000003", exponent: "2.00000000", expected: 0}, // 0.00000003^2 underflows to 0
}

for _, tc := range testCases {

testName := fmt.Sprintf("%s ^ %s", tc.base, tc.exponent)

t.Run(testName, func(t *testing.T) {
t.Parallel()

code := fmt.Sprintf(
`
fun test(): UFix64 {
let base: UFix64 = %s
let exponent: Fix64 = %s
return base.pow(exponent)
}
`,
tc.base,
tc.exponent,
)

inter := parseCheckAndPrepare(t, code)

if tc.expectedError {
_, err := inter.Invoke("test")
require.Error(t, err)
} else {
result, err := inter.Invoke("test")
require.NoError(t, err)

expected := interpreter.NewUnmeteredUFix64Value(tc.expected)
AssertValuesEqual(t, inter, expected, result)
}
})
}
})

t.Run("UFix128", func(t *testing.T) {

t.Parallel()

type testCase struct {
base string
exponent string
expected string
expectedError bool
}

// Expected values were pre-computed using the fixed-point library's UFix128.Pow(Fix128).
testCases := []testCase{
// Edge cases
{base: "0.000000000000000000000000", exponent: "0.000000000000000000000000", expected: "1.000000000000000000000000"}, // 0^0 = 1
{base: "1.000000000000000000000000", exponent: "0.000000000000000000000000", expected: "1.000000000000000000000000"}, // 1^0 = 1
{base: "1.000000000000000000000000", exponent: "5.000000000000000000000000", expected: "1.000000000000000000000000"}, // 1^5 = 1
{base: "2.000000000000000000000000", exponent: "0.000000000000000000000000", expected: "1.000000000000000000000000"}, // 2^0 = 1
{base: "2.000000000000000000000000", exponent: "1.000000000000000000000000", expected: "2.000000000000000000000000"}, // 2^1 = 2

// Integer exponents
{base: "2.000000000000000000000000", exponent: "3.000000000000000000000000", expected: "8.000000000000000000000000"}, // 2^3 = 8
{base: "5.000000000000000000000000", exponent: "2.000000000000000000000000", expected: "25.000000000000000000000000"}, // 5^2 = 25
{base: "10.000000000000000000000000", exponent: "3.000000000000000000000000", expected: "1000.000000000000000000000000"}, // 10^3 = 1000

// Negative exponents
{base: "2.000000000000000000000000", exponent: "-1.000000000000000000000000", expected: "0.500000000000000000000000"}, // 2^(-1) = 0.5
{base: "4.000000000000000000000000", exponent: "-1.000000000000000000000000", expected: "0.250000000000000000000000"}, // 4^(-1) = 0.25

// Fractional base
{base: "0.500000000000000000000000", exponent: "2.000000000000000000000000", expected: "0.250000000000000000000000"}, // 0.5^2 = 0.25

// Fractional exponent
{base: "4.000000000000000000000000", exponent: "0.500000000000000000000000", expected: "2.000000000000000000000000"}, // 4^0.5 = 2
{base: "9.000000000000000000000000", exponent: "0.500000000000000000000000", expected: "3.000000000000000000000000"}, // 9^0.5 = 3
}

for _, tc := range testCases {

testName := fmt.Sprintf("%s ^ %s", tc.base, tc.exponent)

t.Run(testName, func(t *testing.T) {
t.Parallel()

code := fmt.Sprintf(
`
fun test(): UFix128 {
let base: UFix128 = %s
let exponent: Fix128 = %s
return base.pow(exponent)
}
`,
tc.base,
tc.exponent,
)

inter := parseCheckAndPrepare(t, code)

if tc.expectedError {
_, err := inter.Invoke("test")
require.Error(t, err)
} else {
result, err := inter.Invoke("test")
require.NoError(t, err)

expected := parseCheckAndPrepare(t, fmt.Sprintf(
`let expected: UFix128 = %s`,
tc.expected,
)).GetGlobal("expected")

AssertValuesEqual(t, inter, expected, result)
}
})
}
})
}

func TestInterpretNegativeZeroFixedPoint(t *testing.T) {

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

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

Expand Down Expand Up @@ -103,6 +104,18 @@ func getNumberValueFunctionMember(
sema.SaturatingArithmeticTypeFunctionTypes[typ],
NativeNumberSaturatingDivideFunction,
)

case sema.FixedPointNumericTypePowFunctionName:
funcType, ok := sema.FixedPointPowFunctionTypes[typ]
if !ok {
return nil
}
return NewBoundHostFunctionValue(
context,
v,
funcType,
NativeFixedPointPowFunction,
)
}

return nil
Expand Down Expand Up @@ -214,3 +227,24 @@ var NativeNumberSaturatingDivideFunction = NativeFunction(
return receiver.(NumberValue).SaturatingDiv(context, other)
},
)

var NativeFixedPointPowFunction = NativeFunction(
func(
context NativeFunctionContext,
_ TypeArgumentsIterator,
_ ArgumentTypesIterator,
receiver Value,
args []Value,
) Value {
switch v := receiver.(type) {
case UFix64Value:
exponent := AssertValueOfType[Fix64Value](args[0])
return v.Pow(context, exponent)
case UFix128Value:
exponent := AssertValueOfType[Fix128Value](args[0])
return v.Pow(context, exponent)
default:
panic(errors.NewUnreachableError())
}
},
)
10 changes: 10 additions & 0 deletions interpreter/value_ufix128.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,16 @@ func (v UFix128Value) Mod(context NumberValueArithmeticContext, other NumberValu
return NewUFix128Value(context, valueGetter)
}

func (v UFix128Value) Pow(context NumberValueArithmeticContext, other Fix128Value) NumberValue {
valueGetter := func() fix.UFix128 {
result, err := fix.UFix128(v).Pow(fix.Fix128(other))
handleFixedpointError(err)
return result
}

return NewUFix128Value(context, valueGetter)
}

func (v UFix128Value) Less(context ValueComparisonContext, other ComparableValue) BoolValue {
o, ok := other.(UFix128Value)
if !ok {
Expand Down
14 changes: 14 additions & 0 deletions interpreter/value_ufix64.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (

"github.com/onflow/atree"

fix "github.com/onflow/fixed-point"

"github.com/onflow/cadence/ast"
"github.com/onflow/cadence/common"
"github.com/onflow/cadence/errors"
Expand Down Expand Up @@ -355,6 +357,18 @@ func (v UFix64Value) Mod(context NumberValueArithmeticContext, other NumberValue
return UFix64Value{UFix64Value: result}
}

func (v UFix64Value) Pow(context NumberValueArithmeticContext, other Fix64Value) NumberValue {
valueGetter := func() uint64 {
a := fix.UFix64(uint64(v.UFix64Value))
b := fix.Fix64(uint64(other))
result, err := a.Pow(b)
handleFixedpointError(err)
return uint64(result)
}

return NewUFix64Value(context, valueGetter)
}

func (v UFix64Value) Less(context ValueComparisonContext, other ComparableValue) BoolValue {
o, ok := other.(UFix64Value)
if !ok {
Expand Down
Loading
Loading