floats iter 1.3: bit-stability tests for Literal::Float

This commit is contained in:
2026-05-10 14:45:34 +02:00
parent 93bae2d02a
commit 7c95a69780
+84
View File
@@ -152,4 +152,88 @@ mod tests {
let s = std::str::from_utf8(&bytes).unwrap();
assert_eq!(s, r#"{"bits":"3ff8000000000000","kind":"float"}"#);
}
/// A5 guarantee: -0.0 and +0.0 are distinct bit patterns and
/// therefore distinct Form-A literals (they hash distinctly even
/// though IEEE-`==` will report them equal at the value level
/// when arithmetic comparison ships).
#[test]
fn negative_zero_and_positive_zero_serialise_distinctly() {
use crate::ast::Literal;
let pos = Literal::Float { bits: 0x0u64 };
let neg = Literal::Float { bits: 0x8000_0000_0000_0000u64 };
let pos_bytes = to_bytes(&pos);
let neg_bytes = to_bytes(&neg);
assert_ne!(pos_bytes, neg_bytes, "+0 and -0 must hash distinctly");
assert_eq!(
std::str::from_utf8(&pos_bytes).unwrap(),
r#"{"bits":"0000000000000000","kind":"float"}"#
);
assert_eq!(
std::str::from_utf8(&neg_bytes).unwrap(),
r#"{"bits":"8000000000000000","kind":"float"}"#
);
}
/// A1 guarantee: a NaN bit pattern survives canonicalisation
/// without collapse to JSON `null` (which is what the `serde_json`
/// number path does for non-finite floats — and the reason
/// `Literal::Float` routes through the *string* path).
#[test]
fn nan_bits_preserved() {
use crate::ast::Literal;
let qnan = Literal::Float { bits: 0x7ff8_0000_0000_0000u64 };
let bytes = to_bytes(&qnan);
assert_eq!(
std::str::from_utf8(&bytes).unwrap(),
r#"{"bits":"7ff8000000000000","kind":"float"}"#
);
}
/// A1 guarantee: ±Inf bit patterns survive canonicalisation —
/// JSON numbers cannot represent infinity at all, the string path
/// must.
#[test]
fn inf_bits_preserved() {
use crate::ast::Literal;
let pos_inf = Literal::Float { bits: 0x7ff0_0000_0000_0000u64 };
let neg_inf = Literal::Float { bits: 0xfff0_0000_0000_0000u64 };
assert_eq!(
std::str::from_utf8(&to_bytes(&pos_inf)).unwrap(),
r#"{"bits":"7ff0000000000000","kind":"float"}"#
);
assert_eq!(
std::str::from_utf8(&to_bytes(&neg_inf)).unwrap(),
r#"{"bits":"fff0000000000000","kind":"float"}"#
);
}
/// Round-trip: serialise then deserialise via `serde_json` — bits
/// preserved bit-for-bit.
#[test]
fn float_literal_serde_roundtrip() {
use crate::ast::Literal;
let cases = [
0x0u64,
0x8000_0000_0000_0000u64,
0x3ff8_0000_0000_0000u64, // 1.5
0x7ff8_0000_0000_0000u64, // qNaN
0x7ff0_0000_0000_0000u64, // +Inf
0xfff0_0000_0000_0000u64, // -Inf
0xffff_ffff_ffff_ffffu64, // saturated
];
for &bits in &cases {
let lit = Literal::Float { bits };
let json = serde_json::to_string(&lit).unwrap();
let back: Literal = serde_json::from_str(&json).unwrap();
match back {
Literal::Float { bits: got } => assert_eq!(
got, bits,
"round-trip lost bits for {:#018x}: json={}",
bits, json
),
other => panic!("expected Literal::Float, got {:?}", other),
}
}
}
}