浮点数限制(双精度)定义为长双精度后缀L。

6

1.问题:

我有一个关于Linux下gcc v4.8.5中DBL_MAXDBL_MIN定义的问题。
它们在limit.h中定义为:

#define DBL_MAX     __DBL_MAX__
#define DBL_MIN     __DBL_MIN__

其中__DBL_MIN____DBL_MAX__是与编译器相关的,可以通过以下方式获取:

$ gcc -dM -E - < /dev/null
...
#define __DBL_MAX__ ((double)1.79769313486231570815e+308L)
#define __DBL_MIN__ ((double)2.22507385850720138309e-308L)
...

我的问题是:
为什么定义的值以L作为后缀,并转换回double
2.问题:
为什么__DBL_MIN_10_EXP__被定义为-307,但最小指数是-308,因为它在DBL_MIN宏中被使用?在最大指数的情况下,它被定义为308,我可以理解,因为它被DBL_MAX宏使用。
#define __DBL_MAX_10_EXP__ 308
#define __DBL_MIN_10_EXP__ (-307)

不是问题的一部分,只是我做出的观察:

顺便说一下,在使用Windows和Visual Studio 2015时,只定义了DBL_MAXDBL_MIN宏,没有编译器特定的重定向到带有下划线版本的宏。此外,最小正双精度值 DBL_MIN 和最大双精度值 DBL_MAX 比我的Linux gcc编译器的值略大一些(仅与以上gcc v4.8.5定义的宏进行比较):

#define DBL_MAX        1.7976931348623158e+308
#define DBL_MIN        2.2250738585072014e–308

此外,微软编译器将 long double 的限制设置为 double 的值,似乎不支持真正的 long double 实现。

VS编译器(不是操作系统Windows)支持long double作为与double不同的类型。它只是具有与double相同的编码。因此,在那里使用long double几乎没有什么好处。 - chux - Reinstate Monica
那个最小的正双精度值比我的Linux gcc编译器中的值更大,而最大的双精度值则更小:你是如何确定这一点的?是通过比较double类型的"1.7976931348623158e+308"与"((double)1.79769313486231570815e+308L)"之间的转换吗?还是在比较源代码? - chux - Reinstate Monica
1
MSVC自18年前起就不符合标准规范。想知道为什么它不遵循标准规范是毫无意义的。 - too honest for this site
@Olaf:是的,这是真的。我总是会遇到这样的问题,因为我同时在开发Windows和Linux平台上的应用程序。snprintf()函数就是一个很好的例子,两个平台的行为不同,而且微软并不关心标准规定... - Andre Kampling
据我所知,gcc和clang都可以在两个平台上使用。不过还有libc的问题。 - too honest for this site
显示剩余4条评论
2个回答

5

在十进制中指定二进制浮点数存在微妙的问题。

为什么将这些值定义为带有后缀 L 的 long double,然后再将其转换回 double?

对于典型的 binary64,最大的有限值约为 1.795e+308 或者精确地说。

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368

将一个唯一的 double 转换所需的数字位数可能达到 DBL_DECIMAL_DIG (通常为 17 位,至少为 10 位)。无论如何,使用指数符号表示法肯定是清晰的,而且不会过于精确。
/*
1 2345678901234567 */          // Sorted 
1.79769313486231550856124...   // DBL_MAX next smallest for reference
1.79769313486231570814527...   // Exact
1.79769313486231570815e+308L   // gcc
1.7976931348623158e+308        // VS (just a hair closer to exact than "next largerst")
1.7976931348623159077293....   // DBL_MAX next largest if not limited by range

各种编译器可能无法准确地转换此字符串,有时会忽略一些最不重要的数字-尽管这是由编译器控制的。

另一个导致微妙的转换差异的来源,而且我认为这就是添加'L'的原因,即double计算受处理器的浮点单元影响,它可能不完全符合IEEE标准。更糟糕的结果可能是1.797...e+308常量由于微小的转换错误而由"使用double数学代码转换为double" 变成了无限大。通过将其转换为long double,这些long double转换错误非常小。然后将long double结果转换为double,四舍五入到期望的数字。

简而言之,强制使用L数学可以确保常量不会意外地变为无限大

我希望以下内容符合符合IEEE 754标准的FPU,但既不与gcc也不与VS相匹配。

#define __DBL_MAX__ 1.7976931348623157e+308

将类型转换为double是为了使DBL_MAX成为一个double。这符合许多代码的期望,即DBL_MAX是一个double而不是long double。虽然我没有看到要求这样做的规范。

DBL_MIN_10_EXP为什么定义为-307,而最小指数为-308?

这是为了符合DBL_MIN_10_EXP的定义。"...最小负整数,使得10的该幂次方在规范化浮点数范围内"。非整数答案介于-307和-308之间,因此范围内的最小整数是-307。

观察部分

尽管VS将long double视为不同的类型,但使用与double相同的编码,因此在使用L时没有数字优势。


非常感谢!解释得很好! - Andre Kampling

1
我不知道为什么要使用L后缀。 网站概述了IEEE 754浮点数。
指数为11位,偏移量为1023。但是0和2047的指数保留用于特殊数字。因此,这意味着指数可以从2046-1023=1023到1-1023=-1022变化。
因此,对于最大规范化值,我们有2^1023的指数。幅度的最大值略低于2(在二进制中为1.111等,在小数点后有52个1),即~2*2^1023 = ~1.79e308。
对于最小规范化值,我们具有2 ^ -1022的指数。最小幅度正好为1,给出一个值为1 * 2 ^ -1022 = ~2.22e-308。到目前为止还不错。

DBL_MIN_10_EXP和DBL_MAX_10_EXP是规范化的最小/最大10的指数。对于最大值1e308小于~1.79e308,因此该值为308。对于最小值,1e-308太小了 - 它比~2.22e-308还要低。而1e-307比~2.22e-308大,因此该值为-307。


非常感谢您的清晰解释! - Andre Kampling

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