我想知道在特定条件下是否可以在不诉诸任意精度数据类型的情况下消除浮点错误。
问题是常见的。语言是 Ruby,但它适用于任何语言:
f = 1829.82
=> 1829.82
f / 12.0
=> 152.485
(f / 12.0).round(2)
=> 152.48
为什么不是 152.49?因为由于浮点数的精度有限:
format("%18.14f", f)
=> "1829.81999999999994"
format("%18.14f", f / 12.0)
=> "152.48499999999999"
所以四舍五入是正确的。现在我的问题是:有没有办法得到我想要的答案,考虑到以下情况:使用 float 执行的操作(数量)有严格的限制,所需的精度限制为小数点后两位(最多 8 位)总共)和少量剩余的“错误”四舍五入的答案是可以接受的吗?
这种情况下,用户可能会输入有效的 Ruby 字符串,例如:
"foo / 12.0"
其中 foo 是执行字符串的上下文中提供的数字,而“12.0”是用户输入的内容。想象一个带有一些免费公式字段的电子表格。字符串被简单地评估为 Ruby,因此 12.0 变成了 Float。我可以使用 ruby_parser + ruby2ruby gems 构建一个解析树,将数据类型修改为 Bignum、Rational、Flt 库中的某些内容、十进制浮点表示或其他什么,但这很棘手,因为实际的字符串可能会变成有点复杂,所以我不想走这条路。如果没有其他可能的话,我会走那条路,但这个问题专门是为了看看我是否可以避免这条路。因此,12.0 的数据类型是严格的 Float,结果也是严格的 Float,我唯一能做的就是解释代码片段的最终答案,并尝试“纠正”它,如果它以“错误”的方式舍入。
用户所做的唯一计算涉及精度为两位小数的数字(总共最多 8 位)。对于“简单”,我的意思是浮点错误没有机会累积:我可以将其中两个数字相加,然后将其除以一个整数,但随后计算完成,结果被四舍五入并存储,以及任何后续计算基于该四舍五入的数字。通常只会涉及一个浮点误差,但我认为如果两个浮点误差可以累加,问题不会显着改变,尽管根据定义残余误差率可能会更大。
首先想到的可能是先四舍五入到小数点后三位,然后四舍五入到小数点后两位。但是,这是行不通的。这会导致
152.48499999999999 => 152.485 => 152.49
but also
152.4846 => 152.485 => 152.49
这不是你想要的。
接下来我想到的是向浮点数添加尽可能小的增量(正如人们指出的那样,这取决于所考虑的浮点值)if这将其推过 0.5 边界。我主要想知道这可能会导致“误报”的频率:添加最小增量的数字,即使它刚好低于 0.5 边界的事实并不是由于浮点错误,而是因为这只是计算的结果?
第二个选择是:仅always将最小的增量添加到数字中,因为无论如何 0.5 区域是唯一重要的区域。
编辑:
正如 cdiggins 所建议的那样,我只是重写了这个问题,将我的部分答案纳入评论中。我向 Ira Baxter 奖励了他积极参与讨论,尽管我还不相信他是对的:Mark Ransom 和 Emilio M Bumachar 似乎支持我的想法,即在实践中可能会进行更正大多数情况下都会产生“正确”的结果。
我仍然需要进行实验来看看结果正确的频率,我完全打算这么做,但我能花在这上面的时间有些有限,所以我还没有抽出时间来做这件事。这个实验并不简单。