如何才能使下溢导致上溢?

5

我正在阅读英特尔手册 (Intel® 64 和 IA-32 架构软件开发人员手册 *2016),对于关于需要下溢异常的这则摘录,我想确认自己的理解是否正确:

检测和处理下溢能够防止一个非常小的结果通过计算传播,并在后续时刻造成其他异常(例如除法时的溢出)生成。

-第4.9.1.5节

那么我的问题是,这种情况会是什么样子呢?可以有可能的伪代码演算吗?

veryVerySmallNumber = SmallestFloatpossible -1
veryVeryLargeNumber = BigBigFloat
answer = veryVerySmallNumber / veryVeryLargeNumber

我看到处理器可以有两种处理方式,但我更关心的是如何才会导致下溢出现上溢。对于这些情况的处理一般精神的任何澄清也将不胜感激。


4
严格来说,“下溢”是一个纯粹的浮点数概念。你的变量类型是什么,它们与“下溢”有什么关系?你认为 INT_MIN - 1 的值是多少? - AnT stands with Russia
2
除了被零除外,还有一种整数除法会溢出(在二进制补码中):INT_MIN/-1。数学结果是 -INT_MIN,这个结果无法表示。 - rici
1
在C语言中,最小的浮点数是FLT_TRUE_MIN(最小的次正规数)。它非常接近于零。FLT_TRUE_MIN - 1-1.0f,由于舍入误差而丢失了+FLT_TRUE_MIN。即使是FLT_MIN(最小的规格化数),仍然比-1.0nextafterf(-1.0, INFINITY)之间的差异要小得多。(IEEE 32位浮点数的FLT_MIN = 1.175494e-38FLT_EPSILON = 1.192093e-7。) - Peter Cordes
1
简而言之:你可能只需要使用 veryVerySmallNumber = FLT_MIN。任何大于1.0的分子都会导致以 FLT_MIN 作为分母的溢出到+Inf。甚至 1.0 / FLT_TRUE_MIN 也会溢出,因为IEEE FP不会进行渐进式溢出。 - Peter Cordes
1
@指令指针:是的,请看我在Eric对这个问题的回答中的评论。他们选择将那些exp=max,significand!=0的编码用于NaN负载,而不是像Posit/unum一样逐渐溢出。 - Peter Cordes
显示剩余2条评论
2个回答

10

英特尔关于下溢的参考指的是浮点运算。

这个程序:

#include <stdio.h>

int main(void)
{
    float x = 0x1.23456p-70f;   //  Set x to a number around 2**-70.
    float y = x*x;
    float z = 1/y;
    printf("x = %g.\n", x);
    printf("y = %g.\n", y);
    printf("z = %g.\n", z);
}   

在使用IEEE-754二进制32位浮点数打印float的通用C实现中:

x = 9.63735e-22。
y = 9.29061e-43。
z = inf。

x*x中,计算会下溢 - 结果处于次正常范围,在此范围内,float格式无法以完全精度表示它(特别地,其中某些值在将其舍入以适合格式时丢失)。

随后,由于数字非常小,尝试对其进行倒数运算会产生无限大的结果 - 结果超出有限数字的float范围,因此会产生无穷大。此操作被称为溢出。

Intel硬件提供了一种检测下溢的方法:如果未掩码FP异常,则下溢异常实际上会触发(例如,在Linux/Unix上,操作系统会传递SIGFPE浮点异常)。或者像通常一样屏蔽FP异常,它只会在MXCSR中设置一个粘性标志位,记录自上次清零异常状态标志以来发生的下溢异常。溢出、不精确(非零舍入误差)、无效(NaN结果)等还有其他异常标志。请参见MXCSR位的表格,或查看Intel x86手册。类似地,对于遗留的x87,还有单独的屏蔽异常记录标志。

程序可以利用这一点,在x*x中检测下溢,并执行任何操作以避免在以后的操作中完全失去跟踪值。


