การสูญเสียความแม่นยำด้วย dc


12

ฉันต้องการใช้dcเพื่อจัดการกับฐาน 16 จำนวนด้วยจุดเลขฐานสิบหก แต่ฉันพบปัญหาที่แม่นยำ ตัวอย่างด้านล่างฉันคูณF423F.FDด้วย100เลขฐานสิบหกทั้งคู่ คำตอบที่คาดหวังคือF423FFDแทนที่จะให้F423FFA.E1ปิด แต่ไม่แม่นยำพอแม้หลังจากปัดเศษ

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

ฉันอ่านว่าdcเป็นเครื่องคิดเลขที่มีความแม่นยำไม่ จำกัด และนี่ไม่ใช่วิธีการใด ๆ มีบางอย่างที่ฉันทำผิดหรือเปล่า?

ขอบคุณสำหรับคำตอบของคุณ เมื่อให้ปัญหากับdcฉันฉันกระสุนและเขียน parser ของฉันเองสำหรับตัวเลขจริงในฐานอื่น ๆ หากใครสนใจรหัสฉันสามารถโพสต์ได้ที่นี่

คำตอบ:


6

โปรดทราบว่าเพียงพิมพ์หมายเลขเดิมแสดงว่ามีการปัดเศษ:

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

คุณสามารถเดินไปรอบ ๆ ได้โดยการเพิ่มเลขศูนย์ต่อท้ายเพื่อความแม่นยำมากขึ้น:

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

ขอบคุณ ฉันคิดว่ามันจะจบลงด้วยการใช้รหัสเพิ่มเติมเพื่อนวดตัวเลขdcเพื่อใช้แล้วเพียงแค่เขียนโปรแกรมแยกวิเคราะห์โดยตรง! (อินพุตอาจมีหรือไม่มีทศนิยมและสามารถอยู่ในฐานอื่นดังนั้นจำนวนของการแพ็ดจะแตกต่างกันไป)
Yimin Rong

2
ฉันจะทำเครื่องหมายว่าเป็นคำตอบที่ยอมรับได้ ผู้ที่รับผิดชอบในการรักษาการdcตอบกลับ: การจัดการตัวเลขเศษส่วนที่ไม่ใช่ทศนิยมอย่างถูกต้องจะต้องใช้ตัวแบบที่แตกต่างไปจากตัวแบบทศนิยมที่ใช้โดย dc และ bc (ตามที่กำหนดโดย POSIX สำหรับ bc และตามธรรมเนียมประวัติศาสตร์สำหรับทั้งสอง) ดังนั้นในทางเทคนิคมันสามารถแก้ไขได้dcแต่อาจจะแตกbcได้ดังนั้นจึงจัดเป็น WONTFIX
Yimin Rong

8

แสดงเป็นทศนิยม (ใช้dcเพื่อแปลง) สิ่งนี้สอดคล้องกับ 999999.98 (ปัดเศษลง) × 256 เช่น 255999994.88 ซึ่งคือ 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

(ความแม่นยำ 8 หลักจะเพียงพอเนื่องจากเป็นสิ่งที่คุณต้องใช้เพื่อแสดง 1 ÷ 256 เป็นทศนิยม)


1
ที่ดูเหมือนจะเป็นผลลัพธ์ที่ไม่คาดคิดอย่างสมบูรณ์สำหรับเครื่องคิดเลข "ความแม่นยำตามอำเภอใจ"?
Yimin Rong

3
มันก็ยังคงสูญเสียความแม่นยำเมื่อkมีการตั้งค่า: 10 k 16 d i o F423F.FD p→ ดังนั้นฉันจะต้องไต่ขึ้นตัวเลขทั้งหมดก่อนที่จะใช้พวกเขาในF423F.FA dcโดยทั่วไปจะมีการแยกวิเคราะห์ล่วงหน้าอยู่แล้ว
Yimin Rong

2
@Yimin ใช่น่าเสียดายที่จะลดdcขนาดของอินพุตโดยใช้เฉพาะจำนวนหลักซึ่งดูเหมือนว่าเป็นข้อบกพร่องสำหรับฉัน (เนื่องจากจำนวนของตัวเลขถูกคำนวณโดยใช้ Radix อินพุต แต่นำไปใช้กับค่าทศนิยม)
Stephen Kitt

1
@dhag นั่นคือสิ่งที่POSIX ระบุ (สำหรับbcซึ่งdcเป็นไปตาม): "การคำนวณภายในจะต้องดำเนินการราวกับว่าเป็นทศนิยมโดยไม่คำนึงถึงฐานข้อมูลเข้าและขาออกตามจำนวนหลักทศนิยมที่ระบุ"
Stephen Kitt

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พิมพ์ (ต่อ)
NotAnUnixNazi

1

ปัญหา

ปัญหาคือวิธีที่ dc (และ bc) เข้าใจค่าคงที่ตัวเลข
ตัวอย่างเช่นค่า (เป็นฐานสิบหก) 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

