JavaScript floating point numbers can confuse C++ programmers. Anthony Williams draws our attention to some surprising behaviour.
I’ve been playing around with Javascript (strictly, ECMAScript) in my spare time recently, and one thing that I’ve noticed is that numbers are handled slightly strangely. I’m sure that many experienced Javascript programmers will just nod sagely and say “ Everyone knows that ”, but I’ve been using Javascript for a while and not encountered this strangeness before as I’ve not done extensive numerical processing, so I figured it was worth writing down.
Numbers are floating point
For the most part, Javascript numbers are floating point numbers. In particular, they are standard IEEE 754 64bit doubleprecision numbers. Even though the IEEE spec allows for multiple NaN (Notanumber) values, Javascript has exactly one NaN value, which can be referenced in code as
NaN
.
This has immediate consequences: there are upper and lower limits to the stored value, and numbers can only have a certain precision.
For example, 10000000000000001 cannot be represented in Javascript. It is the same value as 10000000000000000.
var x=10000000000000000; if(x==(x+1)) alert("Oops");
This itself isn’t particularly strange: one of the first things you learn about Javascript is that it has floatingpoint numbers. However, it’s something that you need to bear in mind when trying to do any calculations involving very big numbers (larger than 9007199254740992 in magnitude) or where more than 53 bits of precision is needed (since IEEE 754 numbers have binary exponents and mantissas).
You might think that you don’t need the precision, but you quickly hit problems when using decimal fractions:
var x=0.2*0.30.01; if(x!=0.05) alert("Oops");
The rounding errors in the representations of the decimal fractions here mean that the value of
x
in this example is 0.049999999999999996, not 0.05 as you would hope.
Again, this isn’t particularly strange, it’s just an inherent property of the numbers being represented as floating point. However, what I found strange is that sometimes the numbers aren’t treated as floating point.
Numbers aren’t always floating point
Yes, that’s right: Javascript numbers are sometimes not floating point numbers. Sometimes they are 32bit signed integers , and very occasionally 32bit unsigned integers.
The first place this happens is with the bitwise operators (
&
,

,
^
): if you use one of these then both operands are first converted to a 32bit signed integer. This can have surprising consequences.
Look at the following snippet of code:
var x=0x100000000; // 2^32 console.log(x); console.log(x0);
What do you expect it to do? Surely
x0
is
x
? You might be excused for thinking so, but no. Now,
x
is too large for a 32bit integer, so
x0
forces it to be taken modulo 232 before converting to a signed integer. The low 32bits are all zero, so now
x0
is just 0.
OK, what about this case:
var x=0x80000000; // 2^31 console.log(x); console.log(x0);
What do you expect now? We’re under 232, so there’s no dropping of higher order bits, so surely
x0
is
x
now? Again, no.
x0
in this case is
x
, because
x
is first converted to a signed 32bit integer with 2s complement representation, which means the mostsignificant bit is the sign bit, so the number is negative.
I have to confess, that even with the truncation to 32bits, the use of signed integers for bitwise operations just seems odd. Doing bitwise operations on a signed number is a very unusual case, and is just asking for trouble, especially when the result is just a ‘number’, so you can’t rely on doing further operations and having them give you the result you would expect on a 32bit integer value.
For example, you might want to mask off some bits from a value. With normal 2s complement integers,
x(x&mask)
is the same as
x&~mask
: in both cases, you’re left with the bits set in
x
that were not set in mask. With Javascript, this doesn’t work if
x
has bit 31 set.
var x=0xabcdef12; var mask=0xff; console.log(x(x&mask)); console.log(x&~mask);
If you truncate back to 32bits with
x0
then the values are indeed the same, but it’s easy to forget.
Shifting bits
In languages such as C and C++,
x<<y
is exactly the same as
x*(1<<y)
if
x
is an integer. Not so in Javascript. If you do a bitshift operation (
<<
,
>>
, or
>>>
) then Javascript again converts your value to a signed integer before and after the operation. This can have surprising results.
var x=0xaa; console.log(x); console.log(x<<24); console.log(x*(1<<24));
x<<24
converts
x
to a signed 32bit integer, bitshifts the value as a signed 32bit integer, and then converts that result back to a
Number
. In this case,
x<<24
has the bit pattern 0xaa000000, which has the highest bit set when treated as 32bit, so is now a negative number with value 1442840576. On the other hand,
1<<24
does not have the high bit set, so is still positive, so
x*(1<<24)
is a positive number, with the same value as 0xaa000000.
Of course, if the result of shifting would have more than 32 bits then the top bits are lost:
0xaa<<25
would be truncated to 0x54000000, so has the value 1409286144, rather than the 5704253440 that you get from
0xaa*(1<<25)
.
Going right
For rightshifts, there are two operators:
>>
and
>>>
. Why two? Because the operands are converted to
signed
numbers, and the two operators have different semantics for negative operands.
What is 0x80000000 shifted right one bit? That depends. As an unsigned number, right shift is just a dividebytwo operation, so the answer is 0x40000000, and that’s what you get with the
>>>
operator. The
>>>
operator shifts in zeroes. On the other hand, if you think of this as a negative number (since it has bit 31 set), then you might want the answer to stay negative. This is what the
>>
operator does: it shifts in a 1 into the new bit 31, so negative numbers remain negative.
As ever, this can have odd consequences if the initial number is larger than 32 bits.
var x=0x280000000; console.log(x); console.log(x>>1); console.log(x>>>1);
0x280000000 is a large positive number, but it’s greater than 32bits long, so is first truncated to 32bits, and
converted to a signed number
.
0x280000000>>1
is thus not 0x140000000 as you might naively expect, but 1073741824, since the high bits are dropped, giving 0x80000000, which is a negative number, and
>>
preserves the sign bit, so we have 0xc0000000, which is 1073741824.
Using
>>>
just does the truncation, so it essentially treats the operand as an
unsigned
32bit number.
0x280000000>>>1
is thus 0x40000000.
If right shifts are so odd, why not just use division?
Divide and conquer?
If you need to preserve all the bits, then you might think that doing a division instead of a shift is the answer: after all, right shifting is simply dividing by 2 n . The problem here is that Javascript doesn’t have integer division. 3/2 is 1.5, not 1. You’re therefore looking at two floatingpoint operations instead of one integer operation, as you have to discard the fractional part either by removing the remainder beforehand, or by truncating it afterwards.
var x=3; console.log(x); console.log(x/2); console.log((x(x%2))/2); console.log(Math.floor(x/2));
Summary
For the most part, Javascript numbers are doubleprecision floating point, so need to be treated the same as you would floating point numbers in any other language.
However, Javascript also provides bitwise and shift operations, which first convert the operands to 32bit signed 2scomplement values. This can have surprising consequences when either the input or result has a magnitude of more than 231.
This strikes me as a really strange choice for the language designers to make: doing bitwise operations on signed values is a really niche feature, whereas many people will want to do bitwise operations on unsigned values.
As browser Javascript processors get faster, and with the rise of things like Node.js for running Javascript outside a browser, Javascript is getting used for far more than just simple webpage effects. If you’re planning on using it for anything involving numerical work or bitwise operations, then you need to be aware of this behaviour.