我想最多舍入小数点后两位,但仅在必要时四舍五入。

输入:

10
1.7777777
9.1


输出:

10
1.78
9.1


如何用JavaScript做到这一点?

评论

似乎没有人知道Number.EPSILON。使用Math.round(num * 100 + Number.EPSILON)/ 100.

#1 楼

使用Math.round(num * 100) / 100

编辑:为确保1.005圆角正确,我们使用

Math.round((num + Number.EPSILON) * 100) / 100

#2 楼

如果值是文本类型:
parseFloat("123.456").toFixed(2);

如果值是数字:
var numb = 123.23454;
numb = numb.toFixed(2);

不利的一面是,像1.5这样的值将给出“ 1.50”作为输出。 @minitech建议的修复程序:
var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.

似乎Math.round是更好的解决方案。但这不是!在某些情况下,它不会正确舍入:
Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!

toFixed()在某些情况下也不会正确舍入(已在Chrome v.55.0.2883.87中进行了测试)!
示例:
parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.

1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.

我想这是因为1.555实际上是在幕后浮动1.55499994。
解决方案1是使用具有所需舍入算法的脚本,例如:
function roundNumber(num, scale) {
  if(!("" + num).includes("e")) {
    return +(Math.round(num + "e+" + scale)  + "e-" + scale);
  } else {
    var arr = ("" + num).split("e");
    var sig = ""
    if(+arr[1] + scale > 0) {
      sig = "+";
    }
    return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview
注意:这不是每个人的通用解决方案。舍入算法有几种,您的实现可以不同,取决于您的要求。 https://en.wikipedia.org/wiki/Rounding
解决方案2是避免前端计算并从后端服务器提取舍入值。
编辑:另一种可能的解决方案,不是防弹的
Math.round((num + Number.EPSILON) * 100) / 100

在某些情况下,当四舍五入为1.3549999999999998之类的数字时,它将返回错误的结果。应该是1.35,但结果是1.36。

#3 楼

您可以使用

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}


我在MDN上发现了这一点。他们的方法避免了提到的1.005问题。

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57


评论


@Redsandro,+(val)与使用Number(val)等效。将“ e-2”连接到数字会导致需要将字符串转换回数字。

–杰克
14年2月28日在19:11

#4 楼

MarkG的答案是正确的。这是任意位数的通用扩展。

Number.prototype.round = function(places) {
  return +(Math.round(this + "e+" + places)  + "e-" + places);
}


用法:

var n = 1.7777;    
n.round(2); // 1.78


单元测试:

it.only('should round floats to 2 places', function() {

  var cases = [
    { n: 10,      e: 10,    p:2 },
    { n: 1.7777,  e: 1.78,  p:2 },
    { n: 1.005,   e: 1.01,  p:2 },
    { n: 1.005,   e: 1,     p:0 },
    { n: 1.77777, e: 1.8,   p:1 }
  ]

  cases.forEach(function(testCase) {
    var r = testCase.n.round(testCase.p);
    assert.equal(r, testCase.e, 'didn\'t get right number');
  });
})


#5 楼

您应该使用:

Math.round( num * 100 + Number.EPSILON ) / 100


似乎没有人知道Number.EPSILON

此外,值得注意的是,这不是像JavaScript这样的怪异现象有人说。

这只是浮点数在计算机中的工作方式。像99%的编程语言一样,JavaScript没有自制的浮点数。它依赖于CPU / FPU。计算机使用二进制,并且在二进制中,没有像0.1这样的任何数字,而仅仅是一个二进制近似值。为什么?出于同样的原因,不能以十进制写1/3:其值为0.33333333 ...,且无穷三进制。

这里是Number.EPSILON。该数字是1和双精度浮点数中存在的下一个数字之间的差。就是这样:1与1 + Number.EPSILON之间没有数字。

编辑:

正如评论中所问的,让我们澄清一件事:添加Number.EPSILON仅在要舍入的值是算术运算的结果,因为它会吞没一些浮点误差增量。

当值来自直接来源(例如:文字,用户输入或传感器)时,它没有用。 。

编辑(2019):

就像@maganap一样,有些人指出,最好在相乘前加上Number.EPSILON

Math.round( ( num + Number.EPSILON ) * 100 ) / 100


EDIT(2019年12月):

最近,我使用类似于此的函数来比较可识别epsilon的数字:

const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;

function epsilonEquals( a , b ) {
  if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
    return false ;
  }
  if ( a === 0 || b === 0 ) {
    return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
  }
  return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}


我的用例是我正在开发多年的断言+数据验证库。 ),因为我希望相等性检查器足够松散以累积浮点错误。

到目前为止,它对我来说看起来很完美。
我希望它会有所帮助。

#6 楼

这个问题很复杂。假设我们有一个函数roundTo2DP(num),它以浮点数作为参数并返回四舍五入到小数点后两位的值。这些表达式中的每个表达式的计算结果是什么?


roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)

