在我们当前常用的主流桌面级处理器以及移动处理器中都可能会涉及到浮点数的运算。而在计算机中浮点数的表达本身是要遵循IEEE754规范的。在此规范中明确定义了哪些浮点数是规格化的,哪些是非规格化的,哪些属于正负无穷大,哪些又属于我们今天要讨论的非数NaN)。

在IEEE754标准中也提到了非数对于各个处理器实现而言可归为两类:一类是signaling NaN(简写为SNaN),即会触发异常浮点信号的非数,我们也称之为“发信号NaN”;还有一类则是quiet NaN(简写为QNaN),即不会触发浮点异常信号的非数,我们也称之为“静默NaN”。

在Intel开发者指南第1卷的4.2.2小节“Floating-Point Data Types”介绍了x86处理器中关于浮点数的表示方法,并且在该小节中的表4-3也列出了浮点数与非数的编码方式。

而ARMv8官方编程指南也是从A1.4.2小节起开始介绍从半精度浮点一直到定点数的表示方法。其中,A1.4.3小节则介绍单精度浮点的详细表示方法以及关于NaN的编码方法。

从上述文档来看,它们对SNaN与QNaN的编码方式是一样的,即无视符号位;指数部分为全1;尾数部分中,如果最高位为0,且尾数不为全0,则该浮点数为SNaN,而如果尾数最高位为1,则该浮点数为QNaN。

Intel开发者指南第1卷的4.8.3小节描述了实数与非数的的编码方式。其中,4.8.3.4到4.8.3.7小节详细描述了SNaN与QNaN在x86处理器中的行为以及如何应用。

ARMv8编程指南则是从A1.5.2小节起开始详细描述浮点数在ARMv8架构中的表示以及相关术语。其中,A1.5.5小节描述了NaN的处理以及关于QNaN与SNaN的操作执行情况。

概括起来看,无论是x86处理器还是ARM,如果一个浮点计算中其中有一个操作数为NaN,而另一个操作数不为NaN,那么目的操作数则会选取NaN的值。而如果两个源操作数均为NaN,那么x86处理器与ARM处理器的执行则会有所区别:

  • 对于ARM处理器:如果其中一个源操作数为SNaN,而另一个源操作数为QNaN,则选取SNaN作为结果;如果两个源操作数均为SNaN,则选取第一个源操作数作为结果。
  • 对于x86处理器:无论两个操作数哪个是QNaN,哪个是SNaN,一律取第一个源操作数作为结果。后面的例子会阐明这一点。

而对于上述过程中,如果目的操作数所取得的结果是一个SNaN,那么两个处理器都会把它转换为一个QNaN作为计算结果。而且两者在将SNaN转换为QNaN的行为也出奇地一致——直接将尾数部分的最高位置1,其他位保留为原来的状态不变。

前面我们提到过,QNaN对于处理器而言是会发送信号的。那么处理器是如何控制这个行为的呢?

  • 对于x86处理器:可参看Intel编程指南第1卷中的10.2.3小节“MXCSR Control and Status Register”。如果MXCSR寄存器的第7位(IM)为0,那么当发生SNaN的结果时会触发中断,否则不会触发中断,但是MXCSR的第0位(IE),即浮点异常操作标志位仍然会被置1。
  • 对于ARMv8架构处理器:可参看ARMv8编程指南的C5.2.7小节“FPCR, FLoating-point Control Register”以及C5.2.8小节“FPSR, Floating-point Status Register”。如果FPCR的第8位(IOE位)被置为1,那么当产生SNaN的结果时会触发软件中断,但FPSR的第0位(IOC位),即浮点无效操作累计标志位不会被更新。而该标志位可以在软件异常处理中手动对它更新设置。如果FPCR的IOE位被置为0(默认情况下),那么异常中断不会被触发,此时处理器将会自动将FPSR的IOC比特置1。

代码示例

下面我们通过一段代码来测试一下Windows系统下x86_64处理器对于NaN的处理行为。笔者这里的环境是Windows 11,开发工具为Visual Studio 2022,代码采用C++ 20标准。各位倘若使用Visual Studio 2019版本也完全没问题,只要选择C++ 20标准即可。

