Mất độ chính xác với dc


12

Tôi muốn sử dụng dcđể xử lý một số 16 số cơ bản với các điểm thập lục phân nhưng tôi đang gặp vấn đề chính xác. Ví dụ, bên dưới tôi nhân F423F.FDvới 100cả hai hex. Câu trả lời dự kiến ​​là F423FFD, thay vào đó là đưa ra F423FFA.E1, đóng nhưng không đủ chính xác ngay cả sau khi làm tròn.

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

tôi đọc cái đó dc là một máy tính chính xác không giới hạn, và đây không phải là một con số lớn. Có điều gì đó tôi đang làm sai?

Cảm ơn câu trả lời của bạn. Đưa ra các vấn đề với dc, tôi cắn viên đạn và viết trình phân tích cú pháp của riêng tôi cho các số thực trong các cơ sở khác. Nếu bất cứ ai quan tâm đến mã, tôi có thể đăng nó ở đây.

Câu trả lời:


6

Lưu ý rằng chỉ cần in số gốc cho thấy nó được làm tròn:

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

Bạn có thể khắc phục bằng cách thêm nhiều số 0 ở cuối để có độ chính xác cao hơn:

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

Cảm ơn. Tôi nghĩ rằng cuối cùng sẽ lấy thêm mã để xoa bóp các con số dcđể sử dụng sau đó chỉ cần viết một trình phân tích cú pháp trực tiếp! (Đầu vào có thể có hoặc không có số thập phân và có thể ở các cơ sở khác, vì vậy số lượng phần đệm thay đổi.)
Yimin Rong

2
Tôi sẽ đánh dấu đây là câu trả lời được chấp nhận. Những người chịu trách nhiệm duy trì dctrả lời: Để xử lý chính xác các chữ số phân số không thập phân sẽ yêu cầu một mô hình hoàn toàn khác so với mô hình tỷ lệ thập phân được sử dụng bởi dc và bc (theo quy định của POSIX cho bc và theo truyền thống lịch sử cho cả hai). , về mặt kỹ thuật nó có thể được sửa chữa dc, nhưng điều đó có thể sẽ bị phá vỡ bc, do đó được phân loại là WONTFIX.
Yimin Rong

8

Được biểu thị dưới dạng thập phân (sử dụng dcđể chuyển đổi), giá trị này tương ứng với 999999,98 (làm tròn xuống) × 256, tức là 255999994,88, là F423FFA.E1 ở dạng thập lục phân.

Vì vậy, sự khác biệt đến từ dchành vi làm tròn của: thay vì tính toán 256 × (999999 + 253 256), sẽ cho 255999997, làm tròn xuống 255 ÷ 256 và nhân kết quả.

dclà một máy tính chính xác tùy ý , có nghĩa là nó có thể tính toán với bất kỳ độ chính xác nào bạn muốn, nhưng bạn phải nói với nó đó là gì. Theo mặc định, độ chính xác của nó là 0, nghĩa là phép chia chỉ tạo ra các giá trị nguyên và phép nhân sử dụng số chữ số trong đầu vào. Để đặt độ chính xác, hãy sử dụng k(và lưu ý rằng độ chính xác luôn được biểu thị bằng chữ số thập phân, bất kể cơ số đầu vào hay đầu ra):

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

(Độ chính xác 8 chữ số sẽ là đủ vì đó là những gì bạn cần để biểu thị 1 ÷ 256 theo số thập phân.)


1
Đó dường như là một kết quả hoàn toàn bất ngờ đối với một máy tính "chính xác tùy ý"?
Yimin Rong

3
Nó vẫn mất độ chính xác khi kđược đặt: 10 k 16 d i o F423F.FD pF423F.FA, vì vậy tôi sẽ phải mở rộng tất cả các số trước khi sử dụng chúng dc. Về cơ bản, số tiền để phân tích trước chúng.
Yimin Rong

