Log. 4th August, 2013.

So I had a bit of Perl code that went like this: $rsum = (~$rsum + 1) & 0xff;. For almost all inputs, it worked, then one popped up where it failed (dependent on Perl version, architecture, Perl integer width). Suddenly the result (from $rsum = 0x72b8) was zero.

I did the usual kludgy things to ensure I was working with integers: cast to int, added 0, but same result. Broke it up into individual operations, and it turns out it was the +1 that triggered the bug. I'd come to the conclusion that something about Perl's automatic type conversion must be messing with things, but had no idea what. Then William Astle suggested maybe it was being converted to a float - what? Why? But indeed that's what was happening.

So when Perl complements a number, the result is unsigned (of course). But of course, that number is now probably too large to hold in a signed integer. Addition in Perl appears to demand a signed result, so it converts everything to floats instead. Most of the time, that still works, but in this edge case, float precision is obviously not enough, and rounding errors occurred. Back to an integer with the masking, and lots of the least significant bits have been lost, result: zero.

The moral of the story? Mask to the desired number of bits after complement, always. More generally, don't assume Perl will DWYM: you still have to care about the underlying implementation. In C this wouldn't be an issue: unsigned ints overflow predictably (and won't unexpectedly turn into float), and it's just an error to try it with signed ints anyway.

See also http://www.joelonsoftware.com/articles/LeakyAbstractions.html.

Tags: programming, perl, integer, overflow