Why does JScript have rounding errors?

Try this in JScript:

You might expect to get 920, but in fact you get 919.9999999999999.  What the heck is going on here?

Boy, have I ever heard this question a lot.

Well, let me answer that question with another question.  Suppose you did a simple division, say

Would you expect an infinitely large window that said "0.33333333333..." with an infinite number of threes, or would you expect ten or so digits?  Clearly you'd expect it to not fill your computer's entire memory with threes.  But that means that I must ask why are you willing to accept an error of 0.00000000000333333... in the case of dividing one by three but not willing to accept a smaller error of 0.000000000001 in the case of multiplying 9.2 by 100?

The simple fact is that computer arithmetic frequently accumulates tiny errors.  Any mathematics which results in numbers that cannot be represented by a small number of powers of two will result in errors.

Let's look at this cases a little closer.  We're trying to multiply 9.2 by 100.0.  100.0 can be EXACTLY represented as a floating point number because it's an integer.  But 9.2 can't be -- you can't represent 46 / 5 exactly in base two any more than you can represent 1 / 3 exactly in base ten.  So when converting from the string "9.2" to the internal binary representation, a tiny error is accrued.  However, it's not all bad -- the 64 bit binary number which represents 9.2 internally is (a) the 64 bit float closest to 9.2, and (b) the algorithm which converts back and forth between strings and binary representation will round-trip -- that binary representation will be converted back to 9.2 if you try to convert it to a string.

But now we go and throw a wrench in the works by multiplying by one hundred.  That's going to lose the last few bits of precision because we just multiplied the accrued error by a factor of one hundred.  The accrued error is now large enough that the string which most exactly represents the computed value is NOT "920" but rather "919.9999999999999".

The ECMAScript specification mandates that JScript display as much precision as possible when displaying floating point numbers as strings.

You may note that VBScript does not do this -- VBScript has heuristics which look for this situation and deliberately break the round-trip property in order to make this look better.  In JScript you are guaranteed that when you convert back and forth between string and binary representations you lose no data.  In VBScript you sometimes lose data; there are some legal floating point values in VBScript which are impossible to represent in strings accurately. In VBScript it is impossible to represent values like 919.9999999999999 precisely because they are automatically rounded!

Ultimately, the reason that this is an issue is because we as human beings see numbers like "920" as SPECIAL.  If you multiply 3.63874692874  by 4.2984769284 and get a result which is one-billionth of one percent off, no one cares, but when you multiply 9.2 by 100.0 and get a result which is one-billionth off, everyone yells (at me!)  The computer doesn't know that 9.2 is more special than 3.63874692874   -- it uses the same lossy algorithms for both.

All languages which use floating point arithmetic have this feature -- C++, VBScript, JScript, whatever.  If you don't like it, either get in the habit of calling rounding functions, or don't use floating point arithmetic, use only integer arithmetic.  (Note that VBScript supports a "currency" type which is fixed-point number not subject to rounding errors.  It can only represent four places after the decimal point though.)

1. Abhi says:

Hi,

I used JScript in WSH for doing 108 % 100 and i got the answer as 0. i get correct answers for all the other numbers. I think this is a bug too.

2. Eric Lippert says:

Hmm, that works fine for me and I don’t recall ever fixing a bug in the mod operator. Can you send me a SMALL program that reproduces the fault along with the version numbers on JScript.DLL and CScript.exe?

Thanks,
Eric

3. Jon Kale says:

‘Ultimately, the reason that this is an issue is because we as human beings see numbers like "920" as SPECIAL.’

Erm… surely you mean ‘numbers like "920.0" as SPECIAL’. After all, 920 differs from 920.0: one’s in Z, while the other’s in R, and as any fule kno, the numbers in Z have some very interesting properties which don’t generalise to R, and vice versa.

Now, this is of course a mathematically inclined pedant writing this… but even so it raises an interesting point, which is that people in the main conflate integers and rationals when they can, even when the conversion isn’t necessarily valid – so VBS does the "least-suprise" thing whereas JS does the "right" thing.

4. Eric Lippert says:

> after all, 920 differs from 920.0:

Well, if they differ, what’s their difference? Last I checked, 920 – 920.0 = 0, add 920.0 to both sides and you get 920 = 920.0, QED.

As a mathematically inclined pedant with a degree in mathematics from the University of Waterloo, lemme tell ya, you’re going to have to start from the set-theoretic underpinnings of the real number system if you want to make this argument, and it is not an argument that I myself would care to advance. That 920 in Z and the 920 in R are the same number dude.

5. Rob Chartier's Excogitation says:
6. Fabulous Adventures In Coding says:

2003/09/12 What’s Up With Hungarian Notation? 2003/09/12 Eric’s Complete Guide To BSTR Semantics 2003/09/12…

7. Kaspar Karlsson says:

Maestro,

I must agree with Jon Kale.

sometimes it’s important to have an integer when it should be, and a floating point number when needed, not just because an integer is prettier.

for example, if you want to calculate prime numbers, you’ll have to divide the number (x) you want to check by all integers ranging from 2 – ceil(sqrt(x))  (we call that integer y), and see what’s left (z)  (thus executing z = x % y). if z == (int) 0, then you’ll know that x isn’t prime, as it can be divided by y w times (w being an integer and positive). however, if z is not (int) 0, you’ll have to check further (by increasing y). but z can be (int) 0, plus/minus this very very very small JScript rounding error margin, making it NOT (int) 0. in that case you can’t be sure wheter you have a prime number or not…

of course calculating prime numbers in JScript is plain stupid when you can do that with Python several thousands times faster, but this is just an example to show that that very very very small JScript rounding error margin can make a real difference. I can think of other cases.