答案是第一个例子应该四舍五入到0.01(因为它比0.01更接近于0.02),而另外两个应该四舍五入到0.02(因为0.0150000000000000001比0.01更接近0.02,因为0.015正好位于它们之间的中间位置,并且存在数学上的约定,即此类数字(请四舍五入)。

您可能已经猜到的问题是roundTo2DP可能无法实现以给出明显的答案,因为传递给它的所有三个数字都是相同的数字。 IEEE 754二进制浮点数(JavaScript使用的那种)不能精确地表示大多数非整数,因此上面的所有三个数字文字都将四舍五入为附近的有效浮点数。这个数字恰好是

0.014999999999999999999488848768742172978818416595458984375

它比0.01更接近于0.02。

您可以看到所有三个数字在您的浏览器控制台,Node shell或其他JavaScript解释器上是相同的。只需比较一下:

> 0.014999999999999999 === 0.0150000000000000001
true


因此,当我编写m = 0.0150000000000000001时,最终得到的m的确切值比0.01更接近0.02。但是,如果我将m转换为字符串...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015


...我得到0.015,应四舍五入为0.02,但显然不是56 -decimal-place number我之前说过,所有这些数字都完全相等。那这是什么黑魔法?

答案可以在ECMAScript规范的7.1.12.1节:应用于Number类型的ToString中找到。这里规定了将一些数字m转换为字符串的规则。关键部分是点5,其中生成一个整数s,其数字将用于m的String表示形式:


令n,k和s为整数,使得k ≥1,10k-1≤s <10k,s×10n-k的Number值为m,并且k尽可能小。请注意,k是s的十进制表示形式中的位数,s不能被10整除,并且s的最低有效位不一定由这些条件唯一确定。


这里的关键部分是“ k尽可能小”的要求。该要求等于给定数字m的要求,String(m)的值必须具有尽可能少的数字,同时仍满足Number(String(m)) === m的要求。因为我们已经知道0.015 === 0.0150000000000000001,所以现在很清楚为什么String(0.0150000000000000001) === '0.015'必须为真。

当然,这些讨论都没有直接回答roundTo2DP(m)应该返回什么。如果m的精确值为0.014999999999999999999944488848768742172978818416595458984375,但其字符串表示形式为'0.015',那么当我们将其四舍五入到小数点后两位时,正确的答案是什么-在数学上,实践上,从哲学上还是类似的方式?

对此没有一个正确的答案。这取决于您的用例。您可能希望尊重String表示形式并在以下情况下向上舍入:


表示的值本质上是离散的,例如以第3个小数位的货币表示的货币数量,如第纳尔。在这种情况下,像0.015这样的Number的真实值为0.015,并且它在二进制浮点数中得到的0.0149999999 ...表示是舍入误差。 (当然,许多人会合理地认为您应该使用十进制库来处理此类值,而从一开始就不要将它们表示为二进制浮点数。)
该值是由用户键入的。同样,在这种情况下,输入的精确十进制数比最接近的二进制浮点数表示形式更“真实”。另一方面,您可能要尊重二进制浮点值并向下取整当您的值本质上是连续的时,例如,如果它是从传感器读取的值。

这两种方法需要不同的代码。为了尊重Number的String表示形式,我们可以(使用大量相当精巧的代码)实现自己的舍入,该舍入运算将使用您在学校时使用的相同算法,逐位直接对String表示形式进行运算被教导如何四舍五入数字。下面是一个示例,该示例尊重OP要求“仅在必要时”将数字表示为小数点后2位小数点的要求;当然,您可能需要根据自己的实际需要对其进行调整。

/**
 * Converts num to a decimal string (if it isn't one already) and then rounds it
 * to at most dp decimal places.
 *
 * For explanation of why you'd want to perform rounding operations on a String
 * rather than a Number, see http://stackoverflow.com/a/38676273/1709587
 *
 * @param {(number|string)} num
 * @param {number} dp
 * @return {string}
 */
function roundStringNumberWithoutTrailingZeroes (num, dp) {
    if (arguments.length != 2) throw new Error("2 arguments required");

    num = String(num);
    if (num.indexOf('e+') != -1) {
        // Can't round numbers this large because their string representation
        // contains an exponent, like 9.99e+37
        throw new Error("num too large");
    }
    if (num.indexOf('.') == -1) {
        // Nothing to do
        return num;
    }

    var parts = num.split('.'),
        beforePoint = parts[0],
        afterPoint = parts[1],
        shouldRoundUp = afterPoint[dp] >= 5,
        finalNumber;

    afterPoint = afterPoint.slice(0, dp);
    if (!shouldRoundUp) {
        finalNumber = beforePoint + '.' + afterPoint;
    } else if (/^9+$/.test(afterPoint)) {
        // If we need to round up a number like 1.9999, increment the integer
        // before the decimal point and discard the fractional part.
        finalNumber = Number(beforePoint)+1;
    } else {
        // Starting from the last digit, increment digits until we find one
        // that is not 9, then stop
        var i = dp-1;
        while (true) {
            if (afterPoint[i] == '9') {
                afterPoint = afterPoint.substr(0, i) +
                             '0' +
                             afterPoint.substr(i+1);
                i--;
            } else {
                afterPoint = afterPoint.substr(0, i) +
                             (Number(afterPoint[i]) + 1) +
                             afterPoint.substr(i+1);
                break;
            }
        }

        finalNumber = beforePoint + '.' + afterPoint;
    }

    // Remove trailing zeroes from fractional part before returning
    return finalNumber.replace(/0+$/, '')
}


示例用法:

> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'


上面的函数可能是您想要使用的功能,以避免用户看到他们输入的数字被错误舍入。

(作为替代,您也可以尝试使用round10库,该库提供了类似的功能行为而功能却完全不同的实现。)

但是,如果您拥有第二种数字-取自连续刻度的值,而又没有理由认为小数位数较少的近似十进制表示形式比小数位数更为精确,该怎么办?在这种情况下,我们不想遵守String表示形式,因为该表示形式(如规范中所述)已经是四舍五入的;我们不想犯这样的错误:“ 0.014999999 ... 375舍入为0.015,它舍入为0.02,所以0.014999999 ... 375舍入为0.02”。

这里可以简单地使用内置的toFixed方法。请注意,通过在Number()返回的String上调用toFixed,我们得到一个Number,其String表示形式不带尾随零(这要归因于JavaScript计算此Number的String表示形式的方式,此答案前面已进行了讨论)。

/**
 * Takes a float and rounds it to at most dp decimal places. For example
 *
 *     roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
 *
 * returns 1.234
 *
 * Note that since this treats the value passed to it as a floating point
 * number, it will have counterintuitive results in some cases. For instance,
 * 
 *     roundFloatNumberWithoutTrailingZeroes(0.015, 2)
 *
 * gives 0.01 where 0.02 might be expected. For an explanation of why, see
 * http://stackoverflow.com/a/38676273/1709587. You may want to consider using the
 * roundStringNumberWithoutTrailingZeroes function there instead.
 *
 * @param {number} num
 * @param {number} dp
 * @return {number}
 */
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
    var numToFixedDp = Number(num).toFixed(dp);
    return Number(numToFixedDp);
}


#7 楼

考虑.toFixed().toPrecision()

http://www.javascriptkit.com/javatutors/formatnumber.shtml

#8 楼

一个人可以使用.toFixed(NumberOfDecimalPlaces)

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23


#9 楼

精确的舍入方法。来源:Mozilla

(function(){

    /**
     * Decimal adjustment of a number.
     *
     * @param   {String}    type    The type of adjustment.
     * @param   {Number}    value   The number.
     * @param   {Integer}   exp     The exponent (the 10 logarithm of the adjustment base).
     * @returns {Number}            The adjusted value.
     */
    function decimalAdjust(type, value, exp) {
        // If the exp is undefined or zero...
        if (typeof exp === 'undefined' || +exp === 0) {
            return Math[type](value);
        }
        value = +value;
        exp = +exp;
        // If the value is not a number or the exp is not an integer...
        if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
            return NaN;
        }
        // Shift
        value = value.toString().split('e');
        value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
        // Shift back
        value = value.toString().split('e');
        return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
    }

    // Decimal round
    if (!Math.round10) {
        Math.round10 = function(value, exp) {
            return decimalAdjust('round', value, exp);
        };
    }
    // Decimal floor
    if (!Math.floor10) {
        Math.floor10 = function(value, exp) {
            return decimalAdjust('floor', value, exp);
        };
    }
    // Decimal ceil
    if (!Math.ceil10) {
        Math.ceil10 = function(value, exp) {
            return decimalAdjust('ceil', value, exp);
        };
    }
})();


示例:

// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50


#10 楼

这里没有找到正确的答案。 @stinkycheeseman要求四舍五入,你们都四舍五入了这个数字。

要四舍五入,请使用此函数:

#11 楼

这是一个简单的方法:

Math.round(value * 100) / 100


您可能想继续做一个单独的功能来为您做这件事:

function roundToTwo(value) {
    return(Math.round(value * 100) / 100);
}


