0 = 1, 1 = 3, 2 = 5, 100 = 201. What is going on? Ruby has object_ids on objects but they are shorter on small integers. Fire up irb and follow along:
The value of object_id is the normal looking ids I’ve seen in the past. But then I did this:
Ok, why the values? Why is 0’s id 1 and 100’s id 201? It looks like it’s doubling the value and adding one but that seems a little random and probably not what it’s actually doing. So I read a bit by Caleb Tennis on Oreilly and found a tip off that it’s related to binary:
Thus 0×0101 (5) becomes 0×1011 (11).
Notice that Caleb is noting that “101” is shifting left and adding 1 at the lowest bit. So then this should work:
And it does. We can set a bigger number and run it again and it still works.
I will call this predictable behavior later. x.object_id is 131073 on both sides because we know how object_id is generated:
So what is going on? Well first, let’s print out the binary. Taken from icfun.blogspot.com, we’ll define a number to binary string method in irb.
We can test that it works with any integer. Let’s test with the number 5 like this: puts dec2bin(5). Prints out 101. If we shift left by one bit like this: dec2bin((5 << 1)) then we’ll get 1010. Add 1 bit and we get the binary of eleven: 1011. We can see if this is eleven with dec2bin(11) and we get “1011” which is what we expected. It makes sense.
So wtf. Why know this stuff? Well, when doing object comparisons, I always expected some garbage or randomness and length similar to a hash. Like when doing (Object.new).object_id but this isn’t dependable or consistent when getting the object_id of a Fixnum. And even that is not consistent. The examples above are using small numbers. When we try this:
It works fine. 4 is small. Let’s call this “predictable”. We can bit shift and add 1 to get the same value as ruby does with object_id.
But a big enough number like 5000000000000000000 is false. So what’s the inflection point? Maybe the inflection point is 2^32 because it’s memory related:
Nope. Strange.
So I played around manually and started finding that 4 quintillion is true but 5 quintillion is false. Weird. If it’s memory related, it’s no number I recognize. So we know that there’s some inflection point between 4 quintillion and 5 quintillion. I’m not about to figure this thing out by hand. Let’s write a program to find our magic number.
When we run this little number searcher we find our overflow (or inflection) point. It will run for a while, finding each digit like this:
4400000000000000000
4700000000000000000
4500000000000000000
.
.
.
4611686018427387900
4611686018427387904
4611686018427387902
4611686018427387903
4611686018427387904
4611686018427387903
4611686018427387903
Eventually we’ll have a final value of 4611686018427387903 which we can test like this:
You can see it overflow making 9223372036854775807 the largest object_id in Ruby.
This program is not very efficient or well written. There’s a better way to find a number but I decided to go with a digit-by-digit algorithm. It was not very easy and quick to write. I nearly scrapped it and did it the right way but managed to get it working around midnight one night. I hope it makes for a good (or anti-pattern) example.