我有一个向量,它包含一些相互分离的浮点值,并根据某些函数进行排序。例如,
double foo(double x)
{
return 199.1*x;
}
double x = 3000000.3157;
double y = x + DBL_EPSILON;
std::vector<double> s { y,y+10};
std::sort(s.begin(),s.end(),[](double x,double y) { return foo(x) < foo(y) ;} );
现在某人的密钥与s
中的密钥相近,例如x
。在lambda时代,所有人都有自己的搜索功能,比如
std::cout<<std::distance(s.begin(),std::lower_bound(s.begin(),s.end(),x,
[] (double x,double y) { return foo(x) < foo(y);}))<<std::endl;
std::cout<<std::distance(s.begin(),std::lower_bound(s.begin(),s.end(),x,
[] (double x,double y) { double f1 = foo(x);
double f2 = foo(y);
return f1 < f2;}))<<std::endl;
并获得不同的位置(相应的值非常不同)。
在查看用法时,看起来,它们与找到关键字k
r
,其中(理想情况下应为[0,1])附加到连续值x1
&amp; x2
使得函数f(x1,x2,r)
的返回值大约等于k
。 它们看起来都是相关的,与插值有关。我该如何实现它们?
注:
在下面的简短代码中
double f1 = foo(x);
double f2 = foo(y);
bool l = foo(x) < foo(y);
std::cout<<std::boolalpha<<(f1<f2)<< " "<<l<<" "<<(f1 == f2) << std::endl;
std::cout << std::boolalpha << (foo(x) < foo(y)) << " "<< (foo(y) < foo(x))
<< " "<<(foo(x) == foo(y) )<<std::endl;
std::cout << std::boolalpha << std::isless(foo(x) , foo(y))
<< " "<< std::isless(foo(y) , foo(x)) <<std::endl;
我在X86机器上以GCC输出
false true true
true true false
false false
虽然我的猜测是GCC在飞行中的精确度更高(80位),除非我强制它存储结果,导致l
&amp; (f1<f2)
(导致上述问题)。我也很想知道为什么foo(x) < foo(y)
和foo(y) < foo(x)
都说true
!
答案 0 :(得分:6)
这两个陈述没有任何结果,因为DBL_EPSILON
对于这些数字小于1ulp:
double x = 3000000.3157;
double y = x + DBL_EPSILON;
可以肯定的是,我打印了x
和y
的十六进制表示形式,并获得了以下内容:
4146E3602868DB8C
4146E3602868DB8C
当我通过几个不同版本的G ++(4.4.5和4.8.0)在问题的底部运行示例并且在(-O3)和关闭(无标志)时进行优化时,我得到以下输出:
false false true
false false true
0 0
我怀疑你所看到的行为恰恰是你假设的原因:你的编译器对中间结果有更高的精确度,并且正在逐渐渗透到这些比较中。
您使用的是哪种版本的编译器,应用程序中的其他代码是否会调整任何舍入模式?您正在使用哪些编译标志?
编辑1
通过使用优化 off 和32位模式重新编译,我能够重现您的行为。在该模式下,我看到编译器将foo
的结果留在浮点堆栈上:
_Z3food:
.LFB1053:
.cfi_startproc
pushl %ebp #
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp #,
.cfi_def_cfa_register 5
subl $8, %esp #,
movl 8(%ebp), %eax # x, tmp61
movl %eax, -8(%ebp) # tmp61, x
movl 12(%ebp), %eax # x, tmp62
movl %eax, -4(%ebp) # tmp62, x
fldl -8(%ebp) # x
fldl .LC0 #
fmulp %st, %st(1) #,
leave
这表明这是i386 ABI的怪癖。为了测试这个理论,我更仔细地研究了i386 ABI。在page 38 of this PDF(又名“内部页码”第3-12页“)中,我发现吸烟枪很可能:
%st(0)
浮点返回值出现在 浮点寄存器堆栈;这没有区别 浮点数表示单精度或双精度值 寄存器。如果函数没有返回浮点值, 那么这个寄存器必须是空的。 G之前该寄存器必须为空 进入一个功能。
接下来会说几段:
浮点返回值出现在Intel387的顶部 寄存器堆栈。然后调用者必须从中删除值 Intel387堆栈,即使它没有使用该值。两者都失败了 一方面履行其义务导致未定义的程序行为。该 标准调用序列不包括任何检测此类的方法 失败也不检测返回值类型不匹配。因此 用户必须正确声明所有功能。 没有区别 单精度,双精度值或扩展精度值的表示 浮点寄存器。
进一步搜索第3-27页(PDF第53页)和第3-28页(PDF第54页)会产生以下令人困惑的曲折。图3-30中的表格表明初始舍入模式为“53位(双精度)”,这就是进程初始化时的模式。
它继续在下一页上发出以下警告:
应小心更改初始浮点状态。在 特别是,许多浮点例程可能会产生未定义的 行为,如果精度控制设置为小于53位。该 _fpstart例程(参见第6章)将精度控制更改为64位并设置要询问的所有异常。这是默认状态 符合ANSI C标准和IEEE 754要求 浮点标准。
网上的couple reference表明Linux确实将x87设置为扩展精度(至少在32位ABI中)。
编辑2
看来扩展精度确实是罪魁祸首。我将以下代码添加到测试用例as suggested by this page:
void set_fpu (unsigned int mode)
{
asm ("fldcw %0" : : "m" (*&mode));
}
// ...
set_fpu(0x27F);
添加这些行后,测试用例将返回与64位ABI相同的值。
因此,假设您在Linux下编译32位程序,这似乎是您看到奇怪的比较和排序结果的原因。
如上所述,你可以重新运行你的排序和搜索代码,FPU设置为53位精度,看看是否能解决你在两个lambda表达式之间看到的差异?