c - 使用 C11 和 GNU11 编译器标志的机器 epsilon 计算不同
问题描述
在使用 Python 和 Julia 时,我可以使用一个巧妙的技巧来研究机器 epsilon 以获取特定的浮点表示。
例如,在 Julia 1.1.1 中:
julia> 7.0/3 - 4/3 - 1
2.220446049250313e-16
julia> 7.0f0/3f0 - 4f0/3f0 - 1f0
-1.1920929f-7
我目前正在学习 C 并编写了这个程序来尝试实现同样的目标:
#include <stdio.h>
int main(void)
{
float foo;
double bar;
foo = 7.0f/3.0f - 4.0f/3.0f - 1.0f;
bar = 7.0/3.0 - 4.0/3.0 - 1.0;
printf("\nM.E. for float: %e \n\n", foo);
printf("M.E. for double: %e \n\n", bar);
return 0;
}
奇怪的是,我得到的答案取决于我使用的是 C11 还是 GNU11 编译器标准。我的编译器是 GCC 5.3.0,在 Windows 7 上运行并通过 MinGW 安装。
简而言之,当我编译时:gcc -std=gnu11 -pedantic begin.c
我得到:
M.E. for float: -1.192093e-007
M.E. for double: 2.220446e-016
正如我所料,匹配 Python 和 Julia。但是当我编译时:gcc -std=c11 -pedantic begin.c
我得到:
M.E. for float: -1.084202e-019
M.E. for double: -1.084202e-019
这是出乎意料的。我认为这可能是 GNU 的特定功能,这就是我添加-pedantic
标志的原因。我一直在谷歌上搜索并发现:https ://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html但我仍然无法解释行为上的差异。
明确地说,我的问题是:为什么使用不同的标准结果会有所不同?
更新:相同的差异适用于 C99 和 GNU99 标准。
解决方案
在 C 中,获取float
or double
epsilon 的最佳方法是包含<float.h>
并使用FLT_MIN
or DBL_MIN
。
C 标准并未完全指定的值,7.0/3.0 - 4.0/3.0 - 1.0;
因为它允许实现以比标称类型更精确的精度评估浮点表达式。在某种程度上,这可以通过使用强制转换或赋值来处理。C 标准要求强制转换或赋值来“丢弃”多余的精度。这通常不是一个合适的解决方案,因为初始超精度和“丢弃”超精度的操作都可以进行舍入。这种双舍入可能会产生与完全使用标称精度进行计算不同的结果。
对问题中的代码使用强制转换解决方法会产生:
_Static_assert(FLT_RADIX == 2, "Floating-point radix must be two.");
float FloatEpsilon = (float) ((float) (7.f/3) - (float) (4.f/3)) - 1;
double DoubleEpsilon = (double) ((double) (7./3) - (double) (4./3)) - 1;
请注意,需要一个静态断言来确保浮点基数符合预期,以便该 kludge 运行。代码还应该包含解释这个坏主意的文档:
- 分数 ⅓ 的二进制表示以“01010101…”的无限序列结束。
- 当 4/3 或 7/3 的二进制四舍五入到固定精度时,就好像数字被截断并向下或向上舍入,这取决于截断后的下一个二进制数字是 0 还是 1。
- 鉴于我们假设浮点使用以二为底的基数,4/3 和 7/3 在连续的二进制中(4/3 在 [1, 2)中,7/3 在 [2, 4)中。因此,它们的截断点相隔一个位置。
- 因此,我们转换为二进制浮点格式,4/3 和 7/3 的不同之处在于后者比前者多 1 并且其有效数提前一位结束。对可能的截断点的检查表明,除了初始差异 1 之外,有效数字的差异在于 4/3 中低位的位置值,尽管差异可能在任一方向上。
- 根据 Sterbenz 引理,从 7/3 中减去 4/3 没有浮点误差,因此结果正好是 1 加上上述差值。
- 减去 1 会产生那个差值,即 4/3 的低位位置的值,除了它可能是正数或负数。
推荐阅读
- jquery - 悬停时未插入类名
- react-redux - 如何实现 React Redux 搜索和复选框过滤器
- cypress - 如何使用 cypress 检查元素内部文本的相等性?
- testing - 如何启动数据库容器以在 Azure Pipelines 中进行测试?
- python - Python - SyntaxError in --> if ((main_paned = gtk.HPaned())):
- javascript - 按特定列名获取值 - 应用脚本
- scala - Scala:将逆变类型作为隐式参数传递不选择最近的超类型?
- angular - Ionic 3 水平滚动卡片
- python - python字符串操作以匹配Windows防火墙语法
- docusignapi - 我可以使用 Docusign 中的 Powerforms 通过 URL 传递单选按钮值和复选框值吗?