Masalah
Masalahnya adalah cara dc (dan bc) memahami konstanta numerik.
Misalnya, nilai (dalam hex) 0.3
(dibagi dengan 1) ditransformasikan menjadi nilai yang mendekati0.2
$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999
Bahkan, konstanta polos 0.3
juga akan berubah:
$ dc <<<"20 k 16 d i o 0.3 p"
.1
Tampaknya itu dalam cara yang aneh, tetapi tidak (lebih kemudian).
Menambahkan lebih banyak nol membuat jawaban mendekati nilai yang benar:
$ 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
Nilai terakhir adalah tepat dan akan tetap tepat tidak peduli berapa banyak nol yang ditambahkan.
$ dc <<<"20 k 16 d i o 0.30000000 p"
.3000000
Masalahnya juga ada pada 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
Satu digit per bit?
Fakta yang sangat tidak intuitif untuk angka floating point adalah bahwa jumlah digit yang diperlukan (setelah titik) sama dengan jumlah bit biner (juga setelah titik). Angka biner 0,101 persis sama dengan 0,625 dalam desimal. Angka biner 0,0001110001 adalah (persis) sama dengan 0.1103515625
(sepuluh digit desimal)
$ bc <<<'scale=30;obase=10;ibase=2; 0.101/1; 0.0001110001/1'; echo ".1234567890"
.625000000000000000000000000000
.110351562500000000000000000000
.1234567890
Juga, untuk angka floating point seperti 2 ^ (- 10), yang dalam biner hanya memiliki satu (set) bit:
$ bc <<<"scale=20; a=2^-10; obase=2;a; obase=10; a"
.0000000001000000000000000000000000000000000000000000000000000000000
.00097656250000000000
Memiliki jumlah digit biner yang sama .0000000001
(10) dengan digit desimal .0009765625
(10). Itu mungkin tidak terjadi di pangkalan lain tetapi basis 10 adalah representasi internal angka di kedua dc dan bc dan oleh karena itu adalah satu-satunya pangkalan yang benar-benar perlu kita pedulikan.
Bukti matematika ada di akhir jawaban ini.
skala bc
Jumlah digit setelah titik dapat dihitung dengan scale()
bentuk fungsi bawaan bc:
$ bc <<<'obase=16;ibase=16; a=0.FD; scale(a); a; a*100'
2
.FA
FA.E1
Seperti yang ditunjukkan, 2 digit tidak cukup untuk mewakili konstanta 0.FD
.
Dan, juga, hanya menghitung jumlah karakter yang digunakan setelah titik adalah cara yang sangat salah untuk melaporkan (dan menggunakan) skala angka. Skala angka (dalam basis apa pun) harus menghitung jumlah bit yang dibutuhkan.
Digit biner dalam float hex.
Seperti diketahui, setiap digit hex menggunakan 4 bit. Oleh karena itu, setiap digit hex setelah titik desimal membutuhkan 4 digit biner, yang karena fakta (ganjil?) Di atas juga membutuhkan 4 digit desimal.
Oleh karena itu, angka seperti 0.FD
akan membutuhkan 8 angka desimal untuk diwakili dengan benar:
$ bc <<<'obase=10;ibase=16;a=0.FD000000; scale(a);a;a*100'
8
.98828125
253.00000000
Tambahkan nol
Perhitungannya mudah (untuk angka hex):
- Hitung jumlah digit hex (
h
) setelah titik.
- Kalikan
h
dengan 4.
- Tambahkan
h×4 - h = h × (4-1) = h × 3 = 3×h
nol.
Dalam kode shell (untuk 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
Yang akan mencetak (dengan benar dalam dc dan bc):
$ sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000
Secara internal, bc (atau dc) dapat membuat jumlah digit yang diperlukan cocok dengan angka yang dihitung di atas ( 3*h
) untuk mengkonversi hex floats ke representasi desimal internal. Atau beberapa fungsi lain untuk basis lain (dengan asumsi jumlah digit terbatas dalam kaitannya dengan basis 10 (internal bc dan dc) di basis lain tersebut). Seperti 2 saya (2,4,8,16, ...) dan 5,10.
posix
Spesifikasi posix menyatakan bahwa (untuk bc, berdasarkan as mana):
Perhitungan internal harus dilakukan seolah-olah dalam desimal, terlepas dari basis input dan output, dengan jumlah digit desimal yang ditentukan.
Tapi "... jumlah digit desimal yang ditentukan." dapat dipahami sebagai "... jumlah digit desimal yang diperlukan untuk mewakili konstanta angka" (seperti dijelaskan di atas) tanpa memengaruhi "perhitungan internal desimal"
Karena:
bc <<<'scale=50;obase=16;ibase=16; a=0.FD; a+1'
1.FA
bc tidak benar-benar menggunakan 50 ("jumlah digit desimal yang ditentukan") seperti yang ditetapkan di atas.
Hanya jika dibagi itu dikonversi (masih salah karena menggunakan skala 2 untuk membaca konstanta 0.FD
sebelum memperluasnya menjadi 50 digit):
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A
Namun, ini tepat:
$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000
Sekali lagi, membaca string numerik (konstanta) harus menggunakan jumlah bit yang benar.
Bukti matematika
Dalam dua langkah:
Fraksi biner dapat ditulis sebagai / 2 n
Fraksi biner adalah jumlah terbatas dari kekuatan negatif dua.
Sebagai contoh:
= 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 = (dengan nol dihilangkan)
Dalam fraksi biner dari n bit, bit terakhir memiliki nilai 2 -n , atau 1/2 n . Dalam contoh ini: 2 -11 atau 1/2 11 .
= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (dengan terbalik)
Secara umum, penyebut bisa menjadi 2 n dengan eksponen pembilang positif dua. Semua istilah kemudian dapat digabungkan menjadi nilai tunggal a / 2 n . Untuk contoh ini:
= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (dinyatakan dengan 2 11 )
= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (mengekstraksi faktor umum)
= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (dikonversi ke nilai)
= 429/2 11
Setiap Fraksi Biner Dapat Dinyatakan Sebagai b / 10 n
Kalikan a / 2 n dengan 5 n
/ 5 n , dapatkan (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , di mana b = a × 5 n . Ini memiliki n digit.
Sebagai contoh, kami memiliki:
(429 · 5 11 ) / 10 11 = 20947265625/10 11 = 0.20947265625
Telah ditunjukkan bahwa setiap fraksi biner adalah fraksi desimal dengan jumlah digit yang sama.
dc
digunakan kemudian hanya untuk menulis parser secara langsung! (Input mungkin atau mungkin tidak memiliki desimal, dan bisa di pangkalan lain, sehingga jumlah bantalan bervariasi.)