如有必要,如何舍入至多 2 位小数
问:
我想最多四舍五入小数点后两位,但仅限于必要时。
输入:
10
1.7777777
9.1
输出:
10
1.78
9.1
如何在 JavaScript 中做到这一点?
答1:
huntsbot.com – 程序员副业首选,一站式外包任务、远程工作、创意产品分享订阅平台。
使用 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:
huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!
如果值是文本类型:
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。
解决方案 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);}
}
它也是at Plunker。
注意:这不是一个适合所有人的通用解决方案。有几种不同的舍入算法。您的实现可能会有所不同,这取决于您的要求。另请参阅Rounding。
解决方案 2 是避免前端计算并从后端服务器拉取舍入值。
另一种可能的解决方案,也不是万无一失的。
Math.round((num + Number.EPSILON) * 100) / 100
在某些情况下,当您对 1.3549999999999998 之类的数字进行四舍五入时,它会返回错误的结果。它应该是 1.35,但结果是 1.36。
huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!
在这个函数 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
您应该使用 Number(a)、parseFloat(a)(实际上与 stackoverflow.com/a/11988612/16940 的行为相同)而不是 eval(a)。您甚至可以只使用 +a。我更喜欢Number(a)。
答3:
与HuntsBot一起,探索全球自由职业机会–huntsbot.com
您可以使用
function roundToTwo(num) {return +(Math.round(num + "e+2") + "e-2");
}
我找到了这个 on MDN。他们的方式避免了 1.005 was 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
这不适用于负数。
但是,如果 num 为 -2.9e-7,则 +(Math.round(num + "e+2") + "e-2") 返回 NaN,这不是所需的结果。至少在 Chrome 101 上
答4:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
MarkG’s answer 是正确的。这是任意小数位数的通用扩展。
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)) 中收到此错误“字符串”类型的参数不可分配给 Typescript 中“数字”类型的参数
为了适应非常小和非常大的数字,它们将自动呈指数形式,您可以使用 toFixed 解决这个问题。即function round(val, decimals) { return +(Math.round(+(val.toFixed(decimals) + "e+" + decimals)) + "e-" + decimals); }
哦,来吧,不要修改原型
答5:
huntsbot.com全球7大洲远程工作机会,探索不一样的工作方式
你应该使用:
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 之间没有数字。
编辑:
正如评论中所要求的,让我们澄清一件事:添加 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.EPSILON 和 EPSILON_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:
huntsbot.com提供全网独家一站式外包任务、远程工作、创意产品分享与订阅服务!
通常,小数舍入是通过缩放完成的:round(num * p) / p
幼稚的实现
使用以下带有中间数字的函数,您将获得预期的上舍入值,或者有时取决于输入的下舍入值。
此 inconsistency 舍入可能会在客户端代码中引入难以检测的错误。
函数 naiveRound(num, decimalPlaces = 0) { var p = Math.pow(10, decimalPlaces);返回 Math.round(num * p) / p; } console.log( naiveRound(1.245, 2) ); // 1.25 正确(按预期四舍五入) console.log( naiveRound(1.255, 2) ); // 1.25 不正确(应该是 1.26) // 测试边缘情况 console.log( naiveRound(1.005, 2) ); // 1 不正确(应该是 1.01) console.log( naiveRound(2.175, 2) ); // 2.17 不正确(应该是 2.18) console.log( naiveRound(5.015, 2) ); // 5.01 不正确(应该是 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 方法使用了这种技术。
/** * 向上舍入一半(‘向正无穷方向舍入一半’) * 负数舍入不同于正数。 */ function round(num, decimalPlaces = 0) { num = Math.round(num + “e” + decimalPlaces);返回数字(num +“e”+ -decimalPlaces); } // 测试一半的四舍五入 console.log( round(0.5) ); // 1 个控制台.log( round(-0.5) ); // 0 // 测试边缘案例 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 示例中显示的舍入问题,我们可以定义一个自定义舍入函数,该函数执行“几乎相等”测试以确定小数值是否足够接近中点值以进行中点舍入。
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { if (num < 0) return -round(-num, decimalPlaces); var p = Math.pow(10, decimalPlaces);变量 n = 数量 * p; var f = n - Math.floor(n); var e = Number.EPSILON * n; // 判断这个分数是否为中点值。返回 (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p; } // 测试一半的四舍五入 console.log( round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
编号.EPSILON
有一种不同的纯数学技术来执行舍入到最近(使用 “round half away from zero”),其中在调用舍入函数之前应用 epsilon 校正。
简单地说,我们在舍入之前将最小可能的浮点值(= 1.0 ulp;最后一个单位)添加到产品中。这将移动到下一个可表示的浮点值,远离零,因此它将抵消在乘以 10 ** n 期间可能出现的二进制 round-off error。
/** * 从零舍入一半(“商业”舍入) * 使用校正来抵消浮点不准确性。 * 对正数和负数对称地工作。 */ function round(num, decimalPlaces = 0) { var p = Math.pow(10, decimalPlaces); var n = (num * p) * (1 + Number.EPSILON);返回 Math.round(n) / p; } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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。
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { if (num < 0) return -round(-num, decimalPlaces); var p = Math.pow(10, decimalPlaces); var n = (num * p).toPrecision(15);返回 Math.round(n) / p; } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber(); } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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:指数符号的字符串
受 KFish 在此处提供的解决方案的启发:https://stackoverflow.com/a/55521592/4208440
一个简单的解决方案,提供精确的小数四舍五入、地板和天花板到特定的小数位数,而无需添加整个库。它通过修复二进制舍入问题来将浮点数更像小数处理以避免意外结果:例如, floor((0.1+0.7)*10) 将返回预期结果 8。
数字四舍五入到特定数量的小数位数。指定负精度将四舍五入到小数点左侧的任意位数。
// 解决方案 1 var DecimalPrecision = (function() { if (Math.trunc === undefined) { Math.trunc = function(v) { return v < 0 ? Math.ceil(v) : Math.floor(v) ; }; } var decimalAdjust = function 我自己(type,num,decimalPlaces){ if(type === ‘round’ && num < 0)return -myself(type,-num,decimalPlaces); var shift = function(值, exponent) { value = (value + ‘e’).split(‘e’); return +(value[0] + ‘e’ + (+value[1] + (exponent || 0))); }; var n = shift(num, +decimalPlaces); return shift(Mathtype, -decimalPlaces); }; return { // 十进制舍入(距离零的一半) round: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces); }, // 十进制 ceil ceil: function(num, decimalPlaces) { return decimalAdjust(‘ceil’, num, decimalPlaces); }, // 十进制下限 floor: function(num, decimalPlaces) { return decimalAdjust(‘floor’, num, decimalPlaces); }, // 十进制 trunc trunc: function(num, decimalPlaces) { return decimalAdjust(‘trunc’, num, decimalPlaces); }, // 使用 f 格式化定点符号 toFixed: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces).toFixed(decimalPlaces); } }; })(); // 测试一半的四舍五入 console.log(DecimalPrecision.round(0.5)); // 1 console.log(DecimalPrecision.round(-0.5)); // -1 // 测试非常小的数字 console.log(DecimalPrecision.ceil(1e-8, 2) === 0.01); console.log(DecimalPrecision.floor(1e-8, 2) === 0); // 测试简单案例 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); // 测试圆形的边缘情况 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); // 测试 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); // 测试地板的边缘情况 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); // 测试 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); // 测试几十个和几百个 console.log(DecimalPrecision.round(1262.48, -1) === 1260); console.log(DecimalPrecision.round(1262.48, -2) === 1300); // 测试 toFixed() console.log(DecimalPrecision.toFixed(1.005, 2) === “1.01”);
解决方案2:纯数学(Number.EPSILON)
出于性能原因,此解决方案避免了任何类型的字符串转换/操作。
// 解决方案 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 Mathtype / p; }; return { // 十进制舍入(距离零的一半) round: function(num, decim alPlaces) { return decimalAdjust(‘round’, num, decimalPlaces); }, // 十进制 ceil ceil: function(num, decimalPlaces) { return decimalAdjust(‘ceil’, num, decimalPlaces); }, // 十进制地板 floor: function(num, decimalPlaces) { return decimalAdjust(‘floor’, num, decimalPlaces); }, // 十进制 trunc trunc: function(num, decimalPlaces) { return decimalAdjust(‘trunc’, num, decimalPlaces); }, // 使用定点符号格式化 toFixed: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces).toFixed(decimalPlaces); } }; })(); // 测试一半的四舍五入 console.log(DecimalPrecision2.round(0.5)); // 1 console.log(DecimalPrecision2.round(-0.5)); // -1 // 测试非常小的数字 console.log(DecimalPrecision2.ceil(1e-8, 2) === 0.01); console.log(DecimalPrecision2.floor(1e-8, 2) === 0); // 测试简单案例 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); // 测试圆形的边缘情况 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); // 测试 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); // 测试地板的边缘情况 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); // 测试 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); // 测试几十个和几百个 console.log(DecimalPrecision2.round(1262.48, -1) === 1260); console.log(DecimalPrecision2.round(1262.48, -2) === 1300); // 测试 toFixed() console.log(DecimalPrecision2.toFixed(1.005, 2) === “1.01”);
解决方案3:双舍入
此解决方案使用 toPrecision() 方法去除浮点舍入误差。
// 解 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, 1e 1e21, 1e22 ]; var intpow10 = function(power) { if (power < 0 || power > 22) { return Math.pow(10, power); } return powers[power]; }; // 消除二进制浮点数-点不准确 var stripError = function(num) { if (Number.isInteger(num)) return num; return parseFloat(num.toPrecision(15)); }; var decimalAdjust = function 我自己(type, num, decimalPlaces) { if (type === ‘round’ && num < 0) return -myself(type, -num, decimalPlaces); var p = intpow10(decimalPlaces || 0); var n = stripError(num * p); return Mathtype / p; }; return { // 十进制舍入(距离零的一半) round: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces); }, // 十进制 ceil ceil:功能(数量, decimalPlaces) { return decimalAdjust(‘ceil’, num, decimalPlaces); }, // 十进制地板 floor: function(num, decimalPlaces) { return decimalAdjust(‘floor’, num, decimalPlaces); }, // 十进制 trunc trunc: function(num, decimalPlaces) { return decimalAdjust(‘trunc’, num, decimalPlaces); }, // 使用定点符号格式化 toFixed: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces).toFixed(decimalPlaces); } }; })(); // 测试一半的舍入 console.log(DecimalPrecision3.round(0.5)); // 1 console.log(DecimalPrecision3.round(-0.5)); // -1 // 测试非常小的数字 console.log(DecimalPrecision3.ceil(1e-8, 2) === 0.01); console.log(DecimalPrecision3.floor(1e-8, 2) === 0); // 测试简单案例 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); // 测试圆形的边缘情况 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); // 测试 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); // 测试地板的边缘情况 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); // 测试 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); // 测试几十个和几百个 console.log(DecimalPrecision3.round(1262.48, -1) === 1260); console.log(DecimalPrecision3.round(1262.48, -2) === 1300); // 测试 toFixed() console.log(DecimalPrecision3.toFixed(1.005, 2) === “1.01”);
解决方案 4:双舍入 v2
此解决方案与解决方案 3 类似,但它使用自定义 toPrecision() 函数。
// 解 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, 1e 1e21, 1e22 ]; var intpow10 = function(power) { if (power < 0 || power > 22) { return Math.pow(10, power); } return powers[power]; }; var toPrecision = function(num , SignificantDigits) { // 提前返回 ±0、NaN 和 Infinity。if (!num || !Number.isFinite(num)) return num; // 计算小数点的移位 (sf - leftSidedDigits)。var shift = significantDigits - 1 - Math.floor(Math.log10(Math.abs(num))); // 如果舍入到相同或更高的精度,则返回 var decimalPlaces = 0; for (var p = 1; num != Math. round(num * p) / p; p *= 10) decimalPlaces++; if (shift >= decimalPlaces) return num; // 舍入到“移位”小数位 var scale = intpow10(Math.abs(shift)); return shift > 0 ? Math.rou nd(num * scale) / scale : Math.round(num / scale) * scale; }; // 消除二进制浮点数的不准确性。 var stripError = function(num) { if (Number.isInteger(num)) return 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);返回数学[类型](n)/ p; }; return { // 十进制舍入(距离零的一半) round: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces); }, // 十进制 ceil ceil: function(num, decimalPlaces) { return decimalAdjust(‘ceil’, num, decimalPlaces); }, // 十进制地板 floor: function(num, decimalPlaces) { return decimalAdjust(‘floor’, num, decimalPlaces); }, // 十进制 trunc trunc: function(num, decimalPlaces) { return decimalAdjust(‘trunc’, num, decimalPlaces); }, // 使用定点符号格式化 toFixed: function(num, decimalPlaces) { return decimalAdjust(‘round’, num, decimalPlaces).toFixed(decimalPlaces); } }; })(); // 测试一半的四舍五入 console.log(DecimalPrecision4.round(0.5)); // 1 console.log(DecimalPrecision4.round(-0.5)); // -1 // 测试非常小的数字 console.log(DecimalPrecision4.ceil(1e-8, 2) === 0.01); console.log(DecimalPrecision4.floor(1e-8, 2) === 0); // 测试简单案例 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); // 测试圆形的边缘情况 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); // 测试 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); // 测试地板的边缘情况 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); // 测试 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); // 测试几十个和几百个 console.log(DecimalPrecision4.round(1262.48, -1) === 1260); console.log(DecimalPrecision4.round(1262.48, -2) === 1300); // 测试 toFixed() console.log(DecimalPrecision4.toFixed(1.005, 2) === “1.01”);
基准
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac
这是比较 Chrome 85.0.4183.83 上上述解决方案中每秒操作数的基准。显然,所有浏览器都不同,因此您的里程可能会有所不同。
https://i.stack.imgur.com/llFZn.png
感谢@Mike 添加基准的屏幕截图。
很好,我看到您确实对差异进行了更彻底的性能测试。我刚刚在 devtools 中进行了快速比较,它们返回的执行时间差异非常相似,但我想知道性能差异是否会以非常高的音量/频率开始显示。
嘿@AmrAli。这是一个很棒的答案。为数不多的尽可能准确的方法之一。谢谢! 👍 我特别喜欢 Solution 2 的速度。我注意到的一件事是,如果删除 isRound 的提前返回检查,速度可以提高约 5-10%。它增加了比仅仅运行 decimalAdjust 函数更多的操作。使用 isRound 提前返回实际上需要更长的时间。
我浏览了 StackOverflow 上的许多解决方案,这个是最好的。带有负数 mod 的指数表示法解决方案似乎最适用于货币,并且与后端的 Java 轮计算相匹配。
这个答案是一个很好的例子,为什么你不应该只检查 stackoverflow 中的第一条评论。上面那两个完全是错误的。
除了提供基准之外,您还应该运行测试来显示这些技术中的任何一种是否真的有效,例如 0.0001 < x < 0.9999。您可能会惊讶地发现其中有多少失败了。超过 90%。
答7:
huntsbot.com – 高效赚钱,自由工作
这个问题很复杂。
假设我们有一个函数 roundTo2DP(num),它接受一个浮点数作为参数并返回一个四舍五入到小数点后 2 位的值。这些表达式中的每一个应该评估为什么?
roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
“显而易见”的答案是第一个示例应该舍入到 0.01(因为它比 0.02 更接近 0.01),而其他两个应该舍入到 0.02(因为 0.0150000000000000001 比 0.01 更接近 0.02,并且因为 0.015 正好介于两者之间它们,并且有一个数学约定,这些数字会被四舍五入)。
您可能已经猜到,问题在于roundTo2DP 不可能 被实现来给出这些明显的答案,因为传递给它的所有三个数字都是相同的数字。 IEEE 754 二进制浮点数(JavaScript 使用的那种)不能准确地表示大多数非整数,因此上述所有三个数字文字都会四舍五入为附近的有效浮点数。这个数字恰好是准确的
0.01499999999999999944488848768742172978818416595458984375
比 0.02 更接近 0.01。
您可以在浏览器控制台、Node shell 或其他 JavaScript 解释器中看到所有三个数字都是相同的。只需比较它们:
> <b><i>0.014999999999999999 === 0.0150000000000000001</i></b>
true
因此,当我编写 m = 0.0150000000000000001 时,我最终得到的 m 的确切值比 0.02 更接近 0.01。然而,如果我将 m 转换为字符串…
> <b><i>var m = 0.0150000000000000001;</i></b>
> <b><i>console.log(String(m));</i></b>
0.015
> <b><i>var m = 0.014999999999999999;</i></b>
> <b><i>console.log(String(m));</i></b>
0.015
…我得到 0.015,它应该四舍五入到 0.02,这显然不是我之前所说的所有这些数字都完全相等的 56 位小数。那么这是什么黑魔法?
答案可以在 ECMAScript 规范的 7.1.12.1: ToString applied to the Number type 部分中找到。这里制定了将一些数字 m 转换为字符串的规则。关键部分是第 5 点,其中生成了一个整数 s,其数字将用于 m 的 String 表示:
令n、k、s为整数,使得k≥1,10k-1≤s<10k,s×10n-k的数值为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.01499999999999999944488848768742172978818416595458984375,但其字符串表示为 ‘0.015’,那么当我们将其四舍五入到小数点后两位地方?
对此没有单一的正确答案。这取决于您的用例。在以下情况下,您可能希望尊重字符串表示并向上取整:
所表示的值本质上是离散的,例如以 3 位小数表示的货币数量,如第纳尔。在这种情况下,像 0.015 这样的数字的真实值是 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://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+37throw new Error("num too large");}if (num.indexOf('.') == -1) {// Nothing to doreturn 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 stopvar 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 returningreturn finalNumber.replace(/0+$/, '')
}
示例用法:
> <b><i>roundStringNumberWithoutTrailingZeroes(1.6, 2)</i></b>
'1.6'
> <b><i>roundStringNumberWithoutTrailingZeroes(10000, 2)</i></b>
'10000'
> <b><i>roundStringNumberWithoutTrailingZeroes(0.015, 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.015000', 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes(1, 1)</i></b>
'1'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.015', 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)</i></b>
'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://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);
}
您的方法在 16.996 舍入到 2 位小数时失败。它进入这个分支:else if (/^9+$/.test(afterPoint)) {,然后 finalNumber 是数字而不是最后失败的字符串:return finalNumber.replace(/0+$/, ' ')
答8:
huntsbot.com精选全球7大洲远程工作机会,涵盖各领域,帮助想要远程工作的数字游民们能更精准、更高效的找到对方。
考虑 .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 在下面的答案中对此有所了解。
toFixed() 有时无法正确舍入。我自己见过。 Math.round 更好
答9:
HuntsBot周刊–不定时分享成功产品案例,学习他们如何成功建立自己的副业–huntsbot.com
可以使用 .toFixed(NumberOfDecimalPlaces)。
var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
这是 user3711536's answer 的副本——尽管同样没有任何解释或文档链接。至少另一个答案有更多的样本输入和输出。
答10:
保持自己快人一步,享受全网独家提供的一站式外包任务、远程工作、创意产品订阅服务–huntsbot.com
这里找到的答案都不正确。 stinkycheeseman asked 四舍五入,但你们都四舍五入了。
要四舍五入,请使用:
Math.ceil(num * 100)/100;
1.3549999999999998 将返回不正确的结果。应该是 1.35,但结果是 1.36。
大多数值将返回不正确的结果。试试看。
答11:
huntsbot.com汇聚了国内外优秀的初创产品创意,可按收入、分类等筛选,希望这些产品与实践经验能给您带来灵感。
这是一个简单的方法:
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);
}
这是一个简短的视频教程 how to round to 2 decimal in js 使用内置的 toFixed(N) 方法不是更容易吗?
原文链接:https://www.huntsbot.com/qa/w7EQ/how-to-round-to-at-most-2-decimal-places-if-necessary?lang=zh_CN&from=csdn
huntsbot.com聚合了超过10+全球外包任务平台的外包需求,寻找外包任务与机会变的简单与高效。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