然后您只需传递值即可。

您可以通过添加第二个参数来将其四舍五入为任意小数位数。

function myRound(value, places) {
    var multiplier = Math.pow(10, places);

    return (Math.round(value * multiplier) / multiplier);
}


#12 楼

+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12


#13 楼

对我来说Math.round()没有给出正确的答案。我发现toFixed(2)效果更好。
以下是这两个示例:




 console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer 




评论


重要的是要注意,toFixed不会进行舍入,而Math.round只会舍入到最接近的整数。因此,为了保留小数,我们需要将原始数字乘以10的幂,其零代表您希望的小数位数,然后将结果除以相同的数字。在您的情况下:Math.round(43000/80000 * 100 * 100)/100。最后可以应用toFixed(2)来确保结果中始终有两个小数(在需要时尾随零)–完美右对齐一系列垂直显示的数字:)

–涡轮增压
18年5月1日在14:26

#14 楼

使用此功能Number(x).toFixed(2);

评论


如果不想让它以字符串形式返回,请再次将其全部包装在Number中:Number(Number(x())。toFixed(2));

–user993683
2015年9月12日下午5:17

不需要Number调用,x.toFixed(2)起作用。

– bgusach
18/12/14在16:09

@bgusach需要进行数字调用,因为语句x.toFixed(2)返回字符串而不是数字。要再次转换为数字,我们需要用数字换行

– Mohan Ram
19 Mar 15 '19在6:26

使用此方法(1)时,toFixed(2)返回1.00,但在这种情况下,发问者需要1。

– Eugene Mala
19年6月7日在20:40

这是行不通的,当1.005.toFixed(2)应该为“ 1.01”时会产生“ 1”。

–亚当·贾格斯(Adam Jagosz)
19年8月28日14:00



#15 楼

试试这个轻量级的解决方案:

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222


评论


有人知道这与返回Number(x.toFixed(digits))之间是否有区别?

–user993683
2015年9月12日下午5:16

@JoeRocc ...应该没有什么区别,因为.toFixed()反正只允许数字。

–petermeissner
2015年9月12日上午10:16

这个答案与本页多次提到的问题相同。尝试四舍五入(1.005,2)并看到结果为1而不是1.01。

– MilConDoin
16年8月5日在6:54

似乎更舍入算法的问题? -可以想象的不止一个:en.wikipedia.org/wiki/Rounding ... round(0.995,2)=> 0.99; round(1.006,2)=> 1.01;回合(1.005,2)=> 1

–petermeissner
18年2月7日在5:35

#16 楼

2017
只需使用本机代码.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"


如果您需要严格要求并添加数字,则可以使用replace

>
number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');


评论


toFixed方法返回一个字符串。如果需要数字结果,则需要将toFixed的结果发送到parseFloat。

–赞博尼利
17年7月7日在20:04

@Zambonilli或在必要时乘以1。但是由于固定数字大多数情况是用于显示而不是用于计算字符串,因此是正确的格式

–pery mimon
17年11月8日在13:05

-1;不仅在您提出问题的答案之前已被修正,而且还不能满足问题中“仅在必要时”的条件; (1).toFixed(2)在提出要求者要求“ 1”的地方给出“ 1.00”。

– Mark Amery
17年12月7日,0:02

好的,我知道了。我也为这种情况添加一些解决方案

–pery mimon
17年12月14日在17:05

如果您使用lodash,它甚至更容易:_.round(number,decimalPlace)删除了我的最后一条评论,因为它有问题。 Lodash _.round确实可以工作。小数位为2的1.005转换为1.01。

– Devin Fields
18年7月31日在17:40



#17 楼

有两种方法可以做到这一点。对于像我这样的人,Lodash的变体

function round(number, precision) {
    var pair = (number + 'e').split('e')
    var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
    pair = (value + 'e').split('e')
    return +(pair[0] + 'e' + (+pair[1] - precision))
}



round(0.015, 2) // 0.02
round(1.005, 2) // 1.01




如果您的项目使用jQuery或lodash,您还可以在库中找到正确的round方法。

更新1

我删除了变体n.toFixed(2),因为它不正确。谢谢@ avalanche1

评论


第二个选项将返回一个恰好有两个小数点的字符串。该问题仅在必要时要求小数点。在这种情况下,第一种选择更好。

–马科斯·利马(Marcos Lima)
16年7月5日在13:36

@MarcosLima Number.toFixed()将返回一个字符串,但前面带有一个加号,JS解释器会将字符串转换为数字。这是语法糖。

–stanleyxu2005
16年7月5日在14:04

在Firefox上,alert((+ 1234).toFixed(2))显示为“ 1234.00”。

–马科斯·利马(Marcos Lima)
16年7月5日在14:11



在Firefox上,alert(+ 1234.toFixed(2))引发SyntaxError:标识符在数字文字后立即开始。我坚持第一种选择。

–马科斯·利马(Marcos Lima)
16年7月5日在15:19



在某些情况下这不起作用:尝试(jsfiddle)使用362.42499999999995。预期结果(如PHP echo round(362.42499999999995,2)):362.43。实际结果:362.42

– Gianluigi Zane Zanettini博士
17年12月6日在13:42

#18 楼

通常,小数舍入是通过缩放来完成的:round(num * p) / p
天真的实现
使用以下具有中途数的函数,您将获得预期的上舍入值,或者有时根据输入获得下舍入值。
此四舍五入的inconsistency可能会导致难以检测到客户端代码中的错误。



 function naiveRound(num, decimalPlaces = 0) {
    var p = Math.pow(10, decimalPlaces);
    return Math.round(num * p) / p;
}

console.log( naiveRound(1.245, 2) );  // 1.25 correct (rounded as expected)
console.log( naiveRound(1.255, 2) );  // 1.25 incorrect (should be 1.26)

// testing edge cases
console.log( naiveRound(1.005, 2) );  // 1    incorrect (should be 1.01)
console.log( naiveRound(2.175, 2) );  // 2.17 incorrect (should be 2.18)
console.log( naiveRound(5.015, 2) );  // 5.01 incorrect (should be 5.02) 




更好的实现
指数表示法
通过将数字转换为指数表示法的字符串,正数将按预期取整。
但是请注意,负数实际上,它执行基本上等同于“向上舍入”的规则,即使round(-1.005, 2)计算为-1,您也会看到round(1.005, 2)计算为1.01。 lodash _.round方法使用此技术。



 /**
 * Round half up ('round half towards positive infinity')
 * Uses exponential notation to avoid floating-point issues.
 * Negative numbers round differently than positive numbers.
 */
