如何测试浮点数是否是“在机器精度内”的整数

How to test whether a float is an integer "within machine precision"?

本文关键字:精度 机器 整数 是否是 何测试 测试 浮点数      更新时间:2023-09-26

我问题的简要版本是:

在决定浮点数xMath.round(x)何时被视为相等,从而允许浮点运算损失精度时,什么被认为是"最佳实践"?


冗长的版本是:

我经常需要决定一个给定的浮点值x是否应该"被视为整数",或者更学究地说,应该"被视为整数的浮点表示"。

(例如,如果 n 是整数,则数学表达式

日志 10

10N

是表示相同整数 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) < ε。 我认为这样的解决方案会产生比我认为可以接受的更多的误报。

正如您在示例中所看到的x实际上根本不是一个整数。这是由于计算早期的舍入误差,因此您实际上不知道x被定义为近整数还是被舍入误差弄得参差不齐下。

如果你想知道什么数字是一个或另一个,你需要使用你自己建议的限制aproach,或者使用足够高的pericion,这样你的数字就不会首先被锯齿状。最后一种方法并不适用于所有情况。

还可以符号地跟踪所有数学运算,即将1/3存储为1/3而不是0.3333并按需评估它们,从而抵消可以像手动计算表达式时一样取消的因素,但这在几乎所有情况下都是矫枉过正。更不用说这样的系统会有多复杂。如果这是所需的解决方案,您可能可以与 MatLab 或 Mathematica 或其他东西进行交互以处理评估,除非您在浏览器中运行它,对于尝试 WolframAlpha API 的浏览器来说可能有点困难(为什么我第一次没有想到这一点?)。

不过;如果你能通过选择ε来解决这个问题,你会得到一个令人满意的结果,这可能是最好的方法。如果静态ε没有削减它,您可以尝试根据之前对手头的数字进行的计算类型动态选择它。即,相乘的数字往往比除以的数字产生更少的分数部分,依此类推。如果该数字除了加号、减号和乘法(不涉及分数)之外没有其他任何东西,您也可以知道它最多可以有多少位小数,因此可以选择一个合理的ε

假设x不为零,我认为您应该查看比率Math.abs(x-Math.round(x))/x。这涉及这样一个事实,即浮点类型每个存储固定数量的有效位,而不是小数点后的固定位数。

接下来,您需要确定计算的典型舍入误差。如果x是简单计算的结果,那可能很容易。如果没有,请考虑从您知道确切答案的测试用例中收集一些统计信息。理想情况下,您会发现x整数的比率最大值与不应被视为整数的x的最小值之间存在显著差异。如果是这样,请在该范围内选择一个 epsilon。

这是一个有趣的问题,所以我不得不考虑一下。

这是一个单行解决方案,尽管它涉及在数字和字符串类型之间进行转换,所以我不知道它有多理想。但它比仅仅选择一个最小阈值并检查数字是否在该限制范围内要准确得多。

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

http://jsperf.com/number-precision-rounding