你似乎混淆了浮点数的两种舍入(和精度损失)来源。
浮点表示
第一种是由于浮点数在内存中的表示方式,它使用二进制数来表示尾数和指数,就像你刚指出的那样。经典的例子是:
const float a = 0.1f;
const float b = 0.2f;
const float c = a+b;
printf("%.8f + %.8f = %.8f\n",a,b,c);
这将打印输出
0.10000000 + 0.20000000 = 0.30000001
在这里,数学上正确的结果是0.3,但是0.3无法用二进制表示。相反,你会得到最接近的可以表示的数字。
保存为文本
另一个场景,也就是max_digits10
发挥作用的地方,是浮点数的文本表示,例如使用printf
或写入文件时。
当你使用%f
格式说明符打印数字时,你将得到以十进制打印的数字。
在以十进制方式打印数字时,你可以决定打印出多少位小数。在某些情况下,你可能无法精确地打印出实际数字。
例如,考虑以下内容:
const float x = 10.0000095f;
const float y = 10.0000105f;
printf("x = %f ; y = %f\n", x,y);
这将会打印出来
x = 10.000010
另一方面,使用%.8f
将printf
的精度提高到8位小数会给你。
x = 10.00000954
如果您想使用fprintf
或ofstream
将这两个浮点值保存为文本文件,并且使用默认小数位数,则可能会出现保存相同值两次的情况,即使原来的x
和y
有两个不同的值。
max_digits10
是回答“我需要写多少位小数才能避免所有可能的值?”的答案。换句话说,如果您使用max_digits10
位数(对于浮点数而言,这恰好是9)编写您的浮点数并将其加载回来,则保证获得与开始时相同的值。
请注意,所写的十进制值可能与浮点数的实际值不同(由于不同的表示)。但是,当您将十进制数的文本读入float
时,您将获得相同的值。
编辑:一个例子
查看代码运行结果:https://ideone.com/pRTMZM
假设您有之前提到的两个float
:
const float x = 10.0000095f;
const float y = 10.0000105f;
如果你想将它们保存为文本(一个典型的用例是保存到可读性格式,如XML或JSON,甚至使用打印进行调试),那么你需要进行翻译。在我的示例中,我将使用stringstream
将其写入字符串。
首先让我们尝试默认精度:
stringstream def_prec;
def_prec << x <<" "<<y;
// What was written ?
cout <<def_prec.str()<<endl;
在这种情况下的默认行为是在写入文本时将我们的每个数字四舍五入为
10
。因此,如果我们使用该字符串读取回另外两个浮点数,它们将
不会包含原始值:
float x2, y2;
def_prec>>x2 >>y2;
// Check
printf("%.8f vs %.8f\n", x, x2);
printf("%.8f vs %.8f\n", y, y2);
同时会打印出以下结果
10 10
10.00000954 vs 10.00000000
10.00001049 vs 10.00000000
这个从浮点数到文本再返回的过程中丢失了很多可能是重要的数字。显然,我们需要以更高的精度将值保存为文本。文档保证使用 max_digits10
不会在这个过程中丢失数据。让我们试试使用 setprecision
:
const int digits_max = numeric_limits<float>::max_digits10;
stringstream max_prec;
max_prec << setprecision(digits_max) << x <<" "<<y;
cout <<max_prec.str()<<endl;
现在将会打印出来
10.0000095 10.0000105
所以这次我们的值保存了更多的数字。让我们尝试读取回来:
float x2, y2;
max_prec>>x2 >>y2;
printf("%.8f vs %.8f\n", x, x2);
printf("%.8f vs %.8f\n", y, y2);
打印哪一个
10.00000954 vs 10.00000954
10.00001049 vs 10.00001049
啊哈!我们找回了我们的数值!
最后,让我们看看如果我们使用比max_digits10
少一个数字会发生什么。
stringstream some_prec;
some_prec << setprecision(digits_max-1) << x <<" "<<y;
cout <<some_prec.str()<<endl;
这是我们以文本形式保存的内容。
10.00001 10.00001
并且我们读取回来:
10.00000954 vs 10.00000954
10.00001049 vs 10.00000954
因此,在这里,精度足以保持x
的值,但不能保留y
的值,因为它被向下舍入。这意味着我们需要使用max_digits10
,如果想确保不同的浮点数可以进行往返转换并保持不同。
max_digits10
有用,那它必须在你想出的情况下有用的局面。这不是发现某物的好方法。也许你会有运气找到一个有效的用例,但更可能不会。通过将这些草率的假设投入你的问题中,你限制了可以得到的响应类型。 - JaMiT