function round(num, decimalPlaces = 0) {
    num = Math.round(num + "e" + decimalPlaces);
    return Number(num + "e" + -decimalPlaces);
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // 0

// testing edge cases
console.log( round(1.005, 2) );   // 1.01
console.log( round(2.175, 2) );   // 2.18
console.log( round(5.015, 2) );   // 5.02

console.log( round(-1.005, 2) );  // -1
console.log( round(-2.175, 2) );  // -2.17
console.log( round(-5.015, 2) );  // -5.01 




如果想要舍入负数时的通常行为,则需要在调用Math.round()之前将负数转换为正数,然后在返回之前将它们转换回负数。
// Round half away from zero
function round(num, decimalPlaces = 0) {
    num = Math.round(Math.abs(num) + "e" + decimalPlaces) * Math.sign(num);
    return Number(num + "e" + -decimalPlaces);
}

Number.EPSILON
有另一种纯数学方法可以执行舍入至最接近(使用“从零开始的舍入一半”),其中在调用舍入函数之前应用了epsilon校正。
简单地,在四舍五入之前,我们将最小的浮点值(= 1.0 ulp;最后一位)加到产品上。这将移动到下一个可表示的浮点值,从零开始。



 /**
 * Round half away from zero ('commercial' rounding)
 * Uses correction to offset floating-point inaccuracies.
 * Works symmetrically for positive and negative numbers.
 */
function round(num, decimalPlaces = 0) {
    var p = Math.pow(10, decimalPlaces);
    var m = (num * p) * (1 + Number.EPSILON);
    return Math.round(m) / p;
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // -1

// testing edge cases
console.log( round(1.005, 2) );  // 1.01
console.log( round(2.175, 2) );  // 2.18
console.log( round(5.015, 2) );  // 5.02

console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02 




这需要抵消在十进制数字编码期间可能发生的隐式舍入误差,尤其是在最后一个十进制位置具有“ 5”的数字,例如1.005、2.675和16.235。实际上,十进制系统中的1.005被编码为64位二进制浮点数中的1.0049999999999999。而十进制系统中的1234567.005被编码为64位二进制浮点数中的1234567.0049999998882413
值得注意的是,最大二进制round-off error取决于(1)数字的大小和(2)相对机器epsilon( 2 ^ -52)。
双舍入
这里,我们使用toPrecision()方法消除中间计算中的浮点舍入误差。简而言之,我们将15位有效数字取整以去除16位有效数字的舍入误差。 PHP 7舍入函数也使用这种将结果预舍入为有效数字的技术。



 // Round half away from zero
function round(num, decimalPlaces = 0) {
    var p = Math.pow(10, decimalPlaces);
    var m = Number((Math.abs(num) * p).toPrecision(15));
    return Math.round(m) / p * Math.sign(num);
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // -1

// testing edge cases
console.log( round(1.005, 2) );  // 1.01
console.log( round(2.175, 2) );  // 2.18
console.log( round(5.015, 2) );  // 5.02

console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02 




任意精度JavaScript库-decimal.js



 // Round half away from zero
function round(num, decimalPlaces = 0) {
    return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber();
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // -1

// testing edge cases
console.log( round(1.005, 2) );  // 1.01
console.log( round(2.175, 2) );  // 2.18
console.log( round(5.015, 2) );  // 5.02

console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"></script> 




解决方案1:以指数符号表示的字符串
受到KFish提供的解决方案的启发,网址为:https://stackoverflow.com/a/55521592/4208440
简单解决方案无需添加整个库,即可精确地将小数舍入,下限和上限精确到小数位数。它通过修复二进制舍入问题来避免出现意外结果,从而将浮点数更类似于小数,例如:floor((0.1 + 0.7)* 10)将返回预期结果8。
数字将舍入为小数的特定数量数字。指定负精度将舍入到小数点左边的任意数量的位置。



 // Solution 1
var DecimalPrecision = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    var decimalAdjust = function(type, num, decimalPlaces) {
        var shift = function(value, exponent) {
            value = (value + 'e').split('e');
            return +(value[0] + 'e' + (+value[1] + (exponent || 0)));
        };
        var n = type === 'round' ? Math.abs(num) : num;
        var m = shift(n, +decimalPlaces);
        var r = shift(Math[type](m), -decimalPlaces);
        return type === 'round' ? Math.sign(num) * r : r;
    };
    return {
        // Decimal round (half away from zero)
        round: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces);
        },
        // Decimal ceil
        ceil: function(num, decimalPlaces) {
            return decimalAdjust('ceil', num, decimalPlaces);
        },
        // Decimal floor
        floor: function(num, decimalPlaces) {
            return decimalAdjust('floor', num, decimalPlaces);
        },
        // Decimal trunc
        trunc: function(num, decimalPlaces) {
            return decimalAdjust('trunc', num, decimalPlaces);
        },
        // Format using fixed-point notation
        toFixed: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
        }
    };
})();

// test rounding of half
console.log(DecimalPrecision.round(0.5));  // 1
console.log(DecimalPrecision.round(-0.5)); // -1

// testing very small numbers
console.log(DecimalPrecision.ceil(1e-8, 2) === 0.01);         // 0.01
console.log(DecimalPrecision.floor(1e-8, 2) === 0);              // 0

