如有必要,如何舍入至多 2 位小数?

Posted

技术标签:

【中文标题】如有必要,如何舍入至多 2 位小数?【英文标题】:How to round to at most 2 decimal places, if necessary? 【发布时间】:2021-12-05 22:51:31 【问题描述】:

我希望最多舍入 2 位小数,但仅在必要时进行

输入:

10
1.7777777
9.1

输出:

10
1.78
9.1

如何在 javascript 中做到这一点?

【问题讨论】:

【参考方案1】:

使用Math.round()

Math.round(num * 100) / 100

或者更具体地说,为了确保像 1.005 这样的东西正确舍入,请使用 Number.EPSILON :

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

【讨论】:

@PSatishPatro(我假设您的意思是说 224.99 而不是 224.95)。如果您要四舍五入到小数点后第二位(百分之一),那么我们应该只关心小数点后第三位(千位)是什么数字以及之后的所有内容。所以从输入 224.98499999 来看,只有 224.984 重要,这意味着 224.98 是正确的。 Math.round(1.255 * 100) / 100 将是 1.25 。错了 @PSatishPatro 我们跑题了,我意识到,但是将 224.9849... 四舍五入到小数点后两位,无论用任何语言还是手动计算,都应该得到 224.98。如果你得到 224.99,恐怕你做错了。考虑它的最简单方法是,您正在寻找只有两位小数的最接近的数字。虽然没有太大区别,但 224.9849 更接近 224.98 而不是 224.99。 我发现 10.075 舍入错误。给出 10.07 而不是 10.08,即使使用 epsilon 修复。 Math.round((519.805+ Number.EPSILON) * 100) / 100,四舍五入为519.8【参考方案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 * 100)/100 // 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 实际上在幕后类似于 float 1.55499994。

解决方案一是使用具有所需舍入算法的脚本,例如:

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

方案二是避免前端计算,从后端服务器拉取舍入值。

编辑:另一种可能的解决方案,也不是防弹的。

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

在某些情况下,当您对 1.3549999999999998 这样的数字进行四舍五入时,它会返回不正确的结果。应该是 1.35,但结果是 1.36。

【讨论】:

特别喜欢@minitech 的小技巧,以避免不必要的小数 (numb = numb.toFixed(2);) 在这个函数roundNumberV2有这个条件if (Math.pow(0.1, scale) > num) return 0; 。我可以知道这个条件的目的是什么吗? 性能也应该是一个问题,这可能会使这种方法不太理想。 Math.round() 快得多。 jsbin.com/kikocecemu/edit?js,output 注意,作为对某人的提醒,因为这让我很伤心,但是如果你想做类似var a = parseFloat(1/3).toFixed(2); 的事情,当你做var c = a + someNumber; 时它似乎不喜欢它 - 它会把它当作你试图将一个字符串(那个新的a 在那里)添加到一个数字(someNumber)。所以可能需要做var c = eval(a) + someNumber; 注意:“从字符串执行 JavaScript 存在巨大的安全风险。当你使用 eval() 时,坏人很容易运行任意代码”,请参阅 MDN 文档here 【参考方案3】:

你可以使用

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

我在MDN 上找到了这个。他们的方式避免了 1.005 的问题,即 mentioned。

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”连接到一个数字会产生一个需要转换回数字的字符串。 用 e 传递一个数字,它返回 NaN,例如1.19e-7【参考方案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');
  );
)

【讨论】:

我发现这个独立(没有prototype 扩展)版本(ES6)易于阅读和直接:round = (num, precision) => Number(Math.round(num + "e+" + precision) + "e-" + precision); 如果输入数字已经是指数形式怎么办?你会得到 NaN 我在这个 (Math.round(number + "e+" + places)) 中收到这个错误 'string' 类型的参数不能分配给 Typescript 中的 'number' 类型的参数 @Learner 如果输入已经是指数形式,您可以先使用n = n.toFixed(20) 将其转换为十进制形式,其中 20 是该方法的最大精度(因此您不能四舍五入超过 20位数)。 为了适应非常小和非常大的数字,这些数字将自动呈指数形式,您可以使用 toFixed 解决这个问题。 IE。 function round(val, decimals) return +(Math.round(+(val.toFixed(decimals) + "e+" + decimals)) + "e-" + decimals); 【参考方案5】:

