(1.0e300 + pow(2.0, -30.0) > 1.0) 在 STDC 中究竟做了什么?
What exactly does (1.0e300 + pow(2.0, -30.0) > 1.0) do in STDC?
我遇到了一个计算 atan(x)
的函数(来源是 here)。将其简化为我的问题的核心并稍微重新格式化,他们有类似的东西:
static const double one = 1.0,
huge = 1.0e300;
double atan(double x)
{
/* A lot of uninteresting stuff here */
if (ix < 0x3fdc0000) { /* |x| < 0.4375 */
if (ix < 0x3e200000) { /* |x| < 2^-29 */
if ((huge + x) > one) return x; /* raise inexact */
}
id = -1;
}
/* A lot of more uninteresting stuff */
}
我对 if ((huge + x) ...
行应该做什么以及它是如何工作的非常感兴趣。
根据注释,如果 x
的绝对值小于 2^-29
,则表达式或比较会引发 inexact
错误。
我的第一个问题是,我目前不明白为什么这样做:如果 x
的绝对值太高,使用该函数计算 arctan
会导致不精确的结果小,他们为什么不直接使用像 if (fabs(x) < [some_value_here]) ...
这样的东西呢?我怀疑这只是因为 inexact
警告不会在他们的硬件/库中以这种方式发出,但我想确定地知道。
假设我是对的,我的第二个问题是我不明白为什么需要比较。我认为这里的关键点是,一个非常小的数字被添加到一个非常大的数字中,以至于这种添加并没有充分地改变大的数字,甚至根本没有改变。因此,是添加会引发 inexact
警告,而不是比较。所以我问自己比较应该做什么。这是否只是为了强制编译器实际计算 (huge + x)
,否则可能会被优化掉?
最后,如果有人能稍微解释一下数学,我将不胜感激。为 huge
选择值 1.0e300
似乎是一个相当随意的选择。但这只是一个额外的问题,因为我承认我还没有完成作业的 math 部分(关于 double
值及其 IEEE754 表示,我不是一个新手,但理解这段代码的数学方面需要一些时间,除非有人给出简短的解释)。
编辑 1
偶然看到:
该函数的 float32
版本,包括上面讨论的怪异行,几乎字面上仍在 glibc 2.19 中!由于 glibc 旨在可移植,因此该代码也应该如此。它在子目录 sysdeps\ieee754\flt-32
中,所以我想这是 float32
函数的软件仿真,其中可移植性没有问题,因为硬件相关的怪异不会出现(我认为软件仿真会引发与 IEEE754 中定义完全相同的异常。
if ((huge + x) > one) return x;
的目的是生成浮点不精确异常,然后从例程中 return。
浮点异常不是陷阱或处理器异常。这只是意味着在浮点运算中发生了一些不寻常的事情。然后会发生什么取决于操作的情况。特别是,可以设置浮点环境,以便不精确的异常仅在特殊寄存器中引发一个标志并继续操作,提供数字结果。或者它可能被设置为一个不准确的异常导致一个陷阱,并且程序控制被重定向到一个陷阱处理程序。
此实现 atan
的代码不知道如何设置浮点环境。也许它可以获取设置,但它不想为此烦恼。鉴于它已决定不能精确计算反正切函数,触发浮点不精确异常的最简单方法只是执行一个简单的加法,但结果不精确。这种不精确的加法将具有与不精确的反正切所需的相同的行为——它要么只是升起标志,要么导致陷阱,具体取决于设置。
至于为什么要和ix < 0x3e200000
比较,不清楚。一方面,ix
已经调整以反映绝对值,而 x
还没有,所以为什么不使用已经准备好的 ix
而不是使用另一个操作来产生 fabs(x)
?此外,整数比较通常比浮点比较占用更少的处理器资源,尤其是在编写此代码时的处理器中。或者可能是作者只是碰巧在作者之上使用了一个,也许他们的大部分代码都是使用 ix
来对浮点编码进行操作而不是 x
来对浮点值进行操作,他们不想不必要地来回切换。也可能是因为代码是在十六进制浮点表示法可用之前编写的(所以我们可以写 x < 0x1p-29f
),编译器不擅长将十进制数字转换为浮点值,所以他们不想在源代码中写出浮点值。
这种代码是有问题的,并且高度依赖于它所针对的 C 实现。通常,编译器可能无法保证 (huge + x) > one
将在程序执行期间进行计算。编译器可能会在编译时评估它。不过,据推测,如果此代码是为特定的 C 实现编写的,他们知道编译器将在编译时对其进行评估或确保获得相同的结果,包括引发浮点不精确异常。
从表面上看,(huge + x) > one
似乎没有做任何 huge + x
单独做不到的有用的事情,但也许作者知道一些我们不知道的编译器。
huge
不需要是 1.0e300
。任何大到 huge
和 x
之和不能精确的值就足够了。
我遇到了一个计算 atan(x)
的函数(来源是 here)。将其简化为我的问题的核心并稍微重新格式化,他们有类似的东西:
static const double one = 1.0,
huge = 1.0e300;
double atan(double x)
{
/* A lot of uninteresting stuff here */
if (ix < 0x3fdc0000) { /* |x| < 0.4375 */
if (ix < 0x3e200000) { /* |x| < 2^-29 */
if ((huge + x) > one) return x; /* raise inexact */
}
id = -1;
}
/* A lot of more uninteresting stuff */
}
我对 if ((huge + x) ...
行应该做什么以及它是如何工作的非常感兴趣。
根据注释,如果 x
的绝对值小于 2^-29
,则表达式或比较会引发 inexact
错误。
我的第一个问题是,我目前不明白为什么这样做:如果 x
的绝对值太高,使用该函数计算 arctan
会导致不精确的结果小,他们为什么不直接使用像 if (fabs(x) < [some_value_here]) ...
这样的东西呢?我怀疑这只是因为 inexact
警告不会在他们的硬件/库中以这种方式发出,但我想确定地知道。
假设我是对的,我的第二个问题是我不明白为什么需要比较。我认为这里的关键点是,一个非常小的数字被添加到一个非常大的数字中,以至于这种添加并没有充分地改变大的数字,甚至根本没有改变。因此,是添加会引发 inexact
警告,而不是比较。所以我问自己比较应该做什么。这是否只是为了强制编译器实际计算 (huge + x)
,否则可能会被优化掉?
最后,如果有人能稍微解释一下数学,我将不胜感激。为 huge
选择值 1.0e300
似乎是一个相当随意的选择。但这只是一个额外的问题,因为我承认我还没有完成作业的 math 部分(关于 double
值及其 IEEE754 表示,我不是一个新手,但理解这段代码的数学方面需要一些时间,除非有人给出简短的解释)。
编辑 1
偶然看到:
该函数的 float32
版本,包括上面讨论的怪异行,几乎字面上仍在 glibc 2.19 中!由于 glibc 旨在可移植,因此该代码也应该如此。它在子目录 sysdeps\ieee754\flt-32
中,所以我想这是 float32
函数的软件仿真,其中可移植性没有问题,因为硬件相关的怪异不会出现(我认为软件仿真会引发与 IEEE754 中定义完全相同的异常。
if ((huge + x) > one) return x;
的目的是生成浮点不精确异常,然后从例程中 return。
浮点异常不是陷阱或处理器异常。这只是意味着在浮点运算中发生了一些不寻常的事情。然后会发生什么取决于操作的情况。特别是,可以设置浮点环境,以便不精确的异常仅在特殊寄存器中引发一个标志并继续操作,提供数字结果。或者它可能被设置为一个不准确的异常导致一个陷阱,并且程序控制被重定向到一个陷阱处理程序。
此实现 atan
的代码不知道如何设置浮点环境。也许它可以获取设置,但它不想为此烦恼。鉴于它已决定不能精确计算反正切函数,触发浮点不精确异常的最简单方法只是执行一个简单的加法,但结果不精确。这种不精确的加法将具有与不精确的反正切所需的相同的行为——它要么只是升起标志,要么导致陷阱,具体取决于设置。
至于为什么要和ix < 0x3e200000
比较,不清楚。一方面,ix
已经调整以反映绝对值,而 x
还没有,所以为什么不使用已经准备好的 ix
而不是使用另一个操作来产生 fabs(x)
?此外,整数比较通常比浮点比较占用更少的处理器资源,尤其是在编写此代码时的处理器中。或者可能是作者只是碰巧在作者之上使用了一个,也许他们的大部分代码都是使用 ix
来对浮点编码进行操作而不是 x
来对浮点值进行操作,他们不想不必要地来回切换。也可能是因为代码是在十六进制浮点表示法可用之前编写的(所以我们可以写 x < 0x1p-29f
),编译器不擅长将十进制数字转换为浮点值,所以他们不想在源代码中写出浮点值。
这种代码是有问题的,并且高度依赖于它所针对的 C 实现。通常,编译器可能无法保证 (huge + x) > one
将在程序执行期间进行计算。编译器可能会在编译时评估它。不过,据推测,如果此代码是为特定的 C 实现编写的,他们知道编译器将在编译时对其进行评估或确保获得相同的结果,包括引发浮点不精确异常。
从表面上看,(huge + x) > one
似乎没有做任何 huge + x
单独做不到的有用的事情,但也许作者知道一些我们不知道的编译器。
huge
不需要是 1.0e300
。任何大到 huge
和 x
之和不能精确的值就足够了。