C++ `digits10` 对于 IEEE 浮点数是 6,但第一个不可表示的整数已经有 8 位数字?
C++ `digits10` is 6 for IEEE float, but the first non-representable integer already has 8 digits?
C++ 的 std::numeric_limits<float>::digits10
,在 cppref 上是这样描述的:
The value of std::numeric_limits<T>::digits10
is the number of base-10 digits that can be represented by the type T without change, that is, any number with this many decimal digits can be converted to a value of type T and back to decimal form, without change due to rounding or overflow.
C 堂兄也有类似的描述 FLT_DIG。
给出的值为:
float FLT_DIG /* 6 for IEEE float */
然而,所有直到16,777,216
(224)的整数正好是shown here on S.O.以 32 位 IEEE 浮点类型表示。如果我会数,那个数字有 8 位,所以 digits10
的值实际上应该是 7,现在不应该是吗?
很明显,我在这里误解了一些关于 digits10
的东西,那么这个 实际上 告诉我什么?
实际适用性:
有人问我是否可以将 0.00
- 86,400.00
中的所有数字精确地存储在 IEEE 32 位浮点数中。
现在,我非常有信心我们可以将 0
- 8,640,000
中的所有数字存储在 IEEE 32 位浮点数中,但这是否适用于相同的 "integer" 范围向左移动 2 位数?
(将此答案限制为 IEEE754 float
)。
8.589973e9
和 8.589974e9
都映射到 8589973504
。这是第 7 位有效数字被保留的断言的反例。
由于第6位有效数字不存在这样的反例,所以std::numeric_limits<float>::digits10
和FLT_DIG
都是6。
事实上,整数可以精确地表示为 2 的 24th 次方。(16,777,216
和 16,777,217
都映射到 16,777,216
).那是因为 float
有一个 24 位的 significand.
正如其他答案和评论所确定的那样,digits10
涵盖所有 "exponent ranges",即它必须适用于 1234567
以及 1.234567
和 12345670000
-- 这只适用于 6 个数字!
7 位数字的计数器示例:
8.589,973 e9
与 8.589,974 e9
(来自 cppref 示例)
有时查找反例很容易。
#include <stdio.h>
#include <string.h>
int main(void) {
int p6 = 1e6;
int p7 = 1e7;
for (int expo = 0; expo < 29; expo++) {
for (int frac = p6; frac < p7; frac++) {
char s[30];
sprintf(s, "%d.%06de%+03d", frac / p6, frac % p6, expo);
float f = atof(s);
char t[30];
sprintf(t, "%.6e", f);
if (strcmp(s, t)) {
printf("<%s> %.10e <%s>\n", s, f, t);
break;
}
}
}
puts("Done");
}
输出
<8.589973e+09> 8.5899735040e+09 <8.589974e+09>
<8.796103e+12> 8.7961035080e+12 <8.796104e+12>
<9.007203e+15> 9.0072024760e+15 <9.007202e+15>
<9.223377e+18> 9.2233775344e+18 <9.223378e+18>
<9.444738e+21> 9.4447374693e+21 <9.444737e+21>
<9.671414e+24> 9.6714134744e+24 <9.671413e+24>
<9.903522e+27> 9.9035214949e+27 <9.903521e+27>
<1.000000e+28> 9.9999994421e+27 <9.999999e+27> This is an interesting one
另一种观点:
考虑在每对 2 的幂之间,像 IEEE binary 这样的 float
编码线性分布的 223 个值。
示例:介于 20 和 21 或 1.0 和 2.0 之间,
float
值之间的差异为 1.0/223 或 10.192e-06。
以文本形式写成“1.dddddd”,一个7位数字,数字相差1.000e-06。
所以对于十进制文本数字的每一步,大约有10.2 float
。
编码这 7 位数字没有问题。
在此范围内,编码 8 位数字也没有问题。
示例:介于 223 和 224 或 8,388,608.0 和 16,777,216.0 之间。
float
值之间的差异为 223/223 或 1.0。
接近低端的数以文本形式写成“8or9.dddddd*106”,7位有效数,相差1.0
编码这 7 位数字没有问题。
示例:介于 233 和 234 或 8,589,934,592.0 和 17,179,869,184.0 之间,
float
值之间的差异为 233/223 或 1,024.0。
接近低端的数字写成文本形式“8or9.dddddd*109”,7位有效数字,相差1,000.0.
现在我们有问题了。从8,589,934,592.0开始,接下来的1024个文本形式的数字只有1000种不同的float
编码。
d.dddddd * 10expo 形式的 7 位数字组合太多,无法使用 float
.
进行唯一编码
C++ 的 std::numeric_limits<float>::digits10
,在 cppref 上是这样描述的:
The value of
std::numeric_limits<T>::digits10
is the number of base-10 digits that can be represented by the type T without change, that is, any number with this many decimal digits can be converted to a value of type T and back to decimal form, without change due to rounding or overflow.
C 堂兄也有类似的描述 FLT_DIG。
给出的值为:
float FLT_DIG /* 6 for IEEE float */
然而,所有直到16,777,216
(224)的整数正好是shown here on S.O.以 32 位 IEEE 浮点类型表示。如果我会数,那个数字有 8 位,所以 digits10
的值实际上应该是 7,现在不应该是吗?
很明显,我在这里误解了一些关于 digits10
的东西,那么这个 实际上 告诉我什么?
实际适用性:
有人问我是否可以将 0.00
- 86,400.00
中的所有数字精确地存储在 IEEE 32 位浮点数中。
现在,我非常有信心我们可以将 0
- 8,640,000
中的所有数字存储在 IEEE 32 位浮点数中,但这是否适用于相同的 "integer" 范围向左移动 2 位数?
(将此答案限制为 IEEE754 float
)。
8.589973e9
和 8.589974e9
都映射到 8589973504
。这是第 7 位有效数字被保留的断言的反例。
由于第6位有效数字不存在这样的反例,所以std::numeric_limits<float>::digits10
和FLT_DIG
都是6。
事实上,整数可以精确地表示为 2 的 24th 次方。(16,777,216
和 16,777,217
都映射到 16,777,216
).那是因为 float
有一个 24 位的 significand.
正如其他答案和评论所确定的那样,digits10
涵盖所有 "exponent ranges",即它必须适用于 1234567
以及 1.234567
和 12345670000
-- 这只适用于 6 个数字!
7 位数字的计数器示例:
8.589,973 e9
与8.589,974 e9
(来自 cppref 示例)
有时查找反例很容易。
#include <stdio.h>
#include <string.h>
int main(void) {
int p6 = 1e6;
int p7 = 1e7;
for (int expo = 0; expo < 29; expo++) {
for (int frac = p6; frac < p7; frac++) {
char s[30];
sprintf(s, "%d.%06de%+03d", frac / p6, frac % p6, expo);
float f = atof(s);
char t[30];
sprintf(t, "%.6e", f);
if (strcmp(s, t)) {
printf("<%s> %.10e <%s>\n", s, f, t);
break;
}
}
}
puts("Done");
}
输出
<8.589973e+09> 8.5899735040e+09 <8.589974e+09>
<8.796103e+12> 8.7961035080e+12 <8.796104e+12>
<9.007203e+15> 9.0072024760e+15 <9.007202e+15>
<9.223377e+18> 9.2233775344e+18 <9.223378e+18>
<9.444738e+21> 9.4447374693e+21 <9.444737e+21>
<9.671414e+24> 9.6714134744e+24 <9.671413e+24>
<9.903522e+27> 9.9035214949e+27 <9.903521e+27>
<1.000000e+28> 9.9999994421e+27 <9.999999e+27> This is an interesting one
另一种观点:
考虑在每对 2 的幂之间,像 IEEE binary 这样的 float
编码线性分布的 223 个值。
示例:介于 20 和 21 或 1.0 和 2.0 之间,
float
值之间的差异为 1.0/223 或 10.192e-06。
以文本形式写成“1.dddddd”,一个7位数字,数字相差1.000e-06。
所以对于十进制文本数字的每一步,大约有10.2 float
。
编码这 7 位数字没有问题。
在此范围内,编码 8 位数字也没有问题。
示例:介于 223 和 224 或 8,388,608.0 和 16,777,216.0 之间。
float
值之间的差异为 223/223 或 1.0。
接近低端的数以文本形式写成“8or9.dddddd*106”,7位有效数,相差1.0
编码这 7 位数字没有问题。
示例:介于 233 和 234 或 8,589,934,592.0 和 17,179,869,184.0 之间,
float
值之间的差异为 233/223 或 1,024.0。
接近低端的数字写成文本形式“8or9.dddddd*109”,7位有效数字,相差1,000.0.
现在我们有问题了。从8,589,934,592.0开始,接下来的1024个文本形式的数字只有1000种不同的float
编码。
d.dddddd * 10expo 形式的 7 位数字组合太多,无法使用 float
.