2

I'm testing toFixed() method of javascript. The result is seen as below.

(49.175).toFixed(2) => "49.17"
(49.775).toFixed(2) => "49.77"
(49.185).toFixed(2) => "49.19"
(49.785).toFixed(2) => "49.78"

(49.1175).toFixed(3) => "49.117"
(49.1775).toFixed(3) => "49.178"
(49.1185).toFixed(3) => "49.118"
(49.1785).toFixed(3) => "49.178"

I made this test at chrome browser, and I'm surprised with the result. I couldn't catch the logic. It doesn't fit neither 'round away from zero' nor 'round to even'. What is the rule behind of 'toFixed()' function ?

serefbilge
  • 1,654
  • 4
  • 29
  • 55
  • The number of digits to appear after the decimal point; this may be a value between 0 and 20, inclusive, and implementations may optionally support a larger range of values. If this argument is omitted, it is treated as 0. If digits is too small or too large. Values between 0 and 100, inclusive, will not cause a RangeError. – ABC Mar 29 '19 at 21:06
  • You could always just read the [spec](https://www.ecma-international.org/ecma-262/6.0/#sec-number.prototype.tofixed) – Chase Mar 29 '19 at 21:09

3 Answers3

3

About toFixed

Returns a String containing this Number value represented in decimal fixed-point notation with fractionDigits digits after the decimal point. If fractionDigits is undefined, 0 is assumed. Specifically, perform the following steps:

Algorithm Number.prototype.toFixed (fractionDigits): https://www.ecma-international.org/ecma-262/5.1/#sec-15.7.4.5

  • The length property of the toFixed method is 1.

    • If the toFixed method is called with more than one argument, then the behaviour is undefined (see clause 15).

An implementation is permitted to extend the behaviour of toFixed for values of fractionDigits less than 0 or greater than 20. In this case toFixed would not necessarily throw RangeError for such values.

NOTE The output of toFixed may be more precise than toString for some values because toString only prints enough significant digits to distinguish the number from adjacent number values.

JS Work Around

function fix(n, p) {
  return (+(Math.round(+(n + 'e' + p)) + 'e' + -p)).toFixed(p);
}
let exampleA = fix(49.1175, 3);
let exampleB = fix(49.1775, 3);
let exampleC = fix(49.775, 2);
const random = Math.random();
console.log(exampleA);
console.log(exampleB);
console.log(exampleC);
console.log('Before:', random, 'After Custom =>', fix(random, 3), 'Default:', random.toFixed(3));
// 49.118
// 49.178
// 49.78

Precision Needed

I suggest just simply porting set precision from C++ to a Node.JS Module.

  • You could simply rig up and use a child_process also in Node.JS to call a C++ program with an argument, and have the C++ run a function to convert the value and output to the console.
ABC
  • 2,068
  • 1
  • 10
  • 21
3

The issue is, that the numbers you entered do not exist! On scanning, they are (binary) rounded to the nearest possible/existing number. toPrecision(18) shows the numbers after scanning more exact:

(49.175).toPrecision(18); // "49.1749999999999972" => "49.17"
(49.775).toPrecision(18); // "49.7749999999999986" => "49.77"
(49.185).toPrecision(18); // "49.1850000000000023" => "49.19"
(49.785).toPrecision(18); // "49.7849999999999966" => "49.78"

So the number is rounded 2 times: First on scanning, and then by toFixed().

Wiimm
  • 2,971
  • 1
  • 15
  • 25
0

From the MDN:

toFixed() returns a string representation of numObj that does not use exponential notation and has exactly digits digits after the decimal place. The number is rounded if necessary, and the fractional part is padded with zeros if necessary so that it has the specified length. If numObj is greater or equal to 1e+21, this method simply calls Number.prototype.toString() and returns a string in exponential notation.

And later you can read:

WARNING: Floating point numbers cannot represent all decimals precisely in binary which can lead to unexpected results such as 0.1 + 0.2 === 0.3 returning false.

The above warning in conjuntion with the round logic (maybe arithmetical operations on the number) will explain the different behaviours you are experimenting in the rounding procedure (you can read it here).

Shidersz
  • 16,846
  • 2
  • 23
  • 48
  • but the values have the real values, not as the `0.1 + 0.2` problem. – Nina Scholz Mar 29 '19 at 21:14
  • 1
    So I can not trust toFixed function in an enterprise project. – serefbilge Mar 29 '19 at 21:18
  • 2
    @serefbilge you can't trust on any arithmetical operation between decimal numbers if you are looking for precision. I think there are some libraries that can help in this case, but you will have to google about it and do some researching. – Shidersz Mar 29 '19 at 21:28
  • @NinaScholz There is a similar example shown on the [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed#Examples) that refers to that **warning** showing differents behaviours on the rounding procedure. – Shidersz Mar 29 '19 at 21:34