为什么要专门使用整数数学来实现 lround?

Why implement lround specifically using integer math?

我注意到 C++ standard library has separate functions 用于 roundlround 而不是让你使用 long(round(x)) 用于后者。

查看 implementation in glibc,我发现确实,对于使用 IEEE754 浮点的平台,returns 整数版本将直接操作浮点表示中的位,而不是使用浮点运算进行舍入(例如添加 ±0.5)。

当您希望结果为整数类型时,使用不同的实现有什么好处?这应该更快,还是更准确?如果在底层表示上使用整数数学更好,为什么不总是这样做,即使返回结果为 double?

一个原因是添加 .5 是不够的。假设您添加 .5,然后截断为一个整数。 (如何?有相关说明吗?还是你在做更多的工作?)如果 x 是 ½−2−54(小于 ½ 的最大可表示值),添加 .5 得到 1,因为数学和 1−2−54 恰好在最近的两个可表示值 1−2−53[=30 之间的一半=] 和 1,以及常见的默认舍入模式,round-to-nearest-ties-to-even,将其舍入为 1。但是 lround(x) 的正确结果是 0。

而且,当然,lround 被指定为从零开始四舍五入,而不管当前的四舍五入模式如何。您可以设置舍入模式,做一些运算,然后恢复舍入模式,但是这样做有问题。

一个是更改舍入模式通常是一项耗时的操作。舍入模式是影响大多数浮点指令的全局状态。因此处理器必须确保所有未决指令以先前模式完成,更改全局状态,并确保所有后续指令在该更改后启动。

如果幸运的话,您可能拥有一个具有按指令舍入模式或类似模式的处理器,然后您可以使用任何您喜欢的舍入模式而不会造成时间损失。惠普有一些这样的处理器。然而,“从零舍入”是一种不常见的模式。大多数处理器都具有舍入到最近的偶数、向零舍入、向下舍入(向 −∞)和向上舍入(向 +∞),并且舍入到奇数因其避免的价值而变得流行双舍入错误。但是远离零的情况很少见。

另一个原因是执行浮点指令会改变浮点状态标志并可能产生陷阱,但希望库例程表现为单个操作。例如,如果我们添加 .5 并发生舍入,则会引发不精确标志,因为与 .5 的浮点加法产生的结果与数学求和不同。但是对于 lround 的用户来说,不会出现不准确的情况; lround 被定义为 return 一个四舍五入为整数的值,它总是这样做——在 long 范围内,它永远不会 return 计算出的结果与其理想的数学结果不同定义。因此,如果 lround(x) 提出了不精确的标志,那将是不正确的行为。为了避免这种情况,使用浮点指令的实现必须保存当前的浮点标志,完成它的工作,并在 returning.

之前恢复标志。