6 ms·
Here is the approach taken in Go's sort.Search() Do the sum using signed int. Then cast to unsigned int before the division (i.e., use a non-arithmetic shift
by bugfix-66 4y ago
Here is the approach taken in Go's sort.Search()
Do the sum using signed int.
Then cast to unsigned int before the division (i.e., use a non-arithmetic shift low).
Then cast back to signed int.
func Search(n int, f func(int) bool) int {
// Define f(-1) == false and f(n) == true.
// Invariant: f(i-1) == false, f(j) == true.
i, j := 0, n
for i < j {
h := int(uint(i+j) >> 1) // avoid overflow when computing h
// i ≤ h < j
if !f(h) {
i = h + 1 // preserves f(i-1) == false
} else {
j = h // preserves f(j) == true
}
}
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
return i
}
If you care about stuff like this you may enjoy the puzzle "Upside-Down Arithmetic Shift":
https://bugfix-66.com/76b563beb6f4e61801fce4e835be862fb3dbbe08e75caaab80a495ed15a3e58b https://bugfix-66.com/76b563beb6f4e61801fce4e835be862fb3dbbe...
- morelisp 4y agoThe solution here is not really interesting except from a language design perspective. Go avoids this problem by having the maximum array length be int, but doing the math in uint. This won’t work in languages that lack uints (Java) or have maximum array sizes in uint (C/C++).
- LoganDark 4y agoJava lacks a distinct uint type, but (since Java 8) allows you to perform unsigned operations on a regular int, effectively treating it as a uint. It doesn't help that almost nobody knows this, though.
- morelisp 4y agoAt the point where you're writing `>>>` to, ironically, do proper arithmetic - you should probably write a correct version without a shift instead.
- wizeman 4y agoThis wouldn't work for C/C++ because in these languages signed integer overflow is undefined behavior.
- morelisp 4y agoYou could write the same approach in C as `(size_t)i+(size_t)j` without UB. The real reason it doesn't work in C is because a memory region can be large enough to still overflow in that case.
- vlovich123 4y agoPretty sure that’s not the case for 64 bit systems since you can “only” allocate about 48 bits of address space (maybe slightly more on newer systems). For 32 bit systems using 64bit instead of size_t would similarly solve the problem.
- wizeman 4y agoWell, that's not something the C standard (or POSIX, etc) guarantees, is it? Conceptually, a 64-bit kernel today could allow your program to allocate (almost) the entire 64-bit address space, assuming it does memory overcommit (like Linux) and/or uses some kind of memory compression (like Linux supports as well). There might be some MMU limitations on today's mainstream systems, but this doesn't mean that all 64-bit systems have those limitations or that those limitations will remain there in the future. So your code would break as soon as a new system comes along without those limitations. Also, this would be even more true if the code and stack would be stored in different address spaces, as theoretically that would even allow you to allocate the entire address space, I think.
- vlovich123 4y agoThe system you describe simply doesn’t exist, standards or no. A 64-bit kernel can’t hand out 64-bits worth of addresses because no CPU built today supports it. A 48-bit index to an array can represent >240TBytes of RAM minimum - if your records are > 1 byte, you have significantly higher storage requirements. The largest system I could find that’s ever been built was a prototype that has ~160TiB of RAM [1]. Also remember. To make the algorithm incorrect, the sum of two numbers has to exceed 64bits - that means you’d need >63-bits of byte-addressable space. That just simply isn’t happening. Now of course you might be searching through offline storage. 2^63 bits is ~9 exabytes of an array where each element is 1 byte. Note that now we’re talking scales of about about the aggregate total storage capacity of a public hyperscaled cloud. Your binary search simply won’t even finish. So sure. You’re technically right except you’d never find the bug on any system that your algorithm would ever run on for the foreseeable future, so does it even matter? As an aside, at the point where you’re talking about 48-bits worth of addressable bytes you’re searching, you’re choosing a different algorithm because a single lookup is going to take on the order of hours to complete. 63-bits is going to take ~27 years iff you can sustain 20gib/s for comparing the keys (sure binary search is logarithmic but then you’re not going to be hitting 20gib/s). Remember - data doesn’t come presorted either so simply getting all that data into a linearly sorted data structure is similarly impractical.