你应该使用:

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

似乎没有人知道Number.EPSILON

另外值得注意的是,这并不是某些人所说的 JavaScript 怪异

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

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

编辑:

正如 cmets 中所要求的,让我们澄清一件事:仅当要舍入的值是算术运算的结果时,添加 Number.EPSILON 才相关,因为它可以吞下一些浮点误差增量。

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

编辑(2019 年):

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

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

编辑(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 ;

我的用例是an assertion + data validation lib,我已经开发了很多年。

事实上,在代码中我使用了ESPILON_RATE = 1 + 4 * Number.EPSILONEPSILON_ZERO = 4 * Number.MIN_VALUE(四倍的epsilon),因为我想要一个足够宽松的相等检查器来累积浮点错误。

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

【讨论】:

如果我想四舍五入到 3 个十进制数,我应该使用 1000 而不是 100? Math.round((224.98499999 * 100 + Number.EPSILON)) / 100 224.98 而不是 224.99 @PSatishPatro 没错。 .849 比 0.9 更接近 0.8,因此,它向下舍入到 0.8。 @RandomElephant,好的,但通常当我们计算时,我们会向上取整,即从最后一位数字向上取整。 98499 -> .9849 -> .985 -> .99 。有没有办法在js中实现这一点? @PSatishPatro 有,但数学不正确。从最后一个数字开始没有一般的四舍五入,如果你这样做了,你真的需要考虑重新学习数学。编辑:要回答,您需要获取数字的长度,然后从最后一位开始循环它们,四舍五入并更改初始数字,直到达到所需的位数。【参考方案6】:

这个问题很复杂。

假设我们有一个函数roundTo2DP(num),它接受一个浮点数作为参数并返回一个四舍五入到小数点后两位的值。每个表达式的计算结果应该是什么?

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

“显而易见”的答案是第一个示例应该舍入到 0.01(因为它比 0.02 更接近 0.01),而其他两个应该舍入到 0.02(因为 0.0150000000000000001 更接近 0.02 而不是 0.01,因为 0.015 是正好在它们之间,并且有一个数学约定,这些数字会被四舍五入)。

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

0.01499999999999999944488848768742172978818416595458984375

比 0.02 更接近 0.01。

您可以在浏览器控制台、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 位小数。那么这是什么黑魔法呢?

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

nks 为整数,使得 k ≥ 1, 10k-1s ks × 10n-kmk为尽可能小。注意k是s的十进制表示的位数,s不能被10整除,s的最低有效位em> 不一定由这些标准唯一确定。

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

当然,这个讨论都没有直接回答roundTo2DP(m)应该返回什么。如果m 的确切值是 0.01499999999999999944488848768742172978818416595458984375,但它的字符串表示是 '0.015',那么当我们将它四舍五入到小数点后两位时,正确答案是什么?地点?

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

所表示的值本质上是离散的,例如以 3 位小数表示的货币数量,如第纳尔。在这种情况下,像 0.015 这样的数字的 true 0.015,而 0.0149999999... 以二进制浮点表示的形式是舍入错误。 (当然,许多人会合理地争辩说,您应该使用十进制库来处理这些值,并且从一开始就不要将它们表示为二进制浮点数。) 该值由用户键入。在这种情况下,再次输入的确切十进制数比最接近的二进制浮点表示更“真实”。

另一方面,当您的值来自固有的连续刻度时,您可能希望尊重二进制浮点值并向下舍入 - 例如,如果它是来自传感器的读数。

这两种方法需要不同的代码。为了尊重数字的字符串表示,我们可以(使用相当多的相当微妙的代码)实现我们自己的舍入,直接作用于字符串表示,逐个数字,使用您在学校时使用的相同算法被教导如何四舍五入。下面是一个示例,它尊重 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://***.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 方法。请注意,通过在toFixed 返回的字符串上调用Number(),我们得到一个数字,其字符串表示没有尾随零(感谢 JavaScript 计算数字的字符串表示的方式,在本答案前面讨论过)。

/**
 * 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://***.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);

【讨论】:

您的方法在 16.996 舍入到小数点后两位失败。它进入这个分支:else if (/^9+$/.test(afterPoint)) ,然后 finalNumber 是数字而不是最后失败的字符串:return finalNumber.replace(/0+$/, ' ')【参考方案7】:

一般来说,小数舍入是通过缩放来完成的: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 函数将要舍入的原始值乘以 10 ** n,其中 n 是返回值中所需的小数位数,然后确定是否该值的剩余小数部分大于或等于 0.5。这个带有浮点值的"Exact Testing for Equality" 是有问题的,因为浮点格式在二进制表示和精度方面存在问题。这意味着任何略小于 0.5 的小数部分(由于精度损失)都不会向上舍入。

在前面的例子中,5.015是一个中间值,如果要四舍五入到小数点后两位,5.015 * 100的值实际上就是501.49999999999994。因为.49999999999994小于0.5,所以向下舍入到501,最后得到5.01。

更好的实现

指数符号

通过将数字转换为指数符号的字符串,正数按预期四舍五入。 但是,请注意,负数与正数的舍入方式不同。

事实上,它执行的规则基本上等同于"round half up",您会看到round(-1.005, 2) 的计算结果为-1,即使round(1.005, 2) 的计算结果为1.01。 lodash _.round 方法使用了这种技术。

/**
 * Round half up ('round half towards positive infinity')
 * 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) );  // 1
console.log( round(-0.5) ); // 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) 
    if (num < 0)
        return -round(-num, decimalPlaces);

    num = Math.round(num + "e" + decimalPlaces);
    return Number(num + "e" + -decimalPlaces);

近似舍入

为了纠正前面naiveRound 示例中显示的舍入问题,我们可以定义一个自定义舍入函数,该函数执行“几乎相等”测试以确定小数值是否足够接近中点值以服从中点四舍五入。

// round half away from zero
function round(num, decimalPlaces = 0) 
    if (num < 0)
        return -round(-num, decimalPlaces);
    var p = Math.pow(10, decimalPlaces);
    var n = num * p;
    var f = n - Math.floor(n);
    var e = Number.EPSILON * n;

    // Determine whether this fraction is a midpoint value.
    return (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p;


// test rounding of half
console.log( round(0.5) );  // 1
console.log( round(-0.5) ); // -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

Number.EPSILON

有一种不同的纯数学技术来执行舍入到最近(使用"round half away from zero"),其中在调用舍入函数之前应用了 epsilon 校正。

简单地说,我们在舍入之前将最小可能的浮点值(= 1.0 ulp;最后一个单位)添加到产品中。这将移动到下一个可表示的浮点值,远离零,因此它将抵消在乘以 10 ** n 期间可能出现的二进制 round-off error。

/**
 * 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 n = (num * p) * (1 + Number.EPSILON);
    return Math.round(n) / p;


// rounding of half
console.log( round(0.5) );  // 1
console.log( round(-0.5) ); // -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

加上1个ulp后,5.015 * 100的值即501.49999999999994会被修正为501.50000000000006,四舍五入为502,最终结果为5.02。

请注意,unit in last place ("ulp") 的大小由 (1) 数字的大小和 (2) 相对机器 epsilon (2^-52) 决定。 Ulp 在数量级较大的数字上比在数量级较小的数字上相对较大。

双舍入

在这里,我们使用toPrecision() 方法去除中间计算中的浮点舍入误差。简单地说,我们舍入到 15 significant figures 以去除第 16 位有效数字的舍入误差。 PHP 7 round 函数也使用了这种将结果预舍入为有效数字的技术。

5.015 * 100 即501.49999999999994 的值会先四舍五入到 15 位有效数字为501.500000000000,然后再四舍五入到 502,最后得到 5.02。

// Round half away from zero
function round(num, decimalPlaces = 0) 
    if (num < 0)
        return -round(-num, decimalPlaces);
    var p = Math.pow(10, decimalPlaces);
    var n = (num * p).toPrecision(15);
    return Math.round(n) / p;


// rounding of half
console.log( round(0.5) );  // 1
console.log( round(-0.5) ); // -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();


// rounding of half
console.log( round(0.5) );  // 1
console.log( round(-0.5) ); // -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
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"&gt;&lt;/script&gt;

解决方案 1:指数符号的字符串

受到 KFish 提供的解决方案的启发:https://***.com/a/55521592/4208440

一个简单的解决方案,无需添加整个库即可提供精确的小数四舍五入、地板和天花板到特定的小数位数。它通过修复二进制舍入问题来将浮点数更像小数处理以避免意外结果:例如, floor((0.1+0.7)*10) 将返回预期结果 8。

数字四舍五入为特定数量的小数位数。指定负精度将四舍五入到小数点左侧的任意位数。

// Solution 1
var DecimalPrecision = (function() 
    if (Math.trunc === undefined) 
        Math.trunc = function(v) 
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        ;
    
    var decimalAdjust = function myself(type, num, decimalPlaces) 
        if (type === 'round' && num < 0)
            return -myself(type, -num, decimalPlaces);
        var shift = function(value, exponent) 
            value = (value + 'e').split('e');
            return +(value[0] + 'e' + (+value[1] + (exponent || 0)));
        ;
        var n = shift(num, +decimalPlaces);
        return shift(Math[type](n), -decimalPlaces);
    ;
    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);
console.log(DecimalPrecision.floor(1e-8, 2) === 0);

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

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

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

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

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

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

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

解决方案 2:纯数学 (Number.EPSILON)

出于性能原因,此解决方案避免了任何类型的字符串转换/操作。

// 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 powers = [
        1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
        1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
        1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
    ];
    var intpow10 = function(power) 
        if (power < 0 || power > 22) 
            return Math.pow(10, power);
        
        return powers[power];
    ;
    var isRound = function(num, decimalPlaces) 
        //return decimalPlaces >= 0 &&
        //    +num.toFixed(decimalPlaces) === num;
        var p = intpow10(decimalPlaces);
        return Math.round(num * p) / p === num;
    ;
    var decimalAdjust = function(type, num, decimalPlaces) 
        if (type !== 'round' && isRound(num, decimalPlaces || 0))
            return num;
        var p = intpow10(decimalPlaces || 0);
        var n = (num * p) * (1 + Number.EPSILON);
        return Math[type](n) / 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);
console.log(DecimalPrecision2.floor(1e-8, 2) === 0);

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

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

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

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

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

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

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

解决方案 3:双舍入

此解决方案使用toPrecision() 方法去除浮点舍入误差。

// Solution 3
var DecimalPrecision3 = (function() 
    if (Math.trunc === undefined) 
        Math.trunc = function(v) 
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        ;
    
    var powers = [
        1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
        1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
        1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
    ];
    var intpow10 = function(power) 
        if (power < 0 || power > 22) 
            return Math.pow(10, power);
        
        return powers[power];
    ;
    // Eliminate binary floating-point inaccuracies.
    var stripError = function(num) 
        if (Number.isInteger(num))
            return num;
        return parseFloat(num.toPrecision(15));
    ;
    var decimalAdjust = function myself(type, num, decimalPlaces) 
        if (type === 'round' && num < 0)
            return -myself(type, -num, decimalPlaces);
        var p = intpow10(decimalPlaces || 0);
        var n = stripError(num * p);
        return Math[type](n) / 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(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);
console.log(DecimalPrecision3.floor(1e-8, 2) === 0);

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

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

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

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

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

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

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

解决方案 4:双舍入 v2

此解决方案与解决方案 3 类似,但它使用自定义 toPrecision() 函数。

// Solution 4
var DecimalPrecision4 = (function() 
    if (Math.trunc === undefined) 
        Math.trunc = function(v) 
            return v < 0 ? Math.ceil(v) : Math.floor(v);
        ;
    
    var powers = [
        1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,
        1e8,  1e9,  1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
        1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
    ];
    var intpow10 = function(power) 
        if (power < 0 || power > 22) 
            return Math.pow(10, power);
        
        return powers[power];
    ;
    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 (sf - leftSidedDigits).
        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 "shift" fractional digits
        var scale = intpow10(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 myself(type, num, decimalPlaces) 
        if (type === 'round' && num < 0)
            return -myself(type, -num, decimalPlaces);
        var p = intpow10(decimalPlaces || 0);
        var n = stripError(num * p);
        return Math[type](n) / 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(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);
console.log(DecimalPrecision4.floor(1e-8, 2) === 0);

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

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

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

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

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

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

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

基准

http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac

这是一个比较上述解决方案在 Chrome 85.0.4183.83 上的每秒操作数的基准。显然所有浏览器都不同,因此您的里程可能会有所不同。

(注:越多越好)

感谢 @Mike 添加基准的屏幕截图。

【讨论】:

很好,我看到您确实对差异进行了更彻底的性能测试。我刚刚在 devtools 中进行了快速比较,它们返回的执行时间差异非常相似,但我想知道性能差异是否会以非常高的音量/频率开始显示。 第二种解决方案更容易阅读和理解。它还可以移植到其他编程语言。 嘿@AmrAli。这是一个很棒的答案。为数不多的尽可能准确的方法之一。谢谢! ? 我特别喜欢Solution 2 的速度。我注意到的一件事是,如果删除了isRound 的提前返回检查,则速度可以提高约 5-10%。它增加了更多的操作,而不仅仅是运行decimalAdjust 函数。使用 isRound 提前返回实际上需要更长的时间。 嗨@GollyJer。对于ceil, floor and trunc 的边缘情况,需要检查isRoundround 实际上不需要它。 我查看了 *** 上的许多解决方案,这个是最好的。带有负数 mod 的指数表示法解决方案似乎最适用于货币,并且与后端的 Java 轮计算相匹配。【参考方案8】:

考虑.toFixed().toPrecision()

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

【讨论】:

在 Firefox 中,3.9935.toFixed(3) → "3.994"3.9945.toFixed(3) → "3.994"3.9955.toFixed(3) → "3.995"3.9965.toFixed(3) → "3.997"。这是预期的行为吗?比如3.9945.toFixed(3)不应该返回"3.995"或者3.9955.toFixed(3)返回"3.996"吗? A Kunin 在下面的答案中对此有所了解。【参考方案9】:

可以使用.toFixed(NumberOfDecimalPlaces)

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

【讨论】:

【参考方案10】:

这里找到的答案都不正确。 @stinkycheeseman 要求四舍五入,你们都四舍五入了。

要四舍五入,请使用:

Math.ceil(num * 100)/100;

【讨论】:

1.3549999999999998 将返回不正确的结果。应该是 1.35,但结果是 1.36。【参考方案11】:

一种精确的舍入方法。来源: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

【讨论】:

【参考方案12】:

这是一个简单的方法:

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);

【讨论】:

【参考方案13】:

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

【讨论】:

再次将其全部包装在Number 中,如果您不希望它作为字符串返回:Number(Number(x).toFixed(2)); Number 调用不是必需的,x.toFixed(2) 有效。 @bgusach 需要数字调用,因为语句 x.toFixed(2) 返回字符串而不是数字。要再次转换为数字,我们需要用 Number 包装 当使用这个方法(1).toFixed(2)返回1.00,但是在这种情况下提问者需要1 这行不通,1.005.toFixed(2) 在应该是 "1.01" 时产生 "1"【参考方案14】:
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

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

【讨论】:

【参考方案15】:

对我来说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 只是四舍五入到最接近的整数。因此,为了保留小数,我们需要将原始数字乘以十的幂,其零表示您想要的小数位数,然后将结果除以相同的数字。在您的情况下:Math.round(43000 / 80000 * 100 * 100) / 100。最后可以应用 toFixed(2) 以确保结果中始终有两位小数(在需要时带有尾随零) - 完美用于右对齐垂直显示的一系列数字:)【参考方案16】:

这可能会对您有所帮助:

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

更多信息,你可以看看这个链接

Math.round(num) vs num.toFixed(0) and browser inconsistencies

【讨论】:

为什么在世界上接受的答案比这个有更多的选票,因为它们实际上是一样的,但是这个是在接受后 1 分钟发布的?【参考方案17】:

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

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


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

【讨论】:

有人知道这和return Number(x.toFixed(digits))有什么区别吗? @JoeRocc ...据我所知应该没有什么区别,因为.toFixed() 无论如何只允许数字。 此答案与此页面上多次提到的问题相同。尝试round(1.005, 2) 并查看1 而不是1.01 的结果。 似乎更多的是舍入算法的问题? - 有不止一个人可以想象:en.wikipedia.org/wiki/Rounding ... round(0.995, 2) =&gt; 0.99; round(1.006, 2) =&gt; 1.01 ; round(1.005, 2) =&gt; 1 这行得通,但它给系统增加了不必要的复杂性,因为它将浮点数转换为字符串,然后将字符串解析回浮点数。【参考方案18】:

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

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

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

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

【讨论】:

toFixed 方法返回一个字符串。如果你想要一个数字结果,你需要将 toFixed 的结果发送到 parseFloat。 @Zambonilli 或者在必要时乘以 1。但是因为固定数字大多数情况是用于显示而不是用于计算字符串是正确的格式 -1; toFixed 不仅比您早几年的多个答案提出了建议,而且它未能满足问题中的“仅在必要时”条件; (1).toFixed(2) 提供"1.00" 询问者所需的"1" 好的,知道了。我也为这种情况添加了一些解决方案 如果你使用 lodash,那就更简单了:_.round(number, decimalPlace) 删除了我的最后一条评论,因为它有问题。不过,Lodash _.round 确实有效。小数点为 2 的 1.005 转换为 1.01。【参考方案19】:

有几种方法可以做到这一点。对于像我这样的人,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

【讨论】:

第二个选项将返回一个正好有两个小数点的字符串。该问题仅在必要时才要求小数点。在这种情况下,第一个选项更好。 @MarcosLima Number.toFixed() 将返回一个字符串,但前面有一个加号,JS 解释器会将字符串转换为数字。这是一个语法糖。 在 Firefox 上,alert((+1234).toFixed(2)) 显示“1234.00”。 在 Firefox 上,alert(+1234.toFixed(2)) 抛出 SyntaxError: identifier starts immediately after numeric literal。我坚持第一个选项。 这在某些极端情况下不起作用:尝试 (jsfiddle) 和 362.42499999999995。预期结果(如 php echo round(362.42499999999995, 2)):362.43。实际结果:362.42【参考方案20】:

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

_.round(number, precision)

例如:

_.round(1.7777777, 2) = 1.78

【讨论】:

@Peter 与标准 Javascript 相比,Lodash 提供的一组功能非常好。但是,我听说 Lodash 与标准 JS 相比存在一些性能问题。 codeburst.io/… 我接受您的观点,即使用 lodash 存在性能缺陷。我认为这些问题对于许多抽象来说是共同的。但只要看看这个线程上有多少答案,以及直观的解决方案如何在极端情况下失败。我们已经在 jQuery 中看到了这种模式,当浏览器采用解决我们大多数用例的通用标准时,根本问题就解决了。然后将性能瓶颈转移到浏览器引擎上。我认为同样的事情也应该发生在 lodash 上。 :)【参考方案21】:

自 ES6 以来,using 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 舍入问题”——因为它是 float fractions are being processed 所固有的。

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));
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/bignumber.js/2.3.0/bignumber.min.js"&gt;&lt;/script&gt;

【讨论】:

(1.005).toPrecision(3) 实际上仍然返回 1.00 而不是 1.01 toPrecision 返回一个更改所需输出类型的字符串。 @Giacomo 这不是.toPrecision 方法的缺陷,它是浮点数的特殊性(JS 中的数字是)- 尝试1.005 - 0.005,它会返回0.9999999999999999 (1).toPrecision(3) 返回 '1.00',但在这种情况下,提问者想要1 正如@Giacomo 所说,这个答案似乎将“有效数字”与“四舍五入到小数位”混淆了。 toPrecision 采用格式,而不是后者,也不是对 OP 问题的回答,尽管乍一看它可能看起来很相关,但它有很多错误。见en.wikipedia.org/wiki/Significant_figures。例如Number(123.4).toPrecision(2) 返回"1.2e+2"Number(12.345).toPrecision(2) 返回"12"。我也同意@adamduren 的观点,即它返回一个不可取的字符串(不是一个大问题,但不可取)。【参考方案22】:

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

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

【讨论】:

这不适用于所有情况。在发布答案之前进行广泛的测试。 @baburao 请发一个上述解决方案不起作用的案例 常量数 = 15;数字(数字.toFixed(2)); //15.00 而不是 15 @KevinJhangiani const number = 15;数字(数字.toFixed(2)); // 15 - 我在最新的 Chrome 和 Firefox 上都测试过 评论者完全正确,发布后我意识到我的代码中的错误!【参考方案23】:

MarkG 和 Lavamantis 提供了一种比已被接受的解决方案更好的解决方案。可惜他们没有得到更多的支持!

这是我用来解决浮点小数问题的函数also based on 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 的解决方案相反,您的解决方案不涵盖某些情况。虽然它可能更短,但并不准确...... round(-1835.665,2) => -1835.66【参考方案24】:

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

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

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

【讨论】:

这是迄今为止最简洁的解决方案,并且回避了所有复杂的浮点问题,但对per MDN 的支持仍然不完整——Safari 还不支持将参数传递给toLocaleString。跨度> @MarkAmery 目前只有安卓浏览器有一些问题:caniuse.com/#search=toLocaleString【参考方案25】:

它可能对你有用,

Math.round(num * 100)/100;

要知道toFixed 和round 之间的区别。你可以看看Math.round(num) vs num.toFixed(0) and browser inconsistencies

【讨论】:

【参考方案26】:

这是最简单、更优雅的解决方案(我是世界上最好的;):

function roundToX(num, X)     
    return +(Math.round(num + "e+"+X)  + "e-"+X);

//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9

带有后备值的现代语法替代

const roundToX = (num = 0, X = 20) => +(Math.round(num + `e$X`)  + `e-$X`)

【讨论】:

这是重写接受的答案以接受使用E 表示法的参数的好方法。 这在某些极端情况下不起作用:尝试 (jsfiddle) roundToX(362.42499999999995, 2)。预期结果(如 PHP echo round(362.42499999999995, 2)):362.43。实际结果:362.42 恕我直言,您的 PHP 结果是错误的。不管小数点后三位是什么,如果小数点后三位小于 5,那么小数点后二位应该保持不变。这就是数学定义。 为了更简洁,“e+”可以改为“e”。【参考方案27】:
var roundUpto = function(number, upto)
    return Number(number.toFixed(upto));

roundUpto(0.1464676, 2);

toFixed(2) 这里 2 是我们希望将此数字四舍五入的位数。

【讨论】:

这个 .toFixed() 实现起来更简单。只需经历一次。【参考方案28】:

最终更新:

将此答案留给后代,但我建议使用@AmrAli 对 DecimalPrecision 函数的改编,因为它也可以处理指数符号。出于性能原因,我最初试图避免任何类型的字符串转换/操作,但他的实现在性能上几乎没有差异。

编辑 2020 年 8 月 22 日:我认为这里应该澄清的是,这些努力的目标不是完全消除由浮点数据类型引起的固有舍入误差,因为如果不切换到实际将值存储为 base10(十进制)的数据类型。真正的目标应该是将不准确性尽可能推到边缘,以便您可以对给定值执行数学运算而不会产生错误。当您的值达到绝对边缘时,简单地调用该值会导致 JS 在您操作它之前或之后产生错误,您无能为力来缓解这种情况。例如,如果你实例化值 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://***.com/a/48850944/6910392

一个简单的解决方案,提供精确的小数四舍五入、地板和天花板,具有可选的精度变量,无需添加整个库。

2020 年 5 月 19 日更新:正如 Sergey 在 cmets 中指出的那样,这种(或任何)方法存在一个限制,值得指出。对于像 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。为此,我'我们添加了一个额外的函数检查,以识别输入数字中的小数位数是否低于请求的输出小数位数,并返回不变的数字,就像整数一样。

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 好收获!问题在于 JS 中的浮点存储,总会有一些边缘情况。好消息是,您首先应用于 Number.EPSILON 的数学可以更精细地调整,以将这些边缘情况推到更远的边缘。如果您想保证不会出现边缘情况,那么您唯一真正的解决方案就是字符串操作,然后是数学。当您对该值执行任何数学计算时(即使尝试移动小数点),那么您已经产生了错误。 实际上,经过进一步检查,这不是由于所涉及的任何数学问题,而是在调用指定值时问题立即显现。您可以简单地通过在控制台中输入该数字来确认这一点,并看到它立即评估为 0.015。因此,这将代表 JS 中任何浮点数的绝对精度边缘。在这种情况下,您甚至无法转换为字符串并进行操作,因为字符串值将是“0.015” @KFish DecimalPrecision.ceil(17,0); // 18DecimalPrecision.ceil(17,1); // 17.1 @KFish DecimalPrecision.ceil(-5.12, 1); // -5.2DecimalPrecision.floor(-5.12, 1); // -5.1【参考方案29】:

最简单的方法:

+num.toFixed(2)

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

【讨论】:

感谢这个最简单的答案。但是,+num 中的 '+' 是什么?在十进制 val 以字符串形式出现的情况下,它对我不起作用。我做了:(num * 1).toFixed(2). @momo 只需将 toFixed() 的参数更改为 3。所以它将是 +num.toFixed(3)。这是按预期方式工作的,1.005 舍入为 1.00,等于 1 @Edmund 它应该返回 1.01,而不是 1.00【参考方案30】:

这是一个原型方法:

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


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

【讨论】:

以上是关于如有必要,如何舍入至多 2 位小数?的主要内容,如果未能解决你的问题,请参考以下文章

如何优化 spark 函数以将双精度值舍入到小数点后 2 位?

如何在不进行任何舍入的情况下将浮点数转换为小数点后 4 位?

javascript 以2位小数舍入数字的两种方法

在 Python 2 中使用逗号分隔符格式化数字并舍入到小数点后 2 位?

如何在 Torch 的 GPU 上将张量的元素限制/舍入到小数点后 4 位?

将浮点数与字符串连接并舍入到小数点后 2 位