直流精度损失


12

我想dc用十六进制点来处理一些基数为16的数字,但是我遇到了精度问题。例如,下面,我乘F423F.FD100,无论是十六进制。预期的答案是F423FFD,反而给出F423FFA.E1接近,但即使四舍五入也不够准确。

$ dc
16 d i o F423F.FD 100 * p
F423FFA.E1

我读到那dc是一个无限精度的计算器,无论如何这都不是很大的数字。我做错什么了吗?

感谢您的回答。鉴于存在的问题dc,我忍无可忍,并编写了自己的解析器,用于其他基数的实数。如果有人对代码感兴趣,可以在此处发布。

Answers:


6

请注意,仅打印原始数字会显示它是四舍五入的:

$ dc <<<'16 d i o F423F.FD p'
F423F.FA

您可以通过添加很多尾随零来解决它,以提高精度:

$ dc <<<'16 d i o F423F.FD000000 100 * p'
F423FFD.0000000

谢谢。我认为最终将花费更多代码来按摩这些数字以dc供使用,然后直接编写一个解析器!(输入可能有也可能没有小数,并且可以使用其他基数,因此填充量会有所不同。)
Yimin Rong

2
我将其标记为已接受的答案。负责维护的人员dc回答:正确处理非十进制小数位数将需要与dc和bc使用的十进制标度模型完全不同的模型(由POSIX规定bc,以及两者的历史传统都规定)。,因此从技术上讲可以将其固定在中dc,但可能会bc损坏,因此分类为WONTFIX。
荣荣民'18

8

用十进制表示(dc用于转换),它对应于999999.98(向下舍入)×256, 255999994.88,十六进制的F423FFA.E1。

因此,差异来自于dc四舍五入的行为:不是计算256×(999999 + 253÷256)(得出255999997),而是将253÷256向下舍入并乘以结果。

dc是一个任意精度的计算器,这意味着它可以计算出所需的任何精度,但是您必须告诉它是什么。默认情况下,其精度为0,这意味着除法仅生成整数值,并且乘法使用输入中的位数。要设置精度,请使用k(请记住,精度始终以十进制数字表示,而不管输入或输出基数如何):

10 k
16 d i o
F423FFD 100 / p
F423F.FD0000000
100 * p
F423FFD.000000000

(8位数字的精度就足够了,因为这需要用十进制表示1÷256。)


1
对于“任意精度”计算器,这似乎是完全出乎意料的结果?
益民荣

3
k设置时,它仍然会失去精度:10 k 16 d i o F423F.FD pF423F.FA,所以我必须先放大所有数字,然后才能在中使用它们dc。无论如何,基本上等于预先准备它们。
Yimin Rong

2
@Yimin是的,很不幸,dc仅使用数字来缩放其输入,这对我来说似乎是个错误(因为数字是使用输入的基数计算的,但应用于十进制值)。
史蒂芬·基特

1
POSIX指定的 @dhag (针对bcdc基于):“内部计算应以十进制方式进行,而不管输入和输出的底数如何,以指定的十进制数字表示。”
史蒂芬·基特

1
这确实是一个如何解析常量的问题。尝试20 k 16 d i o 0.3 1 / p (打印.19999999999999999)。记者了解到,由于操作只是将0.2通过1(这在理论上不应该改变的值)。While 20 k 16 d i o 0.3000 1 / p(正确)打印.30000000000000000。(续)
NotAnUnixNazi

1

问题

问题是dc(和bc)理解数字常数的方式。
例如,值(十六进制)0.3(除以1)将转换为接近0.2

$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999

实际上,普通常量0.3也发生了变化:

$ dc <<<"20 k 16 d i o     0.3     p"
.1

似乎这是一种奇怪的方式,但事实并非如此(稍后)。
添加更多零将使答案接近正确的值:

$ dc <<<"20 k 16 d i o     0.30     p"
.2E

$ dc <<<"20 k 16 d i o     0.300     p"
.2FD

$ dc <<<"20 k 16 d i o     0.3000     p"
.3000

最后一个值是精确的,并且无论添加多少个零都将保持精确。

$ dc <<<"20 k 16 d i o     0.30000000     p"
.3000000

问题也出现在公元前:

$ bc <<< "scale=20; obase=16; ibase=16;    0.3 / 1"
.19999999999999999

$ bc <<< "scale=20; obase=16; ibase=16;    0.30 / 1"
.2E147AE147AE147AE

