///|
/// An exact number: coefficient × 10^exponent, or a special double.
priv enum Num {
NaN
NegInf
PosInf
Finite(@bigint.BigInt, Int)
}
///|
fn finite(coefficient : @bigint.BigInt, exponent : Int) -> Num {
if coefficient.is_zero() {
Finite(0N, 0)
} else {
Finite(coefficient, exponent)
}
}
///|
fn num_of_int64(n : Int64) -> Num {
finite(@bigint.BigInt::from_int64(n), 0)
}
///|
fn num_of_bigint(n : @bigint.BigInt) -> Num {
finite(n, 0)
}
///|
fn num_of_decimal(d : @ion_core.Decimal) -> Num {
finite(d.coefficient, d.exponent)
}
///|
/// The exact value of a double: m × 2^e, written as a decimal.
fn num_of_double(d : Double) -> Num {
if d.is_nan() {
return NaN
}
if d.is_pos_inf() {
return PosInf
}
if d.is_neg_inf() {
return NegInf
}
let bits = d.reinterpret_as_uint64()
let negative = bits >> 63 == 1UL
let exponent_bits = ((bits >> 52) & 0x7FFUL).to_int()
let fraction = bits & 0xFFFFFFFFFFFFFUL
let (mantissa, e) = if exponent_bits == 0 {
(fraction, -1074)
} else {
(fraction | 0x10000000000000UL, exponent_bits - 1075)
}
let m = @bigint.BigInt::from_uint64(mantissa)
let (coefficient, exponent) = if e >= 0 {
(m * 2N.pow(@bigint.BigInt::from_int(e)), 0)
} else {
(m * 5N.pow(@bigint.BigInt::from_int(-e)), e)
}
finite(if negative { -coefficient } else { coefficient }, exponent)
}
///|
/// -1, 0 or 1. `compare` results are only negative, zero or positive (on
/// wasm-gc, `BigInt::compare` returns other magnitudes), so normalize them.
fn unit(c : Int) -> Int {
if c < 0 {
-1
} else if c > 0 {
1
} else {
0
}
}
///|
fn sign_of(n : @bigint.BigInt) -> Int {
unit(n.compare(0N))
}
///|
fn digit_count(n : @bigint.BigInt) -> Int {
let text = n.to_string()
if text.has_prefix("-") {
text.length() - 1
} else {
text.length()
}
}
///|
/// Compares two finite decimals without scaling by more than their digit
/// counts.
fn compare_finite(
c1 : @bigint.BigInt,
e1 : Int,
c2 : @bigint.BigInt,
e2 : Int,
) -> Int {
let s1 = sign_of(c1)
let s2 = sign_of(c2)
if s1 != s2 {
return if s1 < s2 { -1 } else { 1 }
}
if s1 == 0 {
return 0
}
// Same sign, both non-zero. Compare magnitudes, then apply the sign.
// Int64: an exponent near Int's limits must not overflow.
let a1 = e1.to_int64() + digit_count(c1).to_int64()
let a2 = e2.to_int64() + digit_count(c2).to_int64()
let magnitude = if a1 != a2 {
if a1 < a2 {
-1
} else {
1
}
} else {
// Equal adjusted exponents: the exponent gap is at most the digit gap.
let (m1, m2) = if e1 >= e2 {
(c1 * 10N.pow(@bigint.BigInt::from_int(e1 - e2)), c2)
} else {
(c1, c2 * 10N.pow(@bigint.BigInt::from_int(e2 - e1)))
}
let abs1 = if s1 < 0 { -m1 } else { m1 }
let abs2 = if s1 < 0 { -m2 } else { m2 }
unit(abs1.compare(abs2))
}
if s1 < 0 {
-magnitude
} else {
magnitude
}
}
///|
fn rank_of_num(n : Num) -> Int {
match n {
NaN => 0
NegInf => 1
Finite(_, _) => 2
PosInf => 3
}
}
///|
/// A total order: NaN < -inf < finite < +inf. NaN equals NaN; -0 equals 0.
fn compare_num(a : Num, b : Num) -> Int {
match (a, b) {
(Finite(c1, e1), Finite(c2, e2)) => compare_finite(c1, e1, c2, e2)
_ => unit(rank_of_num(a).compare(rank_of_num(b)))
}
}