1use crate::{
4 error::assert_finite,
5 fbig::FBig,
6 repr::{Context, Repr, Word},
7 round::Round,
8 utils::{shl_digits, shl_digits_in_place},
9};
10use core::iter::{Product, Sum};
11
12fn exact_add<const B: Word>(mut lhs: Repr<B>, rhs: &Repr<B>) -> Repr<B> {
20 if lhs.significand.is_zero() {
21 return rhs.clone();
22 }
23 if rhs.significand.is_zero() {
24 return lhs;
25 }
26
27 if lhs.exponent >= rhs.exponent {
28 let ediff = (lhs.exponent - rhs.exponent) as usize;
29 shl_digits_in_place::<B>(&mut lhs.significand, ediff);
30 lhs.significand += &rhs.significand;
31 lhs.exponent = rhs.exponent;
32 } else {
33 let ediff = (rhs.exponent - lhs.exponent) as usize;
34 let rhs_sig = shl_digits::<B>(&rhs.significand, ediff);
35 lhs.significand += rhs_sig; }
37 Repr::new(lhs.significand, lhs.exponent)
38}
39
40fn precise_sum<R: Round, const B: Word>(
50 mut iter: impl Iterator<Item = (Repr<B>, Context<R>)>,
51) -> FBig<R, B> {
52 let (mut acc, mut context) = match iter.next() {
53 Some((repr, ctx)) => {
54 assert_finite(&repr);
55 (repr, ctx)
56 }
57 None => return FBig::ZERO, };
59 for (repr, ctx) in iter {
60 assert_finite(&repr);
61 acc = exact_add(acc, &repr);
62 context = Context::max(context, ctx);
63 }
64 if acc.significand.is_zero() {
68 acc = if R::IS_ROUND_TOWARD_NEGATIVE {
69 Repr::neg_zero()
70 } else {
71 Repr::zero()
72 };
73 }
74 FBig::new(context.repr_round(acc).value(), context)
75}
76
77impl<R: Round, const B: Word> Sum for FBig<R, B> {
78 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
79 precise_sum(iter.map(|v| (v.repr, v.context)))
80 }
81}
82
83impl<'a, R: Round, const B: Word> Sum<&'a FBig<R, B>> for FBig<R, B> {
84 fn sum<I: Iterator<Item = &'a FBig<R, B>>>(iter: I) -> Self {
85 precise_sum(iter.map(|v| (v.repr.clone(), v.context)))
86 }
87}
88
89impl<R: Round, const B: Word> Product for FBig<R, B> {
90 fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
91 iter.fold(FBig::ONE, |acc, x| acc * x)
92 }
93}
94
95impl<'a, R: Round, const B: Word> Product<&'a FBig<R, B>> for FBig<R, B> {
96 fn product<I: Iterator<Item = &'a FBig<R, B>>>(iter: I) -> Self {
97 iter.fold(FBig::ONE, |acc, x| acc * x)
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104 use crate::round::mode::{HalfAway, HalfEven, Zero};
105 use alloc::vec::Vec;
106 use core::str::FromStr;
107 use dashu_int::IBig;
108
109 type F = FBig<HalfAway, 10>;
110
111 fn r<const B: Word>(sig: i128, exp: isize) -> Repr<B> {
112 Repr::new(IBig::from(sig), exp)
113 }
114
115 #[test]
116 fn sum_empty() {
117 let s: F = core::iter::empty::<F>().sum();
118 assert_eq!(s, F::ZERO);
119 assert!(s.repr().is_pos_zero());
120 }
121
122 #[test]
123 fn sum_single() {
124 let a = F::from_str("1.234").unwrap();
125 let s: F = core::iter::once(a.clone()).sum();
126 assert_eq!(s, a);
127 }
128
129 #[test]
130 fn sum_cancellation_is_positive_zero() {
131 let half = F::from_str("0.5").unwrap();
134 let s: F = [half.clone(), -half].into_iter().sum();
135 assert!(s.repr().is_pos_zero());
136 assert!(!s.repr().is_neg_zero());
137 }
138
139 #[test]
140 fn sum_ref_iter() {
141 let a = F::from_str("1.23").unwrap();
142 let b = F::from_str("4.56").unwrap();
143 let vals = [a, b];
144 let s: F = vals.iter().sum();
145 assert_eq!(s, F::from_str("5.79").unwrap());
146 }
147
148 #[test]
149 fn sum_exact_cancellation() {
150 let a = F::from_str("1.00").unwrap();
151 let b = F::from_str("-1.00").unwrap();
152 let s: F = [a, b].into_iter().sum();
153 assert!(s.repr().is_pos_zero()); }
155
156 #[test]
157 fn sum_uses_max_precision() {
158 let a = F::from_str("1.234").unwrap(); let b = F::from_str("5.6").unwrap(); let s: F = [a, b].into_iter().sum();
161 assert_eq!(s, F::from_str("6.834").unwrap());
162 assert_eq!(s.precision(), 4); }
164
165 #[test]
168 fn sum_precise_beats_naive_truncation() {
169 type Z = FBig<Zero, 10>;
170 let vals: Vec<Z> = (0..11)
171 .map(|_| Z::from_parts(9.into(), -1)) .collect();
173
174 let precise: Z = vals.iter().cloned().sum();
175 assert_eq!(precise, Z::from_parts(9.into(), 0)); let naive: Z = vals.iter().cloned().fold(Z::ZERO, |acc, v| acc + v);
178 assert_eq!(naive, Z::from_parts(1.into(), 0)); assert_ne!(precise, naive);
180 }
181
182 #[test]
185 fn sum_matches_unlimited_oracle() {
186 fn check<const B: Word>(strs: &[&str]) {
187 let vals: Vec<FBig<HalfEven, B>> = strs
188 .iter()
189 .map(|s| FBig::<HalfEven, B>::from_str(s).unwrap())
190 .collect();
191 let target = vals.iter().map(|v| v.precision()).max().unwrap_or(0);
192
193 let precise: FBig<HalfEven, B> = vals.clone().into_iter().sum();
194
195 let exact: FBig<HalfEven, B> = vals
196 .iter()
197 .map(|v| v.clone().with_precision(0).value())
198 .fold(FBig::<HalfEven, B>::ZERO, |acc, v| acc + v);
199 let oracle = exact.with_precision(target).value();
200
201 assert_eq!(precise.precision(), oracle.precision());
202 assert_eq!(precise, oracle);
203 }
204 check::<10>(&["1.234", "5.6", "0.001"]);
205 check::<10>(&["9.99", "0.009", "0.0009"]);
206 check::<2>(&["1.1", "0.01", "0.001", "0.0001"]); }
208
209 #[test]
210 fn product_owned_and_ref() {
211 let a = F::from_str("1.2").unwrap();
212 let b = F::from_str("3.0").unwrap();
213 let expected = &a * &b;
214 let owned: F = [a.clone(), b.clone()].into_iter().product();
215 let by_ref: F = [a, b].iter().product();
216 assert_eq!(owned, expected);
217 assert_eq!(by_ref, expected);
218 }
219
220 #[test]
221 #[should_panic(expected = "arithmetic operations with the infinity are not allowed")]
222 fn sum_infinite_panics() {
223 let _: F = core::iter::once(F::INFINITY).sum();
224 }
225
226 #[test]
229 fn exact_add_neg_zero_is_identity() {
230 let nz = Repr::<10>::neg_zero();
231 let x = r::<10>(5, -2); assert_eq!(exact_add(nz.clone(), &x), x);
234 assert_eq!(exact_add(x.clone(), &nz), x);
235 assert_eq!(exact_add(nz.clone(), &nz), Repr::<10>::neg_zero());
236 assert_eq!(exact_add(r::<10>(1, 0), &r::<10>(-1, 0)), Repr::<10>::zero());
238 }
239}