diff --git a/internal/jsnum/jsnum.go b/internal/jsnum/jsnum.go index 14d8338a08..9e54baaf72 100644 --- a/internal/jsnum/jsnum.go +++ b/internal/jsnum/jsnum.go @@ -41,31 +41,33 @@ func isNonFinite(x float64) bool { } // https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-touint32 -func (n Number) toUint32() uint32 { +func (x Number) toUint32() uint32 { + // The only difference between ToUint32 and ToInt32 is the interpretation of the bits. + return uint32(x.toInt32()) +} + +// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-toint32 +func (n Number) toInt32() int32 { x := float64(n) - // Fast path: if the number is the range (-2^31, 2^32), i.e. an SMI, + + // Fast path: if the number is in the range (-2^31, 2^32), i.e. an SMI, // then we don't need to do any special mapping. if smi := int32(x); float64(smi) == x { - return uint32(smi) + return smi } - // If the number is non-finite (NaN, +Inf, -Inf; exp=0x7FF), it maps to zero. + // 2. If number is not finite or number is either +0𝔽 or -0𝔽, return +0𝔽. + // Zero was covered by the test above. if isNonFinite(x) { return 0 } - // Otherwise, take x modulo 2^32, mapping positive numbers - // to [0, 2^32) and negative numbers to (-2^32, -0.0]. + // Let int be truncate(ℝ(number)). + x = math.Trunc(x) + // Let int32bit be int modulo 2**32. x = math.Mod(x, 1<<32) - - // Convert to uint32, which will wrap negative numbers. - return uint32(x) -} - -// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-toint32 -func (x Number) toInt32() int32 { - // The only difference between ToUint32 and ToInt32 is the interpretation of the bits. - return int32(x.toUint32()) + // If int32bit ≥ 2**31, return 𝔽(int32bit - 2**32); otherwise return 𝔽(int32bit). + return int32(int64(x)) } func (x Number) toShiftCount() uint32 { diff --git a/internal/jsnum/jsnum_test.go b/internal/jsnum/jsnum_test.go index 6036d0be9d..53c358204d 100644 --- a/internal/jsnum/jsnum_test.go +++ b/internal/jsnum/jsnum_test.go @@ -109,18 +109,18 @@ func TestBitwiseNOT(t *testing.T) { t.Parallel() tests := []struct { - input, want Number + got, want Number }{ - {-2147483649, Number(2147483647).BitwiseNOT()}, - {-4294967296, Number(0).BitwiseNOT()}, - {2147483648, Number(-2147483648).BitwiseNOT()}, - {4294967296, Number(0).BitwiseNOT()}, + {Number(-2147483649).BitwiseNOT(), Number(2147483647).BitwiseNOT()}, + {Number(-4294967296).BitwiseNOT(), Number(0).BitwiseNOT()}, + {Number(2147483648).BitwiseNOT(), Number(-2147483648).BitwiseNOT()}, + {Number(4294967296).BitwiseNOT(), Number(0).BitwiseNOT()}, } for _, test := range tests { - t.Run(test.input.String(), func(t *testing.T) { + t.Run(test.got.String(), func(t *testing.T) { t.Parallel() - assertEqualNumber(t, test.input.BitwiseNOT(), test.want) + assertEqualNumber(t, test.got, test.want) }) } }