broken toFixed implementation

后端 未结 6 2186
陌清茗
陌清茗 2020-11-27 21:51

The default implementation of javascript\'s \"Number.toFixed\" appears to be a bit broken.

console.log((8.555).toFixed(2));    // returns 8.56
console.log((         


        
相关标签:
6条回答
  • 2020-11-27 22:16

    Thanks for the answer pst. My implementation almost worked, but didn't in some cases because of floating point errors.

    this line in my function is the culprit: Math.round(this * factor)

    (it's on the Number.prototype, so "this" is the number); 8.575 * 100 comes out to 857.4999999999999, which in turn rounds down. this is corrected by changing the line to read as follows: Math.round(Math.round(this * factor * 100) / 100)

    My entire workaround is now changed to:

    Number.prototype.toFixed = function(decimalPlaces) {
        var factor = Math.pow(10, decimalPlaces || 0);
        var v = (Math.round(Math.round(this * factor * 100) / 100) / factor).toString();
        if (v.indexOf('.') >= 0) {
            return v + factor.toString().substr(v.length - v.indexOf('.'));
        }
        return v + '.' + factor.toString().substr(1);
    };
    
    0 讨论(0)
  • 2020-11-27 22:16

    Check my answer

    function toFixed( num, precision ) {
        return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
    }
    
    0 讨论(0)
  • 2020-11-27 22:21

    A consistent solution would be to add a fixed tolerance (epsilon) to each number before rounding. It should be small, but not too small.

    For example, with an eps = 1e-9, this:

    console.log((8.555).toFixed(2));    // returns 8.56
    console.log((8.565).toFixed(2));    // returns 8.57
    console.log((8.575).toFixed(2));    // returns 8.57
    console.log((8.585).toFixed(2));    // returns 8.59
    

    Becomes this:

    console.log((8.555 + eps).toFixed(2));    // returns 8.56
    console.log((8.565 + eps).toFixed(2));    // returns 8.57
    console.log((8.575 + eps).toFixed(2));    // returns 8.58
    console.log((8.585 + eps).toFixed(2));    // returns 8.59
    
    0 讨论(0)
  • 2020-11-27 22:23

    This is because of floating-point errors.

    Compare (8.575).toFixed(20) with (8.575).toFixed(3) and imagine this proposition: 8.575 < real("8.575"), where real is an imaginary function that creates a real number with infinite precision.

    That is, the original number is not as expected and the inaccuracy has already been introduced.

    One quick "workabout" I can think of is: Multiply by 1000 (or as appropriate), get the toFixed(0) of that (still has a limit, but it's absurd), then shove back in the decimal form.

    Happy coding.

    0 讨论(0)
  • 2020-11-27 22:23

    Maybe it will help someone, this is fixed popular formatMoney() function, but with correct roundings.

    Number.prototype.formatMoney = function() {
      var n = this,
      decPlaces = 2,
      decSeparator = ",",
      thouSeparator = " ",
      sign = n < 0 ? "-" : "",
      i = parseInt(n = Math.abs(+n || 0)) + "",
      j = (j = i.length) > 3 ? j % 3 : 0,
      decimals = Number(Math.round(n +'e'+ decPlaces) +'e-'+ decPlaces).toFixed(decPlaces),
      result = sign + (j ? i.substr(0, j) + thouSeparator : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thouSeparator) + (decPlaces ? decSeparator + Math.abs(decimals-i).toFixed(decPlaces).slice(2) : "");
      return result;
    };
    
    (9.245).formatMoney(); // returns 9,25
    (7.5).formatMoney();   // returns 7,50
    (8.575).formatMoney(); // returns 8,58
    
    0 讨论(0)
  • 2020-11-27 22:32

    It's probably related to floating point problems, see How to deal with floating point number precision in JavaScript?

    0 讨论(0)
提交回复
热议问题