下面先给出main.cpp的代码:

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <utility>
#include <limits>extern "C" void NanOpTest(unsigned dst[4], unsigned srcOp1[4], unsigned srcOp2[4], unsigned* pMXCSR);int main(void)
{printf("Has signaling NaN? %s\n", std::numeric_limits<float>::has_signaling_NaN ? "YES" : "NO");union FloatType{float f;unsigned u;double d;unsigned long long ull;}qnanf = { .f = std::numeric_limits<float>::quiet_NaN() },qnand = { .d = std::numeric_limits<double>::quiet_NaN() },snanf = { .f = std::numeric_limits<float>::signaling_NaN() },snand = { .d = std::numeric_limits<double>::signaling_NaN() };printf("qnanf = 0x%08X\n", qnanf.u);printf("qnand = 0x%.16llX\n", qnand.ull);printf("snanf = 0x%08X\n", snanf.u);printf("snand = 0x%.16llX\n", snand.ull);// Explicitly set it to a SNaNsnanf.u = 0x7fa0'0000U;snand.ull = 0x7ff8'0000'0000'0000ULL;constexpr FloatType normalInt = { .f = 0.5f };struct alignas(64){unsigned dst[4];unsigned src1[4];unsigned src2[4];} opData = { .src1 = { qnanf.u | 1, snanf.u | 1, snanf.u | 1, normalInt.u | 1  },.src2 = { snanf.u | 2, qnanf.u | 2, snanf.u | 2, qnanf.u | 2 }};unsigned mxcsrReg = 0;NanOpTest(opData.dst, opData.src1, opData.src2, &mxcsrReg);printf("Before operaton, MXCSR = 0x%04X\n", mxcsrReg);printf("Op result: 0x%08X 0x%08X 0x%08X 0x%08X\n",opData.dst[0], opData.dst[1], opData.dst[2], opData.dst[3]);NanOpTest(opData.dst, opData.src1, opData.src2, &mxcsrReg);printf("After operaton, MXCSR = 0x%04X\n", mxcsrReg);
}

对于上述代码有个细节需要讲一下。原本当前的C++标准是给出了对于给定运行环境下的SNaN的一个常量表示。但是这里MSVC所给出的一个单精度浮点的SNaN值是 0x7FC00001,一看就知道不是一个真正的SNaN,因为尾数部分最高位(即22位)是1,而不是0。因此我在下面显式地写了一个常量:0x7fa0'0000U,这也是在Linux GCC下获得的常量,是正确的对SNaN的一种表示。

下面给出NanOpTest函数的实现,它是在test.asm汇编文件里:

.code; void NanOpTest(unsigned dst[4], unsigned srcOp1[4], unsigned srcOp2[4], unsigned *pMXCSR)
NanOpTest       proc publicvstmxcsr    dword ptr [r9]vmovdqa     xmm1, xmmword ptr [rdx]vmovdqa     xmm2, xmmword ptr [r8]vaddps      xmm0, xmm1, xmm2vmovdqa     xmmword ptr [rcx], xmm0retNanOpTest       endpend

最后,各位对工程设置一下,引入masm的汇编生成依赖项即可构建运行了。

浮点数的舍入模式

无论是x86还是ARM,两者均支持这四种舍入模式:

  • Round to nearest Even(舍入到最近偶数);
  • Round to -INF(向负无穷大进行舍入);
  • Round to +INF(向正无穷大舍入);
  • Round to Zero(向零的方向进行舍入,有时也称之为截断取整)。

此外,ARM64还额外支持Round to nearest with ties to Away(即我们在数学上所使用的四舍五入法)。

这里解释一下什么是舍入到最近偶数。这种舍入法基于我们数学上常用的四舍五入法,但在实际数据统计时,当一些数据的小数部分都是 .5 的时候,四舍五入法会使得数据样本的总和偏大。而现代典型的CPU所引入的最近偶数舍入法能避免这种情况发生,因为当我们遇到小数部分为 .5 的浮点数时,它会舍入到最近的一个偶数上。比如,1.5会舍入到2;2.5也是舍入到2。这么一来,当我们有一组包含 .5 的数据样本时,倘若要统计这些数据取整之后的总和,那么该总和值不会被过分放大。

下面列一张表概述了上面所提到的五种舍入法的结果:

舍入模式 0.49 -0.49 1.5 -1.5 2.5 -2.5
最近偶数 0 0 2 -2 2 -2
向正无穷大 1 0 2 -1 3 -2
向负无穷大 0 -1 1 -2 2 -3
向零舍入 0 0 1 -1 2 -2
四舍五入 0 0 2 -2 3 -3

这里,所谓正无穷大方向、负无穷大方向,各位可以把这些浮点数想象为在一根一维的数轴(一般记为x轴)上,正无穷大方向就是沿着x轴的正方向,那么负无穷大方向就是x轴的负方向,很容易理解。

