6

I was bored, so I started fidlling around in the console, and stumbled onto this (ignore the syntax error):

js in Chrome console

Some variable "test" has a value, which I multiply by 10K, it suddenly changes into different number (you could call it a rounding error, but that depends on how much accuracy you need). I then multiply that number by 10, and it changes back/again.

That raises a few questions for me:
  • How in accurate is Javascript? Has this been determined? I.e. a number that can be taken into account?
  • Is there a way to fix this? I.e. to do math in Javascript with complete accuracy (within the limitations of its datatype).
  • Should the changed number after the second operation be interpreted as 'changing back to the original number' or 'changing again, because of the inaccuracy'?

I'm not sure whether this should be a separate question, but I was actually trying to round numbers to a certain amount after the decimal point. I've researched it a bit, and have found two methods:

 > Method A

function roundNumber(number, digits) {
    var multiple = Math.pow(10, digits);
    return Math.floor(number * multiple) / multiple;
}

 > Method B

function roundNumber(number, digits) {
    return Number(number.toFixed(digits));
}


Intuitively I like method B more (looks more efficient), but I don't know what going on behind the scenes so I can't really judge. Anyone have an idea on that? Or a way to benchmark this? And why is there no native round_to_this_many_decimals function? (one that returns an integer, not a string)

KJdev
  • 723
  • 1
  • 9
  • 20
  • The size of the error you witnessed was 3 * 10^-18 do you need something more accurate than that? – JLRishe May 06 '14 at 17:29
  • I had a maths class last semester, so that would have been handy do some quick simulations, yes. I understand that you don't need that kind of accuracy when counting the number of items in a shopping cart, but that's not what this is about. Also, when you consider rounding `0.025480999999999997` to 6 decimals you get `0.025480` instead of the `0.025481` you'd expect. Suddenly the compounded inaccuracy becomes 1 * 10^-6. That's a problem for me. I'd appreciate someone answering the actual question instead of telling me I'm wrong in what I need.. I think it's a valid problem. – KJdev May 06 '14 at 17:45
  • What are you doing to round that number? If I execute `var n = 0.025480999999999997; n.toFixed(6);`, I get `0.025481`. What is your "actual question"? I count six questions in your post and we've answered most of them. – JLRishe May 06 '14 at 17:53
  • I slightly changed method A (to what I was thinking of while writing the previous comment), this results in the inaccuracy mentioned in the previous comment. By "actual question" I meant the question as a whole, as in SO's "Ask a question", I apologize if I was unclear about that. And indeed, it seems most of the things I was wondering about have been addressed, SO is awesome ^^ – KJdev May 06 '14 at 18:10
  • Yes, using `floor()` would not get you a correctly rounded number because that's not what `floor()` does. :) – JLRishe May 06 '14 at 18:13
  • Thanks, I'll be using `+numb.toFixed(digits);` instead in the future :) I think it's unfortunate I can't mark two answers as correct, that would be called for in this situation.. – KJdev May 06 '14 at 18:59

3 Answers3

4

How in accurate is Javascript?

Javascript uses standard double precision floating point numbers, so the precision limitations are the same as for any other language that uses them, which is most languages. It's the native format used by the processor to handle floating point numbers.

Is there a way to fix this? I.e. to do math in Javascript with complete accuracy (within the limitations of its datatype).

No. The precision limitations lies in the way that the number is stored. Floating point numbers doesn't have complete accuracy, so no matter how you do the calculations you can't achieve absolute accuracy as the result goes back into a floating point number.

If you want complete accuracy then you need to use a different data type.

Should the changed number after the second operation be interpreted as 'changing back to the original number' or 'changing again, because of the inaccuracy'?

It's changing again.

When a number is converted to text to be displayed, it's rounded to a certain number of digits. The numbers that look like they are exact aren't, it's just that the limitations in precision doesn't show up.

When the number "changes back" it's just because the rounding again hides the limitations in the precision. Each calculation adds or subtracts a small inaccuracy in the number, and sometimes it just happens to take the number closer to the number that you had originally. Eventhough it looks like it's more accurate, it's actually less accurate as each calculation adds a bit of uncertainty.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • Thanks for the clear answer! Would there be a way to use a different data type in Javascript that has a higher accuracy, or a way to compound the accuracy of multiple variables (say using two variables that have 16 digits of accuracy to create the illusion of having one variable with 32 digits accuracy)? I'm just wondering, if it's too much work or you think it sounds ridiculous, don't hesitate to say it :) – KJdev May 06 '14 at 18:21
  • 1
    @DaJF: Yes, there are different ways to handle numbers as composite values, you can for example use a string, or an array where each item holds a few digits. This means that any calculation or comparison has to be done using a library, and naturally is very much slower than the native number type. There exists some libraries already, using different methods to handle numbers in a way the precision limit isn't fixed as in floating point numbers. – Guffa May 06 '14 at 18:46
