#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.1192046585217293 2
长双精度:0.119204658521729311251 VS sqrt:0.119204658521729311251
根据cppreference:
实现通常保证精度小于1 ulp(最后一个单位):GNU,BSD,Open64 std :: hypot(x,y)等同于std :: abs(std :: complex(x,y))POSIX指定仅当两个参数都为非正规且正确的结果也为非正规时才发生下溢(这禁止幼稚的实现)
因此,我假设(假设是朴素的sqrt实现)hypot((double)NUM1,(double)NUM2)应该返回0.11920465852172932。在Windows上,使用MSVC 64位,就是这种情况。
为什么使用glibc会看到这种差异?如何解决这种矛盾?
0x1.e84324de1b576p-4
(双精度)表示0x1.e84324de1b575p-4
(双精度)表示这些FP位模式仅在尾数的低位(也就是有效位)上有所不同,确切的结果就在它们之间。因此,它们每个的舍入误差都小于1 ulp,可以达到典型实现(包括glibc)的目标。
与IEEE-754“基本”操作(add / sub / mul / div / sqrt)不同,hypot
不需要“正确舍入”。这意味着<= 0.5 ulp错误。对于硬件不直接提供的操作而言,要实现这一目标将慢得多。(例如,至少使用几个绝对正确的位进行扩展精度计算,因此您可以四舍五入到最接近的两倍,就像硬件用于基本操作一样)
碰巧在这种情况下,幼稚的计算方法产生了正确的四舍五入结果,而glibc的“安全”实现std::hypot
(在加法之前对小数进行平方运算必须避免下溢)产生的结果大于0.5但误差小于1 ulp。
您没有指定是否在32位模式下使用MSVC。
大概32位模式将使用x87进行FP数学运算,从而提供了额外的临时精度。尽管某些MSVC版本的CRT代码在每次操作后将x87 FPU的内部精度设置为四舍五入为53位尾数,所以它的行为类似于使用SSE2的实际值double
,但指数范围更广。请参阅Bruce Dawson的博客文章。
因此,我不知道是否有除运气之外的其他原因,MSVC可以std::hypot
得出正确的结果。
注意,long double
MSVC中的类型与64位相同double
;C ++实现不会公开x86 / x86-64的80位硬件扩展精度类型。(64位尾数)。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句