对于这个问题,这可能是错误的 stackexchange 站点。math.SE、cs.SE、programmers.SE,当然还有 stackoverflow 都是可能的。我希望能接触到可能实际使用此功能的受众,以获得有关他们希望它如何工作的一些反馈。
C 的double nextafter(double x, double y)
函数从 x 沿 y 方向返回下一个可表示的值。(通常为 + 或 - 无穷大,但可以是任何值)。
我正在整理 GNU libc/libm 的实现以编译成更好的代码。(它目前将每个双精度数提取为两个 32 位整数。它非常笨重并且使用了很多分支,并且没有利用int64_t
.)。如果我要改变它,我可能会尽可能多地改进,但使用 FP compare-equal 会改变 DAZ 模式下的结果。
当 denormals-are-zero 和/或 flush-to-zero 处于活动状态时,它应该如何表现? 在 x86 上,有两个单独的标志会牺牲逐渐下溢来提高性能。默认情况下,两者均未设置。可以设置一个或两个(它们经常一起使用,但不一定是):
DAZ 是一个输入过滤器:当 FP 数学/比较指令读取其输入时,非规范化被认为是 +/-0.0。因此,两个非正规之间的比较发现它们是相等的。不过,算术很容易产生反规范的结果。
FTZ 是一个输出过滤器:非正规输入正常工作,对比较指令没有影响。但是,普通的数学指令不能生成非规范化。异常结果刷新为 +/-0.0。
DBL_MIN/2 + DBL_MIN/1.5
应该给出与没有 FTZ 相同的结果(例如,如果将这些输入非正规加载为常量)。
在这两种情况下(尤其是 DAZ),有人可能会争辩说,下一个可表示的值DBL_MIN
(最小的正常数)是+0.0
,而下一个数字是+0.0
或。实现这一点需要检查 FTZ/DAZ 标志。-0.0
-DBL_MIN
有人可能会争辩说,在 DAZ 模式下,两个不同的非正规应该被视为相等,因为它们确实比较相等。因此,根据POSIX和C11 标准(7.12.11.3 pg 256 和 Annex F.10.8.3 pg 529)的要求,y
在x
equals时返回是正确的。(本规范的动机是返回,反之亦然。)y
nextafter(0.0, -0.0)
-0.0
第二种行为允许非常快速和紧凑的实现(没有采用的分支和 3 个未采用的分支污染分支预测器,并且英特尔 Haswell 上约 9 个时钟周期延迟(作为比较,FP 乘法需要 5 个周期)对于常见情况(x!=y, and x!=0, 结果不会上溢(infinity) 或下溢(denormal))。与glibc 的当前版本相比,不到总代码大小的一半。
C11 标准说:
“即使可能发生下溢或上溢,返回值与当前舍入方向模式无关。”
但 DAZ 不是舍入方向模式。
该boost::math::ulp
函数的文档观察到“我们的经验是,std::nextafter
经常会根据哪些优化和硬件标志生效而中断”。
glibc 的功能需要与其他实现的nextafter
一致性,但与 glibc 现有行为的一致性可能优先。当前实现完全不受任何 FP 设置的影响,因为它仅对 IEEE 浮点位模式使用整数比较。nexttoward
与, 和nextafterf(float,float)
/的一致性nextafterl(long double, long double)
也很重要。我还没有研究过长的双重版本。整数比较在那里比较笨拙,因为int64_t
不能容纳 80 位。
使用仅整数比较实现起来稍微慢一些/体积更大(使用 64 位整数比较以及 x 和 y 的符号,而不是仅使用 FP 比较和 x 的符号)。IEEE 浮点数设计巧妙,因此它们的位可以作为符号大小整数进行比较(因此可以作为 2 的补码整数进行比较,并检查是否为负数)。
nextafter
在其他系统(OS X、MSVC++ 等)上表现如何?如果人们有兴趣测试它,我可能会编写一个小测试程序。
你希望 nextafter
表现如何?(nextafter
除了 DAZ/FTZ 之外,几乎完全指定了,因此不同的功能可能会更好地满足除此之外的愿望。)
我错过了什么吗?
我知道当前的实现对于这种很少使用的功能来说已经“足够好”了。它很丑陋,但它很少使用,因此没有太大影响。(其实它是很少用的吗?有没有人见过用它很多的代码?)
这里的部分动机是我喜欢用汇编语言优化东西,FP数据上的整数操作很有趣,特别是在x86-64上,整数指令可以用于向量寄存器,也用于float
s和double
s(但不是80 位long double
,nextafterl
或 ) 的方向 arg nexttowards
。