在 C++ 中使用 floor 函数的舍入误差
Rounding error using the floor function in C++
有人问我以下代码的输出是什么:
floor((0.7+0.6)*10);
它returns 12.
我知道浮点表示法不允许以无限精度表示所有数字,我应该预料到会有一些差异。
我的问题是:
我怎么知道这段代码returns是12,不是13?为什么 (0.7+0.6)*10 比 13 少,而不是 多?
floor 功能什么时候可以正常工作,什么时候可以正常工作?
注意:我不是在问浮动表示形式如何或为什么输出不完全是 13。我想知道我应该如何推断 (0.7+0.6)*10 有点 少于 少于 13.
How should I know that this piece of code returns 12, not 13?
因为这取决于所涉及的数字,通过尝试。
Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
嗯,因为这是计算的结果。
When can I expect the floor function to work incorrectly and when it works correctly for sure?
肯定正确:仅在 2 的幂的倍数上,当且仅当您的浮点数用二进制表示。
要真正消除所有的困惑:
不计算就不知道结果;这取决于所涉及的 machine/algorithmics 和数量。
非常简短的回答:你不能。这取决于平台和在该平台上使用的 float iso。
像 IEEE floating point standard 这样的标准试图使浮点计算至少有一点预测性
通过定义规则应该如何实现加法和舍入等操作。
要知道结果,您需要计算表达式
根据标准规则。那么你可以确定,
它在每台执行标准的机器上给出相同的结果。
- How should I know that this piece of code returns 12, not 13?
你应该知道它 可以 并且 可能 是 12 或 13。你可以通过测试给定的 cpu.
一般情况下,您无法知道该值是多少,因为 C++ 标准没有指定浮点数的表示形式。如果您知道给定架构的格式(比方说 IEEE 754),那么您可以手动执行计算,但该结果将仅适用于该特定表示。
Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
这是一个实现细节,对程序员来说不是有用的知识。您只需要知道它可能是任何一个。依靠它是其中之一的知识,会让你依赖于实现细节。
- When can I expect the floor function to work incorrectly and when it works correctly for sure?
它始终正常工作,这取决于指定的工作方式。
现在,谈谈您期望看到的价值。如果您知道您的数字非常接近整数,但由于表示错误可能会偏离一点点,您可以在 flooring 之前添加 0.5
。
double calculated_integer = (0.7+0.6)*10;
floor(calculated_integer + 0.5);
这样一来,你总是会得到预期的值,除非误差超过0.5
,这将是一个很大的误差。
如果您不知道结果应该是一个整数,那么您只需接受 floor
和 ceil
操作将计算的最大误差增加到 1.0
.
一般来说,你不能。根本问题在于,从文本表示到浮点值的转换通常没有尽可能准确地实现。这部分是动力,部分是因为获得最接近文本中表达的值的浮点值可能很昂贵,在某些情况下需要大整数计算。因此,转换通常与理想值相差几个 ULP(即低端位),您无法预测 先验 。因此,该代码将产生什么的问题是无法回答的。 应该产生什么的问题可能更容易处理,但它仍然是一个浪费时间的练习。
How should I know that this piece of code returns 12, not 13? Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
假设您的编译平台严格使用 IEEE 754 标准格式和操作。然后,将所有涉及的常数转换为二进制,保留 53 位有效数字,并应用 IEEE 754 中定义的基本运算,通过计算数学结果并在每一步舍入到 53 位有效二进制数字。任何阶段都不需要计算机参与,但使用 C99 的十六进制浮点格式输入和输出可以让您的生活更轻松。
When can I expect the floor function to work incorrectly and when it works correctly for sure?
floor()
对所有正参数都是精确的。它在您的示例中正常工作。让您感到惊讶的行为并非源于 floor
,也与 floor
无关。令人惊讶的行为始于 6/10 和 7/10 不能完全表示为二进制浮点值这一事实,并继续由于这些值具有长扩展、浮点运算 +
和 *
可以产生一个稍微四舍五入的结果 wrt 你可以从它们实际应用的参数中期望的数学结果。 floor()
是您的代码中唯一不涉及近似值的地方。
查看发生了什么的示例程序:
#include <stdio.h>
#include <math.h>
int main(void) {
printf("%a\n%a\n%a\n%a\n%a\n",
0.7,
0.6,
0.7 + 0.6,
(0.7+0.6)*10,
floor((0.7+0.6)*10));
}
结果:
0x1.6666666666666p-1
0x1.3333333333333p-1
0x1.4ccccccccccccp+0
0x1.9ffffffffffffp+3
0x1.8p+3
IEEE 754双精度实际上是针对二进制定义的,但为了简洁起见,有效数字以十六进制表示。 p
之后的指数表示 2 的幂。例如,最后两个结果都是 *23.
0x1.8p+3
是 12。下一个整数 13 是 0x1.ap+3
,但计算并未完全达到该值,因此 floor()
的行为是向下舍入到 12.
有人问我以下代码的输出是什么:
floor((0.7+0.6)*10);
它returns 12.
我知道浮点表示法不允许以无限精度表示所有数字,我应该预料到会有一些差异。
我的问题是:
我怎么知道这段代码returns是12,不是13?为什么 (0.7+0.6)*10 比 13 少,而不是 多?
floor 功能什么时候可以正常工作,什么时候可以正常工作?
注意:我不是在问浮动表示形式如何或为什么输出不完全是 13。我想知道我应该如何推断 (0.7+0.6)*10 有点 少于 少于 13.
How should I know that this piece of code returns 12, not 13?
因为这取决于所涉及的数字,通过尝试。
Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
嗯,因为这是计算的结果。
When can I expect the floor function to work incorrectly and when it works correctly for sure?
肯定正确:仅在 2 的幂的倍数上,当且仅当您的浮点数用二进制表示。
要真正消除所有的困惑:
不计算就不知道结果;这取决于所涉及的 machine/algorithmics 和数量。
非常简短的回答:你不能。这取决于平台和在该平台上使用的 float iso。
像 IEEE floating point standard 这样的标准试图使浮点计算至少有一点预测性 通过定义规则应该如何实现加法和舍入等操作。 要知道结果,您需要计算表达式 根据标准规则。那么你可以确定, 它在每台执行标准的机器上给出相同的结果。
- How should I know that this piece of code returns 12, not 13?
你应该知道它 可以 并且 可能 是 12 或 13。你可以通过测试给定的 cpu.
一般情况下,您无法知道该值是多少,因为 C++ 标准没有指定浮点数的表示形式。如果您知道给定架构的格式(比方说 IEEE 754),那么您可以手动执行计算,但该结果将仅适用于该特定表示。
Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
这是一个实现细节,对程序员来说不是有用的知识。您只需要知道它可能是任何一个。依靠它是其中之一的知识,会让你依赖于实现细节。
- When can I expect the floor function to work incorrectly and when it works correctly for sure?
它始终正常工作,这取决于指定的工作方式。
现在,谈谈您期望看到的价值。如果您知道您的数字非常接近整数,但由于表示错误可能会偏离一点点,您可以在 flooring 之前添加 0.5
。
double calculated_integer = (0.7+0.6)*10;
floor(calculated_integer + 0.5);
这样一来,你总是会得到预期的值,除非误差超过0.5
,这将是一个很大的误差。
如果您不知道结果应该是一个整数,那么您只需接受 floor
和 ceil
操作将计算的最大误差增加到 1.0
.
一般来说,你不能。根本问题在于,从文本表示到浮点值的转换通常没有尽可能准确地实现。这部分是动力,部分是因为获得最接近文本中表达的值的浮点值可能很昂贵,在某些情况下需要大整数计算。因此,转换通常与理想值相差几个 ULP(即低端位),您无法预测 先验 。因此,该代码将产生什么的问题是无法回答的。 应该产生什么的问题可能更容易处理,但它仍然是一个浪费时间的练习。
How should I know that this piece of code returns 12, not 13? Why is (0.7+0.6)*10 a bit less than 13, not a bit more?
假设您的编译平台严格使用 IEEE 754 标准格式和操作。然后,将所有涉及的常数转换为二进制,保留 53 位有效数字,并应用 IEEE 754 中定义的基本运算,通过计算数学结果并在每一步舍入到 53 位有效二进制数字。任何阶段都不需要计算机参与,但使用 C99 的十六进制浮点格式输入和输出可以让您的生活更轻松。
When can I expect the floor function to work incorrectly and when it works correctly for sure?
floor()
对所有正参数都是精确的。它在您的示例中正常工作。让您感到惊讶的行为并非源于 floor
,也与 floor
无关。令人惊讶的行为始于 6/10 和 7/10 不能完全表示为二进制浮点值这一事实,并继续由于这些值具有长扩展、浮点运算 +
和 *
可以产生一个稍微四舍五入的结果 wrt 你可以从它们实际应用的参数中期望的数学结果。 floor()
是您的代码中唯一不涉及近似值的地方。
查看发生了什么的示例程序:
#include <stdio.h>
#include <math.h>
int main(void) {
printf("%a\n%a\n%a\n%a\n%a\n",
0.7,
0.6,
0.7 + 0.6,
(0.7+0.6)*10,
floor((0.7+0.6)*10));
}
结果:
0x1.6666666666666p-1 0x1.3333333333333p-1 0x1.4ccccccccccccp+0 0x1.9ffffffffffffp+3 0x1.8p+3
IEEE 754双精度实际上是针对二进制定义的,但为了简洁起见,有效数字以十六进制表示。 p
之后的指数表示 2 的幂。例如,最后两个结果都是
0x1.8p+3
是 12。下一个整数 13 是 0x1.ap+3
,但计算并未完全达到该值,因此 floor()
的行为是向下舍入到 12.