$ bc <<< "scale=20; obase=16; ibase=16;    0.300 / 1"
.2FDF3B645A1CAC083

$ bc <<< "scale=20; obase=16; ibase=16;    0.3000 / 1"
.30000000000000000

每位一位?

浮点数的一个非常不直观的事实是,所需的位数(点后)等于二进制位数(也在点后)。二进制数0.101等于十进制的0.625。二进制数0.0001110001(完全)等于0.1103515625(十个十进制数字)

$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890

另外,对于像2 ^(-10)这样的浮点数,二进制格式中只有一个(设置)位:

$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000

具有与.0000000001十进制数字.0009765625(10)相同的二进制数字(10)。在其他基数中可能不是这种情况,但是基数10是dc和bc中数字的内部表示,因此是我们真正需要关心的唯一基数。

数学证明在此答案的结尾。

公元规模

可以通过内置函数scale()bc 来计算点后的位数:

$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1

如图所示,2位数字不足以表示常数0.FD

而且,仅计算点后使用的字符数是报告(和使用)数字比例的一种非常不正确的方法。数字的小数位数(以任何基数为单位)应计算所需的位数。

十六进制浮点数中的二进制数字。

众所周知,每个十六进制数字使用4位。因此,小数点后的每个十六进制数字都需要4个二进制数字,由于上述(奇数)事实,这也需要4个十进制数字。

因此,像这样的数字0.FD将需要8个十进制数字才能正确表示:

$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000

加零

数学很简单(对于十六进制数字):

  • 计算h点后的十六进制数字()。
  • 乘以h4。
  • h×4 - h = h × (4-1) = h × 3 = 3×h零。

在shell代码中(用于sh):

a=F423F.FD
h=${a##*.}
h=${#h}
a=$a$(printf '%0*d' $((3*h)) 0)
echo "$a"

echo "obase=16;ibase=16;$a*100" | bc

echo "20 k 16 d i o $a 100 * p" | dc

哪个会打印(在dc和bc中都正确显示):

$  sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000

在内部,bc(或dc)可以使所需的位数与上述(3*h)计算的位数相匹配,以将十六进制浮点数转换为内部十进制表示形式。或其他基数的某些其他功能(假设相对于该其他基数的基数10(bc和dc的内部),数字位数是有限的。就像2 i(2,4,8,16,...)和5,10。

posix

posix规范指出(对于bc,dc基于):

不管输入和输出的底数如何,内部计算都应以十进制形式进行,直到指定的十进制位数。

但是“…指定的十进制数字”。可以理解为“ ...表示数字常量所需的十进制数字”(如上所述),而不会影响“十进制内部计算”

因为:

bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA

bc并没有真正使用上面设置的50(“指定的十进制位数”)。

仅当被除数转换时(仍然会错误地进行转换,因为它使用2的0.FD小数位数在将其扩展为50位之前读取常数):

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A

但是,这是正确的:

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000

同样,读取数字字符串(常量)应使用正确的位数。


数学证明

分两步:

二进制分数可以写为a / 2 n

二进制分数是两个负幂的有限和。

例如:

= 0.00110101101 = 
= 0. 0     0      1     1      0      1     0      1      1     0       1

= 0 + 0×2 -1 + 0×2 -2 + 1×2 -3 + 1×2 -4 + 0×2 -5 + 1×2 -6 + 0×2 -7 + 1×2 -8 + 1×2 -9 + 0×2 -10 + 1×2 -11

= 2 -3 + 2 -4 + 2 -6 + 2 -8 + 2 -9 + 2 -11 =(去掉了零)

在n位的二进制分数中,最后一位的值为2 -n或1/2 n。在此示例中:2 -11或1/2 11

= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 =(反)

通常,分母可以变为2 n,分子的正指数为2。然后可以将所有项组合为一个单个值a / 2 n。对于此示例:

= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 =(2表示11

=(2 8 + 2 7 + 2 5 + 2 3 + 2 2 +1)/ 2 11 =(提取公因数)

=(256 + 128 + 32 + 8 + 4 +1)/ 2 11 =(转换为值)

= 429/2 11

每个二进制分数都可以表示为b / 10 n

将a / 2 n乘以5 n / 5 n,得到(a×5 n)/(2 n ×5 n)=(a×5 n)/ 10 n = b / 10 n,其中b = a×5 n。它有n位数字。

例如,我们有:

(429·5 11)/ 10 11 = 20947265625/10 11 = 0.20947265625

已经证明,每个二进制分数都是具有相同位数的十进制分数。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.