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