malachite_base/num/arithmetic/mul_add_mul.rs
1// Copyright © 2026 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::num::arithmetic::traits::{MulAddMul, MulAddMulAssign, UnsignedAbs};
10use crate::num::basic::signeds::PrimitiveSigned;
11use crate::num::basic::unsigneds::PrimitiveUnsigned;
12use crate::num::conversion::traits::WrappingFrom;
13
14// Where the exact value of $xy \pm zw$ sits relative to the type's range. The products are formed
15// at double width, so no intermediate overflow can hide a result that would have fit.
16pub(crate) enum Wide<T> {
17 Fits(T),
18 Above,
19 Below,
20}
21
22// The exact value of $xy + zw$, or of $xy - zw$ when `sub` is set, for an unsigned type.
23pub(crate) fn mul_add_mul_wide_unsigned<T: PrimitiveUnsigned>(
24 x: T,
25 y: T,
26 z: T,
27 w: T,
28 sub: bool,
29) -> Wide<T> {
30 let (p_1, p_0) = T::x_mul_y_to_zz(x, y);
31 let (q_1, q_0) = T::x_mul_y_to_zz(z, w);
32 let (r_1, r_0) = if sub {
33 if (p_1, p_0) < (q_1, q_0) {
34 return Wide::Below;
35 }
36 T::xx_sub_yy_to_zz(p_1, p_0, q_1, q_0)
37 } else {
38 let (r_1, r_0) = T::xx_add_yy_to_zz(p_1, p_0, q_1, q_0);
39 // Two products can carry out of double width, since $(2^W-1)^2 + (2^W-1)^2 \geq 2^{2W}$,
40 // and `xx_add_yy_to_zz` wraps without saying so. The sum wrapped iff it is now smaller than
41 // one of the addends.
42 if (r_1, r_0) < (p_1, p_0) {
43 return Wide::Above;
44 }
45 (r_1, r_0)
46 };
47 if r_1 == T::ZERO {
48 Wide::Fits(r_0)
49 } else {
50 Wide::Above
51 }
52}
53
54// The exact value of $xy + zw$, or of $xy - zw$ when `sub` is set, for a signed type. The two
55// products are combined as a sign and a double-width magnitude, so the sign of an out-of-range
56// result is known and the saturating variants can pick the right bound.
57pub(crate) fn mul_add_mul_wide_signed<
58 U: PrimitiveUnsigned,
59 T: PrimitiveSigned + UnsignedAbs<Output = U> + WrappingFrom<U>,
60>(
61 x: T,
62 y: T,
63 z: T,
64 w: T,
65 sub: bool,
66) -> Wide<T> {
67 let p_neg = (x < T::ZERO) != (y < T::ZERO);
68 let q_neg = ((z < T::ZERO) != (w < T::ZERO)) != sub;
69 let (p_1, p_0) = U::x_mul_y_to_zz(x.unsigned_abs(), y.unsigned_abs());
70 let (q_1, q_0) = U::x_mul_y_to_zz(z.unsigned_abs(), w.unsigned_abs());
71 let (neg, r_1, r_0) = if p_neg == q_neg {
72 let (r_1, r_0) = U::xx_add_yy_to_zz(p_1, p_0, q_1, q_0);
73 (p_neg, r_1, r_0)
74 } else if (p_1, p_0) >= (q_1, q_0) {
75 let (r_1, r_0) = U::xx_sub_yy_to_zz(p_1, p_0, q_1, q_0);
76 (p_neg, r_1, r_0)
77 } else {
78 let (r_1, r_0) = U::xx_sub_yy_to_zz(q_1, q_0, p_1, p_0);
79 (q_neg, r_1, r_0)
80 };
81 if r_1 != U::ZERO {
82 return if neg { Wide::Below } else { Wide::Above };
83 }
84 if neg {
85 // The negative bound has one more magnitude than the positive one.
86 if r_0 <= T::MIN.unsigned_abs() {
87 Wide::Fits(T::wrapping_from(r_0).wrapping_neg())
88 } else {
89 Wide::Below
90 }
91 } else if r_0 <= T::MAX.unsigned_abs() {
92 Wide::Fits(T::wrapping_from(r_0))
93 } else {
94 Wide::Above
95 }
96}
97
98macro_rules! impl_mul_add_mul_primitive_int {
99 ($t:ident) => {
100 impl MulAddMul for $t {
101 type Output = $t;
102
103 /// Adds the products of two pairs of numbers.
104 ///
105 /// $f(x, y, z, w) = xy + zw$.
106 ///
107 /// Both products and their sum wrap on overflow, as they do for
108 /// [`add_mul`](super::traits::AddMul::add_mul).
109 ///
110 /// # Worst-case complexity
111 /// Constant time and additional memory.
112 ///
113 /// # Examples
114 /// See [here](super::mul_add_mul#mul_add_mul).
115 #[inline]
116 fn mul_add_mul(self, y: $t, z: $t, w: $t) -> $t {
117 self.wrapping_mul(y).wrapping_add(z.wrapping_mul(w))
118 }
119 }
120
121 impl MulAddMulAssign for $t {
122 /// Adds the products of two pairs of numbers, in place.
123 ///
124 /// $x \gets xy + zw$.
125 ///
126 /// Both products and their sum wrap on overflow, as they do for
127 /// [`add_mul`](super::traits::AddMul::add_mul).
128 ///
129 /// # Worst-case complexity
130 /// Constant time and additional memory.
131 ///
132 /// # Examples
133 /// See [here](super::mul_add_mul#mul_add_mul_assign).
134 #[inline]
135 fn mul_add_mul_assign(&mut self, y: $t, z: $t, w: $t) {
136 *self = self.wrapping_mul(y).wrapping_add(z.wrapping_mul(w));
137 }
138 }
139 };
140}
141apply_to_primitive_ints!(impl_mul_add_mul_primitive_int);