Skip to main content

vyre_reference/
ieee754.rs

1//! IEEE 754 float rules enforced by the parity engine.
2//!
3//! Until vyre IR gains full float variants, this module acts as a strict guard:
4//! any code path that would require float semantics returns a deterministic error
5//! rather than falling back to undefined or driver-dependent behavior. When float
6//! support lands, this module will become the source of truth for rounding mode,
7//! NaN propagation, and subnormal handling that the conform gate checks.
8
9use vyre::Error;
10
11/// Maximum accepted reference-oracle error against correctly rounded f32
12/// transcendental results.
13pub const REFERENCE_TRANSCENDENTAL_ULP_BUDGET: u32 = 4;
14
15/// Maximum accepted backend-vs-reference error for programs containing f32
16/// transcendental operations.
17pub const BACKEND_TRANSCENDENTAL_ULP_BUDGET: u32 = 128;
18
19/// Maximum accepted backend-vs-reference error for f32 programs without
20/// transcendental operations under the default parity policy.
21pub const BACKEND_ELEMENTARY_F32_ULP_BUDGET: u32 = 4;
22
23/// Deterministic f32 sine used by the CPU parity oracle.
24///
25/// # Examples
26///
27/// ```
28/// let y = vyre_reference::ieee754::canonical_sin(0.0);
29/// assert_eq!(y.to_bits(), 0.0f32.to_bits());
30/// ```
31#[must_use]
32#[inline]
33pub fn canonical_sin(x: f32) -> f32 {
34    libm::sinf(x)
35}
36
37/// Deterministic f32 cosine used by the CPU parity oracle.
38///
39/// # Examples
40///
41/// ```
42/// let y = vyre_reference::ieee754::canonical_cos(0.0);
43/// assert_eq!(y.to_bits(), 1.0f32.to_bits());
44/// ```
45#[must_use]
46#[inline]
47pub fn canonical_cos(x: f32) -> f32 {
48    libm::cosf(x)
49}
50
51/// Deterministic f32 square root used by the CPU parity oracle.
52///
53/// # Examples
54///
55/// ```
56/// let y = vyre_reference::ieee754::canonical_sqrt(4.0);
57/// assert_eq!(y.to_bits(), 2.0f32.to_bits());
58/// ```
59#[must_use]
60#[inline]
61pub fn canonical_sqrt(x: f32) -> f32 {
62    libm::sqrtf(x)
63}
64
65/// Deterministic f32 inverse square root used by the CPU parity oracle.
66#[must_use]
67#[inline]
68pub fn canonical_inverse_sqrt(x: f32) -> f32 {
69    1.0 / canonical_sqrt(x)
70}
71
72/// Deterministic f32 reciprocal used by the CPU parity oracle.
73#[must_use]
74#[inline]
75pub fn canonical_reciprocal(x: f32) -> f32 {
76    canonical_f32(1.0 / canonical_f32(x))
77}
78
79/// Deterministic f32 exponential used by the CPU parity oracle.
80///
81/// # Examples
82///
83/// ```
84/// let y = vyre_reference::ieee754::canonical_exp(0.0);
85/// assert_eq!(y.to_bits(), 1.0f32.to_bits());
86/// ```
87#[must_use]
88#[inline]
89pub fn canonical_exp(x: f32) -> f32 {
90    libm::expf(x)
91}
92
93/// Deterministic f32 base-2 exponential used by the CPU parity oracle.
94#[must_use]
95#[inline]
96pub fn canonical_exp2(x: f32) -> f32 {
97    libm::exp2f(x)
98}
99
100/// Deterministic f32 natural logarithm used by the CPU parity oracle.
101///
102/// # Examples
103///
104/// ```
105/// let y = vyre_reference::ieee754::canonical_log(1.0);
106/// assert_eq!(y.to_bits(), 0.0f32.to_bits());
107/// ```
108#[must_use]
109#[inline]
110pub fn canonical_log(x: f32) -> f32 {
111    libm::logf(x)
112}
113
114/// Deterministic f32 base-2 logarithm used by the CPU parity oracle.
115#[must_use]
116#[inline]
117pub fn canonical_log2(x: f32) -> f32 {
118    libm::log2f(x)
119}
120
121/// Deterministic f32 tangent used by the CPU parity oracle.
122#[must_use]
123#[inline]
124pub fn canonical_tan(x: f32) -> f32 {
125    libm::tanf(x)
126}
127
128/// Deterministic f32 arc cosine used by the CPU parity oracle.
129#[must_use]
130#[inline]
131pub fn canonical_acos(x: f32) -> f32 {
132    libm::acosf(x)
133}
134
135/// Deterministic f32 arc sine used by the CPU parity oracle.
136#[must_use]
137#[inline]
138pub fn canonical_asin(x: f32) -> f32 {
139    libm::asinf(x)
140}
141
142/// Deterministic f32 arc tangent used by the CPU parity oracle.
143#[must_use]
144#[inline]
145pub fn canonical_atan(x: f32) -> f32 {
146    libm::atanf(x)
147}
148
149/// Deterministic f32 hyperbolic tangent used by the CPU parity oracle.
150#[must_use]
151#[inline]
152pub fn canonical_tanh(x: f32) -> f32 {
153    libm::tanhf(x)
154}
155
156/// Deterministic f32 hyperbolic sine used by the CPU parity oracle.
157#[must_use]
158#[inline]
159pub fn canonical_sinh(x: f32) -> f32 {
160    libm::sinhf(x)
161}
162
163/// Deterministic f32 hyperbolic cosine used by the CPU parity oracle.
164#[must_use]
165#[inline]
166pub fn canonical_cosh(x: f32) -> f32 {
167    libm::coshf(x)
168}
169
170/// Flush subnormal `f32` values to signed zero, preserving the sign bit.
171///
172/// This is the canonicalization step applied by the reference interpreter
173/// before and after every f32 operation so that GPU backends that flush
174/// subnormals, canonicalize NaN payloads, or preserve them all converge on
175/// the same deterministic bit pattern.
176#[must_use]
177#[inline]
178pub fn canonical_f32(value: f32) -> f32 {
179    crate::execution::typed_ops::canonical_f32(value)
180}
181
182/// Compute the deterministic ULP distance after vyre f32 canonicalization.
183///
184/// NaN payloads collapse to one quiet NaN bit pattern, subnormals flush to
185/// signed zero, and `+0.0`/`-0.0` compare at zero distance.
186#[must_use]
187#[inline]
188pub fn canonical_ulp_distance(left: f32, right: f32) -> u32 {
189    let left = canonical_f32(left);
190    let right = canonical_f32(right);
191    if left == right || left.to_bits() == right.to_bits() {
192        return 0;
193    }
194    ordered_f32_key(left).abs_diff(ordered_f32_key(right))
195}
196
197#[inline]
198fn ordered_f32_key(value: f32) -> u32 {
199    let bits = value.to_bits();
200    if bits & 0x8000_0000 == 0 {
201        bits | 0x8000_0000
202    } else {
203        !bits
204    }
205}
206
207/// Return the canonical float-pending error.
208///
209/// This function exists to make the reference interpreter intentionally fail on
210/// float operations until the parity engine has a complete, testable IEEE 754
211/// CPU reference to compare against GPU output.
212///
213/// # Examples
214///
215/// ```rust,ignore
216/// let err = vyre::reference::ieee754::pending_float_types();
217/// ```
218pub fn pending_float_types() -> Error {
219    Error::interp(
220        "pending upstream float variants in vyre::ir; reference interpreter is integer-only until those variants land",
221    )
222}
223
224#[cfg(test)]
225mod tests {
226    use super::*;
227
228    #[test]
229    fn canonical_f32_collapses_nan_payloads() {
230        let quiet_payload = f32::from_bits(0x7FC1_2345);
231        let signaling_payload = f32::from_bits(0x7F81_2345);
232        assert_eq!(canonical_f32(quiet_payload).to_bits(), 0x7FC0_0000);
233        assert_eq!(canonical_f32(signaling_payload).to_bits(), 0x7FC0_0000);
234        assert_eq!(canonical_ulp_distance(quiet_payload, signaling_payload), 0);
235    }
236
237    #[test]
238    fn canonical_ulp_distance_handles_zero_and_neighbors() {
239        assert_eq!(canonical_ulp_distance(0.0, -0.0), 0);
240        assert_eq!(
241            canonical_ulp_distance(1.0, f32::from_bits(1.0f32.to_bits() + 1)),
242            1
243        );
244    }
245}