可以转换为二进制并返回十进制而不损失重要性的最重要的十进制数字精度是 6 还是 7.225?
Is the most significant decimal digits precision that can be converted to binary and back to decimal without loss of significance 6 or 7.225?
我遇到了两个不同精度的浮点数公式。
⌊(N-1) log10(2)⌋ = 6 decimal digits (Single-precision)
和
N log10(2) ≈ 7.225 decimal digits (Single-precision)
其中 N = 24 有效位(单精度)
第一个公式位于“IEEE Standard 754 for Binary Floating-Point Arithmetic”第 4 页的顶部,作者是 W. Kahan 教授。
第二个公式可在维基百科文章“Single-precision floating-point format”的第 IEEE 754 单精度二进制浮点格式下找到:binary32.
对于第一个公式,W. Kahan 教授说
If a decimal string with at most 6 sig. dec. is converted to Single and then converted back to the same number of sig. dec.,
then the final string should match the original.
对于第二个公式,维基百科说
...the total precision is 24 bits (equivalent to log10(224) ≈ 7.225 decimal digits).
两个公式(6 位和 7.225 位十进制数字)的结果不同,我希望它们是相同的,因为我假设它们都表示可以转换为浮点数的最重要的十进制数字二进制,然后转换回十进制,并使用与它开头相同的有效小数位数。
为什么这两个数字不同,可以转换为二进制并返回十进制而不丢失意义的最重要的十进制数字精度是多少?
请记住,它们是完全相同的公式。记住你的高中数学书身份:
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 位错误。
但是永远,永远不要犯这样的错误,您总是必须 包括 在计算中处理数字时出现的任何错误。其中一些很快丢失有效数字,尤其是减法是非常危险的。
这些是在谈论两个略有不同的事情。
7.2251 位是数字可以内部存储的精度。例如,如果您使用双精度数进行计算(因此您从 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;
}
}
这会生成如下所示的 table:
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)。可以看到原来double
的值其实是98x.xx456——但是当我们把单精度浮点数转成十进制的时候,可以看到第7th 数字经常不会被正确读回——因为后续数字大于 5,它应该四舍五入到 98x.xx46,但有些值不会(例如,第一个中的倒数第二个项目)列是 988.154541
,它将向下舍入而不是向上舍入,所以我们最终得到 98x.xx45 而不是 46
。因此,即使值(存储的)精确到 7 位(加上一点),当我们通过转换为十进制并返回来往返该值时,我们不能再依赖于第七位数字的精确匹配(即使有足够的精度它会比不是)。
1. 这基本上意味着 7 位数字,第 8th 位比没有更准确,但不是很多——例如,如果我们从双倍的1.2345678
,.225
位精度意味着最后一位数字大约是开始时的 +/- .775(而没有 .225
位精度,它会基本上是开始时的 +/- 1)。
what is the most significant decimal digits precision that can be
converted to binary and back to decimal without loss of significance?
可以在不损失重要性的情况下(对于单精度浮点数或 24 位)可以转换为二进制并返回十进制的最高有效十进制数字精度为 6 位十进制数字。
Why do these two numbers differ...
数字 6 和 7.225 不同,因为它们定义了两个不同的事物。 6 是可以往返的最多的十进制数字。 7.225 是 24 位二进制整数的近似十进制位数精度,因为 24 位二进制整数可以有 7 位或 8 位小数位,具体取决于其特定值。
7.225 是使用特定的二进制整数公式找到的。
dspec = b·log10(2) (dspec
= specific decimal digits, b = bits)
但是,您通常需要知道的是 b 位整数的最小和最大十进制数字。以下公式用于查找特定二进制整数的最小和最大十进制数字(24 位分别为 7 和 8)。
dmin = ⌈(b-1)·log10(2)⌉ (dmin
= min decimal digits, b = bits, ⌈x⌉ = smallest integer ≥ x)
dmax = ⌈b·log10(2)⌉ (dmax
= max decimal digits, b = bits, ⌈x⌉ = smallest integer ≥ x)
要详细了解这些公式的推导方式,请阅读 Rick Regan 撰写的 Number of Decimal Digits In a Binary Integer。
这一切都很好,但是你可能会问,如果你说一个24位数字的小数位数跨度是7到8,为什么来回转换的小数位数最多是6?
答案是——因为上面的公式只适用于整数而不适用于浮点数!
每个十进制整数都有一个精确的二进制值。但是,不能对每个十进制浮点数都这样说。以 .1
为例。 .1
在二进制中是数字 0.000110011001100...
,这是一个重复或循环的二进制。这会产生舍入误差。
此外,表示一个十进制浮点数比表示一个同等重要的十进制整数多需要一位。这是因为浮点数越接近 0 越精确,越远离 0 越不精确。正因为如此,许多接近最小值和最大值范围的浮点数 (e min = -126 和 emax = +127 对于单精度)由于舍入误差而丢失 1 位精度。要直观地了解这一点,请查看 Josh Haberman 撰写的 What every computer programmer should know about floating point, part 1。
此外,至少有784,757
个正七位十进制数来回转换后不能保留原值。 8.589973e9
是无法在往返过程中幸存下来的此类数字的一个示例。这是不保留其原始值的最小正数。
这是您应该用于浮点数精度的公式,它将为您提供 6 位十进制数字以进行往返转换。
dmax = ⌊(b-1)·log10(2)⌋ (dmax
= max decimal digits, b = bits, ⌊x⌋ = largest integer ≤ x)
要详细了解此公式的推导方式,请阅读同样由 Rick Regan 撰写的 Number of Digits Required For Round-Trip Conversions。 Rick 出色地展示了公式的推导并参考了严格的证明。
因此,您可以以建设性的方式利用上述公式;如果您了解它们的工作原理,则可以将它们应用于任何使用浮点数据类型的编程语言。您所需要知道的只是您的浮点数据类型所具有的有效位数,并且您可以找到它们各自的小数位数,您可以指望这些位数在往返转换后不会丢失任何重要性。
2017 年 6 月 18 日更新: 我想在 Rick Regan 的新文章中包含一篇 link,这篇文章更详细,我认为比此处提供的任何答案。他的文章是“Decimal Precision of Binary Floating-Point Numbers" and can be found on his website www.exploringbinary.com.
我遇到了两个不同精度的浮点数公式。
⌊(N-1) log10(2)⌋ = 6 decimal digits (Single-precision)
和
N log10(2) ≈ 7.225 decimal digits (Single-precision)
其中 N = 24 有效位(单精度)
第一个公式位于“IEEE Standard 754 for Binary Floating-Point Arithmetic”第 4 页的顶部,作者是 W. Kahan 教授。
第二个公式可在维基百科文章“Single-precision floating-point format”的第 IEEE 754 单精度二进制浮点格式下找到:binary32.
对于第一个公式,W. Kahan 教授说
If a decimal string with at most 6 sig. dec. is converted to Single and then converted back to the same number of sig. dec., then the final string should match the original.
对于第二个公式,维基百科说
...the total precision is 24 bits (equivalent to log10(224) ≈ 7.225 decimal digits).
两个公式(6 位和 7.225 位十进制数字)的结果不同,我希望它们是相同的,因为我假设它们都表示可以转换为浮点数的最重要的十进制数字二进制,然后转换回十进制,并使用与它开头相同的有效小数位数。
为什么这两个数字不同,可以转换为二进制并返回十进制而不丢失意义的最重要的十进制数字精度是多少?
请记住,它们是完全相同的公式。记住你的高中数学书身份:
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 位错误。
但是永远,永远不要犯这样的错误,您总是必须 包括 在计算中处理数字时出现的任何错误。其中一些很快丢失有效数字,尤其是减法是非常危险的。
这些是在谈论两个略有不同的事情。
7.2251 位是数字可以内部存储的精度。例如,如果您使用双精度数进行计算(因此您从 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;
}
}
这会生成如下所示的 table:
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)。可以看到原来double
的值其实是98x.xx456——但是当我们把单精度浮点数转成十进制的时候,可以看到第7th 数字经常不会被正确读回——因为后续数字大于 5,它应该四舍五入到 98x.xx46,但有些值不会(例如,第一个中的倒数第二个项目)列是 988.154541
,它将向下舍入而不是向上舍入,所以我们最终得到 98x.xx45 而不是 46
。因此,即使值(存储的)精确到 7 位(加上一点),当我们通过转换为十进制并返回来往返该值时,我们不能再依赖于第七位数字的精确匹配(即使有足够的精度它会比不是)。
1. 这基本上意味着 7 位数字,第 8th 位比没有更准确,但不是很多——例如,如果我们从双倍的1.2345678
,.225
位精度意味着最后一位数字大约是开始时的 +/- .775(而没有 .225
位精度,它会基本上是开始时的 +/- 1)。
what is the most significant decimal digits precision that can be converted to binary and back to decimal without loss of significance?
可以在不损失重要性的情况下(对于单精度浮点数或 24 位)可以转换为二进制并返回十进制的最高有效十进制数字精度为 6 位十进制数字。
Why do these two numbers differ...
数字 6 和 7.225 不同,因为它们定义了两个不同的事物。 6 是可以往返的最多的十进制数字。 7.225 是 24 位二进制整数的近似十进制位数精度,因为 24 位二进制整数可以有 7 位或 8 位小数位,具体取决于其特定值。
7.225 是使用特定的二进制整数公式找到的。
dspec = b·log10(2) (dspec = specific decimal digits, b = bits)
但是,您通常需要知道的是 b 位整数的最小和最大十进制数字。以下公式用于查找特定二进制整数的最小和最大十进制数字(24 位分别为 7 和 8)。
dmin = ⌈(b-1)·log10(2)⌉ (dmin = min decimal digits, b = bits, ⌈x⌉ = smallest integer ≥ x)
dmax = ⌈b·log10(2)⌉ (dmax = max decimal digits, b = bits, ⌈x⌉ = smallest integer ≥ x)
要详细了解这些公式的推导方式,请阅读 Rick Regan 撰写的 Number of Decimal Digits In a Binary Integer。
这一切都很好,但是你可能会问,如果你说一个24位数字的小数位数跨度是7到8,为什么来回转换的小数位数最多是6?
答案是——因为上面的公式只适用于整数而不适用于浮点数!
每个十进制整数都有一个精确的二进制值。但是,不能对每个十进制浮点数都这样说。以 .1
为例。 .1
在二进制中是数字 0.000110011001100...
,这是一个重复或循环的二进制。这会产生舍入误差。
此外,表示一个十进制浮点数比表示一个同等重要的十进制整数多需要一位。这是因为浮点数越接近 0 越精确,越远离 0 越不精确。正因为如此,许多接近最小值和最大值范围的浮点数 (e min = -126 和 emax = +127 对于单精度)由于舍入误差而丢失 1 位精度。要直观地了解这一点,请查看 Josh Haberman 撰写的 What every computer programmer should know about floating point, part 1。
此外,至少有784,757
个正七位十进制数来回转换后不能保留原值。 8.589973e9
是无法在往返过程中幸存下来的此类数字的一个示例。这是不保留其原始值的最小正数。
这是您应该用于浮点数精度的公式,它将为您提供 6 位十进制数字以进行往返转换。
dmax = ⌊(b-1)·log10(2)⌋ (dmax = max decimal digits, b = bits, ⌊x⌋ = largest integer ≤ x)
要详细了解此公式的推导方式,请阅读同样由 Rick Regan 撰写的 Number of Digits Required For Round-Trip Conversions。 Rick 出色地展示了公式的推导并参考了严格的证明。
因此,您可以以建设性的方式利用上述公式;如果您了解它们的工作原理,则可以将它们应用于任何使用浮点数据类型的编程语言。您所需要知道的只是您的浮点数据类型所具有的有效位数,并且您可以找到它们各自的小数位数,您可以指望这些位数在往返转换后不会丢失任何重要性。
2017 年 6 月 18 日更新: 我想在 Rick Regan 的新文章中包含一篇 link,这篇文章更详细,我认为比此处提供的任何答案。他的文章是“Decimal Precision of Binary Floating-Point Numbers" and can be found on his website www.exploringbinary.com.