我有以下虚拟测试脚本:
function test(){
var x = 0.1 * 0.2;
document.write(x);
}
test();
这将打印结果 0.020000000000000004
,而它应该只打印 0.02
(如果您使用计算器) . 据我所知,这是由于浮点乘法精度的误差 .
有没有人有一个很好的解决方案,以便在这种情况下我得到正确的结果 0.02
?我知道有像 toFixed
这样的函数或舍入将是另一种可能性,但我想真正打印整个数字而不进行任何切割和舍入 . 只是想知道你们其中一个人是否有一些漂亮,优雅的解决方案 .
当然,否则我将大约10位左右 .
30 回答
你只需要决定你实际想要多少个十进制数字 - 不能吃蛋糕也吃掉它:-)
数字误差随着每一次进一步的操作而累积,如果你不早点切断,它就会增长 . 呈现干净的结果的数值库在每一步都简单地切断了最后两位数字,数字协处理器也具有“正常”和“完整”长度,原因相同 . Cuf-offs对于处理器来说很便宜,但对于脚本来说非常昂贵(乘法和分割以及使用pov(...)) . 好的数学库会提供楼层(x,n)来为你做截止 .
所以至少你应该使用pov(10,n)创建全局变量/常量 - 这意味着你决定了你需要的精度:-)那么:
你也可以继续做数学并且只在最后切断 - 假设你只是显示而不是if-s结果 . 如果你能做到这一点,那么.toFixed(...)可能会更有效率 .
如果你正在做if-s /比较并且不想削减那么你还需要一个小的常量,通常称为eps,它比最大预期误差小一位小数 . 假设您的截止时间是最后两位小数 - 那么您的eps在最后一位(第三位最低位)的第3位有1位,你可以用它来比较结果是否在预期的eps范围内(0.02 -eps <0.1) * 0.2 <0.02 eps) .
使用以下功能输出:
注意输出
toFixedCurrency(x)
.你是对的,原因是浮点数的精度有限 . 将有理数存储为两个整数的除法,在大多数情况下,您将能够存储数字而不会出现任何精度损失 . 在打印时,您可能希望将结果显示为分数 . 通过我提出的表示,它变得微不足道 .
当然,这对无理数不会有太大帮助 . 但是您可能希望以导致最小问题的方式优化计算(例如,检测像
sqrt(3)^2)
这样的情况 .phpjs.org的round()函数效果很好:http://phpjs.org/functions/round
要避免这种情况,您应该使用整数值而不是浮点值 . 因此,当您希望2个位置精度与值* 100一起使用时,对于3个位置使用1000.当显示时,使用格式化程序放入分隔符 .
许多系统都以这种方式省略了小数 . 这就是为什么许多系统使用美分(作为整数)而不是美元/欧元(作为浮点数)的原因 .
此函数将根据两个浮点数的乘法确定所需的精度,并以适当的精度返回结果 . 优雅虽然不是 .
我喜欢Pedro Ladaria的解决方案并使用类似的东西 .
与Pedros解决方案不同,这将重复0.999 ...重复,并且在最低有效数字上精确到正负1 .
注意:处理32位或64位浮点数时,应使用toPrecision(7)和toPrecision(15)以获得最佳结果 . 有关原因,请参阅this question .
我对编程并不擅长,但对这个主题真的很感兴趣,所以我试着理解如何在不使用任何库或脚本的情况下解决这个问题
我在便笺簿上写了这个
你可能需要重构这段代码,因为我不擅长编程:)
Problem
浮点不能精确存储所有十进制值 . 因此,在使用浮点格式时,输入值将始终存在舍入误差 . 输入上的错误当然会导致输出错误 . 在离散函数或运算符的情况下,函数或运算符离散的点周围的输出可能存在很大差异 .
Input and output for floating point values
因此,在使用浮点变量时,您应该始终注意这一点 . 并且在显示之前,您应该始终对浮点数计算所需的任何输出进行格式化/调节 .
当仅使用连续函数和运算符时,通常会舍入到所需的精度(不截断) . 用于将浮点数转换为字符串的标准格式设置功能通常会为您执行此操作 .
因为舍入会增加一个可能导致错误的错误如果总误差超过所需精度的一半,则应根据输入的预期精度和所需的输出精度来校正输出 . 你应该
舍入到预期精度的输入,或确保不能以更高的精度输入值 .
在舍入/格式化之前向输出添加一个小值,该值小于或等于所需精度的1/4,并且大于输入和计算期间的舍入误差引起的最大预期误差 . 如果不可能,则使用的数据类型的精度组合不足以为计算提供所需的输出精度 .
这两件事通常都没有完成,在大多数情况下,由于没有这样做而导致的差异太小而不适合大多数用户,但我已经有一个项目,如果没有这些更正,用户就不会接受输出 .
Discrete functions or operators (like modula)
当涉及离散运算符或函数时,可能需要额外的更正以确保输出符合预期 . 在舍入之前舍入并添加小的修正不能解决问题 .
可能需要在应用离散函数或运算符后立即对中间计算结果进行特殊检查/校正 . 对于特定情况(modula运算符),请参阅我的问题答案:Why does modulus operator return fractional number in javascript?
Better avoid having the problem
通过使用数据类型(整数或定点格式)进行这样的计算来避免这些问题通常更有效,这样可以存储预期的输入而不会出现舍入错误 . 例如,您不应该使用浮点值进行财务计算 .
试试我的辣椒算术库,你可以看到here . 如果你想要更高版本,我可以给你一个 .
请注意,对于通用用途,此行为可能是可接受的 .
在比较这些浮点值以确定适当的操作时会出现问题 .
随着ES6的出现,定义了一个新常量
Number.EPSILON
来确定可接受的误差范围:所以不要像这样进行比较
你可以定义一个自定义比较函数,如下所示:
资料来源:http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon
我发现BigNumber.js满足了我的需求 .
它很好documentation,作者非常勤奋地回应反馈 .
该作者有2个其他类似的图书馆:
Big.js
和Decimal.js
这是使用BigNumber的一些代码:
令人惊讶的是,此功能尚未发布,尽管其他功能也有类似的变化 . 它来自Math.round()的MDN Web文档 . 它简洁,允许不同的精度 .
console.log(precisionRound(1234.5678,1)); //预期产量:1234.6
console.log(precisionRound(1234.5678,-1)); //预期产量:1230
使用数字(1.234443).toFixed(2);它将打印1.23
不优雅,但做的工作(删除尾随零)
在添加两个浮点值时,它永远不会给出精确的值,因此我们需要将其固定为某个数字,以帮助我们进行比较 .
console.log((parseFloat(0.1)parseFloat(0.2)) . toFixed(1)== parseFloat(0.3).toFixed(1));
0.6 * 3真棒!))对我来说这很好用:
非常非常简单))
如果要绕过此问题进行小型操作,可以使用
parseFloat()
和toFixed()
:这对我有用:
我有一个令人讨厌的舍入错误问题与mod 3.有时当我得到0我会得到.000 ... 01 . 这很容易处理,只需测试<= .01 . 但有时我会得到2.99999999999998 . 哎哟!
BigNumbers解决了这个问题,但又引入了另一个有点讽刺的问题 . 当试图将8.5加载到BigNumbers时,我被告知它确实是8.4999 ...并且有超过15位有效数字 . 这意味着BigNumbers无法接受它(我相信我提到这个问题有些讽刺) .
解决讽刺问题的简单方法:
对于数学上的倾向:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
推荐的方法是使用校正因子(乘以10的适当幂,以便算术在整数之间发生) . 例如,在
0.1 * 0.2
的情况下,修正系数为10
,您正在执行计算:一个(非常快)的解决方案看起来像:
在这种情况下:
我绝对建议使用像SinfulJS这样的测试库
你只是进行乘法运算吗?如果是这样,那么你可以利用十进制算术的秘密 . 那是
NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. 也就是说,如果我们有0.123 * 0.12
那么我们知道将有5个小数位,因为0.123
有3个小数位,0.12
有2个 . 因此,如果JavaScript给我们一个像0.014760000002
这样的数字,我们可以安全地舍入到小数点后第五位而不用担心会失去精度 .---如---
来自Floating-Point Guide:
请注意,第一点仅适用于您确实需要特定的精确十进制行为 . 大多数人只是感到恼火,因为他们的程序如果以1/3发生,甚至会眨眼同样的错误 .
如果第一点确实适用于您,请使用BigDecimal for JavaScript,这根本不是优雅的,但实际上解决了问题而不是提供不完美的解决方法 .
您正在寻找JavaScript的
sprintf
实现,以便您可以以您期望的格式写出其中包含小错误的浮点数(因为它们以二进制格式存储) .试试javascript-sprintf,你会这样称呼它:
将您的号码打印为带有两位小数的浮点数 .
如果您不想仅包含浮动点舍入到给定精度的更多文件,您也可以使用Number.toFixed()进行显示 .
您获得的结果在不同语言,处理器和操作系统的浮点实现中是正确且相当一致的 - 唯一改变的是浮点实际上是双倍(或更高)时的不准确程度 .
二进制浮点数为0.1,就像十进制的1/3(即0.3333333333333 ......永远),没有准确的方法来处理它 .
如果你正在处理浮点数总是期望小的舍入误差,所以你也总是必须将显示的结果舍入到合理的位置 . 作为回报,您将获得非常快速和强大的算术,因为所有计算都在处理器的本机二进制中 .
大多数情况下,解决方案不是切换到定点算术,主要是因为它更慢,99%的时间你不需要精度 . 如果你正在处理需要那种准确度的东西(例如金融交易),Javascript可能不是最好的工具(因为你想强制执行定点类型,静态语言可能更好) ) .
你正在寻找优雅的解决方案然后我担心这就是它:浮动很快但是有小的舍入错误 - 在显示结果时总是圆润到合理的东西 .
看看Fixed-point arithmetic . 如果您想要操作的数字范围很小(例如货币),它可能会解决您的问题 . 我会把它四舍五入到几个十进制值,这是最简单的解决方案 .
使用
除非使用任意精度算术类型或基于十进制的浮点类型,否则您可以't represent most decimal fractions exactly with binary floating point types (which is what ECMAScript uses to represent floating point values). So there isn' t优雅的解决方案 . 例如,the Calculator app that ships with Windows now uses arbitrary precision arithmetic to solve this problem .