我的问题的简要版本是:
什么被认为是“最佳实践”,用于决定何时可以认为浮点数
x
和Math.round(x)
相等,从而导致浮点运算失去精度?
啰嗦的版本是:
我经常需要决定给定的浮点值x
是否应该被视为“整数”,或者更迂腐,应该“被视为整数的浮点表示”。
(例如,如果 n 是整数,则为数学表达式
登录<子> 10 子>(10 名词 )
是表示相同整数 n 的复杂方式。这是一种思维,可以说类似浮点计算的结果可以被视为“整数的表示”。)
只要Math.round(x) == x
评估为true
,决策就很容易:在这种情况下,我们可以说x
确实是(整数的浮点表示)。
但是当评估为Math.round(x) == x
时,测试false
尚无定论。例如,
function log10(x) { return Math.log(x)/Math.LN10; }
// -> function()
x = log10(Math.pow(10, -4))
// -> -3.999999999999999
Math.round(x) == x
// -> false
编辑:我经常看到的一个“解决方案”是选择一些任意容差,例如ε = 1e-6
,并测试Math.abs(Math.round(x) - x) < ε
。我认为这样的解决方案会产生比我认为更可接受的误报。
答案 0 :(得分:5)
正如您在示例中看到的那样x
实际上根本不是整数。这是由于计算中较早出现的舍入错误,因此您实际上不知道x
是否被定义为几乎是圆整数或圆整数,而是由于舍入错误引起的。 / p>
如果你想知道一个或另一个是什么数字,你需要使用你自己建议的限制条件,或者使用足够高的数据,以确保你的数字首先不会出现问题。最后一种方法并不适用于所有情况。
还有可能象征性地跟踪所有数学运算,即将1/3
存储为1/3
而不是0.3333
,并根据需要评估它们,取消可以像您一样取消的因素在手动评估表达式时,这几乎在所有情况下都是完全矫枉过正的。更不用说这样一个系统会有多复杂。如果这是所需的解决方案,您可以与MatLab或Mathematica或其他东西进行交互来处理评估,,除非您在浏览器中运行它,对于浏览器来说可能有些难度尝试使用WolframAlpha API (为什么我第一次没想到这个?)。
尽管如此;如果您可以通过选择ε
来解决此问题,那么您将获得满意的结果,这可能是最好的方法。如果静态ε
没有剪切它,您可以尝试根据手头数字之前的计算类型动态选择它。即相乘的数字往往比分割的数字产生更少的分数部分,依此类推。如果数字不受加号,减号和乘法(不涉及分数)的影响,您可以知道它最多可以有多少小数位,因此可以选择合理的ε
。
答案 1 :(得分:1)
这是一个有趣的,所以我不得不考虑一下。
这是一个单线解决方案,虽然它涉及数字和字符串类型之间的转换,所以我不知道它是多么优化。但它比仅仅选择最小阈值并检查数字是否在该限制范围内要准确得多。
JavaScript数字是双精度64位格式,其精度约为16位十进制数。这是总数,而不仅仅是小数点右边的位数。
JavaScript编号还有一个toPrecision()方法,可以将它们转换为字符串,舍入到给定的精度(总位数,非常适合您使用)。以下内容将任意数字舍入到最接近的15位精度,然后将其转换回浮点数。
function roundToPrecision(number, precision) {
return parseFloat(number.toPrecision(precision));
}
x = roundToPrecision(x, 15);
那么你的例子实际上是一个整数:-4。
编辑:经过一番思考后,这会更快:
var integerDigits = (""+parseInt(Math.abs(x))).length,
threshold = 1e-16 * Math.pow(10, integerDigits);
Math.abs(Math.round(x) - x) < threshold
答案 2 :(得分:1)
假设x
非零,我认为您应该考虑比率Math.abs(x-Math.round(x))/x
。这涉及浮点类型每个存储固定数量的有效位,而不是小数点后固定位数的事实。
接下来,您需要确定计算的典型舍入误差。如果x
是简单计算的结果,那可能很容易。如果没有,请考虑从您知道确切答案的测试用例中收集一些统计信息。理想情况下,您会发现x
整数的最大值与x
的最小值之间存在显着差异,不应将其视为整数。如果是这样,请选择该范围内的epsilon。