Skip to main content

handwired_rs/
numeric.rs

1//! FP32 helpers shared by the builder, the evaluators and the compiler.
2//!
3//! Builder-side arithmetic (ranges, coefficients, biases) runs in f64 and
4//! rounds to f32 at the same points as handwired's `Math.fround`, so a range
5//! inferred here matches the one handwired infers. Evaluation runs in native
6//! f32, which rounds every `+` and `*` exactly like `Math.fround` of the f64
7//! result, and Rust never fuses them into a multiply-add on its own.
8
9/// `Math.fround`: round an f64 to the nearest f32, ties to even.
10pub(crate) fn fround(value: f64) -> f64 {
11    value as f32 as f64
12}
13
14/// The range of values a wire may hold. An open side is infinite.
15#[derive(Copy, Clone, Debug, PartialEq)]
16pub struct Range {
17    pub min: f64,
18    pub max: f64,
19}
20
21impl Range {
22    pub const UNKNOWN: Range = Range { min: f64::NEG_INFINITY, max: f64::INFINITY };
23
24    pub fn new(min: f64, max: f64) -> Range {
25        Range { min, max }
26    }
27
28    pub fn is_finite(&self) -> bool {
29        self.min.is_finite() && self.max.is_finite()
30    }
31}
32
33/// FP32 holds every integer up to 2^24 exactly, so sums on a grid stay exact
34/// within this many grid steps.
35pub(crate) const EXACT_GRID_STEPS: f64 = 16_777_216.0;
36pub(crate) fn smallest_grid() -> f64 {
37    pow2(-149)
38}
39pub(crate) fn largest_grid() -> f64 {
40    pow2(127)
41}
42
43/// 2^exponent as an f64, exact for every exponent an f64 can hold, including
44/// subnormal ones, where `powi` would round through an overflowing reciprocal.
45pub(crate) fn pow2(exponent: i32) -> f64 {
46    if exponent > 1023 {
47        f64::INFINITY
48    } else if exponent >= -1022 {
49        f64::from_bits(((exponent + 1023) as u64) << 52)
50    } else if exponent >= -1074 {
51        f64::from_bits(1u64 << (exponent + 1074))
52    } else {
53        0.0
54    }
55}
56
57/// The largest power of two dividing a finite nonzero value, e.g. 4 for 12 and
58/// 0.25 for 0.75.
59pub(crate) fn lowest_power_of_two(value: f64) -> f64 {
60    debug_assert!(value.is_finite() && value != 0.0);
61    let bits = value.to_bits();
62    let biased_exponent = ((bits >> 52) & 0x7ff) as i32;
63    let fraction = bits & ((1u64 << 52) - 1);
64    if biased_exponent == 0 {
65        pow2(-1074 + fraction.trailing_zeros() as i32)
66    } else {
67        let significand = fraction | (1u64 << 52);
68        pow2(biased_exponent - 1075 + significand.trailing_zeros() as i32)
69    }
70}
71
72pub(crate) fn is_power_of_two_grid(step: f64) -> bool {
73    step.is_finite() && step >= smallest_grid() && step <= largest_grid() && lowest_power_of_two(step) == step
74}
75
76/// The smallest integer e with 2^e >= value, for a finite positive value.
77pub(crate) fn power_of_two_exponent_at_least(value: f64) -> i32 {
78    let mut exponent = value.log2().ceil() as i32;
79    while pow2(exponent) < value {
80        exponent += 1;
81    }
82    while pow2(exponent - 1) >= value {
83        exponent -= 1;
84    }
85    exponent
86}
87
88/// JavaScript's `Math.max(0, x)` on an f32: NaN stays NaN, and -0 becomes +0.
89pub(crate) fn relu32(value: f32) -> f32 {
90    if value > 0.0 {
91        value
92    } else if value.is_nan() {
93        f32::NAN
94    } else {
95        0.0
96    }
97}
98
99/// The boolean atom: `(score + 8,388,609) - 8,388,609` in FP32 rounds a
100/// score within 0..1 to 0 below 0.5 and to 1 from 0.5 up.
101pub(crate) fn evaluate_boolean(score: f32) -> f32 {
102    const BASE: f32 = 8_388_609.0;
103    (score + BASE) - BASE
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn lowest_power_of_two_matches_handwired() {
112        assert_eq!(lowest_power_of_two(12.0), 4.0);
113        assert_eq!(lowest_power_of_two(0.75), 0.25);
114        assert_eq!(lowest_power_of_two(-3.0), 1.0);
115        assert_eq!(lowest_power_of_two(pow2(-149)), pow2(-149));
116        assert_eq!(lowest_power_of_two(f64::from_bits(6)), pow2(-1073));
117    }
118
119    #[test]
120    fn boolean_atom_rounds_at_one_half() {
121        assert_eq!(evaluate_boolean(0.5), 1.0);
122        assert_eq!(evaluate_boolean(0.5 - 2f32.powi(-25)), 0.0);
123        assert_eq!(evaluate_boolean(0.0), 0.0);
124        assert_eq!(evaluate_boolean(1.0), 1.0);
125    }
126
127    #[test]
128    fn exponent_at_least() {
129        assert_eq!(power_of_two_exponent_at_least(16_384.0), 14);
130        assert_eq!(power_of_two_exponent_at_least(16_385.0), 15);
131        assert_eq!(power_of_two_exponent_at_least(pow2(-149)), -149);
132    }
133}