6aef15f7e7
Lua numerals and the sscanf ports used Rust's correctly rounded parse. 1.12.1's C runtime rounds its own way: `__RoundMan` rounds up only when the round bit and some bit two past it are set (`0x40fb43`), so ties truncate and `6e23` reads one ulp low. It flushes results below 2^-1022 to +0 (`0x741ed3`) and keeps 24 digits (`0x41157f`). benilla-re holds these at proof two. One port of that conversion now lives in benilla-lua (`strtod.rs`, `ld12.rs`). It serves `str2d` and `%f`, which rounds once to float (`0x40feef`) and consumes a digitless exponent marker (`0x7451a5`). `%d` wraps in 32 bits (`0x7456c9`). The time zone is read once per process (`0x414568`), and two client faults are written as Deviations. A table of 3487 cases generated from benilla-re's transcription, and the exe's fixed cases, pin it. Fixes #2647 Fixes #2648 Fixes #2649 Fixes #2650 Fixes #2651 Fixes #2652 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
92 lines
3.3 KiB
Rust
92 lines
3.3 KiB
Rust
//! The client's `strtod` (`0x741e39`) and `_atoflt` (`0x40fecc`) against benilla-re's
|
|
//! transcription of them (`crates/crt/src/strtod.rs` at benilla-re 4a83799), which its
|
|
//! `crt_strtod_difftest` checks bit for bit against the exe. `strtod.tsv` is that transcription's
|
|
//! answer, run with the exe's tables of powers of ten, over the difftest's edge numerals, its fixed
|
|
//! cases, its numerals at the doubles' boundaries and 2000 of its random numerals: a line per
|
|
//! numeral, `\\` and `\xHH` escaped, then the `double`'s bits, the end index, `ERANGE` and the
|
|
//! bits `_atoflt` gives the same text.
|
|
|
|
use benilla_lua::strtod::{atoflt, strtod};
|
|
|
|
fn unescape(s: &str) -> Vec<u8> {
|
|
let b = s.as_bytes();
|
|
let mut out = Vec::new();
|
|
let mut i = 0;
|
|
while i < b.len() {
|
|
match (b[i], b.get(i + 1)) {
|
|
(b'\\', Some(b'x')) => {
|
|
out.push(u8::from_str_radix(&s[i + 2..i + 4], 16).expect("hex escape"));
|
|
i += 4;
|
|
}
|
|
(b'\\', Some(b'\\')) => {
|
|
out.push(b'\\');
|
|
i += 2;
|
|
}
|
|
(c, _) => {
|
|
out.push(c);
|
|
i += 1;
|
|
}
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
#[test]
|
|
fn strtod_and_atoflt_are_the_clients() {
|
|
let table = include_str!("strtod.tsv");
|
|
let mut n = 0;
|
|
for line in table.lines() {
|
|
let f: Vec<&str> = line.split('\t').collect();
|
|
let [text, bits, end, erange, float] = f[..] else {
|
|
panic!("a line of five fields: {line:?}");
|
|
};
|
|
let s = unescape(text);
|
|
let r = strtod(&s);
|
|
let want = u64::from_str_radix(bits, 16).unwrap();
|
|
assert_eq!(r.value.to_bits(), want, "strtod({text:?}) value");
|
|
assert_eq!(r.end, end.parse::<usize>().unwrap(), "strtod({text:?}) end");
|
|
assert_eq!(r.erange, erange == "1", "strtod({text:?}) ERANGE");
|
|
let want = u32::from_str_radix(float, 16).unwrap();
|
|
assert_eq!(atoflt(&s).to_bits(), want, "atoflt({text:?})");
|
|
n += 1;
|
|
}
|
|
assert_eq!(n, 3487);
|
|
}
|
|
|
|
/// The difftest's fixed cases, the exe's answers: a round to the double below the nearest, a
|
|
/// result just under and just at 2^-1022, the 12-byte real's flush to a signed zero and the
|
|
/// underflows either side of it, and 30 digits read as 24.
|
|
#[test]
|
|
fn strtod_fixed_cases() {
|
|
let fixed: &[(&str, u64, bool)] = &[
|
|
("6e23", 0x44df_c384_2bd1_f071, false),
|
|
("2.2250738585072011e-308", 0, true),
|
|
("2.2250738585072013e-308", 0x0010_0000_0000_0000, false),
|
|
("1e-4950", 0, true),
|
|
("-1e-5000", 0x8000_0000_0000_0000, false),
|
|
("1e-5201", 0, true),
|
|
("-1e-400", 0, true),
|
|
(
|
|
"123456789012345678901234567890",
|
|
0x45f8_ee90_ff6c_373e,
|
|
false,
|
|
),
|
|
];
|
|
for &(text, bits, erange) in fixed {
|
|
let r = strtod(text.as_bytes());
|
|
assert_eq!(
|
|
(r.value.to_bits(), r.erange),
|
|
(bits, erange),
|
|
"strtod({text:?})"
|
|
);
|
|
}
|
|
}
|
|
|
|
/// `_atoflt` rounds the 12-byte real once, straight to the `float`: `460e8` is 45999996928, where
|
|
/// a `double` rounded again to a `float` is 46000001024.
|
|
#[test]
|
|
fn atoflt_rounds_once() {
|
|
assert_eq!(atoflt(b"460e8"), 45_999_996_928.0);
|
|
assert_ne!(atoflt(b"460e8"), 460e8_f64 as f32);
|
|
}
|