Skip to content

Commit 2e00bb0

Browse files
committed
Add NaNs, infinities and overflows support to sum
Stats::sum handles NaNs and infinities as elements in the list. In addition to that, it also detects overflows. Note: Stats::sum is using std::vec_ng:Vec to improve its performance.
1 parent 8786405 commit 2e00bb0

File tree

1 file changed

+122
-26
lines changed

1 file changed

+122
-26
lines changed

src/libextra/stats.rs

+122-26
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ use std::io;
1616
use std::mem;
1717
use std::num;
1818
use collections::hashmap;
19-
20-
// NB: this can probably be rewritten in terms of num::Num
21-
// to be less f64-specific.
19+
use std::vec_ng::Vec;
2220

2321
fn f64_cmp(x: f64, y: f64) -> Ordering {
2422
// arbitrarily decide that NaNs are larger than everything.
@@ -167,38 +165,111 @@ impl Summary {
167165
}
168166

169167
impl<'a> Stats for &'a [f64] {
170-
171-
// FIXME #11059 handle NaN, inf and overflow
172168
fn sum(self) -> f64 {
173-
let mut partials : ~[f64] = ~[];
174169

170+
fn samesign(x: f64, y: f64) -> bool {
171+
(x >= 0f64) == (y > 0f64)
172+
}
173+
174+
fn twosum(x: f64, y: f64) -> (f64, f64) {
175+
let hi = x + y;
176+
let lo = y - (hi - x);
177+
(hi, lo)
178+
}
179+
180+
let mut partials = Vec::new();
181+
partials.push(0.0);
182+
let twopow: f64 = num::powf(2.0, (Float::max_exp(None::<f64>) - 1) as f64);
175183
for &mut x in self.iter() {
176-
let mut j = 0;
177-
// This inner loop applies `hi`/`lo` summation to each
178-
// partial so that the list of partial sums remains exact.
179-
for i in range(0, partials.len()) {
180-
let mut y = partials[i];
181-
if num::abs(x) < num::abs(y) {
182-
mem::swap(&mut x, &mut y);
184+
if x.is_nan() {
185+
return x;
186+
} else if x.is_infinite() {
187+
*partials.get_mut(0) += x;
188+
} else {
189+
let mut i = 1;
190+
for z in range(1, partials.len()) {
191+
let mut y = *partials.get(z);
192+
if num::abs(x) < num::abs(y) {
193+
mem::swap(&mut x, &mut y);
194+
}
195+
let (mut hi, mut lo) = twosum(x,y);
196+
if hi.is_infinite() {
197+
let sign = if hi > 0.0 { -1.0 } else { 1.0 };
198+
x = x - twopow * sign - twopow * sign;
199+
*partials.get_mut(0) += sign;
200+
if num::abs(x) < num::abs(y) {
201+
mem::swap(&mut x, &mut y);
202+
}
203+
let (h, l) = twosum(x,y);
204+
hi = h;
205+
lo = l;
206+
}
207+
if lo != 0.0 {
208+
*partials.get_mut(i) = lo;
209+
i += 1;
210+
}
211+
x = hi;
183212
}
184-
// Rounded `x+y` is stored in `hi` with round-off stored in
185-
// `lo`. Together `hi+lo` are exactly equal to `x+y`.
186-
let hi = x + y;
187-
let lo = y - (hi - x);
188-
if lo != 0f64 {
189-
partials[j] = lo;
190-
j += 1;
213+
if i >= partials.len() {
214+
partials.push(x);
215+
} else {
216+
*partials.get_mut(i) = x;
217+
partials.truncate(i + 1);
191218
}
192-
x = hi;
193219
}
194-
if j >= partials.len() {
195-
partials.push(x);
220+
}
221+
// special cases arising from infinities
222+
if partials.get(0).is_infinite() {
223+
return *partials.get(0);
224+
} else if partials.get(0).is_nan() {
225+
fail!("infinities of both signs in summands");
226+
}
227+
228+
let plen = partials.len();
229+
if num::abs(*partials.get(0)) == 1.0 && plen > 1 &&
230+
!samesign(*partials.get(plen - 1), *partials.get(0)) {
231+
let (hi, lo) = twosum(*partials.get(0) * twopow, 0.5 * *partials.get(plen - 1));
232+
if (2.0 * hi).is_infinite() {
233+
if (hi + 2.0 * lo - hi) == (2.0 * lo) && plen > 2 &&
234+
samesign(lo, *partials.get(plen - 2)) {
235+
return 2.0 * (hi + 2.0 * lo);
236+
}
196237
} else {
197-
partials[j] = x;
198-
partials.truncate(j+1);
238+
if lo != 0.0 {
239+
partials.push(2.0 * lo);
240+
}
241+
partials.push(2.0 * hi);
242+
*partials.get_mut(0) = 0.0;
199243
}
200244
}
201-
partials.iter().fold(0.0, |p, q| p + *q)
245+
// if there is no overflow, compute the sum of a list of
246+
// nonoverlapping floats.
247+
if *partials.get(0) == 0.0 {
248+
let mut total_so_far = partials.pop().expect("Impossible empty vector in Stats.sum");
249+
loop {
250+
let item = match partials.pop() {
251+
None => break, //partials is empty, we're finished
252+
Some(x) => x
253+
};
254+
let (total, lo) = twosum(total_so_far, item);
255+
total_so_far = total;
256+
if lo != 0.0 {
257+
partials.push(lo);
258+
break;
259+
}
260+
}
261+
262+
let len = partials.len();
263+
if len >= 2 {
264+
let (a, b) = (*partials.get(len - 1), *partials.get(len-2));
265+
if samesign(a, b) && (total_so_far + 2.0 * a - total_so_far) == (2.0 * a) {
266+
total_so_far += 2.0 * a;
267+
*partials.get_mut(len - 1) = -a;
268+
}
269+
}
270+
return total_so_far;
271+
}
272+
fail!("overflow in stats::sum");
202273
}
203274

204275
fn min(self) -> f64 {
@@ -442,6 +513,7 @@ mod tests {
442513
use stats::write_boxplot;
443514
use std::io;
444515
use std::str;
516+
use std::f64;
445517

446518
macro_rules! assert_approx_eq(
447519
($a:expr, $b:expr) => ({
@@ -1022,6 +1094,30 @@ mod tests {
10221094
fn test_sum_f64_between_ints_that_sum_to_0() {
10231095
assert_eq!([1e30, 1.2, -1e30].sum(), 1.2);
10241096
}
1097+
#[test]
1098+
fn test_sum_infs_and_nans_f64() {
1099+
assert_eq!([f64::INFINITY].sum(), f64::INFINITY);
1100+
assert_eq!([f64::INFINITY, f64::INFINITY].sum(), f64::INFINITY);
1101+
1102+
assert!([f64::NAN].sum().is_nan());
1103+
assert!([f64::INFINITY, f64::NEG_INFINITY, f64::NAN].sum().is_nan());
1104+
}
1105+
#[test]
1106+
#[should_fail]
1107+
fn test_sum_inf_and_neg_inf_f64() {
1108+
[f64::INFINITY, f64::NEG_INFINITY].sum();
1109+
}
1110+
#[test]
1111+
fn test_sum_max_value_f64() {
1112+
let n = f64::MAX_VALUE;
1113+
assert_eq!([n, -n, n].sum(), n);
1114+
}
1115+
#[test]
1116+
#[should_fail]
1117+
fn test_sum_overflow_f64() {
1118+
let n = f64::MAX_VALUE;
1119+
[n, -n, n, n / 10.0].sum();
1120+
}
10251121
}
10261122

10271123
#[cfg(test)]

0 commit comments

Comments
 (0)