floats iter 1.3: bit-stability tests for Literal::Float
This commit is contained in:
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user