Skip to content

Commit a49ee31

Browse files
authored
Merge pull request #135 from flyingrobots/F32Scalars/subnormals
feat: Flush subnormals in F32Scalar
2 parents b1d2123 + 770a80f commit a49ee31

File tree

4 files changed

+41
-5
lines changed

4 files changed

+41
-5
lines changed

crates/rmg-core/src/math/scalar.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ impl F32Scalar {
136136
Self {
137137
value: f32::from_bits(0x7fc0_0000),
138138
}
139+
} else if num.is_subnormal() {
140+
Self {
141+
value: f32::from_bits(0),
142+
}
139143
} else {
140144
// Canonicalize -0.0 to +0.0
141145
Self { value: num + 0.0 }

crates/rmg-core/tests/determinism_policy_tests.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ fn test_policy_nan_canonicalization() {
4848
);
4949
}
5050

51-
/*
5251
#[test]
5352
fn test_policy_subnormal_flushing() {
5453
// Smallest positive subnormal: 1 bit set in mantissa, 0 exponent
@@ -60,15 +59,41 @@ fn test_policy_subnormal_flushing() {
6059
let s2 = F32Scalar::new(large_sub);
6160

6261
// Must be flushed to positive zero
63-
assert_eq!(s1.to_f32().to_bits(), 0, "Small subnormal not flushed to +0.0");
64-
assert_eq!(s2.to_f32().to_bits(), 0, "Large subnormal not flushed to +0.0");
62+
assert_eq!(
63+
s1.to_f32().to_bits(),
64+
0,
65+
"Small subnormal not flushed to +0.0"
66+
);
67+
assert_eq!(
68+
s2.to_f32().to_bits(),
69+
0,
70+
"Large subnormal not flushed to +0.0"
71+
);
72+
assert_eq!(
73+
s1.to_f32().to_bits(),
74+
F32Scalar::ZERO.to_f32().to_bits(),
75+
"Small subnormal is not equal to ZERO"
76+
);
77+
assert_eq!(
78+
s2.to_f32().to_bits(),
79+
F32Scalar::ZERO.to_f32().to_bits(),
80+
"Large subnormal is not equal to ZERO"
81+
);
6582

6683
// Verify negative subnormals go to +0.0 too (since -0.0 -> +0.0)
6784
let neg_sub = f32::from_bits(0x80000001);
6885
let s3 = F32Scalar::new(neg_sub);
69-
assert_eq!(s3.to_f32().to_bits(), 0, "Negative subnormal not flushed to +0.0");
86+
assert_eq!(
87+
s3.to_f32().to_bits(),
88+
0,
89+
"Negative subnormal not flushed to +0.0"
90+
);
91+
assert_eq!(
92+
s3.to_f32().to_bits(),
93+
F32Scalar::ZERO.to_f32().to_bits(),
94+
"Negative subnormal is not equal to ZERO"
95+
);
7096
}
71-
*/
7297

7398
#[test]
7499
#[cfg(feature = "serde")]

docs/decision-log.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
| Date | Context | Decision | Rationale | Consequence |
88
| ---- | ------- | -------- | --------- | ----------- |
99
| 2025-11-29 | LICENSE | Add SPDX headers to all files | LEGAL PROTECTION 🛡️✨ |
10+
| 2025-11-30 | `F32Scalar` | Canonicalize subnormal values to zero | Now, they're zero. |
1011
| 2025-12-01 | `Cargo.toml` bench profile | Remove `panic = "abort"` from `[profile.bench]`. | Silence cargo warning as this setting is ignored for bench profiles; rely on release profile inheritance or default behavior. | Cleaner build output; no behavior change for benches (which default to unwind or abort based on target/inheritance). |
1112
| 2025-11-30 | `F32Scalar` NaN reflexivity | Update `PartialEq` implementation to use `total_cmp` (via `Ord`) instead of `f32::eq`. | Ensures `Eq` reflexivity holds even for NaN (`NaN == NaN`), consistent with `Ord`. Prevents violations of the `Eq` contract in collections. | `F32Scalar` now behaves as a totally ordered type; NaN values are considered equal to themselves and comparable. |
1213
| 2025-11-30 | `F32Scalar` NaN canonicalization | Canonicalize all NaNs to `0x7fc00000` in `F32Scalar::new`. Update MSRV to 1.83.0 for `const` `is_nan`/`from_bits`. | Guarantees bit-level determinism for all float values including error states. Unifies representation across platforms. MSRV bump enables safe, readable `const` implementation. | All NaNs are now bitwise identical; `const fn` constructors remain available; `rmg-core` requires Rust 1.83.0+. |

docs/execution-plan.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ This is Codex’s working map for building Echo. Update it relentlessly—each s
4242
- Changes: Removed `panic = "abort"` from `[profile.bench]`.
4343
- Status: Completed; warning no longer appears.
4444

45+
> 2025-11-30 – Handle subnormal f32 values in F32Scalar
46+
47+
- Goal: Canonicalize subnormal f32 values to zero.
48+
- Scope: subnormals, F32Scalars.
49+
- Plan: Make 'em zero.
50+
4551
> 2025-12-01 — Fix “How Echo Works” LaTeX build (non-interactive PDF)
4652
4753
- Goal: unblock `docs/guides/how-do-echo-work` PDF generation without interactive TeX prompts.

0 commit comments

Comments
 (0)