How to accurately convert numbers to strings in JavaScript - javascript

JavaScript stores all numbers in double-precision floating-point format, with a 52-bit mantissa and an 11-bit exponent (the IEEE 754 Standard for storing numeric values), and therefore its Number-to-String conversions are often inaccurate. For instance,
111111111*111111111===12345678987654321
is correct, but
(111111111*111111111).toString()
returns "12345678987654320" instead of "12345678987654321". Likewise, 0.362*100 yields 36.199999999999996.
Is there a simple way to accurately convert numbers to strings?

It is NOT the number to string conversion that is inaccurate. It is the storage of the number itself in floating point as all values cannot be represented precisely in floating point and there is a limit to the number of significant digits that can be stored (about 16). You simply can't use floating point if you need perfect precision and you certainly can't be using a number that has this many significant digits. Alternatives are integers, binary coded decimals, big decimal libraries, etc...
Here's a simple demo of the problem representing certain values in floating point: http://jsfiddle.net/jfriend00/nv4MJ/
Depending upon what problem you are actually trying to solve, decimal precision issues can often be worked around by using toFixed(n) when converting to display.
Other references to read for more explanation:
How to deal with floating point number precision in JavaScript?
Why can't decimal numbers be represented exactly in binary?
What range of numbers can be represented in a 16-, 32- and 64-bit IEEE-754 systems?
Why Floating-Point Numbers May Lose Precision - MSDN - Microsoft
Is floating point math broken?
Accurate floating point arithmetic in JavaScript
https://stackoverflow.com/questions/744099/is-there-a-good-javascript-bigdecimal-library

Related

What's the maximum precision (after the decimal point) of a float in Javascript

