为什么我们需要 std::numeric_limits::max_digits10?

Why do we need std::numeric_limits::max_digits10?

我知道浮点数在内存中是使用符号、指数和尾数形式表示的,它们具有有限的位数来表示每个部分,因此这会导致舍入错误。 本质上,假设我有一个浮点数,那么由于一定数量的位数,它基本上会使用舍入策略映射到最接近的可表示形式之一。

这是否意味着 2 个不同的浮点可以映射到相同的内存表示?如果是,那么我如何以编程方式避免它?

我遇到了这个std::numeric_limits<T>::max_digits10

它表示浮点数在从 float 到 text 再到 float 的往返过程中存活所需的最少位数。

在我编写的 C++ 程序中,这个往返发生在什么地方。据我所知,我有一个 float f1 存储在内存中(可能有舍入误差)并被读回。我可以直接在 C++ 程序中有另一个浮点变量 f2,然后可以将它与原始浮点 f1 进行比较。现在我的问题是在这个用例中我什么时候需要 std::numeric_limits::max_digits10 ?有没有任何用例可以解释我需要使用 std::numeric_limits::max_digits10 来确保我没有做错事。

谁能解释一下上面的场景?

Where does this round trip happens in a c++ program i write.

这取决于您编写的代码,但一个明显的地方是...您在代码中输入的任何浮点 文字

float f = 10.34529848505433;

f 会是那个数字吗?不,它将是该数字的近似值,因为 float 的大多数实现无法存储那么多的精度。如果您将文字更改为 10.34529848505432,则赔率很好 f 将具有相同的值。

这与往返本身无关。 standard defines max_digits10 purely in terms of going from decimal to float:

Number of base 10 digits required to ensure that values which differ are always differentiated.

暂时忘记精确表示,假装你有两位浮点数。位 0 为 1/2,位 1 为 1/4。假设您要将此数字转换为字符串,以便在解析字符串时生成原始数字。

您可能的数字是 0、1/4、1/2、3/4。很明显,您可以用小数点后两位数来表示所有这些,并得到相同的数字,因为在这种情况下表示是准确的。但是你能逃脱一个数字吗?

假设一半总是四舍五入,数字映射到 0、0.3、0.5、0.8。第一个和第三个数字是准确的,而第二个和第四个不是。那么当您尝试解析它们时会发生什么?

0.3 - 0.25 < 0.5 - 0.30.8 - 0.75 < 1 - 0.8。很明显,在这两种情况下,四舍五入都是可行的。这意味着您只需要小数点后一位就可以捕获我们设计的两位浮点数的值。

您可以将位数从 2 扩展到 53(对于 double),并添加一个指数来改变数字的比例,但概念完全相同。

您似乎将舍入(和精度损失)的两个来源与浮点数混淆了。

浮点表示法

第一个是由于浮点数 在内存中表示的方式 ,正如您刚才指出的那样,它使用二进制数作为尾数和指数。经典的例子是:

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 ; y = 10.000010

另一方面,使用 %.8fprintf 的精度提高到 8 位会给你。

 x = 10.00000954 ; y = 10.00001049

因此,如果您想使用 fprintfofstream 以默认位数将这两个浮点值作为文本保存到文件中,您可能已经在最初的位置保存了相同的值两次xy.

有两个不同的值

max_digits10 是问题“我需要写多少个小数位才能避免所有可能值出现这种情况的答案?”。换句话说,如果你用 max_digits10 数字写你的浮点数(对于浮点数来说恰好是 9)然后加载它,你 保证 得到与你相同的值开始于。

注意写入的十进制值可能与浮点数的实际值不同(由于表示不同。但还是保证比你读取的文本将小数转换成 float 你会得到相同的值。

编辑:一个例子

查看那里的代码 runt: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 的值,但 而不是 [=34 的值=] 向下舍入。这意味着我们 需要 使用 max_digits10 如果我们想确保不同的浮动可以往返文本并保持不同。

Why do we need std::numeric_limits::max_digits10?

要知道有多少 有效小数 数字可以将 浮点数 类型转换为该类型所有可能值的文本。


Does this mean that 2 different floating points can get mapped to same memory representation? If yes, then how can i avoid it programmatically?

不,不同的浮点对象不同,会有不同的编码。

是的,在 text 中不同的不同浮点 code 可能映射到相同的内存表示。 x1, x2下面肯定有相同的编码。一个 32 位 float 只能编码大约 232 个不同的值。许多不同的浮点常量映射到相同的 float.

float x1 = 1.000000000000000001f;
float x2 = 1.000000000000000001000000000000000001f;
assert(x1 == x2);

Where does this round trip happens in a c++ program i write. Now my question is when will i need std::numeric_limits::max_digits10 in this use case? Is there any use case which explains that i need to use std::numeric_limits::max_digits10 to ensure that i don't do things wrong.

如果代码将 浮点数 x 转换为字符串 s 然后返回 浮点数 y,那么就是关注的往返

要使 x == y 成立,则 s 应至少包含 max_digits10 个有效的小数位,以适用于所有 x.

有效小数位数少于 max_digits10 个,x == y 可能对某些 x 仍然适用,但并非全部。

有超过 max_digits10 个有效小数位,x == y 对所有 x 都是正确的,但 s 变得不必要地长。


有效小数位数

有效数字开始计数是不是.右边的数字,而是从最重要的非零数字开始计数。以下所有代码或文本均具有 9 位有效小数位。

1.23456789
12345.6789
123456789.
123456789f
1.23456789e10
1.23456789e-10
-1.23456789
12345.0000
00012345.6789