2

I have a simple (perhaps naive) question - I'm trying to test a scenario, and was wondering if it's possible to cause a floating point precision error when parsing a string to number (is there a magic number that will cause a FPPE?).

e.g.

parseFloat("4.015") // = 4.0149999999999994
const foo = 4.015 // = 4.0149999999999994

Or, is it only possible to produce a floating point precision error as a result of an operation?

console.log(4.015)
console.log(parseFloat("4.015"))
console.log(4.015 * 100)
Nick Grealy
  • 24,216
  • 9
  • 104
  • 119
  • According to the docs it simply takes the first part of the string if its valid and returns it 1 by 1 as float. Having a number causing that kind of bug would be a huge issue. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat – Aaron Feb 18 '20 at 06:12
  • 1
    Not to my knowledge. If you parse any floating point number, you'd get the nearest representation of that number which will look like the number. So, if you parse `"0.1"` and `"0.2"` you'd get values that represent them well enough. But if you add these two representations together you get the infamous `0.30000000000000004` because neither of the two is actually completely correct. The level of inaccuracy only shows up after a mathematical operation. – VLAZ Feb 18 '20 at 06:12
  • Try specifying more than 17 digits. – Patricia Shanahan Feb 18 '20 at 09:07
  • @VLAZ: “Looking like” a number does not make a thing the number. If you convert x to the nearest representable value, and the result is not x, then there has been a rounding error even if the result of converting it back to decimal for display is x. The error is just hidden—it still exists. And it is a problem not to know this, as one cannot reason correctly about floating-point operations unless one knows what the true values are. – Eric Postpischil Feb 18 '20 at 14:41
  • parseFloat("4.015").toFixed(17) displays "4.01499999999999968" – QuentinUK Feb 18 '20 at 15:52

1 Answers1

2

Converting a number from a decimal in a string to binary floating-point is an operation, and it does produce “precision errors.” If the source number is not representable in the destination format, it is rounded.

In the examples you show, every operation has a rounding error, except for converting the source text 100 to the number 100.

In console.log(4.015), the decimal numeral “4.015” represents the number 4.015. When this is converted to the IEEE-754 64-bit binary floating-point that JavaScript uses, the result is 4.01499999999999968025576890795491635799407958984375. (That has lots of digits, but it is actually represented internally in a form equivalent to 4520488125973135 times 2−50.) Then, to print this to the console, it is converted from binary floating-point to decimal. When doing the conversion, JavaScript uses just enough decimal digits to uniquely distinguish the value from neighboring values that are representable in binary floating-point. The result is the string “4.015”. So the binary floating-point value has been rounded to a different decimal value—producing the same string as the original. So, in console.log(4.015), there are actually two conversion operations, there is rounding error in both of them, and the behavior of the software acts to conceal the rounding errors.

In console.log(parseFloat("4.015")), the same thing happens, except the first conversion is done when parseFloat is called instead of when 4.015 is interpreted in the source code.

In console.log(4.015 * 100), there are four operations and three rounding errors:

  • 4.015 is converted to binary floating-point, which has the rounding error described above.
  • 100 is converted to binary floating-point, and there is no rounding error because 100 is representable in binary floating-point.
  • The two binary floating-point numbers are multiplied, and there is a rounding error, producing 401.49999999999994315658113919198513031005859375.
  • That result is converted to a decimal string. The result is “401.49999999999994” because we need that many digits to distinguish it from the two neighboring representable values, 401.4999999999998863131622783839702606201171875 and 401.5.

In summary:

  • Every operation, including conversions between binary and decimal, may be subject to rounding error.
  • The default JavaScript formatting acts to conceal some rounding errors; round trips from decimal numerals shorter than 16 digits will always produce the original number (within the finite range of the format).
Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • I guess "shorter than 17 digits" should be "shorter than 16 digits" here: the 16-digit string "9007199254740993" wouldn't be expected to roundtrip. – Mark Dickinson Feb 18 '20 at 20:46