Skip to content

Commit b4ed6f9

Browse files
committed
auto merge of #10927 : g3xzh/rust/sum_bugfix, r=huonw
`[1e20, 1.0, -1e20].sum()` returns `0.0`. This happens because during the summation, `1.0` is too small relative to `1e20`, making it negligible. I have tried Kahan summation but it hasn't fixed the problem. Therefore, I've used Python's `fsum()` implementation. For more details, read: www.cs.cmu.edu/~quake-papers/robust-arithmetic.ps #10851 Python's fsum (msum) http://code.activestate.com/recipes/393090/ @huonw, your feedback is more than welcome. It looks unpolished; Do you have suggestions how to make it more beautiful and elegant? Thanks in advance,
2 parents 3c2c13b + 05395cb commit b4ed6f9

File tree

1 file changed

+66
-1
lines changed

1 file changed

+66
-1
lines changed

src/libextra/stats.rs

+66-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::cmp;
1515
use std::hashmap;
1616
use std::io;
1717
use std::num;
18+
use std::util;
1819

1920
// NB: this can probably be rewritten in terms of num::Num
2021
// to be less f64-specific.
@@ -23,6 +24,12 @@ use std::num;
2324
pub trait Stats {
2425

2526
/// Sum of the samples.
27+
///
28+
/// Note: this method sacrifices performance at the altar of accuracy
29+
/// Depends on IEEE-754 arithmetic guarantees. See proof of correctness at:
30+
/// ["Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates"]
31+
/// (http://www.cs.cmu.edu/~quake-papers/robust-arithmetic.ps)
32+
/// *Discrete & Computational Geometry 18*, 3 (Oct 1997), 305-363, Shewchuk J.R.
2633
fn sum(self) -> f64;
2734

2835
/// Minimum value of the samples.
@@ -142,8 +149,37 @@ impl Summary {
142149

143150
impl<'a> Stats for &'a [f64] {
144151

152+
// FIXME #11059 handle NaN, inf and overflow
145153
fn sum(self) -> f64 {
146-
self.iter().fold(0.0, |p,q| p + *q)
154+
let mut partials : ~[f64] = ~[];
155+
156+
for &mut x in self.iter() {
157+
let mut j = 0;
158+
// This inner loop applies `hi`/`lo` summation to each
159+
// partial so that the list of partial sums remains exact.
160+
for i in range(0, partials.len()) {
161+
let mut y = partials[i];
162+
if num::abs(x) < num::abs(y) {
163+
util::swap(&mut x, &mut y);
164+
}
165+
// Rounded `x+y` is stored in `hi` with round-off stored in
166+
// `lo`. Together `hi+lo` are exactly equal to `x+y`.
167+
let hi = x + y;
168+
let lo = y - (hi - x);
169+
if lo != 0f64 {
170+
partials[j] = lo;
171+
j += 1;
172+
}
173+
x = hi;
174+
}
175+
if j >= partials.len() {
176+
partials.push(x);
177+
} else {
178+
partials[j] = x;
179+
partials.truncate(j+1);
180+
}
181+
}
182+
partials.iter().fold(0.0, |p, q| p + *q)
147183
}
148184

149185
fn min(self) -> f64 {
@@ -950,5 +986,34 @@ mod tests {
950986
t(&Summary::new([-2.0, 0.0]), ~"-2 |[------******#******---]| 0");
951987

952988
}
989+
#[test]
990+
fn test_sum_f64s() {
991+
assert_eq!([0.5, 3.2321, 1.5678].sum(), 5.2999);
992+
}
993+
#[test]
994+
fn test_sum_f64_between_ints_that_sum_to_0() {
995+
assert_eq!([1e30, 1.2, -1e30].sum(), 1.2);
996+
}
997+
}
998+
999+
#[cfg(test)]
1000+
mod bench {
1001+
use extra::test::BenchHarness;
1002+
use std::vec;
1003+
1004+
#[bench]
1005+
fn sum_three_items(bh: &mut BenchHarness) {
1006+
bh.iter(|| {
1007+
[1e20, 1.5, -1e20].sum();
1008+
})
1009+
}
1010+
#[bench]
1011+
fn sum_many_f64(bh: &mut BenchHarness) {
1012+
let nums = [-1e30, 1e60, 1e30, 1.0, -1e60];
1013+
let v = vec::from_fn(500, |i| nums[i%5]);
9531014

1015+
bh.iter(|| {
1016+
v.sum();
1017+
})
1018+
}
9541019
}

0 commit comments

Comments
 (0)