2
这是IEEE Float具有渐进下溢(次正常数)的很好的例子,但非渐进溢出到Inf(因为指数= max的所有有效数字模式都用于NaN负载,而不是更多的指数位或其他内容)。Posit / unum是一种有趣的替代实数格式,它也可以进行渐进性溢出,而没有固定宽度的指数字段。(但完全没有inf / nan。)https://posithub.org/about和http://www.johngustafson.net/pdfs/BeatingFloatingPoint.pdf。如果我理解正确,每个Posit都有一个可表示的倒数,并且精度在+-1.0周围对称,与浮点数在0.0周围不同。 - Peter Cordes
哇,现在我想更多地了解Unum/Posit,这篇论文看起来是一个不错的开始,16页对我来说现在不会是一个坏的副业。也许渐进式下溢与溢出的原因是有道理的,因为从正常浮点数到次正常浮点数的转换中,正常浮点数中的隐含1变成了次正常浮点数中的隐含0。我会阅读这篇论文,并且肯定很快会发布一个相关问题 :) - Robert Houghton

3

我喜欢Eric的答案,但想发布一些关于通过MXCSR寄存器观察和响应下溢和上溢标志的额外信息。

首先,MXCSR寄存器是一个附加的控制寄存器,可用于SSE指令来控制和检查异常的状态。该寄存器是32位的,在SSE3中,只有0-15位被定义(使用CPUID指令查看处理器允许的功能)。

以下是另一种查看MXCSR寄存器每个位所表示意义的方式:

+----------+----------------------+------------------------+
| 助记符   | 位的位置            | 描述                   |
+----------+----------------------+------------------------+
| FZ       | 第15位               | 舍入为零              |
| R+       | 第14位               | 舍入正数              |
| R-       | 第13位               | 舍入负数              |
| RZ       | 第13和14位           | 舍入为零              |
| RN       | 第13和14位都为0      | 舍入最近              |
| PM       | 第12位               | 精度屏蔽              |
| UM       | 第11位               | 下溢屏蔽              |
| OM       | 第10位               | 上溢屏蔽              |
| ZM       | 第9位                | 零除屏蔽              |
| DM       | 第8位                | 非规格化屏蔽          |
| IM       | 第7位                | 无效操作屏蔽          |
| DAZ      | 第6位                | 非规格化数转零         |
| PE       | 第5位                | 精度标志              |
| UE       | 第4位                | 下溢标志              |
| OE       | 第3位                | 上溢标志              |
| ZE       | 第2位                | 零除标志              |
| DE       | 第1位                | 非规格化标志          |
| IE       | 第0位                | 无效操作标志          |
+----------+----------------------+------------------------+

我真的很喜欢我在http://softpixel.com/~cwright/programming/simd/sse.php上找到的这个指令速查表。

FZ模式会导致所有下溢操作直接变为零。这可以节省一些处理时间,但会损失精度。
R+,R-,RN和RZ舍入模式决定如何生成最低位。通常使用RN。
PM,UM,MM,ZM,DM和IM是掩码,告诉处理器忽略发生的异常,如果有的话。这可以使程序不必处理问题,但可能导致无效结果。
DAZ告诉CPU强制将所有非规格化数转换为零。规格化数是一种由于指数范围有限而FPU无法重新规范化的数字。它们与普通数字一样,但处理起来需要更长时间。注意,并非所有处理器都支持DAZ。
PE, UE, ME, ZE, DE和IE是异常标志位,如果发生异常则被设置,并且不会被取消掩码。程序可以检查这些标志位以查看是否发生了有趣的事情。这些位是"粘着的",这意味着一旦它们被设置,它们会永久保持设置状态,直到程序将其清除。这意味着指示的异常可能已经在几次操作之前发生了,但没有人费心去清除它。

现在的挑战/乐趣将是利用这些指令和它们返回的值的信息,观察何时下溢导致上溢,尽管这并不常见,但很有趣。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接