An algorithm I'm using needs to squeeze as many levels of precision as possible from a float number in Javascript. I don't mind whether the precision comes from a number that is very large or with a lot of numbers after the decimal point, I just literally need as many numerals in it as possible.
(If you care why, it is for a drag n' drop ranking algorithm which has to deal with a lot of halvings before rebalancing itself. I do also know there are better string-based algorithms but the numerical approach suits my purposes)
The MDN Docs say that:
The JavaScript Number type is a double-precision 64-bit binary format IEEE 754 value, like double in Java or C#. This means it can represent fractional values, but there are some limits to what it can store. A Number only keeps about 17 decimal places of precision; arithmetic is subject to rounding.
How should I best use the "17 decimal places of precision"?
Does the 17 decimal places mean "17 numerals in total, inclusive of those before and after the decimal place"
e.g. (adding underscores to represent thousand-separators for readability)
# 17 numerals: safe
111_222_333_444_555_66
# 17 numerals + decimal point: safe
111_222_333_444_555_6.6
1.11_222_333_444_555_66
# 18 numerals: unsafe
111_222_333_444_555_666
# 18 numerals + decimal point: unsafe
1.11_222_333_444_555_666
111_222_333_444_555_66.6
I assume that the precision of the number determines the number of numerals that you can use and that the position of the decimal point in those numerals is effectively academic.
Am I thinking about the problem correctly?
Does the presence of the decimal point have any bearing on the calculation or is it simply a matter of the number of numerals present
Should I assume that 17 numerals is safe / 18 is unsafe?
Does this vary by browser (not just today but over say, a 10 year window, should one assume that browser precision may increase)?
Short answer: you can probably squeeze out 15 "safe" digits, and it doesn't matter where you place your decimal point.
It's anyone's guess how the JavaScript standard is going to evolve and use other number representations.
Notice how the MDN doc says "about 17 decimals"? Right, it's because sometimes you can represent that many digits, and sometimes less. It's because the floating point representation doesn't map 1-to-1 to our decimal system.
Even numbers with seemingly less information will give rounding errors.
For example
0.1 + 0.2 => 0.30000000000000004
console.log(0.1 + 0.2);
However, in this case we have a lot of margin in the precision, so you can just ask for the precision you want to get rid of the rounding error
console.log((0.1 + 0.2).toPrecision(1));
For a larger illustration of this, consider the following snippet:
for(let i=0;i<22;i++) {
console.log(Number.MAX_SAFE_INTEGER / (10 ** i));
}
You will see a lot of rounding errors on digit 16. However, there would be cases where even the 16th decimal shows a rounding error. If you look here
https://en.wikipedia.org/wiki/IEEE_754
it states that binary 64 has 15.95 decimal digits. That's why I'd guess that 15 digits is the max precision you will get out of this.
You'd have to do your operations, and before you save back the number to any representational form, you'd have to do .toPrecision(15).
Finally this has some good explanations. https://floating-point-gui.de/formats/fp/
BTW, I got curious by reading this question so I read up as I wrote this answer. There are many people with better knowledge of this than me.
Does the presence of the decimal point have any bearing on the calculation or is it simply a matter of the number of numerals present
Kinda. To answer that, you'll need to look into how 64bit "double precision" floating point numbers are represented in memory. The "number of numerals" roughly translates into "length of the mantissa", which is indeed fixed and independent from the position of the point. However: it's binary digits and a binary point, not decimal digits and the decimal point. They do not correspond to each other directly. And then there's stuff like subnormal numbers.
Should I assume that 17 numerals is safe / 18 is unsafe?
No. In fact, only 15 decimal numerals would be "safe" if that's the representation you're starting with and want to exactly represent as a double.
Does this vary by browser (not just today but over say, a 10 year window, should one assume that browser precision may increase)?
No, it doesn't vary. The JavaScript number type will always be 64bit doubles.
Am I thinking about the problem correctly?
No.
You say you're considering this in the context of a drag'n'drop ranking algorithm, and you don't want do this string-based. However, thinking about decimal places in numbers is essentially thinking about string representation of numbers. Don't do that - either go all the way to strings, or treat numbers as binary.
Since you also mention "rebalancing", I assume you want to use numbers to encode the position of each item in a binary tree. That's a reasonable approach, but you really need to consider the binary representation of the number for that. And you really should use integers there, not floating-point numbers, as the logic would be much more complex otherwise. Start by deciding how many bits you want to use. There are some limitations for each, so choose wisely:
31/32 bit are what JS bitwise operators for numbers work on. Supported by all browsers easily.
53 bit are the range of integers you can exactly represent with floating-point numbers. Integer arithmetic will work as expected up to that size. Bitwise operations require extra code.
Fixed multiples of 8 (say, 64 bit) are what you can represent with typed arrays. Bitwise operations can be done part-wise, arithmetic operations require extra code. Or use a BigUint64Array that gives you 64 bits as a bigint to calculate with/operate on, but is not supported in old browsers.
Arbitrary precision can be achieved with bigint numbers, which support both bitwise and arithmetic operations, but again don't work in old browsers. Polyfills and bigint libraries are available though.

Handle javascript numbers with digits larger than 16 [duplicate]

This question already has answers here:
How can I handle numbers bigger than 17-digits in Firefox/IE7?
(4 answers)
Closed 7 years ago.
I tried looking on the forum on how to handle digits larger than 16. So far I found this post:
How can I handle numbers bigger than 17-digits in Firefox/IE7?
which does not provide a solution but only recommends to do some tricks to handle the operations.
I also looked online for libraries that could cope with this, such as this library, which is again limited by 15 digits.
http://mikemcl.github.io/decimal.js/#precision
What other ways are there to handle arithmetic operations for numbers with digits larger than 15?
Try looking for decimal math and arbitrary precision libraries.
"Decimal math" means it operates on decimal digits instead of binary digits.
Decimal floating point (DFP) arithmetic refers to both a representation and operations on decimal floating point numbers. Working directly with decimal (base 10) fractions can avoid the rounding errors that otherwise typically occur when converting between decimal fractions (common in human-entered data, such as measurements or financial information) and binary (base 2) fractions.
Arbitrary precision means without hard limits on number of digits.
In computer science, arbitrary-precision arithmetic, also called bignum arithmetic, multiple precision arithmetic, or sometimes infinite-precision arithmetic, indicates that calculations are performed on numbers whose digits of precision are limited only by the available memory of the host system. This contrasts with the faster fixed-precision arithmetic found in most arithmetic logic unit (ALU) hardware, which typically offers between 8 and 64 bits of precision.
Is there a decimal math library for JavaScript? might be a good starting point.

Convert a number into a 16-bit float (stored as bytes) and back?

For (lossy) compression purposes, I'd like to be able to convert Javascript numbers into 16-bit float representation to be stored in Uint16Arrays (or Uint8Arrays, whichever's easiest.) Then I'd like to be able to convert back from the 2 bytes to a number. I don't need to perform any arithmetic on the 16-bit numbers, it's just for compact storage.
I'm having trouble finding an algorithm to do this. It doesn't need to be a IEEE standard, just something accurate to a few decimal places.
Floats are preferable to fixed point because I'd rather not have to pre-determine the range of values.
Encoding
The first step is to extract the exponent and normalized fraction of the number. In C, this is done using the frexp function which isn't available in JavaScript. Googling for frexp javascript yields a couple of implementations.
For example, here's a straight-forward implementation that extracts the bits directly from the IEEE representation using typed arrays. Here's a quick-and-dirty, inexact version using only Math functions. Here's an exact (?) version.
The second step is to create your 16-bit FP number from the obtained exponent and mantissa using bit operations. Make sure to check the exponent range and to round the lower-precision mantissa for better accuracy.
Decoding
Extract exponent and mantissa from your 16-bit FP number. Convert them to a JavaScript number either with
// The value of 'adjust' depends on the size of the mantissa.
Math.pow(2, exponent - adjust) * mantissa
or by directly creating an IEEE bit pattern with typed arrays.
Subnormal numbers, infinity, NaN
Subnormal JavaScript numbers can be simply rounded to zero. You'll have to decide whether you want to support subnormal numbers in your 16-bit format. This will complicate the conversion process in exchange for better accuracy of numbers near zero.
You'll also have to decide whether to support infinity and NaN in your format. These values could be handled similarly to the IEEE format.

