fsum/lib.rs
1#![doc = include_str!("../README.md")]
2
3use std::mem;
4use std::ops::{AddAssign, SubAssign};
5use std::borrow::Borrow;
6
7/// Accumulator that represents the exact sum of `f64` values and
8/// allows additional `f64` values to be added without loss of precision.
9#[derive(Default, Clone, Debug)]
10pub struct FSum {
11 partials: Vec<f64>
12}
13
14impl FSum {
15 /// Constructs zeroed accumulator.
16 ///
17 /// # Example
18 ///
19 /// ```
20 /// use fsum::FSum;
21 ///
22 /// assert_eq!(FSum::new().value(), 0.0);
23 /// ```
24 pub fn new() -> FSum {
25 FSum{ partials: Vec::new() }
26 }
27
28 /// Constructs accumulator with given `initial_value`.
29 ///
30 /// # Example
31 ///
32 /// ```
33 /// use fsum::FSum;
34 ///
35 /// assert_eq!(FSum::with_value(5.0).value(), 5.0);
36 /// ```
37 pub fn with_value(initial_value: f64) -> FSum {
38 FSum{ partials: vec![initial_value] }
39 }
40
41 /// Constructs accumulator with all values from `iter`.
42 ///
43 /// # Examples
44 ///
45 /// ```
46 /// use fsum::FSum;
47 ///
48 /// assert_eq!(FSum::with_all((0..10).map(|_| 0.1)).value(), 1.0);
49 /// ```
50 pub fn with_all<Bf64, InIter>(values: InIter) -> FSum
51 where Bf64: Borrow<f64>, InIter: IntoIterator<Item=Bf64>
52 {
53 let mut result = Self::new();
54 result.add_all(values);
55 result
56 }
57
58 /// Increases the sum by `x` and returns `self`.
59 ///
60 /// # Example
61 ///
62 /// ```
63 /// use fsum::FSum;
64 ///
65 /// let mut s = FSum::new();
66 /// assert_eq!(s.value(), 0.0);
67 /// s.add(1.0);
68 /// assert_eq!(s.value(), 1.0);
69 /// s.add(2.0);
70 /// assert_eq!(s.value(), 3.0);
71 /// assert_eq!(s.add(5.0).value(), 8.0);
72 /// ```
73 ///
74 /// # Complexity
75 ///
76 /// The complexities are:
77 /// - time: from *O(1)* (optimistic) to *O(n)* (pessimistic), where *n* is the number of values added so far,
78 /// - memory: *O(1)*, but internal vector stored in `self` can be increased by 1 element.
79 ///
80 /// Usually the time complexity is close to optimistic.
81 pub fn add(&mut self, mut x: f64) -> &mut FSum {
82 // https://github.com/python/cpython/blob/master/Modules/mathmodule.c#L1323
83 let mut j = 0usize;
84 // This inner loop applies `hi`/`lo` summation to each
85 // partial so that the list of partial sums remains exact.
86 for i in 0..self.partials.len() {
87 let mut y: f64 = self.partials[i];
88 if x.abs() < y.abs() { mem::swap(&mut x, &mut y); }
89 // Rounded `x+y` is stored in `hi` with round-off stored in
90 // `lo`. Together `hi+lo` are exactly equal to `x+y`.
91 let hi = x + y;
92 let lo = y - (hi - x);
93 if lo != 0.0 {
94 self.partials[j] = lo;
95 j += 1;
96 }
97 x = hi;
98 }
99 if j >= self.partials.len() {
100 self.partials.push(x);
101 } else {
102 self.partials[j] = x;
103 self.partials.truncate(j + 1);
104 }
105 self
106 }
107
108 /// Increases the sum by all values from `iter`. Returns `self`.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// use fsum::FSum;
114 ///
115 /// assert_eq!(FSum::new().add_all((0..10).map(|_| 0.1)).value(), 1.0);
116 /// ```
117 pub fn add_all<InIter, Bf64>(&mut self, values: InIter) -> &mut FSum
118 where Bf64: Borrow<f64>, InIter: IntoIterator<Item=Bf64>
119 {
120 for x in values { self.add(*x.borrow()); }
121 self
122 }
123
124 /// Returns the current value of the sum.
125 ///
126 /// The complexities are:
127 /// - time: from *O(1)* (optimistic) to *O(n)* (pessimistic), where *n* is the number of values added so far,
128 /// - memory: *O(1)*.
129 ///
130 /// Usually the time complexity is close to optimistic.
131 ///
132 /// # Example
133 ///
134 /// ```
135 /// use fsum::FSum;
136 ///
137 /// assert_eq!(FSum::with_value(2.0).value(), 2.0);
138 /// ```
139 pub fn value(&self) -> f64 {
140 // https://github.com/python/cpython/blob/2b7411df5ca0b6ef714377730fd4d94693f26abd/Lib/test/test_math.py#L647
141 let mut n = self.partials.len();
142 if n == 0 { return 0.0; }
143 n -= 1;
144 let mut total = self.partials[n];
145 if n == 0 { return total; }
146 loop { // sum partials from the top, stop when the sum becomes inexact:
147 let old_total = total;
148 n -= 1;
149 let x = self.partials[n];
150 total = old_total + x;
151 if n == 0 { return total; }
152 let error = x - (total - old_total);
153 if error != 0.0 {
154 /* Make half-even rounding work across multiple partials.
155 Needed so that sum([1e-16, 1, 1e16]) will round-up the last
156 digit to two instead of down to zero (the 1e-16 makes the 1
157 slightly closer to two). With a potential 1 ULP rounding
158 error fixed-up, math.fsum() can guarantee commutativity. */
159 if (error < 0.0 && self.partials[n - 1] < 0.0)
160 || (error > 0.0 && self.partials[n - 1] > 0.0)
161 {
162 let y = error * 2.0;
163 let x = total + y;
164 if y == x - total { return x; }
165 }
166 return total;
167 }
168 };
169 //self.partials.iter().fold(0.0f64, |p, q| p + *q)
170 }
171
172 /// Sets the current sum to `0` and returns `self`.
173 pub fn reset(&mut self) -> &mut FSum {
174 self.partials.clear();
175 self
176 }
177
178 /// Sets the current sum to `value` and returns `self`.
179 ///
180 /// # Example
181 ///
182 /// ```
183 /// use fsum::FSum;
184 ///
185 /// assert_eq!(FSum::new().set(1.0).value(), 1.0);
186 /// ```
187 pub fn set(&mut self, value: f64) -> &mut FSum {
188 self.partials = vec![value];
189 self
190 }
191}
192
193impl AddAssign<f64> for FSum {
194 #[inline] fn add_assign(&mut self, other: f64) { self.add(other); }
195}
196
197impl SubAssign<f64> for FSum {
198 #[inline] fn sub_assign(&mut self, other: f64) { self.add(- other); }
199}
200
201impl From<f64> for FSum {
202 #[inline] fn from(initial_value: f64) -> Self {
203 Self::with_value(initial_value)
204 }
205}
206
207impl From<FSum> for f64 {
208 #[inline] fn from(fsum: FSum) -> Self {
209 fsum.value()
210 }
211}
212
213impl From<&FSum> for f64 {
214 #[inline] fn from(fsum: &FSum) -> Self {
215 fsum.value()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn fsum() {
225 assert_eq!(FSum::new().add(2.0).add(3.0).value(), 5.0);
226 assert_eq!(FSum::with_value(2.0).add(3.0).value(), 5.0);
227 assert_eq!(FSum::with_all((0..10).map(|_| 0.1)).value(), 1.0);
228 assert_eq!(FSum::new().add(1e100).add(1.0).add(-1e100).value(), 1.0);
229 assert_eq!(
230 FSum::with_all([1e100, 1.0, -1e100, 1e-100, 1e50, -1.0, -1e50]).value(),
231 1e-100
232 );
233 assert_eq!(FSum::with_all([-1e308, 1e308, 1e308]).value(), 1e308);
234 assert_eq!(FSum::with_all([1e308, -1e308, 1e308]).value(), 1e308);
235 }
236
237 #[test]
238 fn fsum_add_sub_assign_reset_into() {
239 let mut s: FSum = Default::default();
240 assert_eq!(s.value(), 0.0);
241 s += 1.0;
242 assert_eq!(s.value(), 1.0);
243 s += 2.0;
244 assert_eq!(s.value(), 3.0);
245 s -= 1.0;
246 assert_eq!(s.value(), 2.0);
247 s.reset();
248 assert_eq!(s.value(), 0.0);
249 s.set(5.0);
250 assert_eq!(s.value(), 5.0);
251 assert_eq!(5.0, s.into());
252 }
253}