65.9K
CodeProject 正在变化。 阅读更多。
Home

NaN、IND、INF 和 DEN 的概念

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (17投票s)

2014年10月3日

CPOL

9分钟阅读

viewsIcon

69021

downloadIcon

232

使用 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 之间有无限多个有理数。您是否曾想过计算机能存储其中多少数字?

由于计算机是有限的机器,存在局限性。它在表示浮点数方面存在局限。

我们知道 floatdouble 数据类型由 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 浮点表示法。

所有这些 NaN、IND、INF 和 DEN 都是一种优雅的方式,用于告知用户某些事情超出了边界。它为浮点单元(FPU),也称为数学协处理器,在无法再表示浮点值时提供了一种出路。
 
以下是一些有用的参考资料。
 
 
© . All rights reserved.