質問

質問

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

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

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

答えてくれてありがとう。問題を考慮して、dc私は弾丸を壊し、他の基礎の間違いを解析するために独自のパーサーを作成しました。誰でもコードに興味がある場合は、ここに投稿できます。

答え1

10進表記法(dc変換用)で表現すると999999.98(捨て)×256に該当し、つまり255999994.88(16進 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

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

答え2

元の数字を印刷すると丸められたマークが付けられます。

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

より良い精度のために末尾のゼロをたくさん追加することでこの問題を解決できます。

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

答え3

質問

問題は、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

奇妙に見えるかもしれませんが、そうではありません(詳細は後で説明します)。
0 を追加すると、答えは正しい値に近づきます。

$ 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

各数字?

浮動小数点数の場合、非常に非直感的な事実は、必要なビット数(ドットの後)が2進数(やはりドットの後)に等しいということです。 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).0009765625は10進数桁(10)と同じです。これは他の進数には当てはまらないかもしれませんが、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正しく表示するには、10進数8桁が必要です。

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

0を加える

数学は簡単です(16進数の場合)。

  • hポイントの後の16進数の桁( )を数えます。
  • 4を掛けますh
  • 0を追加します 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の内部)に比べて有限であると仮定)。2i(2、4、8、16、...)、5,10などがあります。

POSIX

posix 仕様には次のように指定されています (bc の場合は dc の基盤である)。

入出力基数に関係なく、内部計算は指定された小数点以下の10進数でなければなりません。

ただし、「...指定された小数点以下の桁数」は、「内部小数点数の計算」に影響を与えることなく、「...数値定数を表すために必要な小数点以下の桁数」(上記と同じ)として理解できます。

なぜなら:

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

bcは上記で設定した50(「指定された小数点以下」)を実際には使用しません。

変換は分割した場合にのみ発生します。 (2の小数点を使用して定数を読み、0.FD50桁に拡張するため、まだ正しくありません。)

$ 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の負の数の二乗の有限和です。

たとえば、

= 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 = (0除去)

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 = (逆数を含む)

一般に、分母は2n、分子指数は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/2n5n / 5nを掛けて (a× 5n )/(2n × 5n ) = (a×5n ) /10n = b/10n求めます。ここでb = a× 5n。 n個の数字があります。

たとえば、次のようになります。

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

すべてのバイナリ10進数は、同じ桁数を持つ10進10進数であることが証明されています。

関連情報