DCによる精度の低下


12

dc16進数で16進数を処理するために使用したいのですが、精度の問題に直面しています。たとえば、以下では、両方を16進数で乗算F423F.FDしてい100ます。予想される答えはF423FFD、代わりに、与えることですが、F423FFA.E1丸めても十分に正確ではありません。

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

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

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

回答:


6

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

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

精度を高めるために、後続のゼロを多数追加することで回避できます。

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

ありがとう。dc使用する数値をマッサージするために、パーサーを直接書くために、より多くのコードが必要になると思います!(入力には小数がある場合とない場合があり、他の
Rong

2
これを承認済みの回答としてマークします。dc回答を維持する責任者:10進数以外の小数桁を適切に処理するには、dcおよびbcで使用される10進スケールモデルとはまったく異なるモデルが必要になります(bcのPOSIX、および両方の歴史的伝統により規定)。、技術的にはで修正できますdcが、それはおそらく壊れてしまいbc、WONTFIXとして分類されます。
イーミンロン

8

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

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


1
それは、「任意の精度」の計算機にとってはまったく予期しない結果のように思えますか?
義民栄

3
k設定すると精度が失われます:10 k 16 d i o F423F.FD pF423F.FA、で使用する前にすべての数値を拡大する必要がありdcます。基本的に、とにかくそれらを事前解析することになります。
義民栄

2
@Yiminはい、残念ながらdc桁数のみを使用して入力をスケーリングしますが、これはバグのようです(桁数は入力基数を使用して計算されますが、小数値に適用されるため)。
スティーブンキット

1
@dhag これはPOSIXが指定するものです(for bcdc基づいています):「内部計算は、指定された10進数の桁まで、入力ベースと出力ベースに関係なく、10進数で行われます。」
スティーブンキット

1
それは本当に定数がどのように解析されているかという問題です。試してください20 k 16 d i o 0.3 1 / p (.19999999999999999を出力します)。操作が単に除算0.2していることを理解して1ください(理論上は値を変更すべきではありません)。一方で20 k 16 d i o 0.3000 1 / p(正確に)プリント.30000000000000000。(続き)
アイザック

1

問題

問題は、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)のような浮動小数点数の場合、バイナリでは1(セット)ビットのみです:

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

.000000000110 進数(10)と同じ数の2進数(10)を持ってい.0009765625ます。他の基数ではそうではないかもしれませんが、基数10はdcとbcの両方の数値の内部表現であるため、実際に注意する必要がある唯一の基数です。

数学の証明は、この答えの最後にあります。

bcスケール

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

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

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

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

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進数の場合):

  • hドットの後の16進数()の数を数えます。
  • h4を掛けます。
  • 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*h16進浮動小数点数を内部10進表現に変換するために、必要な桁数を上記で計算された数()と一致させることができます。または、他の基数に対する他の関数(そのような他の基数の基数10(bcおよびdcの内部)に関して桁数が有限であると仮定します)。2 i(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進数の桁数)を使用していません。

分割された場合にのみ変換されます(0.FD50桁に拡張する前に定数を読み取るために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つのステップで:

バイナリ分数は、a / 2 nとして記述できます。

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 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 の正の分子指数で2 nになります。すべての項は、単一の値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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.