Why this calculation doesn't return a whole number? [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 9 years ago.
console.log((57 / 100) * 100);
When I did this math in Javascript it always return 56.99999999. Why its not 57.00000?
That has to do with the way JavaScript handles floating point numbers. Check out this topic: How to deal with floating point number precision in JavaScript?
In order to get an exact answer, you will need to work in integers, or use a decimal library. There are some decimal libraries for Javascript which compute the values using integers, then convert the values back to decimal. For example, there is the BigNumber library for Javascript.
The reason for this is that the section (57 / 100) returns a Float, or floating point number in Javascript. JavaScript uses 64-bit floating point numbers, which on most systems, gives 53 bits of precision to represent the mantissa, or numeric part of the float (the 3.2 in 3.2e4). Most systems use the IEEE-754 floating point standard, which allows for any error of the floating point operations as long as it's less than one unit in the last place (which in the case of IEEE-754 64-bit is the 54th bit). Therefore, all floats are cut off, rounded and/or truncated, which is why the result returns a fractional number.
For more information on why this happens, you can also see: Is floating point math broken?

Convert large number in javascript

After pasting the number t=3.7333333258105216E16 in jsconsole.com or in Web Inspector, I get 37333333258105220.
parseFloat(3.7333333258105216E16) gives the same result.
What is the reason ?
JavaScript represents numbers as floats. This storage format consists of 64 bits. One bit is for the sign, 11 bits are for the power of 10 to multiply the number by, and 52 bits are for the number.
Because of the above, numbers can be acurate to the 1/2^52, or 1 / 4,503,599,627,370,496. Thus, numbers are accurate to within this fraction. Check out this wikipedia page for more information on floating point numbers.
I tested this out by trying to add one to 4,503,599,627,370,495. It gets to 4,503,599,627,370,496, but does not get past it. Here's the fiddle for testing.
You are encountering floating-point roundoff. JavaScript numbers are implemented as double precision, 64-bit floats according to the IEEE 754 standard.
You can't always accurately represent a floating point decimal number in binary. It is losing precision at the end of the number so it can fit in 64 bits.

Categories

Resources