3

Internally, JavaScript uses 64-bit IEEE 754 floating-point numbers, which are a widely used standard and usually guarantee about 16 digits of accuracy. The error you witnessesed was on the 17th significant digit of the number and was reeeally tiny.

Is there a way to [...] do math in Javascript with complete accuracy (within the limitations of its datatype).

I would say that JavaScript's math is completely accurate within the limitations of its datatype. The error you witnessed was outside of those limitations.

Are you working with calculations that require a higher degree of precision than that?

Should the changed number after the second operation be interpreted as 'changing back to the original number' or 'changing again, because of the inaccuracy'?

The number never really became more or less accurate than the original value. It was only when the value was converted into a decimal value that a rounding error became apparent. But this was not a case of the value "changing back" to an accurate number. The rounding error was just too small to display.

And why is there no native round_to_this_many_decimals function? (one that returns an integer, not a string)

"Why is the language this way" questions are not considered very productive here, but it is easy to get around this limitation (assuming you mean numbers and not integers). This answer has 337 upvotes: +numb.toFixed(digits);, but note that if you try to display a number produced with that expression, there's no guarantee that it will actually display with only six digits. That's probably one of the reasons why JavaScript's "round to N places" function produces a string and not a number.

Community
  • 1
  • 1
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • "reeeally tiny" depends on what you're trying to do, and I'm wondering if it would be possible to do "reeeally" accurate calculations.. Anyway, the 'toFixed()' function returns a string, won't that trigger a implicit conversion from string to number every time you use it in a calculation, thus being a bit inefficient? I'm not criticizing, just asking a question. – KJdev May 06 '14 at 17:54
  • Why would you be rounding your intermediate values mid-calculation? – JLRishe May 06 '14 at 17:57
  • The accuracy of the calculations you can do with JavaScript's built-in math functionality is limited by the IEEE 754 standard and is a well-known commodity. If you need something more accurate than that, you'll need a custom library that's designed for high-precision calculations. If so, you might want to [check this out](http://blog.smartbear.com/testing/four-serious-math-libraries-for-javascript/). – JLRishe May 06 '14 at 17:59
  • "why rounding mid-calculation?" is a valid point/question. The reason I'm doing it is because I have code in a for-loop that loops for example 100k times (in a test-run, the real thing will run many times more), and after doing a calculation each iteration I round the variable because I think it saves memory. If each time there's an inaccuracy of 10^-6, that'll compound to a considerable inaccuracy after 100k times. Am I wrong in assuming what I'm trying to do saves memory? – KJdev May 06 '14 at 18:32
  • And the libraries you've linked to look very promising, thanks! – KJdev May 06 '14 at 18:33
  • A numeric value will occupy 64 bits (8 bytes) no matter how many digits it has. So no, rounding your result will not save any memory, and if you skip the rounding, your algorithm will run faster and you can count on about 16 digits of accuracy. – JLRishe May 06 '14 at 18:46
  • Thanks, I was afraid of that.. I'm used to languages with different datatypes, and writing code optimized for memory-usage that way. – KJdev May 06 '14 at 18:54
0

I came across the same few times and with further research I was able solve the little issues by using the library below

Math.js Library

Sample

import {
  atan2, chain, derivative, e, evaluate, log, pi, pow, round, sqrt
} from 'mathjs'

// functions and constants
round(e, 3)                    // 2.718
atan2(3, -3) / pi              // 0.75
log(10000, 10)                 // 4
sqrt(-4)                       // 2i
pow([[-1, 2], [3, 1]], 2)      // [[7, 0], [0, 7]]
derivative('x^2 + x', 'x')     // 2 * x + 1

// expressions
evaluate('12 / (2.3 + 0.7)')   // 4
evaluate('12.7 cm to inch')    // 5 inch
evaluate('sin(45 deg) ^ 2')    // 0.5
evaluate('9 / 3 + 2i')         // 3 + 2i
evaluate('det([-1, 2; 3, 1])') // -7

// chaining
chain(3)
    .add(4)
    .multiply(2)
    .done()  // 14
Charith Jayasanka
  • 4,033
  • 31
  • 42