Skip to main content

machina_softfloat/ops/
fma.rs

1// SPDX-License-Identifier: MIT
2// IEEE 754 fused multiply-add: a*b + c with single rounding.
3
4use crate::env::{ExcFlags, FloatEnv, RoundMode};
5use crate::parts::{return_nan, round_pack, unpack, FloatClass, FloatParts};
6use crate::types::{
7    BFloat16, Float128, Float16, Float32, Float64, FloatFormat, FloatX80,
8};
9
10const INT_BIT: u32 = 126;
11
12/// Fused multiply-add: a * b + c with a single rounding step.
13pub fn fma<F: FloatFormat>(a: F, b: F, c: F, env: &mut FloatEnv) -> F {
14    let pa = unpack::<F>(a);
15    let pb = unpack::<F>(b);
16    let pc = unpack::<F>(c);
17    fma_parts::<F>(&pa, &pb, &pc, env)
18}
19
20fn fma_parts<F: FloatFormat>(
21    a: &FloatParts,
22    b: &FloatParts,
23    c: &FloatParts,
24    env: &mut FloatEnv,
25) -> F {
26    let ab_sign = a.sign ^ b.sign;
27
28    // NaN propagation: check all three operands.
29    if a.is_nan() || b.is_nan() || c.is_nan() {
30        // 3-operand NaN propagation.
31        if a.cls == FloatClass::SNaN
32            || b.cls == FloatClass::SNaN
33            || c.cls == FloatClass::SNaN
34        {
35            env.raise(ExcFlags::INVALID);
36        }
37        // Also check for Inf * 0 (INVALID even with NaN c).
38        if (a.cls == FloatClass::Inf && b.cls == FloatClass::Zero)
39            || (a.cls == FloatClass::Zero && b.cls == FloatClass::Inf)
40        {
41            env.raise(ExcFlags::INVALID);
42        }
43        // Pick a NaN to propagate.
44        let mut r = if a.is_nan() {
45            *a
46        } else if b.is_nan() {
47            *b
48        } else {
49            *c
50        };
51        if r.cls == FloatClass::SNaN {
52            r.cls = FloatClass::QNaN;
53            r.frac |= 1u128 << (INT_BIT - 1);
54        }
55        if env.default_nan() {
56            r = FloatParts::default_nan::<F>();
57        }
58        return round_pack::<F>(&mut r, env);
59    }
60
61    // Inf * 0 + c = NaN (INVALID)
62    if (a.cls == FloatClass::Inf && b.cls == FloatClass::Zero)
63        || (a.cls == FloatClass::Zero && b.cls == FloatClass::Inf)
64    {
65        return return_nan::<F>(env);
66    }
67
68    // Inf * x + c
69    if a.cls == FloatClass::Inf || b.cls == FloatClass::Inf {
70        if c.cls == FloatClass::Inf && c.sign != ab_sign {
71            // Inf + (-Inf) = NaN
72            return return_nan::<F>(env);
73        }
74        let mut r = FloatParts {
75            sign: ab_sign,
76            exp: 0,
77            frac: 0,
78            cls: FloatClass::Inf,
79        };
80        return round_pack::<F>(&mut r, env);
81    }
82
83    // a*b is finite. If c is Inf, result is c.
84    if c.cls == FloatClass::Inf {
85        let mut r = *c;
86        return round_pack::<F>(&mut r, env);
87    }
88
89    // a*b when one of a,b is zero.
90    let ab_zero = a.cls == FloatClass::Zero || b.cls == FloatClass::Zero;
91
92    if ab_zero {
93        if c.cls == FloatClass::Zero {
94            let sign = if ab_sign == c.sign {
95                ab_sign
96            } else {
97                env.round_mode() == RoundMode::Down
98            };
99            let mut r = FloatParts {
100                sign,
101                exp: 0,
102                frac: 0,
103                cls: FloatClass::Zero,
104            };
105            return round_pack::<F>(&mut r, env);
106        }
107        let mut r = *c;
108        return round_pack::<F>(&mut r, env);
109    }
110
111    // Both a,b are normal. Compute product a*b.
112    let p_exp = a.exp + b.exp;
113
114    // u128 * u128 -> (hi, lo) 256-bit product.
115    let (p_hi, p_lo) = mul_u128(a.frac, b.frac);
116
117    // Product integer bits are at position 252 of the
118    // 256-bit result = bit 124 of p_hi. Shift so integer
119    // bit is at INT_BIT (126) of p_hi.
120    let p_frac = (p_hi << 2) | (p_lo >> 126);
121    let p_sticky = if p_lo & ((1u128 << 126) - 1) != 0 {
122        1
123    } else {
124        0
125    };
126    let p_frac = p_frac | p_sticky;
127
128    if c.cls == FloatClass::Zero {
129        let mut r = FloatParts {
130            sign: ab_sign,
131            exp: p_exp,
132            frac: p_frac,
133            cls: FloatClass::Normal,
134        };
135        return round_pack::<F>(&mut r, env);
136    }
137
138    // Add the product to c.
139    let product = FloatParts {
140        sign: ab_sign,
141        exp: p_exp,
142        frac: p_frac,
143        cls: FloatClass::Normal,
144    };
145
146    // Reuse the addition logic.
147    add_parts_fma::<F>(&product, c, env)
148}
149
150/// Addition for FMA (called with pre-computed product).
151fn add_parts_fma<F: FloatFormat>(
152    a: &FloatParts,
153    b: &FloatParts,
154    env: &mut FloatEnv,
155) -> F {
156    if a.sign == b.sign {
157        // Same sign: add magnitudes.
158        let (mut big, mut small) =
159            if a.exp >= b.exp { (*a, *b) } else { (*b, *a) };
160        let exp_diff = (big.exp - small.exp) as u32;
161        if exp_diff > 0 {
162            if exp_diff >= 128 {
163                small.frac = if small.frac != 0 { 1 } else { 0 };
164            } else {
165                let sticky = if small.frac & ((1u128 << exp_diff) - 1) != 0 {
166                    1u128
167                } else {
168                    0
169                };
170                small.frac = (small.frac >> exp_diff) | sticky;
171            }
172        }
173        big.frac = big.frac.wrapping_add(small.frac);
174        let mut r = FloatParts {
175            sign: big.sign,
176            exp: big.exp,
177            frac: big.frac,
178            cls: FloatClass::Normal,
179        };
180        round_pack::<F>(&mut r, env)
181    } else {
182        // Different signs: subtract.
183        let (big, small, sign) = if a.exp > b.exp {
184            (*a, *b, a.sign)
185        } else if a.exp < b.exp {
186            (*b, *a, b.sign)
187        } else if a.frac > b.frac {
188            (*a, *b, a.sign)
189        } else if a.frac < b.frac {
190            (*b, *a, b.sign)
191        } else {
192            let sign = env.round_mode() == RoundMode::Down;
193            let mut r = FloatParts {
194                sign,
195                exp: 0,
196                frac: 0,
197                cls: FloatClass::Zero,
198            };
199            return round_pack::<F>(&mut r, env);
200        };
201
202        let exp_diff = (big.exp - small.exp) as u32;
203        let mut small_frac = small.frac;
204        if exp_diff > 0 {
205            if exp_diff >= 128 {
206                small_frac = if small_frac != 0 { 1 } else { 0 };
207            } else {
208                let sticky = if small_frac & ((1u128 << exp_diff) - 1) != 0 {
209                    1u128
210                } else {
211                    0
212                };
213                small_frac = (small_frac >> exp_diff) | sticky;
214            }
215        }
216
217        let frac = big.frac.wrapping_sub(small_frac);
218        if frac == 0 {
219            let sign = env.round_mode() == RoundMode::Down;
220            let mut r = FloatParts {
221                sign,
222                exp: 0,
223                frac: 0,
224                cls: FloatClass::Zero,
225            };
226            return round_pack::<F>(&mut r, env);
227        }
228
229        let mut r = FloatParts {
230            sign,
231            exp: big.exp,
232            frac,
233            cls: FloatClass::Normal,
234        };
235        round_pack::<F>(&mut r, env)
236    }
237}
238
239fn mul_u128(a: u128, b: u128) -> (u128, u128) {
240    let a_lo = a as u64 as u128;
241    let a_hi = (a >> 64) as u64 as u128;
242    let b_lo = b as u64 as u128;
243    let b_hi = (b >> 64) as u64 as u128;
244
245    let ll = a_lo * b_lo;
246    let lh = a_lo * b_hi;
247    let hl = a_hi * b_lo;
248    let hh = a_hi * b_hi;
249
250    let mid = (ll >> 64)
251        + (lh & 0xFFFF_FFFF_FFFF_FFFF)
252        + (hl & 0xFFFF_FFFF_FFFF_FFFF);
253
254    let lo =
255        (ll & 0xFFFF_FFFF_FFFF_FFFF) | ((mid & 0xFFFF_FFFF_FFFF_FFFF) << 64);
256    let hi = hh + (lh >> 64) + (hl >> 64) + (mid >> 64);
257
258    (hi, lo)
259}
260
261// ---------------------------------------------------------------
262// Convenience methods
263// ---------------------------------------------------------------
264
265macro_rules! impl_fma {
266    ($ty:ty) => {
267        impl $ty {
268            pub fn fma(self, b: Self, c: Self, env: &mut FloatEnv) -> Self {
269                fma::<Self>(self, b, c, env)
270            }
271        }
272    };
273}
274
275impl_fma!(Float16);
276impl_fma!(BFloat16);
277impl_fma!(Float32);
278impl_fma!(Float64);
279impl_fma!(Float128);
280impl_fma!(FloatX80);