web-dev-qa-db-ja.com

DCで精度を失う

dcを使用して16進数のポイントを持つ16進数を処理したいのですが、精度の問題が発生しています。たとえば、以下ではF423F.FD100を掛けていますが、どちらも16進数です。期待される答えはF423FFDですが、代わりにF423FFA.E1を提供しています。丸めた後でも、近いですが正確ではありません。

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

私はdcは無制限の精度計算機であり、これは決して大きな数ではないことを読みました。私が間違っていることはありますか?

回答ありがとうございます。 dcの問題を考慮して、私は弾丸を噛み、他のベースの実数用に独自のパーサーを書きました。誰かがコードに興味があれば、ここに投稿できます。

12
Yimin Rong

元の数値を印刷するだけで丸められていることがわかります。

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

より多くの精度のために末尾のゼロをたくさん追加することで回避できます:

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

10進数として表現すると(dcを使用して変換)、これは999999.98(切り捨て)×256に対応しますつまり 255999994.88、つまり16進数のF423FFA.E1です。

したがって、違いはdcの丸め動作にあります。256×(999999 + 253÷256)を計算する代わりに、255999997を与える代わりに、253÷256を丸め、結果を乗算します。

dc任意精度計算機です。つまり、任意の精度で計算できますが、それが何であるかを伝える必要があります。デフォルトでは、その精度は0です。つまり、除算は整数値のみを生成し、乗算は入力の桁数を使用します。精度を設定するには、kを使用します(精度は、入力または出力の基数に関係なく、常に10進数で表現されることに注意してください)。

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

(10進数で1÷256を表すために必要なのは8桁の精度で十分です。)

8
Stephen Kitt

問題

問題は、dc(およびbc)が数値定数を理解する方法です。
たとえば、値(16進数)_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にも存在します。

_$ 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
_

ビットあたり1桁ですか?

浮動小数点数の非常に直感的でない事実は、必要な桁数(ドットの後に)がバイナリビットの数(これもドットの後ろにある)と等しいことです。 2進数0.101は、10進数で0.625と正確に等しくなります。 2進数0.0001110001は(正確に)_0.1103515625_(10進数で10桁)に等しい

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

また、2 ^(-10)のような浮動小数点数の場合、2進数では1(セット)ビットしかありません。

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

2進数の数_.0000000001_(10)は、10進数_.0009765625_(10)と同じです。他のベースではそうではないかもしれませんが、ベース10はdcとbcの両方の数値の内部表現であり、したがって、私たちが本当に気にする必要がある唯一のベースです。

数学の証明はこの答えの終わりにあります。

bCスケール

ドットの後の桁数は、組み込み関数scale() form bcでカウントできます。

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

示されているように、定数_0.FD_を表すには2桁では不十分です。

また、ドットの後に使用されている文字数を数えるだけでは、数値のスケールを報告(および使用)するのに非常に不適切な方法です。数値のスケール(任意の基数)は、必要なビット数を計算する必要があります。

16進浮動小数点数の2進数。

知られているように、各16進数は4ビットを使用します。したがって、小数点の後の各16進数には4桁の2進数が必要です。これは、上記の(奇数?)という事実により、4桁の10進数も必要です。

したがって、_0.FD_のような数値では、8桁の10進数を正しく表現する必要があります。

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

ゼロを追加

計算は簡単です(16進数の場合):

  • ドットの後の16進数(h)の数を数えます。
  • hに4を掛けます。
  • h×4 - h = h × (4-1) = h × 3 = 3×hゼロを追加します。

シェルコード(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_)と一致させて、16進浮動小数点を内部10進表現に変換できます。または、他の基数に関する他の関数(桁数がそのような他の基数の基数10(bcおよびdcの内部)に関連して有限であると仮定)。 2のように (2,4,8,16、...)および5,10。

posix

Posix仕様では、次のように述べられています(bcの場合、dcはこれに基づいています)。

内部計算は、入力および出力の基数に関係なく、指定された10進数の桁数に対して、10進数であるかのように行われます。

しかし、「…指定された10進数の桁数」。 「10進数の内部計算」に影響を与えずに、「…数値定数を表すために必要な10進数の桁数」(上記のとおり)であると理解できます。

なぜなら:

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

上記で設定したように、bcは実際には50(「指定された10進数の桁数」)を使用していません。

分割された場合にのみ変換されます(50桁に展開する前に、定数_0.FD_を読み取るために2のスケールを使用するため、依然として正しくありません)。

_$ 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
_

この場合も、数値文字列(定数)の読み取りには正しいビット数を使用する必要があります。


数学の証明

2つのステップで:

2進分数は、a/2と書くことができます。

2進分数は、2の負の累乗の有限和です。

例えば:

_= 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進小数では、最後のビットの値は2です。-n、または1/2。この例では:2-11 または1/211

= 1/2 + 1/24 + 1/26 + 1/28 + 1/29 + 1/211 =(反転あり)

一般に、分母は2になる可能性があります 分子指数が2の正の値。次に、すべての項を単一の値a/2に組み合わせることができます。この例では:

= 28/ 211 + 27/ 211 + 25/ 211 + 2/ 211 + 22/ 211 + 1/211 =(2で表される11

=(28 + 27 + 25 + 2 + 22 + 1)/ 211 =(共通因子の抽出)

=(256 + 128 + 32 + 8 + 4 + 1)/ 211 =(値に変換)

= 429/211

すべてのバイナリ分数はb/10として表すことができます

A/2を掛ける 5/5、取得(a×5)/(2×5)=(a×5)/ 10 = b/10、ここでb = a×5。 n桁です。

この例では、次のようになります。

(429・511)/ 1011 = 20947265625/1011 = 0.20947265625

すべての2進数の小数は、同じ桁数の小数であることが示されています。

1
Isaac