///|
// Work on the exact IEEE-754 rational value. Decimal rounding uses ties to
// even and does not depend on JS/host printf or a shortest-decimal heuristic.
fn double_fraction(value : Double) -> (@bigint.BigInt, @bigint.BigInt) {
let bits = value.reinterpret_as_uint64()
let exponent = ((bits >> 52) & 2047UL).to_int()
let fraction = bits & 4503599627370495UL
let mantissa = if exponent == 0 {
fraction
} else {
fraction | 4503599627370496UL
}
let shift = (if exponent == 0 { -1022 } else { exponent - 1023 }) - 52
let n = @bigint.BigInt::from_uint64(mantissa)
if shift >= 0 {
(n << shift, 1N)
} else {
(n, 1N << -shift)
}
}
///|
fn decimal_power(exponent : Int) -> @bigint.BigInt {
10N.pow(@bigint.BigInt::from_int(exponent))
}
///|
// The caller has already recognized a decimal number. Core parsing reports
// extreme exponents as errors; Tcl accepts signed overflow/underflow values.
fn conversion_parse_double(text : String) -> Double {
@strconv.parse_double(text) catch {
_ => {
let cs = utf16_units(text)
let negative = cs.length() > 0 && cs[0] == '-'
let mut i = if cs.length() > 0 && (cs[0] == '-' || cs[0] == '+') {
1
} else {
0
}
let mut digits = 0
let mut before_point = 0
let mut first_nonzero = -1
let mut fractional = false
while i < cs.length() && cs[i] != 'e' && cs[i] != 'E' {
if cs[i] == '.' {
fractional = true
} else {
if cs[i] != '0' && first_nonzero < 0 {
first_nonzero = digits
}
digits += 1
if !fractional {
before_point += 1
}
}
i += 1
}
let mut exponent = 0
if i < cs.length() {
i += 1
let sign = if i < cs.length() && cs[i] == '-' { -1 } else { 1 }
if i < cs.length() && (cs[i] == '-' || cs[i] == '+') {
i += 1
}
while i < cs.length() {
exponent = (exponent * 10 + cs[i].to_int() - 48).min(1000000)
i += 1
}
exponent *= sign
}
let value = if first_nonzero >= 0 &&
before_point - first_nonzero - 1 + exponent >= 308 {
1.0 / 0.0
} else {
0.0
}
if negative {
-value
} else {
value
}
}
}
}
///|
// Tcl 8.6 applies the power-of-two asymmetric interval in reversed order
// when shortening (larger tolerance below, smaller above). Preserve that
// observable text choice; typed object identity and
// its subsequent coercions remain a separate interpreter concern.
fn tcl_shortest_power(value : Double) -> String {
let bits = value.reinterpret_as_uint64()
let biased = ((bits >> 52) & 2047UL).to_int()
if biased == 0 || (bits & 4503599627370495UL) != 0 {
return value.to_string()
}
let (n, d) = double_fraction(value)
let exponent = decimal_exponent(n, d)
let half_shift = biased - 1023 - 53
let en = if half_shift >= 0 { 1N << half_shift } else { 1N }
let ed = if half_shift < 0 { 1N << -half_shift } else { 1N }
for precision in 1..<=17 {
let scale = precision - 1 - exponent
let scale_n = if scale >= 0 { decimal_power(scale) } else { 1N }
let scale_d = if scale < 0 { decimal_power(-scale) } else { 1N }
let denominator = d * scale_d
let remainder = n * scale_n % denominator
// Tcl first tests the truncated candidate, then rounds it to nearest
// even. That rounded candidate need not satisfy the other-side bound.
if remainder * ed <= en * d * scale_n ||
(denominator - remainder) * ed * 2N <= en * d * scale_n {
return format_real(value, 'g', precision, false)
}
}
value.to_string()
}
///|
fn decimal_round(
n : @bigint.BigInt,
d : @bigint.BigInt,
scale : Int,
) -> @bigint.BigInt {
let a = if scale >= 0 { n * decimal_power(scale) } else { n }
let b = if scale < 0 { d * decimal_power(-scale) } else { d }
let q = a / b
let twice = a % b * 2N
if twice > b || (twice == b && (q & 1N) == 1N) {
q + 1N
} else {
q
}
}
///|
fn decimal_exponent(n : @bigint.BigInt, d : @bigint.BigInt) -> Int {
if n == 0N {
return 0
}
if n >= d {
return (n / d).to_string().length() - 1
}
let mut scaled = n
let mut exponent = 0
while scaled < d {
scaled *= 10N
exponent -= 1
}
exponent
}
///|
fn decimal_place(digits : String, point : Int, force_point : Bool) -> String {
if point <= 0 {
"0." + "0".repeat(-point) + digits
} else if point >= digits.length() {
digits +
"0".repeat(point - digits.length()) +
(if force_point { "." } else { "" })
} else {
unit_slice(digits, 0, point) +
"." +
unit_slice(digits, point, digits.length())
}
}
///|
fn decimal_scientific(
digits : String,
exponent : Int,
uppercase : Bool,
force_point : Bool,
) -> String {
let sign = if exponent < 0 { "-" } else { "+" }
let exp = (if exponent < 0 { -exponent } else { exponent }).to_string()
unit_slice(digits, 0, 1) +
(if digits.length() > 1 || force_point { "." } else { "" }) +
unit_slice(digits, 1, digits.length()) +
(if uppercase { "E" } else { "e" }) +
sign +
(if exp.length() < 2 { "0" } else { "" }) +
exp
}
///|
fn format_real(
value : Double,
kind : Char,
precision : Int,
alternate : Bool,
) -> String {
let (n, d) = double_fraction(value)
if kind == 'f' {
// Binary64 fractions terminate within 1074 decimal fractional digits.
let effective = precision.min(1074)
let raw = decimal_round(n, d, effective).to_string()
let digits = "0".repeat((effective + 1 - raw.length()).max(0)) + raw
let point = digits.length() - effective
return decimal_place(
digits + "0".repeat(precision - effective),
point,
alternate,
)
}
let general = kind == 'g' || kind == 'G'
let significant = if general { precision.max(1) } else { precision + 1 }
let effective = significant.min(1100)
let mut exponent = decimal_exponent(n, d)
let mut rounded = decimal_round(n, d, effective - 1 - exponent)
if rounded.to_string().length() > effective {
rounded = rounded / 10N
exponent += 1
}
let raw = rounded.to_string()
let mut digits = "0".repeat((effective - raw.length()).max(0)) +
raw +
"0".repeat(significant - effective)
if general && !alternate {
let mut end = digits.length()
while end > 1 && digits.get(end - 1).unwrap().to_int() == 48 {
end -= 1
}
digits = unit_slice(digits, 0, end)
}
if !general || exponent < -4 || exponent >= significant {
decimal_scientific(digits, exponent, kind == 'E' || kind == 'G', alternate)
} else {
decimal_place(digits, exponent + 1, alternate)
}
}