NaN、IND、INF 和 DEN 的概念






4.82/5 (17投票s)
使用 C++ 的 NaN、IND、INF 和 DEN 的数值概念。
引言
软件开发者每天都处理两种主要数据类型。我们知道它们是字符串和数字。当某些数据被描述为“非数字”时,一些开发者可能会将其理解为字符串。但事实并非如此!
当我们处理对浮点数进行大量数值计算的算法时,会出现一些情况,导致结果无法称之为数字!
本文旨在通过 C++ 代码示例,为读者提供对以下数值概念的简要概述。
- NaN
- IND
- INF
- DEN
可下载的示例项目在 VS 2012 中编译。您可以在任何版本的 Visual C++(从 VC 6.0 开始)中进行编译。
背景
我曾参与一个项目,需要将一些 Matlab 图像处理算法移植到 C++。Matlab 算法的计算强度很高。它需要处理许多浮点运算,其中每一项都会重复数千次,直到满足特定条件为止。
该项目最具挑战性的要求之一是生成与 Matlab 输出完全匹配的浮点输出。小数部分哪怕只有一个数字的差异,都会产生与 Matlab 输出截然不同的结果。
在验证 Matlab 到 C++ 移植的代码时,发现在某些点,某些 double
变量存储着奇怪的数字,例如“1.#QNAN00000000000”、“-1.#IND000000000000”等。这是怎么发生的?
Matlab 拥有一套丰富且易于使用的实用函数和运算符。其中一个函数如下所示。
Pixels(isnan(Pixels)) = 0 ;
上面的 Matlab 语句将遍历一个名为 Pixels
的数组,并将值非数字(not-a-number)的每个数组位置赋值为 0。
由于急于在 C++ 中生成输出,在从 Matlab 移植到 C++ 的过程中,遗漏了一个类似上述的语句,结果显而易见。我遇到了“1.#QNAN00000000000”这样的“奇怪”数字。
1. NaN 的概念
NaN 的意思是“非数字”(Not A Number)。
当计算机进行大量数值计算时,结果可能无法被视为一个数字!
例如,请看下面的代码。
double dSQRTValue = sqrt( -1.00 ); // An image processing algorithm may eventually invoke sqrt() with -1 as its input.
double dResult = -dSQRTValue; // An image processing algorithm may involve taking the negative of another value.
在这里,变量 dResult
将存储一个 NaN。因此,NaN 代表一个无法被视为有效数量的数值。
该如何表示呢?通常,我们会指定 0 或 -1 来标记浮点数或双精度数变量中的无效条目。但这种方法在这里行不通,因为 0 和 -1 本身就是有效数字。
这里就体现了 NaN 表示法的重要性。
A. NaN 的表示法
我想同时介绍 NaN 的非标准表示法和标准表示法。非标准表示法仅出于学术兴趣介绍。
I. 非标准表示法
定义一个长度为 2 的 long 类型数组。
const unsigned long lnNAN[2] = {0x00000000, 0x7ff80000};
然后,将其强制转换为 double 值!
const double NOT_A_NUMBER = *( double* )lnNAN;
现在,常量变量 NOT_A_NUMBER 就包含了一个 NaN。
II. 标准表示法
<limits>
头文件定义了一个名为 numeric_limits
的模板类,其中包含以下用于获取 NaN 的函数。
const double STD_NOT_A_NUMBERD = std::numeric_limits<double>::quiet_NaN();
B. NaN 是什么样子的?
以上是 NaN 在 MS Visual C++ 调试器中的显示方式。使用 sprintf()
等函数以及 stringstream
等流类,我们将获得相同的字符串表示。
C. NaN 的比较
在此,我想再次介绍非标准和标准两种比较方法。如前所述,非标准方法仅为学术目的提供。
I. 非标准比较
bool bNaN = false;
if( 0 == memcmp( &NOT_A_NUMBER, &dQNan, sizeof(double)))
{
bNaN = true;
}
II. 标准比较
头文件 <float.h>
包含了用于检查数字是否为 NaN 的 _isnan()
函数的声明。
D. NaN 的属性
本节将解释 NaN 在数值运算中的一些属性。这并非全部属性。
I. 等于检查返回 False
NaN 的一个属性是,对其进行相等比较总是返回 false。
if( dResult == dResult )
{
// Code inside this block will NEVER be executed.
int nNumber = 0;
}
II. 任何与 NaN 的计算都会返回 NaN
假设变量 dResult
包含一个 NaN。
dResult += 1234;
执行上述操作后,变量 dResult
将包含一个 NaN。
IND 的概念
IND 的意思是“不确定数”(Indeterminate Number)。
IND 是比 NaN 低一个层次的值。也就是说,IND 是一个几乎等同于 NaN 的值。计算中存在一些情况,其结果无法由 FPU(浮点处理单元)确定。在这种情况下,结果将被设为不确定数。
请看下面的代码。
double dInfinity = <INF>;// Concept of Infinity will be explained next.
double dIND = ( dInfinity / dInfinity ); // An arithmetic operation may eventually divide two infinite numbers.
除法运算后,变量 dIND
将包含一个 IND。下面是另一个示例。
double dZero = 0.00; // This is defined just for demonstration.
double dIND1 = ( dZero / dZero ); // Extensive algorithmic operations may consequently perform 0/0.
除法运算后,变量 dIND
将包含一个 IND。
此处仅提供示例以作演示。也可能存在其他情况,算术表达式的结果会产生不确定值。
A. IND 的表示法
I. 非标准表示法
定义一个长度为 2 的 long 数组。
const unsigned long lnIND[2] = {0x00000000, 0xfff80000};
现在,将其强制转换为 double 值。
const double AN_INDETERMINATE = *( double* )lnIND;
请注意,常量变量 lnIND
的值与相应的 NaN 表示法不同。
II. 标准表示法
我找不到任何函数可以提供 IND 的标准表示法。这可能是因为 C++(Microsoft)将 IND 视为 NaN。_isnan()
函数在输入为 IND 时返回 true(非零)的事实也印证了这一点。
B. IND 是什么样子的?
以上是 IND 在调试器中的显示方式。使用 sprintf()
等函数以及 stringstream
等流类,我们将获得相同的字符串表示。IND 值可以有正负之分。像 1.#IND000000000000 这样的字符串表示是 Windows 操作系统/Microsoft 特有的表示。
概念和内部表示(即 IEEE 浮点格式)在不同平台/环境中是相同的,但用户级别的关键字/字符串会不同。
C. IND 的比较
I. 非标准方法
bool bIND = false;
if( 0 == memcmp( &AN_INDETERMINATE, &dIND, sizeof(double)))
{
bIND = true;
}
II. 标准方法
到目前为止,我还没有找到任何标准函数。
一个巧妙的解决方案(在 Windows 平台上)是获取 double 值的字符串表示,然后检查是否包含子字符串“#IND”。
C. IND 的属性
I. 等于检查返回 False
IND 的一个重要属性是,相等比较总是返回 false。请看下面的代码。
if( dIND == dIND )
{
// Code inside this block will NEVER be executed.
int a = 0;
}
II. 任何与 IND 的计算都会返回 IND 或 NaN
dIND += 1234; // dIND will hold an IND
dIND += -dIND; // dIND will hold a NaN
3. INF 的概念
INF 的意思是“无穷大”(Infinity)。
当运算结果无法在相应的数据类型中存储时,算术运算会产生一个无穷大的数。此时,结果被认为发生了溢出(overflow)。也就是说,结果超出了可用存储空间。在这种情况下,结果被标记为 INF。
例如,请看下面的代码。
double dZero = 0.00; // This is defined just for demonstration.
double dINF = 1/dZero ;
在这里,变量 dINF
将包含一个无穷大。
此处提供的示例仅为帮助理解。也可能存在其他情况,表达式的结果会产生 INF 值。
A. INF 的表示法
I. 非标准表示法
定义一个长度为 2 的 long 数组。
const unsigned long lnINF[2] = {0x00000000, 0x7ff00000};
现在,将其强制转换为 double 值。
const double AN_INFINITY_POSITIVE = *( double* )lnINF;
II. 标准表示法
<limits>
头文件定义了以下用于获取 INF 值的函数。
const double STD_AN_INFINITY_POSITIVE = std::numeric_limits<double>::infinity()
由于存在正负无穷大,上述函数返回正无穷大。负无穷大可以如下获得。
const double AN_INFINITY_NEGATIVE = -AN_INFINITY_POSITIVE;
B. INF 是什么样子的?
以上是 +VE INF 在调试器中的显示方式。使用 sprint() 等函数以及 stringstream 等流类,我们将获得相同的字符串表示。像 1.#INF000000000000 这样的字符串表示是 Windows 操作系统/Microsoft 特有的表示。
概念和内部表示(即 IEEE 浮点格式)在不同平台/环境中是相同的,但用户级别的关键字/字符串会不同。
C. INF 的比较
I. 非标准方法
bool bINF = false;
if( 0 == memcmp( &AN_INFINITY_POSITIVE, &dINF, sizeof(double)) ||
0 == memcmp( &AN_INFINITY_NEGATIVE, &dINF, sizeof(double)))
{
bINF = true;
}
II. 标准方法
"float.h" 头文件定义了用于检查数字是否为 INF 的 _finite()
函数。还有其他标准方法。
C. INF 的属性
I. 等于检查返回 True
INF 的一个属性是,相等比较总是返回 True。也就是说
if( dINF == dINF )
{
// Code inside this block WILL be executed.
int a = 0;
}
if( -dINF == -dINF )
{
// Code inside this block WILL be executed.
int a = 0;
}
II. 任何与 INF 的计算都会返回 IND 或 NaN
dINF += -dINF; // dINF will hold an IND
dINF += NOT_A_NUMBER; // dINF will hold a NaN
4. DEN 的概念
DEN 的意思是“非规格化数”(Denormalized)。它也称为“亚规格化数”(Subnormal)。
我们都知道 0 和 1 之间有无限多个有理数。您是否曾想过计算机能存储其中多少数字?
由于计算机是有限的机器,存在局限性。它在表示浮点数方面存在局限。
我们知道 float
和 double
数据类型由 IEEE 754 浮点表示法表示。这种表示法有两个部分。一个是尾数部分,另一个是指数部分。示例如下。
假设算术运算产生一个非常接近零但**不等于**零的数。由于浮点表示法的限制,CPU 可能无法将其用于进一步计算。在这种情况下,该数被标记为非规格化数。
例如,请看下面的代码。
double dDenTest = 0.01E-305;
dDenTest /= 10; // This will produce a denormalized number.
A. DEN 的表示法
I. 非标准表示法
定义一个长度为 2 的 long 数组。
const unsigned long lnDEN[2] = {0x00000001, 0x00000000};
现在,将其强制转换为 double 值。
const double A_DENORMAL = *( double* )lnDEN;
II. 标准表示法
<limits>
头文件定义了以下用于获取 DEN 值的函数。
double dDEN = std::numeric_limits<double>::denorm_min();
B. DEN 是什么样子的?
以上是 DEN 值在 VS 调试器中的显示方式。使用 sprintf()
等函数以及 stringstream
等流类,我们将获得相同的字符串表示。该字符串表示是 Windows 操作系统/Microsoft 特有的表示。
概念和内部表示(即 IEEE 754 浮点格式)在不同平台/环境中是相同的,但用户级别的关键字/字符串会不同。
C. DEN 的比较
I. 非标准方法
bool bDEN = false;
if( 0 == memcmp( &A_DENORMAL, &dDEN, sizeof(double)))
{
bDEN = true;
}
II. 标准方法
if ( dDEN != 0 && fabs ( dDEN ) <= std::numeric_limits<double>::denorm_min())
{
// it's denormalized
bDEN = true;
}
C. DEN 的属性
I. 等于检查与数值比较相同
if( dDEN == dDEN )
{
// Code inside this block WILL be executed.
int a = 0;
}
II. 任何与 DEN 的计算与正常计算相同
dDenTest = 0.01E-305;
dDenTest /= 10; // This will produce a denormalized number.
dDenTest *= 10; // This will result in the previous normalized value.
关注点
所有 NaN、IND、INF 和 DEN 的非标准表示法仅出于学术兴趣介绍。它仅仅展示了它们在内存中可能的表示方式。请注意,这**并非**在内存中表示 NaN、IND、INF 和 DEN 的唯一方法。可能存在其他表示方法。有关更多信息,请参阅 IEEE 浮点表示法。