x86_64与ARM64的signaling NaN与Quiet NaN,以及浮点数的舍入模式相关推荐

  1. NaN 是什么 NaN == NaN ?

    NaN是一个值类型,同是也是一个数值.意思是Not A Number,这个都知道是什么意思.值比较特殊,特殊在于NaN是一个数值,是一个与任何数值都不相等的数值.在javascript中我们通过typ ...

  2. matlab nan 无色_Matlab NAN如何去掉

    展开全部 % NaN returns the IEEE arithmetic representation for Not-a-Number (NaN). % These result from op ...

  3. Double.NaN和Float.NaN

    在网上 看到一个问题,便将其记录下来 问题所述:存在使 i>j || i<= j不成立的数嘛? 答案是存在的 如:Double.NaN 或Float.NaN 那么Double.NaN 究竟 ...

  4. python将姓王的都改成老王_Python 实现将numpy中的nan和inf,nan替换成对应的均值

    inf:infinity;正无穷 numpy中的nan和inf都是float类型 t!=t 返回bool类型的数组(矩阵) np.count_nonzero() 返回的是数组中的非0元素个数:true ...

  5. js NaN不等于NaN

    NaN代表非数字值的特殊值.该属性用于指示某个值不是数字. 这个非数字可以是不同的数字,因此 NaN 不等于 NaN. NaN == NaN // false

  6. python 温度插值nan处理_Python处理inf和Nan值,pytorch,nan,数值

    在构建网络框架后,运行代码,发现很多tensor出现了inf值或者nan,在很多博客上没有找到对应的解决方法,大部分是基于numpy写的,比较麻烦.下面基于torch BIF函数实现替换这2个值. a ...

  7. 【解决方案】pytorch中loss变成了nan | 神经网络输出nan | MSE 梯度爆炸/梯度消失

    loss_func = nn.MSELoss() loss = loss_func(val, target) 最近在跑一个项目,计算loss时用了很普通的MSE,在训练了10到300个batch时,会 ...

  8. numpy中np.nan(pandas中NAN)

    在处理数据时遇到NAN值的几率还是比较大的,有的时候需要对数据值是否为nan值做判断,但是如下处理时会出现一个很诡异的结果: import numpy as npnp.nan == np.nan #此 ...

  9. html点击之后出现nan,javascript的nan是什么

    NaN(Not a Number,非数)是计算机科学中数值数据类型的一类值,表示未定义或不可表示的值.常在浮点数运算中使用.首次引入NaN的是1985年的IEEE 754浮点数标准. 在javascr ...

最新文章

  1. iOS旋钮动画-CircleKnob
  2. Java 编程的动态性,第3部分: 应用反射--转载
  3. 倒排索引(Inverted File Index )
  4. Kettle使用_16 闭包Closure Generator树形数据
  5. 飞秋本机如何与虚拟机传送文件_某度扩容之虚拟机
  6. docker安装消息队列延时插件
  7. apt-get出错,由于出现了太多错误,处理过程被终止
  8. centos一键清理磁盘空间_MySQLbinlog如何设置自动清理日志
  9. pycharm不能输入代码
  10. 2021-03-05 网站资源数据搜集
  11. 关于安控RTU和宏电DTU的通讯设置
  12. 写好一份属于自己的简历
  13. c语言中平方根函数(sqrt)
  14. java jxl导出excel小结
  15. 怎么识别图片中的文字?这三款识别软件还不错
  16. DM368 UBL和u-boot的裁剪 .
  17. android实现首页倒计时,Android倒计时 Android仿京东倒计时 android电商app源码倒计时源码...
  18. 告别编码5分钟,命名2小时!史上超全的Java命名规范参考!
  19. ts泛型和补充类型基础
  20. mysql | Incorrect string value: ‘\xE7) \xE5\xA4\xB1...‘

热门文章

  1. java设计模式 gof_gof设计模式
  2. python3 购物车 增改查终极版~
  3. 双核跟四核的区别linux,双核好还是四核好 双核和四核区别详解
  4. 自媒体人涨粉攻略:3个免费的有效渠道,快收好
  5. Android 高效播放apng文件(支持在RecycleView、ListView中显示)
  6. Perl/Tk模块的安装终极教程(针对于NMAKE不好用的解决办法)
  7. 【云原生-白皮书】简章1:为什么我们需要云原生架构?
  8. 微端在H5方面的使用调研
  9. Java:实训五 常用实用类应用
  10. 一些手机不能使用HierachyViewer和android.util.Log的问题解决