The JIT that runs makepad apps from source: lexer, front end, type checker, lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and warm/cold caches keyed by compiler stamp, provided crates bound late, startup images, live patching. User-facing name is `makepad run` (binary `makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal library name. `makepad check` reports rustc-style diagnostics with rustc error codes and checks a whole workspace (`--workspace`). Runs on macOS, Linux, Windows and Android. makepad-remote: one tool schema for apps and the WM (app/1): grabs, captures, traces, frame profiles and tweaks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
281 lines
10 KiB
Rust
281 lines
10 KiB
Rust
//! Float Display/Debug/LowerExp/UpperExp: Relax's fmt vs real format!, on edge cases and
|
|
//! large randomized corpora (f64 and f32).
|
|
|
|
mod fmt_common;
|
|
|
|
use fmt_common::*;
|
|
use relax_std_check::fmt as hf;
|
|
use relax_std_check::fmt::rt;
|
|
use std::fmt as sf;
|
|
|
|
const P: u32 = rt::FLAG_PLUS;
|
|
const Z: u32 = rt::FLAG_ZERO_PAD;
|
|
const A: u32 = rt::FLAG_ALTERNATE;
|
|
const L: u8 = rt::ALIGN_LEFT;
|
|
const R: u8 = rt::ALIGN_RIGHT;
|
|
const C: u8 = rt::ALIGN_CENTER;
|
|
const U: u8 = rt::ALIGN_UNKNOWN;
|
|
|
|
#[derive(Clone, Copy)]
|
|
enum Tr {
|
|
Display,
|
|
Debug,
|
|
LowerExp,
|
|
UpperExp,
|
|
}
|
|
|
|
const NCASES: usize = 23;
|
|
|
|
/// (trait, spec) of case `i` for width `w` and precision `p`; real_case renders the same.
|
|
fn case(i: usize, w: usize, p: usize) -> (Tr, Spec) {
|
|
match i {
|
|
0 => (Tr::Display, DEF),
|
|
1 => (Tr::Display, spec(' ', U, 0, None, Some(p))),
|
|
2 => (Tr::Display, spec(' ', U, 0, Some(w), None)),
|
|
3 => (Tr::Display, spec(' ', U, 0, Some(w), Some(p))),
|
|
4 => (Tr::Display, spec(' ', L, 0, Some(w), None)),
|
|
5 => (Tr::Display, spec(' ', C, 0, Some(w), Some(p))),
|
|
6 => (Tr::Display, spec('*', R, 0, Some(w), None)),
|
|
7 => (Tr::Display, spec(' ', U, P, None, None)),
|
|
8 => (Tr::Display, spec(' ', U, P, None, Some(p))),
|
|
9 => (Tr::Display, spec(' ', U, Z, Some(w), None)),
|
|
10 => (Tr::Display, spec(' ', U, P | Z, Some(w), Some(p))),
|
|
11 => (Tr::Debug, DEF),
|
|
12 => (Tr::Debug, spec(' ', U, 0, None, Some(p))),
|
|
13 => (Tr::Debug, spec(' ', U, A, None, None)),
|
|
14 => (Tr::LowerExp, DEF),
|
|
15 => (Tr::UpperExp, DEF),
|
|
16 => (Tr::LowerExp, spec(' ', U, 0, None, Some(p))),
|
|
17 => (Tr::UpperExp, spec(' ', U, P, Some(w), Some(p))),
|
|
18 => (Tr::LowerExp, spec(' ', U, Z, Some(w), None)),
|
|
19 => (Tr::Debug, spec(' ', U, P, None, None)),
|
|
20 => (Tr::Debug, spec(' ', U, Z, Some(10), None)),
|
|
21 => (Tr::Debug, spec('x', L, 0, Some(w), None)),
|
|
_ => (Tr::Display, spec('_', C, Z, Some(w), None)),
|
|
}
|
|
}
|
|
|
|
fn real_case<T: sf::Display + sf::Debug + sf::LowerExp + sf::UpperExp>(i: usize, v: T, w: usize, p: usize) -> String {
|
|
match i {
|
|
0 => format!("{}", v),
|
|
1 => format!("{:.p$}", v, p = p),
|
|
2 => format!("{:w$}", v, w = w),
|
|
3 => format!("{:w$.p$}", v, w = w, p = p),
|
|
4 => format!("{:<w$}", v, w = w),
|
|
5 => format!("{:^w$.p$}", v, w = w, p = p),
|
|
6 => format!("{:*>w$}", v, w = w),
|
|
7 => format!("{:+}", v),
|
|
8 => format!("{:+.p$}", v, p = p),
|
|
9 => format!("{:0w$}", v, w = w),
|
|
10 => format!("{:+0w$.p$}", v, w = w, p = p),
|
|
11 => format!("{:?}", v),
|
|
12 => format!("{:.p$?}", v, p = p),
|
|
13 => format!("{:#?}", v),
|
|
14 => format!("{:e}", v),
|
|
15 => format!("{:E}", v),
|
|
16 => format!("{:.p$e}", v, p = p),
|
|
17 => format!("{:+w$.p$E}", v, w = w, p = p),
|
|
18 => format!("{:0w$e}", v, w = w),
|
|
19 => format!("{:+?}", v),
|
|
20 => format!("{:010?}", v),
|
|
21 => format!("{:x<w$?}", v, w = w),
|
|
_ => format!("{:_^0w$}", v, w = w),
|
|
}
|
|
}
|
|
|
|
fn mine64(v: f64, i: usize, w: usize, p: usize) -> String {
|
|
let (tr, s) = case(i, w, p);
|
|
match tr {
|
|
Tr::Display => render(&v, <f64 as hf::Display>::fmt, &s),
|
|
Tr::Debug => render(&v, <f64 as hf::Debug>::fmt, &s),
|
|
Tr::LowerExp => render(&v, <f64 as hf::LowerExp>::fmt, &s),
|
|
Tr::UpperExp => render(&v, <f64 as hf::UpperExp>::fmt, &s),
|
|
}
|
|
}
|
|
|
|
fn mine32(v: f32, i: usize, w: usize, p: usize) -> String {
|
|
let (tr, s) = case(i, w, p);
|
|
match tr {
|
|
Tr::Display => render(&v, <f32 as hf::Display>::fmt, &s),
|
|
Tr::Debug => render(&v, <f32 as hf::Debug>::fmt, &s),
|
|
Tr::LowerExp => render(&v, <f32 as hf::LowerExp>::fmt, &s),
|
|
Tr::UpperExp => render(&v, <f32 as hf::UpperExp>::fmt, &s),
|
|
}
|
|
}
|
|
|
|
struct Fails {
|
|
n: usize,
|
|
shown: usize,
|
|
}
|
|
|
|
impl Fails {
|
|
fn check(&mut self, what: &str, got: String, want: String) {
|
|
if got != want {
|
|
self.n += 1;
|
|
if self.shown < 25 {
|
|
self.shown += 1;
|
|
eprintln!("MISMATCH {}: got {:?} want {:?}", what, got, want);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn edge64() -> Vec<f64> {
|
|
let mut v = vec![
|
|
0.0, -0.0, 1.0, -1.0, 0.1, 0.2, 0.3, 0.5, 1.5, 2.5, -2.5, 0.25, 0.125, 0.05, 0.15, 0.35, 0.45, 9.5, 99.5,
|
|
999.9999, 1e-4, 9.9999e-5, 1e-5, 1e16, 9.999999999999998e15, 1e15, 1e17, 1e21, 1e22, 1e23, 1e100, 1e300,
|
|
1e308, 1.7976931348623157e308, 2.2250738585072014e-308, 2.225073858507201e-308, 5e-324, 1e-323, 4.9e-324,
|
|
f64::EPSILON, f64::MIN_POSITIVE, f64::MAX, f64::MIN, f64::NAN, -f64::NAN, f64::INFINITY, f64::NEG_INFINITY,
|
|
123456789.0, 0.1 + 0.2, 1.0 / 3.0, 2.0 / 3.0, 100.0, 1234.5, 0.000123, 12345678901234567890.0,
|
|
9007199254740992.0, 9007199254740993.0, 4503599627370496.5, 0.30000000000000004, 3.141592653589793,
|
|
2.718281828459045, 6.02214076e23, 1.602176634e-19, 299792458.0, 0.999999999999, 0.9999999999999999,
|
|
];
|
|
let mut x = 1.0f64;
|
|
for _ in 0..64 {
|
|
v.push(x);
|
|
v.push(1.0 / x);
|
|
x *= 10.0;
|
|
}
|
|
let mut e = -1074i32;
|
|
while e <= 1023 {
|
|
v.push(2f64.powi(e));
|
|
e += 7;
|
|
}
|
|
v
|
|
}
|
|
|
|
fn edge32() -> Vec<f32> {
|
|
let mut v = vec![
|
|
0.0, -0.0, 1.0, -1.0, 0.1, 0.2, 0.3, 0.5, 1.5, 2.5, 0.25, 0.05, 0.15, 9.5, 1e-4, 9.9999e-5, 1e16, 1e15,
|
|
1e17, 1e38, 3.4028235e38, 1.1754944e-38, 1e-45, 1.4e-45, f32::EPSILON, f32::MIN_POSITIVE, f32::MAX, f32::MIN,
|
|
f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 16777216.0, 16777217.0, 3.1415927, 0.33333334, 123456.79,
|
|
];
|
|
let mut e = -149i32;
|
|
while e <= 127 {
|
|
v.push(2f32.powi(e));
|
|
e += 3;
|
|
}
|
|
v
|
|
}
|
|
|
|
#[test]
|
|
fn float_edge_cases() {
|
|
let mut fails = Fails { n: 0, shown: 0 };
|
|
let mut total = 0;
|
|
let wps = [(0usize, 0usize), (1, 1), (8, 3), (12, 0), (25, 17), (30, 25), (5, 60)];
|
|
for &v in &edge64() {
|
|
for i in 0..NCASES {
|
|
for &(w, p) in &wps {
|
|
total += 1;
|
|
fails.check(&format!("f64 {:e} case {} w{} p{}", v, i, w, p), mine64(v, i, w, p), real_case(i, v, w, p));
|
|
}
|
|
}
|
|
}
|
|
for &v in &edge32() {
|
|
for i in 0..NCASES {
|
|
for &(w, p) in &wps {
|
|
total += 1;
|
|
fails.check(&format!("f32 {:e} case {} w{} p{}", v, i, w, p), mine32(v, i, w, p), real_case(i, v, w, p));
|
|
}
|
|
}
|
|
}
|
|
eprintln!("float_edge_cases: {} comparisons, {} mismatches", total, fails.n);
|
|
assert_eq!(fails.n, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn float_large_precision() {
|
|
let mut fails = Fails { n: 0, shown: 0 };
|
|
let vals = [5e-324, 2.2250738585072014e-308, 1e-300, 0.1, 1.0 / 3.0, 1e300, f64::MAX, 123.456];
|
|
for &v in &vals {
|
|
for &p in &[50usize, 100, 340, 767, 800, 1100, 1500] {
|
|
for i in [1usize, 12, 16] {
|
|
fails.check(&format!("f64 {:e} case {} p{}", v, i, p), mine64(v, i, 0, p), real_case(i, v, 0, p));
|
|
}
|
|
}
|
|
}
|
|
assert_eq!(fails.n, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn float_param_counts() {
|
|
let mut fails = Fails { n: 0, shown: 0 };
|
|
for &v in &[0.1f64, -3.75, 1e20, 5e-324] {
|
|
for w in [0usize, 7, 30] {
|
|
for p in [0usize, 2, 9] {
|
|
let got = render_param(&v, <f64 as hf::Display>::fmt, w, p);
|
|
let want = format!("<{:w$.p$}>", v, w = w, p = p);
|
|
fails.check("param", got, want);
|
|
}
|
|
}
|
|
}
|
|
assert_eq!(fails.n, 0);
|
|
}
|
|
|
|
fn random_f64(r: &mut Rng) -> f64 {
|
|
match r.below(6) {
|
|
0 => f64::from_bits(r.next()),
|
|
1 => f64::from_bits(r.next() & 0x000f_ffff_ffff_ffff), // subnormal
|
|
2 => (r.below(2_000_000) as f64) / 10f64.powi(r.below(12) as i32), // short decimals
|
|
3 => (r.below(1 << 53) as f64) * 2f64.powi(r.below(200) as i32 - 100),
|
|
4 => {
|
|
// near powers of ten
|
|
let p = 10f64.powi(r.below(600) as i32 - 300);
|
|
f64::from_bits(p.to_bits().wrapping_add(r.below(5)).wrapping_sub(2))
|
|
}
|
|
_ => (r.next() as f64) * if r.below(2) == 0 { 1.0 } else { -1e-9 },
|
|
}
|
|
}
|
|
|
|
fn random_f32(r: &mut Rng) -> f32 {
|
|
match r.below(4) {
|
|
0 => f32::from_bits(r.next() as u32),
|
|
1 => f32::from_bits((r.next() as u32) & 0x007f_ffff),
|
|
2 => (r.below(200_000) as f32) / 10f32.powi(r.below(8) as i32),
|
|
_ => (r.below(1 << 24) as f32) * 2f32.powi(r.below(80) as i32 - 40),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn float_random_f64() {
|
|
let mut r = Rng(0x9E3779B97F4A7C15);
|
|
let mut fails = Fails { n: 0, shown: 0 };
|
|
let mut total = 0;
|
|
for _ in 0..300_000 {
|
|
let v = random_f64(&mut r);
|
|
let i = r.below(NCASES as u64) as usize;
|
|
let w = r.below(40) as usize;
|
|
let p = if r.below(10) == 0 { r.below(120) as usize } else { r.below(20) as usize };
|
|
total += 1;
|
|
fails.check(&format!("f64 bits {:#x} case {} w{} p{}", v.to_bits(), i, w, p), mine64(v, i, w, p), real_case(i, v, w, p));
|
|
}
|
|
// shortest Display/Debug/exp on every value of a second stream
|
|
for _ in 0..300_000 {
|
|
let v = random_f64(&mut r);
|
|
for i in [0usize, 11, 14] {
|
|
total += 1;
|
|
fails.check(&format!("f64 bits {:#x} case {}", v.to_bits(), i), mine64(v, i, 0, 0), real_case(i, v, 0, 0));
|
|
}
|
|
}
|
|
eprintln!("float_random_f64: {} comparisons, {} mismatches", total, fails.n);
|
|
assert_eq!(fails.n, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn float_random_f32() {
|
|
let mut r = Rng(0xD1B54A32D192ED03);
|
|
let mut fails = Fails { n: 0, shown: 0 };
|
|
let mut total = 0;
|
|
for _ in 0..300_000 {
|
|
let v = random_f32(&mut r);
|
|
let i = r.below(NCASES as u64) as usize;
|
|
let w = r.below(40) as usize;
|
|
let p = r.below(30) as usize;
|
|
total += 1;
|
|
fails.check(&format!("f32 bits {:#x} case {} w{} p{}", v.to_bits(), i, w, p), mine32(v, i, w, p), real_case(i, v, w, p));
|
|
total += 1;
|
|
fails.check(&format!("f32 bits {:#x} case 0", v.to_bits()), mine32(v, 0, 0, 0), real_case(0, v, 0, 0));
|
|
}
|
|
eprintln!("float_random_f32: {} comparisons, {} mismatches", total, fails.n);
|
|
assert_eq!(fails.n, 0);
|
|
}
|