Mean of two floating point numbers can be dangerous
blog.honzabrabec.cz
blog.honzabrabec.cz
[1] http://dl.acm.org/citation.cfm?id=2493882 [2] https://hal.archives-ouvertes.fr/hal-00576641v1/document
Tl;dr - the best way to compute mid-points is:
round_to_nearest_even((a - a/2) + b/2)
Why?It can't be (a+b)/2 because (a+b) can overflow.
It can't be (a/2 + b/2) because a/2 or b/2 can underflow. However, this works if you explicitly handle the case when a == b.
It can't be (a + (b/2 - a/2)) for similar but more complicated reasons.
However, if you switch things around, ((a - a/2) + b/2) works great without the special case. (Though all three options need to handle a second special case: when a == -b.)
Regarding the rounding method, you want it to be symmetric and moreover you want nearby numbers to not always be biased in the same direction (as might happen if you always round towards zero). Hence rounding to the nearest 'even' floating point number (i.e. nearest bit pattern with a zero in the LSB)
---
In the case of OP, I think the paper would argue that the mid-point was working just fine, and that the code surrounding it isn't considering all possibilities (symmetrically). But then the discussion of infinities in the paper suggests that no matter what you do, there are issues for surrounding code to consider. Which in turn suggests that floating point is absolutely busted as an abstraction.
The fix is the right one, but in C/C++, hardware is not what dictates rounding mode, the language implementation does, and that can be changed at runtime (http://www.cplusplus.com/reference/cfenv/fesetround/)
I think C/C++ default to 'round to nearest', with ties doing 'round to even'. Gnu libc certainly does (http://www.gnu.org/software/libc/manual/html_node/Rounding.h...)
static int compare (void *dpa, void *dpb)
{
return *(double*)dpa - *(double*)dpb;
}
The doubles represented times in nanoseconds, and the program failed to sort properly when the total runtime was larger than (IIRC) 2 seconds. I wonder if you can work out why.(Spoiler alert) the fix was: https://github.com/libguestfs/libguestfs/commit/1f4a0bd90df3...
double a = *(double *) dpa;
double b = *(double *) dpb;
if (a > b) return 1;
if (b < a) return -1;
return 0;What I haven't seen is the suggestion
0.5*x + 0.5*y
Yes, we now have two multiplications, but floating point multiplication is considerably faster than division.overflow breaks (x + y) / 2. where x and y are large underflow breaks (x / 2.) + (y / 2.) where x and y are small.
and of course the problem where x is outside the exponent of y. But one of these values gets treated as though it were insignificant and effectively 0.
I hope that the input to the program is sensible enough that floating point overflow won't be an issue, since there are more complicated formulas in the program than this simple one.
The only difference, really, is that floating point can lull you into believing they have unlimited precision. With integer math, the problem would have been more obvious in the first place.
That said, it's always safer to compute the mean of two integers as
min + (max - min) / 2
to avoid integer overflow.
#include <iostream>
using namespace std;
int avg(int a, int b) {
return (a&b) + ((a^b)>>1);
}
int main() {
cout << avg(int(2e9), int(2e9 + 10)) << endl;
cout << avg(int(2e9), int(-2e9)) << endl;
cout << avg(int(-2e9), int(-2e9 - 10)) << endl;
return 0;
}
Gives: 2000000005
0
-2000000005[1] http://www.inwap.com/pdp10/hbaker/hakmem/boolean.html#item23
It's actually really simple. We'll write a and b in binary notation, for example:
a = 1001101
b = 0100111
Now what happens when you add two numbers in binary? We essentially add the numbers in each column together, and if it overflows, we carry to the next column (this is how you carry out addition in general).So what are the columns where we need to carry, the ones that overflow? These are given by (a&b) - the columns where both a and b contain a one. To actually carry we just move everything one position to the left: ((a&b)<<1). And what are the columns where we don't need to carry, the ones that don't overflow? These are the ones where we have exactly one zero, either in a or in b, so: a^b.
In other words, a + b = ((a&b)<<1) + (a^b). To compute the average, we divide by two, or in other words, we bitshift to the right by one place: (a + b)/2 = (a&b) + ((a^b)>>1)
If anything is unclear, feel free to ask :)
int mid = (low + high) >>> 1;
This was a fix because the original code had a bug: int mid =(low + high) / 2;
Which was part of the Java code base for many years. Joshua Bloch based the code on Programming Pearls, which also contained the bug.More info: https://research.googleblog.com/2006/06/extra-extra-read-all...