หนึ่งหลักต่อบิต?

ข้อเท็จจริงที่ไม่เข้าใจง่ายมากสำหรับตัวเลขจำนวนจุดลอยตัวคือจำนวนของตัวเลขที่ต้องการ (หลังจุด) เท่ากับจำนวนบิตบิต (หลังจากจุด) เลขฐานสอง 0.101 เท่ากับ 0.625 ในทศนิยม เลขฐานสอง 0.0001110001 คือ (ตรง) เท่ากับ0.1103515625(สิบหลักสิบ)

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

นอกจากนี้สำหรับจำนวนจุดลอยตัวเช่น 2 ^ (- 10) ซึ่งเป็นเลขฐานสองมีบิตเดียว (ชุด):

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

มีเลขฐานสองจำนวนเดียวกัน.0000000001(10) เป็นเลขฐานสิบ.0009765625(10) นั่นอาจไม่ใช่ในฐานอื่น ๆ แต่ base 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หลักไม่เพียงพอที่จะเป็นตัวแทนอย่างต่อเนื่อง

และเพียงแค่นับจำนวนตัวอักษรที่ใช้หลังจากจุดเป็นวิธีที่ไม่ถูกต้องมากในการรายงาน (และใช้) สเกลของตัวเลข สเกลของตัวเลข (ในฐานใด ๆ ) ควรคำนวณจำนวนบิตที่ต้องการ

เลขฐานสองในทศนิยมหกเหลี่ยม

ตามที่ทราบกันแล้วเลขฐานสิบหกแต่ละตัวใช้ 4 บิต ดังนั้นแต่ละเลขฐานสิบหกหลังจุดทศนิยมจำเป็นต้องมีเลขฐานสอง 4 หลักซึ่งเนื่องจากข้อเท็จจริง (คี่?) ข้างต้นจึงต้องมีตัวเลขทศนิยม 4 หลัก

ดังนั้นตัวเลขที่คล้ายกัน0.FDจะต้องมีตัวเลขทศนิยม 8 หลักในการแสดงอย่างถูกต้อง:

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

เพิ่มศูนย์

คณิตศาสตร์ตรงไปตรงมา (สำหรับเลขฐานสิบหก):

  • นับจำนวนตัวเลขฐานสิบหก ( h) หลังจุด
  • คูณhด้วย 4
  • เพิ่ม 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*h) เพื่อแปลงเลขฐานสิบหกให้เป็นทศนิยมภายใน หรือฟังก์ชั่นอื่น ๆ สำหรับฐานอื่น ๆ (สมมติว่าจำนวนหลักนั้น จำกัด ในความสัมพันธ์กับฐาน 10 (ภายในของ bc และ dc) ในฐานอื่น ๆ ดังกล่าว) ชอบ 2 ฉัน (2,4,8,16, ... ) และ 5,10

POSIX

ข้อมูลจำเพาะ posix ระบุว่า (สำหรับ bc ซึ่ง dc ยึดตาม):

การคำนวณภายในจะต้องดำเนินการเสมือนเป็นทศนิยมโดยไม่คำนึงถึงฐานอินพุตและเอาต์พุตตามจำนวนหลักทศนิยมที่ระบุ

แต่ "... คือจำนวนทศนิยมที่ระบุ" สามารถเข้าใจได้ว่าเป็น "... จำนวนทศนิยมที่ต้องการเพื่อแสดงค่าคงที่ตัวเลข" (ตามที่อธิบายไว้ข้างต้น) โดยไม่มีผลต่อ "การคำนวณภายในทศนิยม"

เพราะ:

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

bc ไม่ได้ใช้ 50 ("จำนวนทศนิยมที่ระบุ") ตามที่กำหนดไว้ด้านบน

เฉพาะเมื่อแบ่งแล้วจะถูกแปลง (ยังไม่ถูกต้องเนื่องจากใช้มาตราส่วน 2 เพื่ออ่านค่าคงที่0.FDก่อนที่จะขยายเป็น 50 หลัก):

$ 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

อีกครั้งการอ่านสตริงตัวเลข (ค่าคงที่) ควรใช้จำนวนบิตที่ถูกต้อง


หลักฐานทางคณิตศาสตร์

ในสองขั้นตอน:

เศษส่วนไบนารีสามารถเขียนเป็น a / 2 n

เศษส่วนไบนารีคือผลรวมที่แน่นอนของกำลังลบของสอง

ตัวอย่างเช่น:

= 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 -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 nโดยมีเลขชี้กำลังเป็นจำนวนเต็มบวก เงื่อนไขทั้งหมดแล้วสามารถนำมารวมเป็นค่าเดียว / 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

มันแสดงให้เห็นว่าทุกเศษส่วนไบนารีเป็นเศษทศนิยมที่มีจำนวนหลักเดียวกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.