【发布时间】:2021-03-14 06:57:39
【问题描述】:
#include <tgmath.h>
#include <iostream>
int main(int argc, char** argv) {
#define NUM1 -0.031679909079365576
#define NUM2 -0.11491794452567111
std::cout << "double precision :"<< std::endl;
typedef std::numeric_limits< double > dbl;
std::cout.precision(dbl::max_digits10);
std::cout << std::hypot((double)NUM1, (double)NUM2);
std::cout << " VS sqrt :" << sqrt((double )NUM1*(double )NUM1
+ (double )NUM2*(double )NUM2) << std::endl;
std::cout << "long double precision :"<< std::endl;
typedef std::numeric_limits<long double > ldbl;
std::cout.precision(ldbl::max_digits10);
std::cout << std::hypot((long double)NUM1, (long double)NUM2);
std::cout << " VS sqrt :" << sqrt((long double )NUM1*(long double )NUM1 + (long double )NUM2*(long double )NUM2);
}
在 Linux 下返回(Ubuntu 18.04 clang 或 gcc,无论优化,glic 2.25):
双精度: 0.1192046585217293 VS sqrt:0.11920465852172932
long 双精度: 0.119204658521729311251 VS sqrt:0.119204658521729311251
根据 cppreference :
实现通常保证小于 1 ulp(最后一个单位)的精度:GNU、BSD、Open64 std::hypot(x, y) 等价于 std::abs(std::complex(x,y)) POSIX 规定只有当两个参数都低于正常且正确的结果也低于正常时才会发生下溢(这禁止幼稚的实现)
所以,hypot((double)NUM1, (double)NUM2) 应该返回 0.11920465852172932,我想(作为天真的 sqrt 实现)。 在 Windows 上,使用 MSVC 64 位,就是这种情况。
为什么我们使用 glibc 会看到这种差异?如何解决这种不一致?
【问题讨论】:
-
std::abs(std::complex(x,y))不需要计算为sqrt(x*x + y*y),std::hypot(x,y)也不是。你隐含地假设它是。通常,计算将以不会溢出的方式完成,即使x*x或y*y的计算会溢出。计算方法的这种差异可以解释您所看到的实际上微不足道的差异。请记住(具有非常特殊属性的值除外)浮点值是近似值,错误往往会通过操作传播。 -
这些值在 IEEE754 双重表示中是相邻的。十六进制浮点数:
0x1.e84324de1b575p-4与0x1.e84324de1b576p-4。两个答案都与“确切”答案相差 long double 结果在double值之间)。 -
顺便说一句,您可能应该在 C++ 中包含
<cmath>,而不是<tgmath.h>。我不像 C 那样熟悉 C++ 规范,但我不希望<tgmath.h>定义的宏可以被 C++ 的std::sqrt模板访问。
标签: c++ floating-point sse glibc hypotenuse