23

我遇到了两种不同的浮点数精度公式。

⌊(N-1) log 10 (2)⌋ = 6 位十进制数字(单精度)

N log 10 (2) ≈ 7.225 个十进制数字(单精度)

其中N = 24 个有效位(单精度)

第一个公式位于W. Kahan 教授撰写的“ IEEE Standard 754 for Binary Floating-Point Arithmetic ”的第 4 页顶部。

第二个公式可在IEEE 754 单精度二进制浮点格式部分下的 Wikipedia 文章“单精度浮点格式”中找到: binary32 。

对于第一个公式,W. Kahan 教授说

如果一个十进制字符串最多有 6 个 sig。十二月 转换为 Single,然后转换回相同数量的 sig。dec.,那么最终的字符串应该与原始字符串匹配。

对于第二个公式,维基百科说

...总精度为 24 位(相当于 log 10 (2 24 ) ≈ 7.225 decimal digits)。

两个公式的结果(6 位和 7.225 位十进制数字)是不同的,我希望它们是相同的,因为我认为它们都是为了表示可以转换为浮点二进制然后再转换回来的最重要的十进制数字以与它开始时相同的有效小数位数进行十进制。

为什么这两个数字不同,可以转换为二进制并返回十进制而不丢失重要性的最重要的十进制数字精度是多少?

4

3 回答 3

15

这些是在谈论两个略有不同的事情。

7.225 1位是数字可以在内部存储的精度。例如,如果您使用双精度数进行计算(因此您从 15 位精度开始),然后将其四舍五入为单精度数,那么您在该点留下的精度大约为7位数。

6位数字是指通过从十进制数字字符串到浮点数,然后再返回到另一个十进制数字字符串的往返转换可以保持的精度。

所以,假设我从一个像1.23456789字符串这样的数字开始,然后将其转换为 float32,然后将结果转换回字符串。完成此操作后,我可以期望 6 位数字完全匹配。第七位数字可能是四舍五入的,所以我不一定期望它匹配(尽管它可能是原始字符串的 +/- 1。

例如,考虑以下代码:

#include <iostream>
#include <iomanip>

int main() {
    double init = 987.23456789;
    for (int i = 0; i < 100; i++) {
        float f = init + i / 100.0;
        std::cout << std::setprecision(10) << std::setw(20) << f;
    }
}

这将生成如下表:

     987.2345581         987.2445679         987.2545776         987.2645874
     987.2745972         987.2845459         987.2945557         987.3045654
     987.3145752          987.324585         987.3345947         987.3445435
     987.3545532          987.364563         987.3745728         987.3845825
     987.3945923          987.404541         987.4145508         987.4245605
     987.4345703         987.4445801         987.4545898         987.4645386
     987.4745483         987.4845581         987.4945679         987.5045776
     987.5145874         987.5245972         987.5345459         987.5445557
     987.5545654         987.5645752          987.574585         987.5845947
     987.5945435         987.6045532          987.614563         987.6245728
     987.6345825         987.6445923          987.654541         987.6645508
     987.6745605         987.6845703         987.6945801         987.7045898
     987.7145386         987.7245483         987.7345581         987.7445679
     987.7545776         987.7645874         987.7745972         987.7845459
     987.7945557         987.8045654         987.8145752          987.824585
     987.8345947         987.8445435         987.8545532          987.864563
     987.8745728         987.8845825         987.8945923          987.904541
     987.9145508         987.9245605         987.9345703         987.9445801
     987.9545898         987.9645386         987.9745483         987.9845581
     987.9945679         988.0045776         988.0145874         988.0245972
     988.0345459         988.0445557         988.0545654         988.0645752
      988.074585         988.0845947         988.0945435         988.1045532
      988.114563         988.1245728         988.1345825         988.1445923
      988.154541         988.1645508         988.1745605         988.1845703
     988.1945801         988.2045898         988.2145386         988.2245483

如果我们看一下,我们可以看到前六位有效数字始终精确地遵循该模式(即,每个结果都比其前一个结果正好大 0.01)。正如我们在原始文件中看到的,该值实际上是98x.xx456——double但是当我们将单精度浮点数转换为十进制时,我们可以看到第 7位数字经常不会被正确读回——因为随后digit 大于 5,它应该四舍五入到 98x.xx46,但有些值不会(例如,第一列中倒数第二个项目是,它会向下舍入而不是向上舍入,所以我们' d 以 98x.xx45 结束,而不是988.15454146. 因此,即使该值(如存储的)精确到 7 位(加上一点),但当我们通过转换为十进制并返回该值时,我们不能依赖与任何精确匹配的第七位更多(即使有足够的精度,它会经常出现)。


1. 这基本上意味着 7 位数字,而第 8数字将比没有更准确一点,但不是很多——例如,如果我们从一个 double 转换,精度1.2345678.225数字意味着最后一个数字将是从那里开始的大约 +/- .775 (而没有.225精度的数字,它基本上是从那里开始的 +/- 1)。

于 2015-06-06T23:48:37.300 回答
4
于 2015-06-15T19:24:26.803 回答
2

请记住,它们是完全相同的公式。记住你的高中数学书身份:

    Log(x^y) == y * Log(x)

它有助于使用计算器实际计算 N = 24 的值:

  Kahan's:      23 * Log(2) = 6.924
  Wikipedia's:   Log(2^24)  = 7.225

由于 floor(),Kahan 被迫将 6.924 截断为 6 位数,这很糟糕。唯一的实际区别是 Kahan 使用的精度少了 1 位。

很难猜出原因,教授可能依赖于旧笔记。写在 IEEE-754 之前,没有考虑到第 24 位精度是免费的。该格式使用了一个技巧,非 0 的浮点值的最高有效位始终为 1。因此不需要存储它。处理器在执行计算之前将其添加回来。将 23 位存储精度转换为 24 位有效精度。

或者他考虑到从十进制字符串到二进制浮点值的转换本身会产生错误。许多漂亮的四舍五入十进制值,例如 0.1,不能完美地转换为二进制。它有无穷无尽的位数,就像十进制的 1/3。然而,这会产生一个相差 +/- 0.5 位的结果,这是通过简单的舍入实现的。所以结果精确到 23.5 * Log(2) = 7.074 个十进制数字。如果他假设转换例程很笨拙并且没有正确舍入,那么结果可能会偏离 +/-1 位,并且 N-1 是合适的。他们并不笨拙。

或者他像一个典型的科学家或(天堂禁止)会计师那样思考,并希望将计算结果也转换回十进制。例如,当您简单地寻找一个 7 位十进制数,其来回转换不会产生相同的数字时,您会得到。是的,这增加了另一个 +/- 0.5 位错误,总计 1 位错误。

但是永远,永远不要犯那个错误,你总是必须在计算中包括你在计算中操纵数字时得到的任何错误。其中一些会很快丢失有效数字,尤其是减法非常危险。

于 2015-06-06T23:43:09.630 回答