Nearly All Binary Searches and Mergesorts Are Broken (2006)
research.googleblog.com
research.googleblog.com
At the risk of sounding stupid, I had a question about the following solution:
int mid = (low + high) >>> 1;
I understand that right shift is like dividing by 2, just like in decimal right shifting the digits is like dividing by 10 (654/10 = 065). But why can we just ignore the low + high overflow issue? Is it because the overflow will be stored in the two's complement bit and right shift doesn't care about what that bit represents? If unsigned ints were used instead, I suppose that trick wouldn't work?When low and hi are summed together, the high bit of the result might be set. For a signed int, this would be an accidental overflow and would be falsely interpreted as a negative number. However, if you interpret the result as an unsigned int, it would be the correct sum.
The Java logical shift operator >>> does not retain the sign when shifting. So, it will always shift a 0 into the high bit, making the result a positive signed int.
In most C implementations, >> is an arithmetic shift, which would retain the sign in the result. So in C, we would need to cast low and high as unsigned first; otherwise, the high bit will be retained rather than shifted.
You cannot just assume nobody will use your algorithms with large inputs and act like your data types have unlimited range. Such bugs will typically manifest at important customers and be hard to analyze. Better think about your types beforehand and do add runtime checks!
A function, especially when part of the standard library, must work correctly for all possible inputs or reject them outright.
Huh? How did he prove it correct, then?
Generally that isn't a problem and the 'small' details or assumptions don't 'leak' up to higher levels and do any real damage to the proof.
There's a movement to make proofs formally verified, to try move towards eliminating this issue, but that's hard, because writing out every little detail, specifying every last piece of context & assumption & step is laborious.
Can code be written and subsequently formally verified that can then help in the process of formally verifying code?
In this case the proof did not properly account for the full range of inputs, of at least skipped over the fact that in real hardware the integer domains are finite.
There's a lot of formal verification tools out there though, they do make it easier,easy enough to at least properly verify the correctness of a simple sorting algorithm.
I'm pretty sure you're right though. Integers must have been defined as infinite in the proof for it to have been a proof at all.
int mid = (low + high) >>> 1;
I don't get how this fixes anything, the overflow still occurs.Why not simply
int mid = (low/2) + (high/2) + (low & high & 1);Here's another approach: represent the range as a (low, length) pair instead of (low, high). The update becomes either length /= 2 or low += length/2, length -= length/2. IIRC this was what I did when I adapted Bentley's code to C 25 years ago, mainly because of overflow. It never occurred to me to check if standard libraries got it wrong.
As to C++ 2+ billion byte arrays are not uncommon. It's just a few gigs of ram for int[] arrays.
Why this instead of the more intuitive `low/2 + high/2` ?
Edit: Nevermind, I get it. The reason is instructive: In the case where high and low are odd numbers, the "more intuitive" way is off by one.
No, this just indicates that you didn't prove it rigorously enough :p