741 lines
18 KiB
Go
741 lines
18 KiB
Go
package jsnum
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/microsoft/typescript-go/internal/json"
|
|
"github.com/microsoft/typescript-go/internal/testutil/jstest"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func assertEqualNumber(t *testing.T, got, want Number) {
|
|
t.Helper()
|
|
|
|
if got.IsNaN() || want.IsNaN() {
|
|
assert.Equal(t, got.IsNaN(), want.IsNaN(), "got: %v, want: %v", got, want)
|
|
} else {
|
|
assert.Equal(t, got, want)
|
|
}
|
|
}
|
|
|
|
// assertWithinOneULP checks that got and want are either equal or differ by
|
|
// at most 1 ULP (unit in the last place).
|
|
func assertWithinOneULP(t *testing.T, got, want Number) {
|
|
t.Helper()
|
|
|
|
if got.IsNaN() || want.IsNaN() {
|
|
assert.Equal(t, got.IsNaN(), want.IsNaN(), "got: %v, want: %v", got, want)
|
|
return
|
|
}
|
|
|
|
if got == want {
|
|
return
|
|
}
|
|
|
|
gotBits := math.Float64bits(float64(got))
|
|
wantBits := math.Float64bits(float64(want))
|
|
if gotBits == wantBits {
|
|
return
|
|
}
|
|
|
|
var ulpDist uint64
|
|
if gotBits > wantBits {
|
|
ulpDist = gotBits - wantBits
|
|
} else {
|
|
ulpDist = wantBits - gotBits
|
|
}
|
|
|
|
if ulpDist > 1 {
|
|
t.Errorf("got %v (%016x), want %v (%016x) within 1 ULP (off by %d ULPs)",
|
|
got, gotBits, want, wantBits, ulpDist)
|
|
}
|
|
}
|
|
|
|
func numberFromBits(b uint64) Number {
|
|
return Number(math.Float64frombits(b))
|
|
}
|
|
|
|
func numberToBits(n Number) uint64 {
|
|
return math.Float64bits(float64(n))
|
|
}
|
|
|
|
type binaryInput struct {
|
|
X [2]uint32 `json:"x"`
|
|
Y [2]uint32 `json:"y"`
|
|
}
|
|
|
|
type binaryResult struct {
|
|
X [2]uint32 `json:"x"`
|
|
Y [2]uint32 `json:"y"`
|
|
Result [2]uint32 `json:"result"`
|
|
}
|
|
|
|
type unaryInput struct {
|
|
X [2]uint32 `json:"x"`
|
|
}
|
|
|
|
type unaryResult struct {
|
|
X [2]uint32 `json:"x"`
|
|
Result [2]uint32 `json:"result"`
|
|
}
|
|
|
|
func numToUint32s(n Number) [2]uint32 {
|
|
bits := numberToBits(n)
|
|
return [2]uint32{uint32(bits), uint32(bits >> 32)}
|
|
}
|
|
|
|
func uint32sToNum(a [2]uint32) Number {
|
|
bits := uint64(a[0]) | uint64(a[1])<<32
|
|
return numberFromBits(bits)
|
|
}
|
|
|
|
// evalBinaryOp evaluates a binary JS expression on all cases using Node.js.
|
|
// Skips the calling test if Node.js is not available.
|
|
func evalBinaryOp(t *testing.T, op string, xs, ys []Number) []Number {
|
|
t.Helper()
|
|
jstest.SkipIfNoNodeJS(t)
|
|
|
|
tmpdir := t.TempDir()
|
|
inputs := make([]binaryInput, len(xs))
|
|
for i := range xs {
|
|
inputs[i] = binaryInput{X: numToUint32s(xs[i]), Y: numToUint32s(ys[i])}
|
|
}
|
|
|
|
jsonInput, err := json.Marshal(inputs)
|
|
assert.NilError(t, err)
|
|
|
|
inputPath := filepath.Join(tmpdir, "input.json")
|
|
err = os.WriteFile(inputPath, jsonInput, 0o644)
|
|
assert.NilError(t, err)
|
|
|
|
script := fmt.Sprintf(`
|
|
import fs from 'fs';
|
|
|
|
function fromBits(bits) {
|
|
const buffer = new ArrayBuffer(8);
|
|
(new Uint32Array(buffer))[0] = bits[0];
|
|
(new Uint32Array(buffer))[1] = bits[1];
|
|
return new Float64Array(buffer)[0];
|
|
}
|
|
|
|
function toBits(number) {
|
|
const buffer = new ArrayBuffer(8);
|
|
(new Float64Array(buffer))[0] = number;
|
|
return [(new Uint32Array(buffer))[0], (new Uint32Array(buffer))[1]];
|
|
}
|
|
|
|
export default function(inputFile) {
|
|
const input = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
|
return input.map(({x, y}) => {
|
|
const a = fromBits(x);
|
|
const b = fromBits(y);
|
|
return { x, y, result: toBits(%s) };
|
|
});
|
|
};
|
|
`, op)
|
|
|
|
results, err := jstest.EvalNodeScript[[]binaryResult](t, script, tmpdir, inputPath)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, len(results), len(xs))
|
|
|
|
out := make([]Number, len(results))
|
|
for i, r := range results {
|
|
out[i] = uint32sToNum(r.Result)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// evalUnaryOp evaluates a unary JS expression on all cases using Node.js.
|
|
// Skips the calling test if Node.js is not available.
|
|
func evalUnaryOp(t *testing.T, op string, xs []Number) []Number {
|
|
t.Helper()
|
|
jstest.SkipIfNoNodeJS(t)
|
|
|
|
tmpdir := t.TempDir()
|
|
inputs := make([]unaryInput, len(xs))
|
|
for i, x := range xs {
|
|
inputs[i] = unaryInput{X: numToUint32s(x)}
|
|
}
|
|
|
|
jsonInput, err := json.Marshal(inputs)
|
|
assert.NilError(t, err)
|
|
|
|
inputPath := filepath.Join(tmpdir, "input.json")
|
|
err = os.WriteFile(inputPath, jsonInput, 0o644)
|
|
assert.NilError(t, err)
|
|
|
|
script := fmt.Sprintf(`
|
|
import fs from 'fs';
|
|
|
|
function fromBits(bits) {
|
|
const buffer = new ArrayBuffer(8);
|
|
(new Uint32Array(buffer))[0] = bits[0];
|
|
(new Uint32Array(buffer))[1] = bits[1];
|
|
return new Float64Array(buffer)[0];
|
|
}
|
|
|
|
function toBits(number) {
|
|
const buffer = new ArrayBuffer(8);
|
|
(new Float64Array(buffer))[0] = number;
|
|
return [(new Uint32Array(buffer))[0], (new Uint32Array(buffer))[1]];
|
|
}
|
|
|
|
export default function(inputFile) {
|
|
const input = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
|
return input.map(({x}) => {
|
|
const a = fromBits(x);
|
|
return { x, result: toBits(%s) };
|
|
});
|
|
};
|
|
`, op)
|
|
|
|
results, err := jstest.EvalNodeScript[[]unaryResult](t, script, tmpdir, inputPath)
|
|
assert.NilError(t, err)
|
|
assert.Equal(t, len(results), len(xs))
|
|
|
|
out := make([]Number, len(results))
|
|
for i, r := range results {
|
|
out[i] = uint32sToNum(r.Result)
|
|
}
|
|
return out
|
|
}
|
|
|
|
var toInt32Tests = []struct {
|
|
name string
|
|
input Number
|
|
want int32
|
|
bench bool
|
|
}{
|
|
{"0.0", 0, 0, true},
|
|
{"-0.0", Number(negativeZero), 0, false},
|
|
{"NaN", NaN(), 0, true},
|
|
{"+Inf", Inf(1), 0, true},
|
|
{"-Inf", Inf(-1), 0, true},
|
|
{"MaxInt32", Number(math.MaxInt32), math.MaxInt32, false},
|
|
{"MaxInt32+1", Number(int64(math.MaxInt32) + 1), math.MinInt32, true},
|
|
{"MinInt32", Number(math.MinInt32), math.MinInt32, false},
|
|
{"MinInt32-1", Number(int64(math.MinInt32) - 1), math.MaxInt32, true},
|
|
{"MIN_SAFE_INTEGER", MinSafeInteger, 1, false},
|
|
{"MIN_SAFE_INTEGER-1", MinSafeInteger - 1, 0, false},
|
|
{"MIN_SAFE_INTEGER+1", MinSafeInteger + 1, 2, false},
|
|
{"MAX_SAFE_INTEGER", MaxSafeInteger, -1, true},
|
|
{"MAX_SAFE_INTEGER-1", MaxSafeInteger - 1, -2, true},
|
|
{"MAX_SAFE_INTEGER+1", MaxSafeInteger + 1, 0, true},
|
|
{"-8589934590", -8589934590, 2, false},
|
|
{"0xDEADBEEF", 0xDEADBEEF, -559038737, true},
|
|
{"4294967808", 4294967808, 512, false},
|
|
{"-0.4", -0.4, 0, false},
|
|
{"SmallestNonzeroFloat64", math.SmallestNonzeroFloat64, 0, false},
|
|
{"-SmallestNonzeroFloat64", -math.SmallestNonzeroFloat64, 0, false},
|
|
{"MaxFloat64", math.MaxFloat64, 0, false},
|
|
{"-MaxFloat64", -math.MaxFloat64, 0, false},
|
|
{"Largest subnormal number", numberFromBits(0x000FFFFFFFFFFFFF), 0, false},
|
|
{"Smallest positive normal number", numberFromBits(0x0010000000000000), 0, false},
|
|
{"Largest normal number", math.MaxFloat64, 0, false},
|
|
{"-Largest normal number", -math.MaxFloat64, 0, false},
|
|
{"1.0", 1.0, 1, false},
|
|
{"-1.0", -1.0, -1, false},
|
|
{"1e308", 1e308, 0, false},
|
|
{"-1e308", -1e308, 0, false},
|
|
{"math.Pi", math.Pi, 3, false},
|
|
{"-math.Pi", -math.Pi, -3, false},
|
|
{"math.E", math.E, 2, false},
|
|
{"-math.E", -math.E, -2, false},
|
|
{"0.5", 0.5, 0, false},
|
|
{"-0.5", -0.5, 0, false},
|
|
{"0.49999999999999994", 0.49999999999999994, 0, false},
|
|
{"-0.49999999999999994", -0.49999999999999994, 0, false},
|
|
{"0.5000000000000001", 0.5000000000000001, 0, false},
|
|
{"-0.5000000000000001", -0.5000000000000001, 0, false},
|
|
{"2^31 + 0.5", 2147483648.5, -2147483648, false},
|
|
{"-2^31 - 0.5", -2147483648.5, -2147483648, false},
|
|
{"2^40", 1099511627776, 0, false},
|
|
{"-2^40", -1099511627776, 0, false},
|
|
{"TypeFlagsNarrowable", 536624127, 536624127, true},
|
|
}
|
|
|
|
func TestToInt32(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
inputs := make([]Number, len(toInt32Tests))
|
|
zeros := make([]Number, len(toInt32Tests))
|
|
for i, test := range toInt32Tests {
|
|
inputs[i] = test.input
|
|
}
|
|
for _, test := range toInt32Tests {
|
|
t.Run(fmt.Sprintf("%s (%v)", test.name, float64(test.input)), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.input.toInt32()
|
|
assert.Equal(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a | b", inputs, zeros)
|
|
for i, test := range toInt32Tests {
|
|
t.Run(fmt.Sprintf("%s (%v)", test.name, float64(test.input)), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, Number(test.input.toInt32()), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkToInt32(b *testing.B) {
|
|
for _, test := range toInt32Tests {
|
|
if !test.bench {
|
|
continue
|
|
}
|
|
|
|
b.Run(fmt.Sprintf("%s (%v)", test.name, float64(test.input)), func(b *testing.B) {
|
|
for b.Loop() {
|
|
test.input.toInt32()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBitwiseNOT(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, want Number
|
|
}{
|
|
// Original pairs: ~(-2147483649) == ~(2147483647)
|
|
{Number(-2147483649), -2147483648},
|
|
{Number(2147483647), -2147483648},
|
|
// Original pairs: ~(-4294967296) == ~(0)
|
|
{Number(-4294967296), -1},
|
|
{0, -1},
|
|
// Original pairs: ~(2147483648) == ~(-2147483648)
|
|
{Number(2147483648), 2147483647},
|
|
{Number(-2147483648), 2147483647},
|
|
// Original pairs: ~(4294967296) == ~(0)
|
|
{Number(4294967296), -1},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("~%v", test.x), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.BitwiseNOT()
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalUnaryOp(t, "~a", xs)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("~%v", test.x), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.BitwiseNOT(), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBitwiseAND(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{0, 0, 0},
|
|
{0, 1, 0},
|
|
{1, 0, 0},
|
|
{1, 1, 1},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v & %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.BitwiseAND(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a & b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v & %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.BitwiseAND(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBitwiseOR(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{0, 0, 0},
|
|
{0, 1, 1},
|
|
{1, 0, 1},
|
|
{1, 1, 1},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v | %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.BitwiseOR(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a | b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v | %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.BitwiseOR(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestBitwiseXOR(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{0, 0, 0},
|
|
{0, 1, 1},
|
|
{1, 0, 1},
|
|
{1, 1, 0},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v ^ %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.BitwiseXOR(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a ^ b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v ^ %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.BitwiseXOR(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestSignedRightShift(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{1, 0, 1},
|
|
{1, 1, 0},
|
|
{1, 2, 0},
|
|
{1, 31, 0},
|
|
{1, 32, 1},
|
|
{-4, 0, -4},
|
|
{-4, 1, -2},
|
|
{-4, 2, -1},
|
|
{-4, 3, -1},
|
|
{-4, 4, -1},
|
|
{-4, 31, -1},
|
|
{-4, 32, -4},
|
|
{-4, 33, -2},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v >> %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.SignedRightShift(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a >> b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v >> %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.SignedRightShift(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestUnsignedRightShift(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{1, 0, 1},
|
|
{1, 1, 0},
|
|
{1, 2, 0},
|
|
{1, 31, 0},
|
|
{1, 32, 1},
|
|
{-4, 0, 4294967292},
|
|
{-4, 1, 2147483646},
|
|
{-4, 2, 1073741823},
|
|
{-4, 3, 536870911},
|
|
{-4, 4, 268435455},
|
|
{-4, 31, 1},
|
|
{-4, 32, 4294967292},
|
|
{-4, 33, 2147483646},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v >>> %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.UnsignedRightShift(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a >>> b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v >>> %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.UnsignedRightShift(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLeftShift(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{1, 0, 1},
|
|
{1, 1, 2},
|
|
{1, 2, 4},
|
|
{1, 31, -2147483648},
|
|
{1, 32, 1},
|
|
{-4, 0, -4},
|
|
{-4, 1, -8},
|
|
{-4, 2, -16},
|
|
{-4, 3, -32},
|
|
{-4, 31, 0},
|
|
{-4, 32, -4},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v << %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.LeftShift(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a << b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v << %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.LeftShift(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRemainder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{NaN(), 1, NaN()},
|
|
{1, NaN(), NaN()},
|
|
{Inf(1), 1, NaN()},
|
|
{Inf(-1), 1, NaN()},
|
|
{123, Inf(1), 123},
|
|
{123, Inf(-1), 123},
|
|
{123, 0, NaN()},
|
|
{123, negativeZero, NaN()},
|
|
{0, 123, 0},
|
|
{negativeZero, 123, negativeZero},
|
|
// Normal cases
|
|
{10, 3, 1},
|
|
{-10, 3, -1},
|
|
{10, -3, 1},
|
|
{-10, -3, -1},
|
|
{5.5, 2, 1.5},
|
|
{-5.5, 2, -1.5},
|
|
{1, 0.5, 0},
|
|
{-1, 0.5, negativeZero},
|
|
{1.5, 1, 0.5},
|
|
{-1.5, 1, -0.5},
|
|
// Edge cases that prove the bug in the manual formula:
|
|
// The manual formula n - d*(n/d).trunc() accumulates floating-point
|
|
// rounding errors that IEEE 754 fmod (math.Mod) avoids.
|
|
{7, 0.1, Number(math.Mod(7, 0.1))},
|
|
{7, 0.2, Number(math.Mod(7, 0.2))},
|
|
{7, 0.3, Number(math.Mod(7, 0.3))},
|
|
{100, 0.3, Number(math.Mod(100, 0.3))},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v %% %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.Remainder(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a % b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v %% %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertEqualNumber(t, test.x.Remainder(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestExponentiate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
x, y, want Number
|
|
}{
|
|
{2, 3, 8},
|
|
{Inf(1), 3, Inf(1)},
|
|
{Inf(1), -5, 0},
|
|
{Inf(-1), 3, Inf(-1)},
|
|
{Inf(-1), 4, Inf(1)},
|
|
{Inf(-1), -3, negativeZero},
|
|
{Inf(-1), -4, 0},
|
|
{0, 3, 0},
|
|
{0, -10, Inf(1)},
|
|
{negativeZero, 3, negativeZero},
|
|
{negativeZero, 4, 0},
|
|
{negativeZero, -3, Inf(-1)},
|
|
{negativeZero, -4, Inf(1)},
|
|
{3, Inf(1), Inf(1)},
|
|
{-3, Inf(1), Inf(1)},
|
|
{3, Inf(-1), 0},
|
|
{-3, Inf(-1), 0},
|
|
{NaN(), 3, NaN()},
|
|
{1, Inf(1), NaN()},
|
|
{1, Inf(-1), NaN()},
|
|
{-1, Inf(1), NaN()},
|
|
{-1, Inf(-1), NaN()},
|
|
{1, NaN(), NaN()},
|
|
// Cases where math.Pow diverges from V8 by >1 ULP.
|
|
// Expected values are the correctly-rounded IEEE 754 results
|
|
// computed via exact integer arithmetic (big.Int).
|
|
// Cross-engine testing (V8, SpiderMonkey, QuickJS, XS via jsvu)
|
|
// confirmed these match the majority of JS engines.
|
|
{10, 308, numberFromBits(0x7fe1ccf385ebc8a0)},
|
|
{5, 210, numberFromBits(0x5e68557f31326bbb)},
|
|
{10, 200, numberFromBits(0x6974e718d7d7625a)},
|
|
}
|
|
|
|
xs := make([]Number, len(tests))
|
|
ys := make([]Number, len(tests))
|
|
for i, test := range tests {
|
|
xs[i] = test.x
|
|
ys[i] = test.y
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v ** %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
got := test.x.Exponentiate(test.y)
|
|
assertEqualNumber(t, got, test.want)
|
|
})
|
|
}
|
|
|
|
// The ES spec says exponentiate is "implementation-approximated".
|
|
// Different JS engines (V8, SpiderMonkey, JSC) use different pow
|
|
// implementations that can differ by 1 ULP. Allow that tolerance.
|
|
t.Run("Node", func(t *testing.T) {
|
|
jsResults := evalBinaryOp(t, "a ** b", xs, ys)
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("%v ** %v", test.x, test.y), func(t *testing.T) {
|
|
t.Parallel()
|
|
assertWithinOneULP(t, test.x.Exponentiate(test.y), jsResults[i])
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkExponentiate(b *testing.B) {
|
|
cases := []struct {
|
|
name string
|
|
base Number
|
|
exponent Number
|
|
}{
|
|
{"2**10_exact", 2, 10}, // small, fits in 53 bits → math.Pow
|
|
{"2**53_exact", 2, 53}, // boundary, exactly 53 bits → math.Pow
|
|
{"10**20_bigint", 10, 20}, // exceeds 53 bits → big.Int
|
|
{"10**308_bigint", 10, 308}, // large exponent → big.Int
|
|
{"3**34_bigint", 3, 34}, // medium → big.Int
|
|
{"0.5**-0.5_mathpow", 0.5, -0.5}, // non-integer → math.Pow
|
|
}
|
|
|
|
for _, c := range cases {
|
|
b.Run(c.name, func(b *testing.B) {
|
|
for b.Loop() {
|
|
c.base.Exponentiate(c.exponent)
|
|
}
|
|
})
|
|
}
|
|
}
|