f256/binops/
mul.rs

1// ---------------------------------------------------------------------------
2// Copyright:   (c) 2022 ff. Michael Amrhein (michael@adrhinum.de)
3// License:     This program is part of a larger application. For license
4//              details please read the file LICENSE.TXT provided together
5//              with the application.
6// ---------------------------------------------------------------------------
7// $Source:     src/binops/mul.rs $
8// $Revision:   2025-04-19T20:10:31+02:00 $
9
10use core::{
11    cmp::{max, min},
12    ops::{Mul, MulAssign},
13};
14
15use crate::{
16    abs_bits, abs_bits_sticky, exp_bits, f256, left_adj_signif, norm_bit,
17    signif, BigUInt, BinEncAnySpecial, HiLo, EMAX, EMIN, EXP_BIAS, EXP_BITS,
18    EXP_MAX, FRACTION_BITS, HI_ABS_MASK, HI_FRACTION_BIAS, HI_FRACTION_BITS,
19    HI_FRACTION_MASK, HI_SIGN_MASK, INF_HI, MAX_HI, SIGNIFICAND_BITS,
20    TOTAL_BITS, U256, U512,
21};
22
23#[inline]
24#[allow(clippy::cast_possible_truncation)]
25fn mul_signifs(x: &U256, y: &U256) -> (U256, u32, u32) {
26    debug_assert!(x.hi.0 >= HI_SIGN_MASK);
27    debug_assert!(y.hi.0 >= HI_SIGN_MASK);
28    let (lo, mut hi) = x.widening_mul(y);
29    let carry = (hi.hi.0 >= HI_SIGN_MASK) as u32;
30    let shift = EXP_BITS - 1 + carry;
31    let rem = hi.rem_pow2(shift).lo;
32    let rnd_bits = (rem.0 >> (shift - 2)) as u32
33        | (rem.0 > (1 << (shift - 1))) as u32
34        | !lo.is_zero() as u32;
35    hi >>= shift;
36    (hi, carry, rnd_bits)
37}
38
39#[allow(clippy::cast_sign_loss)]
40#[allow(clippy::cast_possible_wrap)]
41#[allow(clippy::cast_possible_truncation)]
42pub(crate) fn mul_abs_finite(
43    abs_bits_x: &U256,
44    abs_bits_y: &U256,
45) -> (U256, u32) {
46    // Extract biased exponents and normalized significands.
47    let mut exp_bits_x = exp_bits(abs_bits_x) as i32;
48    let norm_bit_x = norm_bit(abs_bits_x) as i32;
49    let (mut norm_signif_x, norm_shift_x) = left_adj_signif(abs_bits_x);
50    let mut exp_bits_y = exp_bits(abs_bits_y) as i32;
51    let norm_bit_y = norm_bit(abs_bits_y) as i32;
52    let (mut norm_signif_y, norm_shift_y) = left_adj_signif(abs_bits_y);
53
54    // Calculate |x| * |y|.
55    let (mut signif_z, carry, mut rnd_bits) =
56        mul_signifs(&norm_signif_x, &norm_signif_y);
57    const EMIN_EXTRA_SHIFT_BIAS: i32 = EMIN + 2 * EXP_BITS as i32;
58    let mut exp_bits_z_minus_1 = (exp_bits_x - norm_bit_x)
59        + (exp_bits_y - norm_bit_y)
60        - (norm_shift_x + norm_shift_y) as i32
61        + EMIN_EXTRA_SHIFT_BIAS
62        + carry as i32;
63
64    // If the result overflows the range of values representable as `f256`,
65    // return +/- Infinity.
66    if exp_bits_z_minus_1 >= (EXP_MAX - 1) as i32 {
67        return (U256::new(INF_HI, 0), 0_u32);
68    }
69
70    // If the calculated biased exponent <= 0, the result may be subnormal or
71    // underflow to ZERO.
72    if exp_bits_z_minus_1 < 0 {
73        let shift = exp_bits_z_minus_1.unsigned_abs();
74        if shift > SIGNIFICAND_BITS + 1 {
75            // Result underflows to zero.
76            return (U256::ZERO, 0_u32);
77        }
78        if shift > 0 {
79            // Adjust the rounding bits for correct final rounding.
80            match shift {
81                1 => {
82                    rnd_bits = (((signif_z.lo.0 & 1) as u32) << 1)
83                        | (rnd_bits != 0) as u32;
84                }
85                2 => {
86                    rnd_bits =
87                        ((signif_z.lo.0 & 3) as u32) | (rnd_bits != 0) as u32;
88                }
89                3..=127 => {
90                    let rem = signif_z.rem_pow2(shift).lo.0;
91                    rnd_bits = (rem >> (shift - 2)) as u32
92                        | (rem > (1_u128 << (shift - 1))) as u32
93                        | (rnd_bits != 0) as u32;
94                }
95                _ => {
96                    let rem = signif_z.rem_pow2(shift);
97                    rnd_bits = (rem >> (shift - 2)).lo.0 as u32
98                        | (rem > (U256::ONE << (shift - 1))) as u32
99                        | (rnd_bits != 0) as u32;
100                }
101            }
102            signif_z >>= shift;
103        }
104        exp_bits_z_minus_1 = 0;
105    }
106
107    // Assemble the result.
108    let abs_bits_z = U256::new(
109        signif_z.hi.0 + ((exp_bits_z_minus_1 as u128) << HI_FRACTION_BITS),
110        signif_z.lo.0,
111    );
112    (abs_bits_z, rnd_bits)
113}
114
115/// Compute z = x * y, rounded tie to even.
116#[allow(clippy::cast_possible_truncation)]
117#[allow(clippy::cast_possible_wrap)]
118#[allow(clippy::cast_sign_loss)]
119#[inline]
120pub(crate) fn mul(x: f256, y: f256) -> f256 {
121    // The products sign is the XOR of the signs of the operands.
122    let sign_bits_hi_z = (x.bits.hi.0 ^ y.bits.hi.0) & HI_SIGN_MASK;
123    let mut abs_bits_x = abs_bits(&x);
124    let mut abs_bits_y = abs_bits(&y);
125    // Check whether one or both operands are NaN, infinite or zero.
126    // We mask off the sign bit and mark subnormals having a significand less
127    // than 2¹²⁸ in least bit of the representations high u128. This allows to
128    // use only that part for the handling of special cases.
129    let abs_bits_sticky_x = abs_bits_sticky(&abs_bits_x);
130    let abs_bits_sticky_y = abs_bits_sticky(&abs_bits_y);
131    if (abs_bits_sticky_x, abs_bits_sticky_y).any_special() {
132        let max_abs_bits_sticky = max(abs_bits_sticky_x, abs_bits_sticky_y);
133        let min_abs_bits_sticky = min(abs_bits_sticky_x, abs_bits_sticky_y);
134        if min_abs_bits_sticky == 0 {
135            // Atleast one operand is zero.
136            if max_abs_bits_sticky < INF_HI {
137                // ±0 × ±finite or ±finite × ±0
138                return f256 {
139                    bits: U256::new(sign_bits_hi_z, 0),
140                };
141            };
142            if max_abs_bits_sticky == INF_HI {
143                // ±0 × ±Inf or ±Inf × ±0
144                return f256::NAN;
145            }
146        }
147        if max_abs_bits_sticky > INF_HI {
148            // Atleast one operand is NAN.
149            return f256::NAN;
150        }
151        // Atleast one operand is infinite and the other non-zero.
152        return f256 {
153            bits: U256::new(sign_bits_hi_z | INF_HI, 0),
154        };
155    }
156
157    // Both operands are finite and non-zero.
158    let (mut bits_z, rnd_bits) = mul_abs_finite(&abs_bits_x, &abs_bits_y);
159    bits_z.hi.0 |= sign_bits_hi_z;
160
161    // Final rounding. Possibly overflowing into the exponent, but that is ok.
162    if rnd_bits > 0b10 || (rnd_bits == 0b10 && bits_z.lo.is_odd()) {
163        bits_z.incr();
164    }
165    f256 { bits: bits_z }
166}
167
168impl Mul for f256 {
169    type Output = Self;
170
171    fn mul(self, rhs: Self) -> Self::Output {
172        mul(self, rhs)
173    }
174}
175
176forward_ref_binop!(impl Mul, mul);
177
178forward_op_assign!(impl MulAssign, mul_assign, Mul, mul);