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) = match iter.next() {
24 Some((repr, ctx)) => {
25 assert_finite(&repr);
26 (repr, ctx)
27 }
28 None => return FBig::ZERO, };
30 for (repr, ctx) in iter {
31 assert_finite(&repr);
32 acc = acc + &repr;
33 context = Context::max(context, ctx);
34 }
35 if acc.significand.is_zero() {
39 acc = if R::IS_ROUND_TOWARD_NEGATIVE {
40 Repr::neg_zero()
41 } else {
42 Repr::zero()
43 };
44 }
45 FBig::new(context.repr_round(acc).value(), context)
46}
47
48impl<R: Round, const B: Word> Sum for FBig<R, B> {
49 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
50 precise_sum(iter.map(|v| (v.repr, v.context)))
51 }
52}
53
54impl<'a, R: Round, const B: Word> Sum<&'a FBig<R, B>> for FBig<R, B> {
55 fn sum<I: Iterator<Item = &'a FBig<R, B>>>(iter: I) -> Self {
56 precise_sum(iter.map(|v| (v.repr.clone(), v.context)))
57 }
58}
59
60impl<R: Round, const B: Word> Product for FBig<R, B> {
61 fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
62 iter.fold(FBig::ONE, |acc, x| acc * x)
63 }
64}
65
66impl<'a, R: Round, const B: Word> Product<&'a FBig<R, B>> for FBig<R, B> {
67 fn product<I: Iterator<Item = &'a FBig<R, B>>>(iter: I) -> Self {
68 iter.fold(FBig::ONE, |acc, x| acc * x)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::round::mode::{HalfAway, HalfEven, Zero};
76 use alloc::vec::Vec;
77 use core::str::FromStr;
78 use dashu_int::IBig;
79
80 type F = FBig<HalfAway, 10>;
81
82 fn r<const B: Word>(sig: i128, exp: isize) -> Repr<B> {
83 Repr::new(IBig::from(sig), exp)
84 }
85
86 #[test]
87 fn sum_empty() {
88 let s: F = core::iter::empty::<F>().sum();
89 assert_eq!(s, F::ZERO);
90 assert!(s.repr().is_pos_zero());
91 }
92
93 #[test]
94 fn sum_single() {
95 let a = F::from_str("1.234").unwrap();
96 let s: F = core::iter::once(a.clone()).sum();
97 assert_eq!(s, a);
98 }
99
100 #[test]
101 fn sum_cancellation_is_positive_zero() {
102 let half = F::from_str("0.5").unwrap();
105 let s: F = [half.clone(), -half].into_iter().sum();
106 assert!(s.repr().is_pos_zero());
107 assert!(!s.repr().is_neg_zero());
108 }
109
110 #[test]
111 fn sum_ref_iter() {
112 let a = F::from_str("1.23").unwrap();
113 let b = F::from_str("4.56").unwrap();
114 let vals = [a, b];
115 let s: F = vals.iter().sum();
116 assert_eq!(s, F::from_str("5.79").unwrap());
117 }
118
119 #[test]
120 fn sum_exact_cancellation() {
121 let a = F::from_str("1.00").unwrap();
122 let b = F::from_str("-1.00").unwrap();
123 let s: F = [a, b].into_iter().sum();
124 assert!(s.repr().is_pos_zero()); }
126
127 #[test]
128 fn sum_uses_max_precision() {
129 let a = F::from_str("1.234").unwrap(); let b = F::from_str("5.6").unwrap(); let s: F = [a, b].into_iter().sum();
132 assert_eq!(s, F::from_str("6.834").unwrap());
133 assert_eq!(s.precision(), 4); }
135
136 #[test]
139 fn sum_precise_beats_naive_truncation() {
140 type Z = FBig<Zero, 10>;
141 let vals: Vec<Z> = (0..11)
142 .map(|_| Z::from_parts(9.into(), -1)) .collect();
144
145 let precise: Z = vals.iter().cloned().sum();
146 assert_eq!(precise, Z::from_parts(9.into(), 0)); let naive: Z = vals.iter().cloned().fold(Z::ZERO, |acc, v| acc + v);
149 assert_eq!(naive, Z::from_parts(1.into(), 0)); assert_ne!(precise, naive);
151 }
152
153 #[test]
156 fn sum_matches_unlimited_oracle() {
157 fn check<const B: Word>(strs: &[&str]) {
158 let vals: Vec<FBig<HalfEven, B>> = strs
159 .iter()
160 .map(|s| FBig::<HalfEven, B>::from_str(s).unwrap())
161 .collect();
162 let target = vals.iter().map(|v| v.precision()).max().unwrap_or(0);
163
164 let precise: FBig<HalfEven, B> = vals.clone().into_iter().sum();
165
166 let exact: FBig<HalfEven, B> = vals
167 .iter()
168 .map(|v| v.clone().with_precision(0).value())
169 .fold(FBig::<HalfEven, B>::ZERO, |acc, v| acc + v);
170 let oracle = exact.with_precision(target).value();
171
172 assert_eq!(precise.precision(), oracle.precision());
173 assert_eq!(precise, oracle);
174 }
175 check::<10>(&["1.234", "5.6", "0.001"]);
176 check::<10>(&["9.99", "0.009", "0.0009"]);
177 check::<2>(&["1.1", "0.01", "0.001", "0.0001"]); }
179
180 #[test]
181 fn product_owned_and_ref() {
182 let a = F::from_str("1.2").unwrap();
183 let b = F::from_str("3.0").unwrap();
184 let expected = &a * &b;
185 let owned: F = [a.clone(), b.clone()].into_iter().product();
186 let by_ref: F = [a, b].iter().product();
187 assert_eq!(owned, expected);
188 assert_eq!(by_ref, expected);
189 }
190
191 #[test]
192 #[should_panic(expected = "arithmetic operations with the infinity are not allowed")]
193 fn sum_infinite_panics() {
194 let _: F = core::iter::once(F::INFINITY).sum();
195 }
196
197 #[test]
200 fn repr_add_neg_zero_is_identity() {
201 let nz = Repr::<10>::neg_zero();
202 let x = r::<10>(5, -2); assert_eq!(&nz + &x, x);
205 assert_eq!(&x + &nz, x);
206 assert_eq!(&nz + &nz, Repr::<10>::neg_zero());
207 assert_eq!(&r::<10>(1, 0) + &r::<10>(-1, 0), Repr::<10>::zero());
209 }
210}