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

问题描述

我注意到 roundlroundC++ standard library has separate functions 而不是让您将 long(round(x)) 用于后者。

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

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

解决方法

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

当然,无论当前的舍入模式如何,lround 都被指定为远离零舍入关系。你可以设置舍入模式,做一些算术,然后恢复舍入模式,但是这有问题。

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

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

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