我正在阅读这个,但我真的不明白为什么text-float-text
保证6位数字,而float-text-flot
应该是9(考虑到单精度
)。
将ext-float-text
存储转换为正确精度的浮点数。仅当打印发生“四舍五入”版本时。但这是“打印机”故障。
代码:
int main()
{
float decimalFloat = 8.589973e9;
char const *decimalString = "8.589973e9";
float const floatFromDecimalString = strtof(decimalString, nullptr);
std::cout << decimalString << std::endl << std::scientific << floatFromDecimalString << std::endl;
std::cout << "text-float-text: 6 digit preserved, not 7" << std::endl << std::endl;
std::cout << "but the value is correctly converted..." << std::endl;
std::cout << std::bitset<sizeof decimalFloat*8>(*(long unsigned int*)(&decimalFloat)) << std::endl;
std::cout << std::bitset<sizeof floatFromDecimalString*8>(*(long unsigned int*)(&floatFromDecimalString)) << std::endl;
}
二进制文件被保留。它等于直接声明楼层或从存储为字符串的相同小数转换后:
01010000000000000000000000100110
为什么我们需要digits10
?保留的位数是max_digits10
。如果打印“糟糕”,那么……这似乎是打印机的问题。
应该知道,实际的浮点值有效数字是max_digits10
,而不是digits10
(即使您在打印后查看digits10)。
有时一些显示计数器示例的代码会有所帮助。这是C代码,但是浮点
特性在C中是相同的 /C.
“文本-浮动-文本”保证6位数的原因是什么。
7位十进制数字不会往返。考虑像"9999999e3"
这样的文本。该值转换为浮点数
。然而,只有有效的24位有效二进制数字,下一个浮点数
在1024之外。由于该区域中的后续文本值在1e3
或1,000之外,最终附近的文本值转换为相同的浮点数
。
6位十进制数字始终有效,因为后续文本值中的步长始终小于二进制数字中的步长。
void text_to_float_test(void) {
unsigned long ten = 10*1000*1000;
float f1,f2;
for (unsigned long i = ten; i>0; i--) {
char s1[40];
sprintf(s1+0, "%lue3", i);
sscanf(s1, "%f", &f1);
char s2[40];
sprintf(s2 + 0, "%lue3", i-1);
sscanf(s2, "%f", &f2);
if (f1 == f2) {
printf("\"%s\" and \"%s\" both convert to %.*e\n", s1, s2, 7-1, f1);
return;
}
}
puts("Done");
}
输出
"9999979e3" and "9999978e3" both convert to 9.999978e+09
但是“浮子-文本-浮子”可以9吗?
在每个2的幂对之间,通常有2个不同的浮点数。当只使用8个有效十进制数字时,FP值1.000000954e 01
和下一个flot
1.000001049e 01
都转换为相同的文本。
更深层次:在8到16之间,由于FP数字的二进制编码,有2个不同的浮点数线性分布。其中1/8在10到11或1,048,576之间。仅使用10.xxxxxx
只能产生1,000,000种不同的文本。需要更多的十进制数字。
#include <math.h>
#include <stdio.h>
int float_to_text(float x0, float x1, int significant_digits) {
char s0[100];
char sn[100];
while (x0 <= x1) {
sprintf(s0, "%.*e", significant_digits-1, x0);
float xn = nextafterf(x0, x0*2); // next higher float
sprintf(sn, "%.*e", significant_digits-1, xn);
if (strcmp(s0,sn) == 0) {
printf("%2d significant_digits: %.12e and the next float %.12e both are \"%s\"\n",
significant_digits, x0, xn, s0);
fflush(stdout);
return 1;
}
x0 = xn;
}
return 0;
}
void float_to_text_test(float x0) {
int significant_digits = 5;
while (float_to_text(x0, x0*2, significant_digits)) {
significant_digits++;
}
printf("%2d significant digits needed %.*e to %.*e\n", //
significant_digits, significant_digits, x0, significant_digits, x0*2);
}
int main(void) {
float_to_text_test(8.0);
}
输出
5 significant_digits: 8.000000000000e+00 and the next float 8.000000953674e+00 both are "8.0000e+00"
6 significant_digits: 8.000000000000e+00 and the next float 8.000000953674e+00 both are "8.00000e+00"
7 significant_digits: 8.000009536743e+00 and the next float 8.000010490417e+00 both are "8.000010e+00"
8 significant_digits: 1.000000953674e+01 and the next float 1.000001049042e+01 both are "1.0000010e+01"
9 significant digits needed 8.000000000e+00 to 1.600000000e+01
考虑7位十进制浮点值9,999,979•103(9,999,979,000)和9,999,978•103(9,999,978,000)。当您将这些值转换为具有24位有效数的二进制浮点数时,在这两种情况下都会得到1001 0101 0000 0010 1110 0100•210(9,999,978,496),因为这是与每个数字最接近的二进制浮点值。(下一个较低和较高的二进制浮点数是1001 0101 0000 1110•210(9,999,977,472)和1001 0101 0000 0010 1110 0101•210(9,999,979,520)。)
因此,24位有效数无法区分所有具有七位有效数的十进制浮点数。我们最多可以做到六位。
考虑两个24位有效的二进制浮点值1111 1111 1111 1111 1111 1101•23(134,217,704)和1111 1111 1111 1111 1111 1100•23(134,217,696)。如果您将它们转换为具有八位有效数的十进制浮点数,则在这两种情况下都将获得13,421,770•101。然后您无法区分它们。因此您至少需要九个十进制数字。
你可以认为这是数字位置所迫使的一些“分块”。在十进制数的顶部,我们需要足够大的数字来超过第一位的5。但是,最接近的2的幂不一定会以该位置的5开头——它可能以6、7、8或9开头,所以它有一些浪费。在底部,我们需要比最后一位的1低一点。但是最接近的2的幂不一定以下一个较低位置的9开头。它可能以8、7、6甚至5开头。同样,还有一些浪费。从二进制到十进制再到二进制,你需要足够的十进制数字来适应浪费,所以你需要额外的十进制数字。从十进制到二进制再到十进制,你必须保持十进制数字足够少,这样它们加上浪费就适合二进制,所以你需要更少的十进制数字。
“文本-浮动-文本”保证6位但“浮动-文本-浮动”做9位的原因是什么?
>
从二进制FP的角度来看,1到9的前导十进制数字包含不同数量的信息量:1到3位。
其中flot
(0.125
、0.5
、2.0
1.6
等)和十进制文本("0.001"、"0.1"、"10.0"、"'1000.0"等)的绝对精度变化不同。
正是这两者的作用导致了摆动精度。
为了看到这一点,让我们使用鸽子洞原理。
对于具有n
有效十进制数字的文本,它的形式为
-1符号×1_to_9。(n-1)decimal_digits×10指数
C通常将flot
编码为binary32。大多数值的形式为:
-1sign×1。23_bit_fraction×2指数-偏移
文本-浮点
-文本
考虑一个“最坏情况”条件,其中文本包含大量信息——它的最高有效数字更接近9而不是1。
在[1.0e9…10.0e9)的范围内,并使用7位有效十进制数字,文本值间隔1000。
在选择范围[233…234)或[8.589934592e9…17.179869184e9)中,有223不同的浮点数
线性间隔1024。
9.999872000e9和
9.999744000e9可以完全编码为flot
和7位十进制文本。区别是
0.000128000e9或128,000。
它们之间有127个不同的7位十进制文本值和124个不同的flot
。如果代码尝试将所有127个文本值编码为flot
并返回相同的文本,它只会成功124次。
示例:“9.999851e9”和“9.999850e9”都转换为flot
9.999850496000e 09
相反,如果文本值只有6位有效的十进制数字,则往返始终有效。
浮点
-文本-浮点
考虑一个“最坏情况”条件,其中文本包含的信息很少——它的最高有效数字接近1而不是9。
在[8.0…16.0)范围内,有2个23或8,388,608个不同的浮点数
线性间隔。
在[10.0…11.0)范围内,有1/8×223或1,048,576个不同的浮点值
值。
在[10,000000…11,000000)范围内,使用8位有效十进制数字,有1,000,000个不同的文本值。
如果代码尝试将所有1,048,576个浮点数
值编码为只有8个十进制数字的文本,然后返回相同的浮点数
,它只会成功1,000,000次。
需要9位小数。