Kehilangan presisi dengan dc


12

Saya ingin menggunakan dcuntuk menangani beberapa angka dasar 16 dengan poin heksadesimal tapi saya mengalami masalah presisi. Sebagai contoh, di bawah ini saya mengalikan F423F.FDdengan 100, keduanya hex. Jawaban yang diharapkan adalah F423FFD, sebaliknya memberi F423FFA.E1, menutup tetapi tidak cukup akurat bahkan setelah pembulatan.

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

Saya membaca bahwa dcitu adalah kalkulator presisi tak terbatas, dan ini bukan angka yang besar. Apakah ada yang salah yang saya lakukan?

Terima kasih atas jawaban Anda. Mengingat masalah dengan dc, saya menggigit peluru dan menulis parser saya sendiri untuk bilangan real di pangkalan lain. Jika ada yang tertarik dengan kode ini, saya dapat mempostingnya di sini.

Jawaban:


6

Perhatikan bahwa hanya mencetak nomor asli menunjukkan dibulatkan:

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

Anda dapat menyiasatinya dengan menambahkan banyak nol jejak untuk lebih presisi:

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

Terima kasih. Saya pikir itu akan berakhir dengan mengambil lebih banyak kode untuk memijat angka untuk dcdigunakan kemudian hanya untuk menulis parser secara langsung! (Input mungkin atau mungkin tidak memiliki desimal, dan bisa di pangkalan lain, sehingga jumlah bantalan bervariasi.)
Yimin Rong

2
Saya akan menandai ini sebagai jawaban yang diterima. Orang-orang yang bertanggung jawab untuk pemeliharaan dcmenjawab: Untuk menangani dengan benar angka fraksional non-desimal akan membutuhkan model yang sama sekali berbeda dari model skala desimal yang digunakan oleh dc dan bc (seperti yang didikte oleh POSIX untuk bc, dan oleh tradisi sejarah untuk keduanya). , jadi secara teknis bisa diperbaiki dc, tapi itu mungkin akan rusak bc, jadi diklasifikasikan sebagai WONTFIX.
Yimin Rong

8

Dinyatakan sebagai desimal (menggunakan dcuntuk mengkonversi), ini sesuai dengan 999999,98 (dibulatkan ke bawah) × 256, yaitu 255999994.88, yang merupakan F423FFA.E1 dalam heksadesimal.

Jadi perbedaannya berasal dari dcperilaku pembulatan: alih-alih menghitung 256 × (999999 + 253 ÷ 256), yang akan menghasilkan 255999997, itu membulatkan 253 ÷ 256 ke bawah dan mengalikan hasilnya.

dcadalah kalkulator presisi yang berubah - ubah , yang artinya dapat menghitung dengan presisi apa pun yang Anda inginkan, tetapi Anda harus mengatakannya apa itu. Secara default, ketelitiannya adalah 0, pembagian makna menghasilkan nilai integer saja, dan perkalian menggunakan jumlah digit pada input. Untuk mengatur presisi, gunakan k(dan ingatlah bahwa presisi selalu dinyatakan dalam angka desimal, terlepas dari input atau output radix):

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

(Presisi 8 digit akan cukup karena itu yang Anda butuhkan untuk mewakili 1 ÷ 256 dalam desimal.)


1
Tampaknya itu merupakan hasil yang sepenuhnya tidak terduga untuk kalkulator "presisi arbitrer"?
Yimin Rong

3
Masih kehilangan presisi saat kdiset: 10 k 16 d i o F423F.FD pF423F.FA, jadi saya harus meningkatkan semua angka sebelum menggunakannya dc. Pada dasarnya sama dengan pra-parsing mereka.
Yimin Rong

2
@Yimin ya, sayangnya dcskala inputnya hanya menggunakan jumlah digit, yang tampaknya seperti bug bagi saya (karena jumlah digit dihitung menggunakan input radix, tetapi diterapkan pada nilai desimal).
Stephen Kitt

1
@hhag itulah yang ditentukan POSIX (untuk bc, yang dcdidasarkan pada): "Perhitungan internal akan dilakukan seolah-olah dalam desimal, terlepas dari basis input dan output, ke jumlah digit desimal yang ditentukan."
Stephen Kitt

1
Ini benar-benar masalah bagaimana konstanta diurai. Coba 20 k 16 d i o 0.3 1 / p (yang mencetak .19999999999999999). Memahami bahwa operasi itu hanya membagi0.2 oleh 1(yang dalam teori tidak harus mengubah nilai). Saat 20 k 16 d i o 0.3000 1 / p(dengan benar) mencetak .30000000000000000. (Lanj.)
Isaac

1

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.3juga 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.FDakan 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 hdengan 4.
  • Tambahkan h×4 - h = h × (4-1) = h × 3 = 3×hnol.

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.FDsebelum 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.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.