///|
/// `10^n` as a `BigInt` (`n >= 0`).
fn big_pow10(n : Int) -> @bigint.BigInt {
let mut r = @bigint.BigInt::from_int(1)
let ten = @bigint.BigInt::from_int(10)
for _ in 0.. (@bigint.BigInt, Bool) {
let q = a / b
(q, (q * b).equal(a))
}
///|
/// The shortest decimal `digits * 10^k` inside the round-to-nearest-even
/// interval of the positive, finite f32 with significand `m` and binary
/// exponent `e` (value `m * 2^e`), choosing the candidate closest to the
/// exact value (ties to even), as Ryu, Schubfach and zmij do.
///
/// Computed exactly with big integers: f32 has few enough bits that this is
/// cheap, and it is only used for serialization of `f32` fields.
fn shortest_f32_digits(m : Int, e : Int, lower_closer : Bool) -> (String, Int) {
// Scale the value and its interval bounds by 4 so that they are integers
// times 2^(e-2).
let v = m * 4
let hi = v + 2
let lo = if lower_closer { v - 1 } else { v - 2 }
let inclusive = m % 2 == 0
let be = e - 2
// value(x) = x * 2^be = num(x) / den
let den = if be < 0 { @bigint.BigInt::from_int(1) << -be } else { 1N }
let num = (x : Int) => {
if be < 0 {
@bigint.BigInt::from_int(x)
} else {
@bigint.BigInt::from_int(x) << be
}
}
let (nv, nlo, nhi) = (num(v), num(lo), num(hi))
// kk: 10^(kk-1) <= value < 10^kk.
let mut kk = @math.floor(
@math.log10(m.to_double() * @math.pow(2.0, e.to_double())),
).to_int() +
1
let scaled = (k : Int) => {
if k >= 0 {
(nv, den * big_pow10(k))
} else {
(nv * big_pow10(-k), den)
}
}
// Correct the floating-point estimate of kk.
while true {
let (a, b) = scaled(kk)
if a >= b {
kk += 1
continue
}
let (a, b) = scaled(kk - 1)
if a < b {
kk -= 1
continue
}
break
}
for p in 1..<=9 {
let k = kk - p
let (mul, d) = if k >= 0 {
(1N, den * big_pow10(k))
} else {
(big_pow10(-k), den)
}
// Candidates c with lo <= c * 10^k <= hi (strict when not inclusive).
let (qlo, exact_lo) = big_divmod(nlo * mul, d)
let cand_lo = if exact_lo && inclusive { qlo } else { qlo + 1N }
let (qhi, exact_hi) = big_divmod(nhi * mul, d)
let cand_hi = if exact_hi && !inclusive { qhi - 1N } else { qhi }
if cand_lo > cand_hi {
continue
}
// Round the exact value to the nearest candidate, ties to even.
let (q, _) = big_divmod(nv * mul, d)
let rem2 = (nv * mul - q * d) * 2N
let rounded = if rem2 > d || (rem2 == d && q % 2N != 0N) {
q + 1N
} else {
q
}
let c = if rounded < cand_lo {
cand_lo
} else if rounded > cand_hi {
cand_hi
} else {
rounded
}
// Strip trailing zeros into the exponent.
let digits = c.to_string()
let mut end = digits.length()
let mut exp = k
while end > 1 && digits[end - 1] == '0' {
end -= 1
exp += 1
}
return (digits.unsafe_substring(start=0, end~), exp)
}
abort("unreachable: 9 digits always round-trip an f32")
}
///|
/// Format a finite `f32` exactly like serde_json 1.0.151's float writer
/// (zmij: shortest round-trip digits for `f32`, plain notation for decimal
/// exponents in -6..=12, otherwise `d.ddde±x`).
pub fn format_f32(f : Float) -> String {
let bits = f.reinterpret_as_uint()
let buf = StringBuilder()
if bits >> 31 != 0 {
buf.write_char('-')
}
let biased = ((bits >> 23) & 0xFF).reinterpret_as_int()
let frac = (bits & 0x7FFFFF).reinterpret_as_int()
if biased == 0 && frac == 0 {
buf.write_string("0.0")
return buf.to_string()
}
let (m, e) = if biased == 0 {
(frac, -149)
} else {
(frac | 0x800000, biased - 150)
}
let (digits, k) = shortest_f32_digits(m, e, frac == 0 && biased > 1)
write_layout(buf, digits, k, min_kk=-5, max_kk=13)
buf.to_string()
}