2
@Yimin có, không may thay đổi dctỷ lệ đầu vào của nó bằng cách chỉ sử dụng số chữ số, có vẻ như là một lỗi đối với tôi (vì số chữ số được tính bằng cơ số đầu vào, nhưng được áp dụng cho giá trị thập phân).
Stephen Kitt

1
@dhag đó là những gì POSIX chỉ định (cho bc, dcdựa trên): Việc tính toán nội bộ sẽ được tiến hành như thể ở dạng thập phân, bất kể cơ sở đầu vào và đầu ra, với số chữ số thập phân được chỉ định.
Stephen Kitt

1
Nó thực sự là một vấn đề về cách một hằng số được phân tích cú pháp. Hãy thử 20 k 16 d i o 0.3 1 / p (in .19999999999999999). Hiểu rằng hoạt động chỉ là chia 0.2cho 1(trong lý thuyết không nên thay đổi giá trị). Trong khi 20 k 16 d i o 0.3000 1 / p(chính xác) in .30000000000000000. (Tiếp)
Isaac

1

Vấn đề

Vấn đề là cách mà dc (và bc) hiểu các hằng số.
Ví dụ: giá trị (ở dạng hex) 0.3(chia cho 1) được chuyển đổi thành giá trị gần với0.2

$ dc <<<"20k 16 d i o 0.3 1 / p"
.199999999999999999999999999

Trong thực tế, hằng số đồng bằng 0.3cũng được thay đổi:

$ dc <<<"20 k 16 d i o     0.3     p"
.1

Có vẻ như đó là một cách kỳ lạ, nhưng nó không phải là (muộn hơn).
Thêm nhiều số không làm cho câu trả lời tiếp cận giá trị chính xác:

$ 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

Giá trị cuối cùng là chính xác và sẽ giữ chính xác cho dù có thể thêm nhiều số không.

$ dc <<<"20 k 16 d i o     0.30000000     p"
.3000000

Vấn đề cũng có trong 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

Một chữ số trên mỗi bit?

Một thực tế không trực quan cho các số dấu phẩy động là số chữ số được yêu cầu (sau dấu chấm) bằng với số bit nhị phân (cũng sau dấu chấm). Số nhị phân 0.101 chính xác bằng 0,625 trong số thập phân. Số nhị phân 0,0001110001 là (chính xác) bằng 0.1103515625(mười chữ số thập phân)

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

Ngoài ra, đối với số dấu phẩy động như 2 ^ (- 10), trong nhị phân chỉ có một bit (bộ):

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

Có cùng số chữ số nhị phân .0000000001(10) là chữ số thập phân.0009765625 (10). Đó có thể không phải là trường hợp trong các cơ sở khác nhưng cơ sở 10 là biểu diễn bên trong của các số ở cả dc và bc và do đó là cơ sở duy nhất mà chúng ta thực sự cần quan tâm.

Bằng chứng toán học là ở cuối câu trả lời này.

quy mô bc

Số chữ số sau dấu chấm có thể được tính bằng scale()mẫu hàm tích hợp bc:

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

Như được hiển thị, 2 chữ số là không đủ để đại diện cho hằng số 0.FD .

Và, ngoài ra, chỉ cần đếm số lượng ký tự được sử dụng sau dấu chấm là một cách rất không chính xác để báo cáo (và sử dụng) tỷ lệ của số. Tỷ lệ của một số (trong bất kỳ cơ sở nào) sẽ tính toán số lượng bit cần thiết.

Chữ số nhị phân trong một hình nổi hex.

Như đã biết, mỗi chữ số hex sử dụng 4 bit. Do đó, mỗi chữ số hex sau dấu thập phân yêu cầu 4 chữ số nhị phân, do thực tế (lẻ?) Ở trên cũng yêu cầu 4 chữ số thập phân.

Do đó, một số như thế 0.FDsẽ yêu cầu 8 chữ số thập phân được thể hiện chính xác:

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

Thêm số không