// testing simple cases
console.log(DecimalPrecision.round(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.round(-5.12, 1) === -5.1);       // -5.1
console.log(DecimalPrecision.ceil(5.12, 1) === 5.2);           // 5.2
console.log(DecimalPrecision.ceil(-5.12, 1) === -5.1);        // -5.1
console.log(DecimalPrecision.floor(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.floor(-5.12, 1) === -5.2);       // -5.2
console.log(DecimalPrecision.trunc(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision.trunc(-5.12, 1) === -5.1);       // -5.1

// testing edge cases for round
console.log(DecimalPrecision.round(1.005, 2) === 1.01);       // 1.01
console.log(DecimalPrecision.round(39.425, 2) === 39.43);    // 39.43
console.log(DecimalPrecision.round(-1.005, 2) === -1.01);    // -1.01
console.log(DecimalPrecision.round(-39.425, 2) === -39.43); // -39.43

// testing edge cases for ceil
console.log(DecimalPrecision.ceil(9.130, 2) === 9.13);        // 9.13
console.log(DecimalPrecision.ceil(65.180, 2) === 65.18);     // 65.18
console.log(DecimalPrecision.ceil(-2.260, 2) === -2.26);     // -2.26
console.log(DecimalPrecision.ceil(-18.150, 2) === -18.15);  // -18.15

// testing edge cases for floor
console.log(DecimalPrecision.floor(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision.floor(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision.floor(-9.130, 2) === -9.13);    // -9.13
console.log(DecimalPrecision.floor(-65.180, 2) === -65.18); // -65.18

// testing edge cases for trunc
console.log(DecimalPrecision.trunc(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision.trunc(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision.trunc(-2.260, 2) === -2.26);    // -2.26
console.log(DecimalPrecision.trunc(-18.150, 2) === -18.15); // -18.15

// testing round to tens and hundreds
console.log(DecimalPrecision.round(1262.48, -1) === 1260);    // 1260
console.log(DecimalPrecision.round(1262.48, -2) === 1300);    // 1300

// testing toFixed()
console.log(DecimalPrecision.toFixed(1.005, 2) === "1.01");   // "1.01" 




解决方案2:纯粹是数学(Number.EPSILON)
出于性能原因,此解决方案避免了任何字符串转换/任何形式的操作。
Solution 1: 25,838 ops/sec
Solution 2: 655,087 ops/sec
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac



 // Solution 2
var DecimalPrecision2 = (function() {
    if (Number.EPSILON === undefined) {
        Number.EPSILON = Math.pow(2, -52);
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    var isRound = function(num, decimalPlaces) {
        //return decimalPlaces >= 0 &&
        //    +num.toFixed(decimalPlaces) === num;
        var p = Math.pow(10, decimalPlaces);
        return Math.round(num * p) / p === num;
    };
    var decimalAdjust = function(type, num, decimalPlaces) {
        if (isRound(num, decimalPlaces || 0))
            return num;
        var p = Math.pow(10, decimalPlaces || 0);
        var m = (num * p) * (1 + Number.EPSILON);
        return Math[type](m) / p;
    };
    return {
        // Decimal round (half away from zero)
        round: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces);
        },
        // Decimal ceil
        ceil: function(num, decimalPlaces) {
            return decimalAdjust('ceil', num, decimalPlaces);
        },
        // Decimal floor
        floor: function(num, decimalPlaces) {
            return decimalAdjust('floor', num, decimalPlaces);
        },
        // Decimal trunc
        trunc: function(num, decimalPlaces) {
            return decimalAdjust('trunc', num, decimalPlaces);
        },
        // Format using fixed-point notation
        toFixed: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
        }
    };
})();

// test rounding of half
console.log(DecimalPrecision2.round(0.5));  // 1
console.log(DecimalPrecision2.round(-0.5)); // -1

// testing very small numbers
console.log(DecimalPrecision2.ceil(1e-8, 2) === 0.01);         // 0.01
console.log(DecimalPrecision2.floor(1e-8, 2) === 0);              // 0

// testing simple cases
console.log(DecimalPrecision2.round(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision2.round(-5.12, 1) === -5.1);       // -5.1
console.log(DecimalPrecision2.ceil(5.12, 1) === 5.2);           // 5.2
console.log(DecimalPrecision2.ceil(-5.12, 1) === -5.1);        // -5.1
console.log(DecimalPrecision2.floor(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision2.floor(-5.12, 1) === -5.2);       // -5.2
console.log(DecimalPrecision2.trunc(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision2.trunc(-5.12, 1) === -5.1);       // -5.1

// testing edge cases for round
console.log(DecimalPrecision2.round(1.005, 2) === 1.01);       // 1.01
console.log(DecimalPrecision2.round(39.425, 2) === 39.43);    // 39.43
console.log(DecimalPrecision2.round(-1.005, 2) === -1.01);    // -1.01
console.log(DecimalPrecision2.round(-39.425, 2) === -39.43); // -39.43

// testing edge cases for ceil
console.log(DecimalPrecision2.ceil(9.130, 2) === 9.13);        // 9.13
console.log(DecimalPrecision2.ceil(65.180, 2) === 65.18);     // 65.18
console.log(DecimalPrecision2.ceil(-2.260, 2) === -2.26);     // -2.26
console.log(DecimalPrecision2.ceil(-18.150, 2) === -18.15);  // -18.15

// testing edge cases for floor
console.log(DecimalPrecision2.floor(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision2.floor(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision2.floor(-9.130, 2) === -9.13);    // -9.13
console.log(DecimalPrecision2.floor(-65.180, 2) === -65.18); // -65.18

// testing edge cases for trunc
console.log(DecimalPrecision2.trunc(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision2.trunc(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision2.trunc(-2.260, 2) === -2.26);    // -2.26
console.log(DecimalPrecision2.trunc(-18.150, 2) === -18.15); // -18.15

// testing round to tens and hundreds
console.log(DecimalPrecision2.round(1262.48, -1) === 1260);    // 1260
console.log(DecimalPrecision2.round(1262.48, -2) === 1300);    // 1300

// testing toFixed()
console.log(DecimalPrecision2.toFixed(1.005, 2) === "1.01");   // "1.01" 




解决方案3:双重舍入
此解决方案使用toPrecision()方法消除浮点舍入错误。



 // Solution 3
var DecimalPrecision3 = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    // Eliminate binary floating-point inaccuracies.
    var stripError = function(num) {
        if (Number.isInteger(num))
            return num;
        return parseFloat(num.toPrecision(15));
    };
    var decimalAdjust = function(type, num, decimalPlaces) {
        var n = type === 'round' ? Math.abs(num) : num;
        var p = Math.pow(10, decimalPlaces || 0);
        var m = stripError(n * p);
        var r = Math[type](m) / p;
        return type === 'round' ? Math.sign(num) * r : r;
    };
    return {
        // Decimal round (half away from zero)
        round: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces);
        },
        // Decimal ceil
        ceil: function(num, decimalPlaces) {
            return decimalAdjust('ceil', num, decimalPlaces);
        },
        // Decimal floor
        floor: function(num, decimalPlaces) {
            return decimalAdjust('floor', num, decimalPlaces);
        },
        // Decimal trunc
        trunc: function(num, decimalPlaces) {
            return decimalAdjust('trunc', num, decimalPlaces);
        },
        // Format using fixed-point notation
        toFixed: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
        }
    };
})();

// test rounding of half
console.log(DecimalPrecision3.round(0.5));  // 1
console.log(DecimalPrecision3.round(-0.5)); // -1

// testing very small numbers
console.log(DecimalPrecision3.ceil(1e-8, 2) === 0.01);         // 0.01
console.log(DecimalPrecision3.floor(1e-8, 2) === 0);              // 0

// testing simple cases
console.log(DecimalPrecision3.round(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision3.round(-5.12, 1) === -5.1);       // -5.1
console.log(DecimalPrecision3.ceil(5.12, 1) === 5.2);           // 5.2
console.log(DecimalPrecision3.ceil(-5.12, 1) === -5.1);        // -5.1
console.log(DecimalPrecision3.floor(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision3.floor(-5.12, 1) === -5.2);       // -5.2
console.log(DecimalPrecision3.trunc(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision3.trunc(-5.12, 1) === -5.1);       // -5.1

// testing edge cases for round
console.log(DecimalPrecision3.round(1.005, 2) === 1.01);       // 1.01
console.log(DecimalPrecision3.round(39.425, 2) === 39.43);    // 39.43
console.log(DecimalPrecision3.round(-1.005, 2) === -1.01);    // -1.01
console.log(DecimalPrecision3.round(-39.425, 2) === -39.43); // -39.43

// testing edge cases for ceil
console.log(DecimalPrecision3.ceil(9.130, 2) === 9.13);        // 9.13
console.log(DecimalPrecision3.ceil(65.180, 2) === 65.18);     // 65.18
console.log(DecimalPrecision3.ceil(-2.260, 2) === -2.26);     // -2.26
console.log(DecimalPrecision3.ceil(-18.150, 2) === -18.15);  // -18.15

// testing edge cases for floor
console.log(DecimalPrecision3.floor(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision3.floor(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision3.floor(-9.130, 2) === -9.13);    // -9.13
console.log(DecimalPrecision3.floor(-65.180, 2) === -65.18); // -65.18

// testing edge cases for trunc
console.log(DecimalPrecision3.trunc(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision3.trunc(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision3.trunc(-2.260, 2) === -2.26);    // -2.26
console.log(DecimalPrecision3.trunc(-18.150, 2) === -18.15); // -18.15

// testing round to tens and hundreds
console.log(DecimalPrecision3.round(1262.48, -1) === 1260);    // 1260
console.log(DecimalPrecision3.round(1262.48, -2) === 1300);    // 1300

// testing toFixed()
console.log(DecimalPrecision3.toFixed(1.005, 2) === "1.01");   // "1.01" 




解决方案4:双舍入v2
此解决方案与解决方案3相似,但是它使用了自定义的toPrecision()函数。



 // Solution 4
var DecimalPrecision4 = (function() {
    if (Math.sign === undefined) {
        Math.sign = function(x) {
            return ((x > 0) - (x < 0)) || +x;
        };
    }
    if (Math.trunc === undefined) {
        Math.trunc = function(v) {
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        };
    }
    var toPrecision = function(num, significantDigits) {
        // Return early for ±0, NaN and Infinity.
        if (!num || !Number.isFinite(num))
            return num;
        // Compute shift of the decimal point.
        var shift = significantDigits - 1 - Math.floor(Math.log10(Math.abs(num)));
        // Return if rounding to the same or higher precision.
        var decimalPlaces = 0;
        for (var p = 1; !Number.isInteger(num * p); p *= 10) decimalPlaces++;
        if (shift >= decimalPlaces)
            return num;
        // Round to sf-1 fractional digits of normalized mantissa x.dddd
        var scale = Math.pow(10, Math.abs(shift));
        return shift > 0 ?
            Math.round(num * scale) / scale :
            Math.round(num / scale) * scale;
    };
    // Eliminate binary floating-point inaccuracies.
    var stripError = function(num) {
        if (Number.isInteger(num))
            return num;
        return toPrecision(num, 15);
    };
    var decimalAdjust = function(type, num, decimalPlaces) {
        var n = type === 'round' ? Math.abs(num) : num;
        var p = Math.pow(10, decimalPlaces || 0);
        var m = stripError(n * p);
        var r = Math[type](m) / p;
        return type === 'round' ? Math.sign(num) * r : r;
    };
    return {
        // Decimal round (half away from zero)
        round: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces);
        },
        // Decimal ceil
        ceil: function(num, decimalPlaces) {
            return decimalAdjust('ceil', num, decimalPlaces);
        },
        // Decimal floor
        floor: function(num, decimalPlaces) {
            return decimalAdjust('floor', num, decimalPlaces);
        },
        // Decimal trunc
        trunc: function(num, decimalPlaces) {
            return decimalAdjust('trunc', num, decimalPlaces);
        },
        // Format using fixed-point notation
        toFixed: function(num, decimalPlaces) {
            return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
        }
    };
})();

// test rounding of half
console.log(DecimalPrecision4.round(0.5));  // 1
console.log(DecimalPrecision4.round(-0.5)); // -1

// testing very small numbers
console.log(DecimalPrecision4.ceil(1e-8, 2) === 0.01);         // 0.01
console.log(DecimalPrecision4.floor(1e-8, 2) === 0);              // 0

// testing simple cases
console.log(DecimalPrecision4.round(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision4.round(-5.12, 1) === -5.1);       // -5.1
console.log(DecimalPrecision4.ceil(5.12, 1) === 5.2);           // 5.2
console.log(DecimalPrecision4.ceil(-5.12, 1) === -5.1);        // -5.1
console.log(DecimalPrecision4.floor(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision4.floor(-5.12, 1) === -5.2);       // -5.2
console.log(DecimalPrecision4.trunc(5.12, 1) === 5.1);          // 5.1
console.log(DecimalPrecision4.trunc(-5.12, 1) === -5.1);       // -5.1

// testing edge cases for round
console.log(DecimalPrecision4.round(1.005, 2) === 1.01);       // 1.01
console.log(DecimalPrecision4.round(39.425, 2) === 39.43);    // 39.43
console.log(DecimalPrecision4.round(-1.005, 2) === -1.01);    // -1.01
console.log(DecimalPrecision4.round(-39.425, 2) === -39.43); // -39.43

// testing edge cases for ceil
console.log(DecimalPrecision4.ceil(9.130, 2) === 9.13);        // 9.13
console.log(DecimalPrecision4.ceil(65.180, 2) === 65.18);     // 65.18
console.log(DecimalPrecision4.ceil(-2.260, 2) === -2.26);     // -2.26
console.log(DecimalPrecision4.ceil(-18.150, 2) === -18.15);  // -18.15

// testing edge cases for floor
console.log(DecimalPrecision4.floor(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision4.floor(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision4.floor(-9.130, 2) === -9.13);    // -9.13
console.log(DecimalPrecision4.floor(-65.180, 2) === -65.18); // -65.18

// testing edge cases for trunc
console.log(DecimalPrecision4.trunc(2.260, 2) === 2.26);       // 2.26
console.log(DecimalPrecision4.trunc(18.150, 2) === 18.15);    // 18.15
console.log(DecimalPrecision4.trunc(-2.260, 2) === -2.26);    // -2.26
console.log(DecimalPrecision4.trunc(-18.150, 2) === -18.15); // -18.15

// testing round to tens and hundreds
console.log(DecimalPrecision4.round(1262.48, -1) === 1260);    // 1260
console.log(DecimalPrecision4.round(1262.48, -2) === 1300);    // 1300

// testing toFixed()
console.log(DecimalPrecision4.toFixed(1.005, 2) === "1.01");   // "1.01" 




基准测试
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac
这里是一个基准,用于比较上述Chrome 85.0.4183.83解决方案中的每秒操作数。显然,所有浏览器都不同,所以您的里程可能会有所不同。

评论


很好,我发现您确实对差异进行了更彻底的性能测试。我只是在devtools中进行了快速比较,它们返回时的执行时间非常相似,但是我想知道性能差异是否会开始以很高的音量/频率显示出来。

– KFish
8月24日13:58

第二种解决方案更易于阅读和理解。也可以将其移植到其他编程语言。

– Amr Ali
8月25日15:54

好的,我刚刚为您添加了它,以及基准测试的屏幕截图。

–迈克
9月3日22:40

感谢@Mike更新答案

– Amr Ali
9月3日22:54

嘿@AmrAli。这是一个了不起的答案。尽可能精确的少数几个之一。谢谢! 👍我特别喜欢解决方案2的速度。我注意到的一件事是,如果删除了isRound的早期返回检查,则速度可以提高〜5-10%。它增加了比仅运行decimalAdjust函数更多的操作。使用isRound提前返回实际上需要更长的时间。

– GollyJer
10月16日5:47



#19 楼

如果您使用lodash库,则可以使用lodash的round方法,如下所示。

_.round(number, precision)


例如:

_.round(1.7777777, 2) = 1.78


评论


@Peter Lodash提供的功能集与标准Javascript相比确实不错。但是,我听说Lodash与标准JS相比存在一些性能问题。 codeburst.io/…

– Madura Pradeep
18-10-4在8:26

我接受您的观点,即使用lodash有一些性能缺陷。我认为这些问题是许多抽象所共有的。但是,只需看看该线程上有多少个答案,以及针对边缘情况的直观解决方案是如何失败的。我们已经在jQuery中看到了这种模式,并且当浏览器采用解决了我们大多数用例的通用标准时,根本问题就解决了。然后将性能瓶颈转移到浏览器引擎。我认为同样的事情也会发生。 :)

–彼得
18-10-4在10:17



#20 楼

从ES6开始,通过使用toPrecision


,有一种“正确”的方法(没有覆盖静态变量和创建解决方法)来做到这一点。

 var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));

var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));

var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4)); 





然后您就可以parseFloat,并且零将“消失”。




 console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3))); 





但是,它不能解决“ 1.005舍入问题”,因为它是浮点数处理方式所固有的。




 console.log(1.005 - 0.005); 





如果您对图书馆开放,可以使用bignumber.js




 console.log(1.005 - 0.005);
console.log(new BigNumber(1.005).minus(0.005));

console.log(new BigNumber(1.005).round(4));
console.log(new BigNumber(1.005).round(3));
console.log(new BigNumber(1.005).round(2));
console.log(new BigNumber(1.005).round(1)); 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"></script> 




评论


(1.005).toPrecision(3)仍然返回1.00而不是实际的1.01。

– Giacomo
18-2-23在22:21

toPrecision返回一个字符串,该字符串将更改所需的输出类型。

– adamduren
18年11月16日在22:59

@Giacomo这不是.toPrecision方法的缺陷,它是浮点数的特殊性(JS中的数字是)—尝试1.005-0.005,它将返回0.9999999999999999。

– shau-kote
18年11月26日在1:44

(1).toPrecision(3)返回'1.00',但发问者在这种情况下希望有1。

– Eugene Mala
19年6月7日在20:39

正如@Giacomo所说,此答案似乎将“有效位数”与“舍入到小数位数”混淆了。 toPrecision进行格式设置,而不是后者,也不是OP的问题的答案,尽管乍一看似乎很相关,但却有很多错误。参见en.wikipedia.org/wiki/Significant_figures。例如,Number(123.4).toPrecision(2)返回“ 1.2e + 2”,而Number(12.345).toPrecision(2)返回“ 12”。我也同意@adamduren的观点,即它返回的字符串是不希望的(不是一个大问题,但不是所希望的)。

–́Neek
19年7月9日在4:02

#21 楼

这可能对您有帮助:

var result = Math.round(input*100)/100;


有关更多信息,您可以查看此链接

Math.round(num)vs num .toFixed(0)和浏览器不一致

评论


为什么世界上被接受的答案实际上比同一件事具有更多的票数,但是在被接受的答案之后一分钟就发布了这个投票?

–行情戴夫
1月25日1:10

#22 楼

MarkG和Lavamantis提供了比已被接受的解决方案更好的解决方案。可惜他们没有更多的投票!

这里是我用来解决基于MDN的浮点小数问题的函数。它比Lavamantis的解决方案更加通用(但不那么简洁):

function round(value, exp) {
  if (typeof exp === 'undefined' || +exp === 0)
    return Math.round(value);

  value = +value;
  exp  = +exp;

  if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
    return NaN;

  // Shift
  value = value.toString().split('e');
  value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));

  // Shift back
  value = value.toString().split('e');
  return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}


与以下产品一起使用:

round(10.8034, 2);      // Returns 10.8
round(1.275, 2);        // Returns 1.28
round(1.27499, 2);      // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46


与Lavamantis的解决方案相比,我们可以做到...

round(1234.5678, -2); // Returns 1200
round("123.45");      // Returns 123


评论


与MDN的解决方案不同,您的解决方案不包含某些情况。虽然可能会更短,但并不准确...

– astorije
15年7月5日在21:42

回合(-1835.665,2)=> -1835.66

–乔治·桑帕约
16年7月29日在1:38

#23 楼

最简单的方法是先使用toFixed,然后使用Number函数去除尾随零:

const number = 15.5;
Number(number.toFixed(2)); // 15.5


const number = 1.7777777;
Number(number.toFixed(2)); // 1.78


评论


这并不适用于所有情况。在发布答案之前进行大量测试。

–狒狒
4月19日10:23

@baburao请发布以上解决方案不起作用的情况

– Marcin Wanago
4月20日10:05

const number = 15; Number(number.toFixed(2)); //15.00而不是15

– Kevin Jhangiani
4月23日22:10



@KevinJhangiani常量数= 15; Number(number.toFixed(2)); // 15-我在最新的Chrome和Firefox上都进行了测试

– Marcin Wanago
4月24日11:22



@KevinJhangiani你怎么得到15.00? JS中的数字不存储小数位,并且任何显示都会自动截断多余的小数位(末尾为零)。

– VLAZ
5月18日11:18

#24 楼

var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);


toFixed(2)这里2是我们要舍入该数字的位数。

评论


这个.toFixed()实现起来更简单。只需经历一次。

–Ritesh Dhuri
15年5月15日在7:19

#25 楼

可能适合您,

Math.round(num * 100)/100;


要了解toFix和round的区别。您可以查看Math.round(num)与num.toFixed(0)和浏览器不一致的情况。

#26 楼

仅在必要时实现这种舍入的一种方法是使用Number.prototype.toLocaleString():

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})


这将提供您期望的输出,但是作为字符串。如果不是您期望的数据类型,您仍然可以将它们转换回数字。

评论


到目前为止,这是最干净的解决方案,可避免所有复杂的浮点问题,但每个MDN支持仍不完全-Safari尚不支持将参数传递给LocaleString。

– Mark Amery
16 Jul 30'23:12



@MarkAmery目前,只有Android浏览器存在一些问题:caniuse.com/#search=toLocaleString

– ptyskju
1月10日20:56

#27 楼

最终更新:
在后人留下此答案,但我建议使用@AmrAli对DecimalPrecision函数的改编,因为它也可以处理指数表示法。出于性能原因,我本来试图避免进行任何形式的字符串转换/操作,但实际上与他的实现在性能上没有区别。
编辑8/22/2020:我想在这里应该澄清一下,这些努力的目的不是要完全消除由浮点数据类型引起的内在舍入误差,因为如果不切换到实际上将值存储为base10(十进制)。目标实际上应该是将不准确性尽可能地提高到最边缘,以便您可以在给定值上执行数学运算而不会产生错误。当您的值达到绝对极限时,在操作它之前或之后,简单地调用该值将导致JS产生bug,您无能为力。例如,如果实例化值0.014999999999999999,JS将立即将其舍入为0.015。因此,如果将该值传递给任何这些函数,则实际上是传递0.015。到那时,您甚至无法先转换为字符串然后再对其进行操作,该值必须从一开始就被实例化为字符串才能起作用。为减轻此错误而创建的任何函数的目的(只有合理的期望)只是允许对浮点值执行数学运算,同时将错误一直推到起始值或结果值的边缘无论如何都会产生该错误。唯一的其他替代解决方案是将整数和十进制值分别存储为整数,这样就只能这样调用它们,或者始终将值存储为字符串并结合使用字符串操作和基于整数的值数学以对其执行操作。
在经过所有可能的方式的各种迭代以达到真正准确的十进制舍入精度之后,很明显,最准确,最有效的解决方案是使用Number.EPSILON。这为浮点数学精度问题提供了一个真正的数学解决方案。可以很容易地对其进行填充,如下所示:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/EPSILON以支持所有剩余的IE用户(那么也许我们再次应该停止执行此操作。)
改编自此处提供的解决方案:https://stackoverflow.com/a/48850944/6910392
简单的解决方案可提供准确的小数舍入,下限和上限,
05-19-2020更新:正如Sergey在评论中所指出的,值得指出的是这种(或任何一种)方法的局限性。对于数字如0.014999999999999999,您仍然会遇到不准确的情况,这是由于达到浮点值存储的精度限制的绝对边缘而导致的。没有数学或其他解决方案可用于解决此问题,因为该值本身会立即评估为0.015。您可以通过在控制台中自行调用该值来确认这一点。由于此限制,甚至不可能使用字符串操作来减小此值,因为其字符串表示形式只是“ 0.015”。任何解决此问题的解决方案都需要在将逻辑值接受到脚本之前在数据源上进行逻辑处理,例如,限制字段的字符长度等。这是需要考虑的考虑因素根据具体情况确定最佳方法。
2020年8月19日更新:根据Amr的评论,当输入值为整数时,ceil和floor函数将产生不希望的结果。这是由于将Number.EPSILON应用于输入以抵消预期的浮点误差。该函数已更新,可以检查输入值是否为整数,并返回不变的值,因为这是将任一函数应用于整数时的正确结果。
*注意:此问题还表明ceil和floor函数仍然需要应用Number.EPSILON调整,当将其应用于输入数字中的小数位数小于输出请求的小数位数(p)的值时,它们确实会产生不良结果。例如,当将ceil(17.1,5)应用于数学中的整数时,相对于预期的“ ceil”函数行为,应该返回17.1,其中假定“ 1”之后的所有小数位均为0。为此,我要进行修正ve添加了附加的功能检查,以识别输入数字中的小数位数是否小于请求的输出小数位数,并与整数一样返回数字不变。



 var DecimalPrecision = (function(){
        if (Number.EPSILON === undefined) {
            Number.EPSILON = Math.pow(2, -52);
        }
        if(Number.isInteger === undefined){
            Number.isInteger = function(value) {
                return typeof value === 'number' && 
                isFinite(value) && 
                Math.floor(value) === value;
            };
        }
        this.isRound = function(n,p){
            let l = n.toString().split('.')[1].length;
            return (p >= l);
        }
        this.round = function(n, p=2){
            if(Number.isInteger(n) || this.isRound(n,p))
                return n;
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            if(n<0)
                o *= -1;
            return Math.round((n + r) * o) / o;
        }
        this.ceil = function(n, p=2){
            if(Number.isInteger(n) || this.isRound(n,p))
                return n;
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            
            return Math.ceil((n + r) * o) / o;
        }
        this.floor = function(n, p=2){
            if(Number.isInteger(n) || this.isRound(n,p))
                return n;
            let r = 0.5 * Number.EPSILON * n;
            let o = 1; while(p-- > 0) o *= 10;
            
            return Math.floor((n + r) * o) / o;
        }
        return this;
    })();
    console.log(DecimalPrecision.round(1.005));
    console.log(DecimalPrecision.ceil(1.005));
    console.log(DecimalPrecision.floor(1.005));
    console.log(DecimalPrecision.round(1.0049999));
    console.log(DecimalPrecision.ceil(1.0049999));
    console.log(DecimalPrecision.floor(1.0049999));
    console.log(DecimalPrecision.round(2.175495134384,7));
    console.log(DecimalPrecision.round(2.1753543549,8));
    console.log(DecimalPrecision.round(2.1755465135353,4));
    console.log(DecimalPrecision.ceil(17,4));
    console.log(DecimalPrecision.ceil(17.1,4));
    console.log(DecimalPrecision.ceil(17.1,15)); 




评论


(DecimalPrecision.round(0.014999999999999999,2))//返回0.02

–谢尔盖
5月12日14:42

接得好!问题在于JS中的浮点存储,总是会有一些边缘情况。好消息是,您可以更好地调整您首先应用于Number.EPSILON的数学运算,以将这些极端情况进一步推向边缘。如果您不希望出现极端情况,则唯一的解决方案是字符串处理,然后是数学运算。在对值进行任何数学计算(甚至尝试移动小数)的那一刻,您就已经产生了该错误。

– KFish
5月14日18:42

实际上,在进一步检查时,这不是由于所涉及的任何数学运算,而是在调用指定值后立即发现了问题。您可以简单地通过在控制台中键入该数字来确认这一点,并查看它立即得出的值为0.015。因此,这将代表JS中任何浮点数的准确性的绝对优势。在这种情况下,您甚至无法转换为字符串并进行操作,因为字符串值为“ 0.015”

– KFish
5月19日1:06

@KFish DecimalPrecision.ceil(17,0); // 18和DecimalPrecision.ceil(17,1); // 17.1

– Amr Ali
8月17日19:56

@KFish DecimalPrecision.ceil(-5.12,1); // -5.2和DecimalPrecision.floor(-5.12,1); // -5.1

– Amr Ali
8月20日21:44

#28 楼

最简单的方法:

+num.toFixed(2)

将其转换为字符串,然后转换为整数/浮点数。

评论


感谢您提供最简单的答案。但是,+ num中的“ +”是什么?十进制val出现在字符串中对我来说不起作用。我做了:(num * 1).toFixed(2)。

– Ethan
15年3月22日在8:21

@momo只是将toFixed()的参数更改为3。因此它将是+ num.toFixed(3)。按照预期的方式运行,将1.005舍入为1.00,等于1

–bigpotato
15年5月4日在16:13



@Edmund应该返回1.01,而不是1.00

–mmm
2015年5月5日19:20

#29 楼

这是一个原型方法:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);


#30 楼

使用类似这样的内容
“ parseFloat(parseFloat(value).toFixed(2))”

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1


评论


如果不准确是float表示形式固有的,则不是这样。您将要删除它,然后通过再次转换为float来重新引入相同的错误!

–本·麦金太尔
18-10-28在20:00