2025-11-08 09:37:30 +03:00

160 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package jsnum provides JS-like number handling.
package jsnum
import (
"math"
)
const (
MaxSafeInteger Number = 1<<53 - 1
MinSafeInteger Number = -MaxSafeInteger
)
// Number represents a JS-like number.
//
// All operations that can be performed directly on this type
// (e.g., conversion, arithmetic, etc.) behave as they would in JavaScript,
// but any other operation should use this type's methods,
// not the "math" package and conversions.
type Number float64
func NaN() Number {
return Number(math.NaN())
}
func (n Number) IsNaN() bool {
return math.IsNaN(float64(n))
}
func Inf(sign int) Number {
return Number(math.Inf(sign))
}
func (n Number) IsInf() bool {
return math.IsInf(float64(n), 0)
}
func isNonFinite(x float64) bool {
// This is equivalent to checking `math.IsNaN(x) || math.IsInf(x, 0)` in one operation.
const mask = 0x7FF0000000000000
return math.Float64bits(x)&mask == mask
}
// https://tc39.es/ecma262/2024/multipage/abstract-operations.html#sec-touint32
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 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 smi
}
// 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
}
// Let int be truncate((number)).
x = math.Trunc(x)
// Let int32bit be int modulo 2**32.
x = math.Mod(x, 1<<32)
// If int32bit ≥ 2**31, return 𝔽(int32bit - 2**32); otherwise return 𝔽(int32bit).
return int32(int64(x))
}
func (x Number) toShiftCount() uint32 {
return x.toUint32() & 31
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-signedRightShift
func (x Number) SignedRightShift(y Number) Number {
return Number(x.toInt32() >> y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-unsignedRightShift
func (x Number) UnsignedRightShift(y Number) Number {
return Number(x.toUint32() >> y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-leftShift
func (x Number) LeftShift(y Number) Number {
return Number(x.toInt32() << y.toShiftCount())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseNOT
func (x Number) BitwiseNOT() Number {
return Number(^x.toInt32())
}
// The below are implemented by https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numberbitwiseop.
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseOR
func (x Number) BitwiseOR(y Number) Number {
return Number(x.toInt32() | y.toInt32())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseAND
func (x Number) BitwiseAND(y Number) Number {
return Number(x.toInt32() & y.toInt32())
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-bitwiseXOR
func (x Number) BitwiseXOR(y Number) Number {
return Number(x.toInt32() ^ y.toInt32())
}
func (x Number) trunc() Number {
return Number(math.Trunc(float64(x)))
}
func (x Number) Floor() Number {
return Number(math.Floor(float64(x)))
}
func (x Number) Abs() Number {
return Number(math.Abs(float64(x)))
}
var negativeZero = Number(math.Copysign(0, -1))
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-remainder
func (n Number) Remainder(d Number) Number {
switch {
case n.IsNaN() || d.IsNaN():
return NaN()
case n.IsInf():
return NaN()
case d.IsInf():
return n
case d == 0:
return NaN()
case n == 0:
return n
}
r := n - d*(n/d).trunc()
if r == 0 && n < 0 {
return negativeZero
}
return r
}
// https://tc39.es/ecma262/2024/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-exponentiate
func (base Number) Exponentiate(exponent Number) Number {
switch {
case (base == 1 || base == -1) && exponent.IsInf():
return NaN()
case base == 1 && exponent.IsNaN():
return NaN()
}
return Number(math.Pow(float64(base), float64(exponent)))
}