Toán học rất đơn giản (đối với số hex):

  • Đếm số chữ số hex ( h) sau dấu chấm.
  • Nhân hvới 4.
  • Thêm h×4 - h = h × (4-1) = h × 3 = 3×hsố không.

Trong mã shell (cho 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

Mà sẽ in (chính xác cả trong dc và bc):

$  sh ./script
F423F.FD000000
F423FFD.0000000
F423FFD.0000000

Trong nội bộ, bc (hoặc dc) có thể làm cho số chữ số được yêu cầu khớp với số được tính ở trên ( 3*h) để chuyển đổi số float thành biểu diễn thập phân nội bộ. Hoặc một số hàm khác cho các cơ sở khác (giả sử số chữ số là hữu hạn so với cơ sở 10 (nội bộ của bc và dc) trong cơ sở khác đó). Thích 2 tôi (2,4,8,16, ...) và 5,10.

tích cực

Đặc tả posix nói rằng (đối với bc, dc dựa trên):

Việc tính toán nội bộ phải được tiến hành như thể ở dạng thập phân, bất kể cơ sở đầu vào và đầu ra, với số chữ số thập phân được chỉ định.

Nhưng "Số lượng chữ số thập phân đã chỉ định." có thể được hiểu là "Sắp xếp số chữ số thập phân cần thiết để biểu diễn hằng số" (như được mô tả ở trên) mà không ảnh hưởng đến "tính toán nội bộ thập phân"

Bởi vì:

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

bc không thực sự sử dụng 50 ("số chữ số thập phân được chỉ định") như đã đặt ở trên.

Chỉ khi được chia, nó mới được chuyển đổi (vẫn không chính xác vì nó sử dụng thang đo 2 để đọc hằng số 0.FDtrước khi mở rộng thành 50 chữ số):

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD/1; a'
.FAE147AE147AE147AE147AE147AE147AE147AE147A

Tuy nhiên, đây là chính xác:

$ bc <<<'scale=50;obase=16;ibase=16; a=0.FD000000/1; a'
.FD0000000000000000000000000000000000000000

Một lần nữa, đọc các chuỗi số (hằng số) nên sử dụng đúng số bit.


Chứng minh toán

Trong hai bước:

Một phần nhị phân có thể được viết là a / 2 n

Một phần nhị phân là tổng hữu hạn của lũy thừa âm của hai.

Ví dụ:

= 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 = (đã xóa số không)

Trong một phần nhị phân của n bit, bit cuối cùng có giá trị 2 -n hoặc 1/2 n . Trong ví dụ này: 2 -11 hoặc 1/2 11 .

= 1/2 3 + 1/2 4 + 1/2 6 + 1/2 8 + 1/2 9 + 1/2 11 = (có nghịch đảo)

Nói chung, mẫu số có thể trở thành 2 n với số mũ dương là hai. Tất cả các thuật ngữ sau đó có thể được kết hợp thành một giá trị duy nhất a / 2 n . Ví dụ này:

= 2 8 /2 11 + 2 7 /2 11 + 2 5 /2 11 + 2 3 /2 11 + 2 2 /2 11 + 1/2 11 = (thể hiện với 2 11 )

= (2 8 + 2 7 + 2 5 + 2 3 + 2 2 + 1) / 2 11 = (trích hệ số chung)

= (256 + 128 + 32 + 8 + 4 + 1) / 2 11 = (chuyển đổi thành giá trị)

= 429/2 11

Mọi phân số nhị phân có thể được biểu thị bằng b / 10 n

Nhân a / 2 n với 5 n / 5 n , nhận (a × 5 n ) / (2 n × 5 n ) = (a × 5 n ) / 10 n = b / 10 n , trong đó b = a × 5 n . Nó có n chữ số.

Ví dụ, chúng tôi có:

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

Nó đã được chỉ ra rằng mỗi phân số nhị phân là một phân số thập phân có cùng số chữ số.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.