///|
// Validate the unsigned Java decimal grammar and locate its decimal magnitude.
// Ordinary inputs keep the runtime's fast conversion. Tiny values use a single
// exact rational rounding instead of repeated decimal-array binary shifts.
fn decimal_double(text : String) -> Double raise ParseError {
let mut i = 0
let mut digits = 0
let mut leading = 0
let mut trailing = 0
let mut fraction = 0
let mut dotted = false
let mut coefficient = 0.0
while i < text.length() {
let c = text[i].to_int()
if c >= 48 && c <= 57 {
if c == 48 {
if leading == digits {
leading += 1
}
trailing += 1
} else {
trailing = 0
}
digits += 1
if digits - leading <= 16 {
coefficient = coefficient * 10.0 + (c - 48).to_double()
}
if dotted {
fraction += 1
}
} else if c == 46 && !dotted {
dotted = true
} else {
break
}
i += 1
}
if digits == 0 {
raise Invalid("invalid decimal number")
}
let mantissa_end = i
let mut exponent = 0
if i < text.length() && (text[i] == 69 || text[i] == 101) {
i += 1
let negative = i < text.length() && text[i] == 45
if i < text.length() && (text[i] == 43 || text[i] == 45) {
i += 1
}
let start = i
while i < text.length() && text[i] >= 48 && text[i] <= 57 {
exponent = (exponent * 10 + text[i].to_int() - 48).min(1000000)
i += 1
}
if i == start {
raise Invalid("invalid decimal exponent")
}
if negative {
exponent = -exponent
}
}
if i != text.length() {
raise Invalid("invalid decimal number")
}
if leading == digits {
return 0.0
}
let magnitude = digits - leading + exponent - fraction - 1
if magnitude < -324 {
return 0.0
}
if magnitude > 308 {
return 1.0 / 0.0
}
let power = exponent - fraction
// Each accumulated integer up to 2^53-1 and each 10^k, 0 <= k <= 22,
// is exactly representable. A single multiply/divide therefore gives the
// correctly rounded decimal value. The strict bound excludes integers
// above 2^53 that the accumulation itself might already have rounded.
if digits - leading <= 16 &&
coefficient < 9007199254740992.0 &&
power >= -22 &&
power <= 22 {
return if power < 0 {
coefficient / decimal_exact_powers[-power]
} else {
coefficient * decimal_exact_powers[power]
}
}
let significant_count = digits - leading - trailing
if magnitude <= -308 {
let power = exponent - fraction + trailing
// A discarded suffix lies between consecutive prefix integers. Equal
// rounded endpoints prove the full input has that same binary64 result.
// If 32 digits straddle a boundary, 768 suffice for an exact decision:
// prefix_power = magnitude - 767 <= -1075. Every binary64 midpoint is
// an integer multiple of 2^-1075, hence of this decimal grid spacing.
// No midpoint is strictly inside the discarded suffix's open interval.
// A nonzero suffix therefore only changes an exact lower-endpoint tie.
for keep in [32, 768] {
let count = keep.min(significant_count)
let prefix = decimal_coefficient(text, mantissa_end, leading, count)
let prefix_power = power + significant_count - count
if keep == 768 {
// significant_count excludes trailing zeros, so a truncated suffix
// is strictly positive. Exact inputs retain round-to-nearest-even.
return tiny_decimal_text(
prefix,
prefix_power,
sticky=count < significant_count,
)
}
let lower = tiny_decimal_text(prefix, prefix_power)
if count == significant_count {
return lower
}
let upper = tiny_decimal_ratio(
@bigint.BigInt::from_string(prefix) + @bigint.BigInt::from_int(1),
prefix_power,
)
if lower == upper {
return lower
}
}
}
@string.parse_double(text) catch {
Failure::Failure("value out of range") => 1.0 / 0.0
_ => raise Invalid("invalid decimal number")
}
}
///|
let decimal_exact_powers : ReadOnlyArray[Double] = [
1.0, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, 10000000.0, 100000000.0,
1000000000.0, 10000000000.0, 100000000000.0, 1000000000000.0, 10000000000000.0,
100000000000000.0, 1000000000000000.0, 10000000000000000.0, 100000000000000000.0,
1000000000000000000.0, 10000000000000000000.0, 100000000000000000000.0, 1000000000000000000000.0,
10000000000000000000000.0,
]
///|
fn decimal_coefficient(
text : String,
mantissa_end : Int,
leading : Int,
count : Int,
) -> String {
let coefficient = StringBuilder()
let mut position = 0
for j = 0; j < mantissa_end && position < leading + count; j = j + 1 {
if text[j] != 46 {
if position >= leading {
coefficient.write_char(text[j].to_int().unsafe_to_char())
}
position += 1
}
}
coefficient.to_string()
}
///|
fn tiny_decimal_text(
coefficient : String,
power : Int,
sticky? : Bool = false,
) -> Double {
let mut end = coefficient.length()
while end > 1 && coefficient[end - 1] == 48 {
end -= 1
}
tiny_decimal_ratio(
@bigint.BigInt::from_string(coefficient.view(end_offset=end).to_owned()),
power + coefficient.length() - end,
sticky~,
)
}
///|
// For N * 10^p, p < 0, write the exact value as N / 5^-p * 2^p.
// Locate its binary exponent, divide at the required binary64 spacing, then
// round the exact quotient/remainder once, with midpoint ties to even.
// sticky is permitted only when the caller proves that a strictly positive
// discarded suffix cannot cross another midpoint; it moves a tie upward.
fn tiny_decimal_ratio(
numerator : @bigint.BigInt,
power : Int,
sticky? : Bool = false,
) -> Double {
let denominator = @bigint.BigInt::from_int(5).pow(
@bigint.BigInt::from_int(-power),
)
let mut ratio_exponent = numerator.bit_length() - denominator.bit_length()
let below = if ratio_exponent >= 0 {
numerator < denominator << ratio_exponent
} else {
numerator << -ratio_exponent < denominator
}
if below {
ratio_exponent -= 1
}
let mut exponent = ratio_exponent + power
if exponent < -1075 {
return 0.0
}
let subnormal = exponent < -1022
let shift = if subnormal { power + 1074 } else { 52 - ratio_exponent }
let numerator = if shift >= 0 { numerator << shift } else { numerator }
let denominator = if shift < 0 { denominator << -shift } else { denominator }
let quotient = numerator / denominator
let remainder = numerator - quotient * denominator
let twice = remainder << 1
let mut rounded = quotient.to_int64()
if twice > denominator ||
(twice == denominator && (sticky || rounded % 2L == 1L)) {
rounded += 1L
}
if subnormal {
return rounded.reinterpret_as_double()
}
if rounded == 9007199254740992L {
rounded = rounded >> 1
exponent += 1
}
(((exponent + 1023).to_int64() << 52) | (rounded - 4503599627370496L)).reinterpret_as_double()
}