การย้ายตำแหน่งทศนิยมซ้ำเป็นสองเท่า


98

ดังนั้นฉันจึงตั้งค่าสองเท่าให้เท่ากับ 1234 ฉันต้องการย้ายตำแหน่งทศนิยมไปที่เพื่อให้เป็น 12.34

เพื่อทำสิ่งนี้ผมคูณ. 1 ถึง 1234 สองคูณแบบนี้

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.println(x);

สิ่งนี้จะพิมพ์ผลลัพธ์ "12.340000000000002"

มีวิธีโดยไม่ต้องจัดรูปแบบให้เป็นทศนิยมสองตำแหน่งโดยไม่ต้องจัดรูปแบบเป็นทศนิยมสองตำแหน่งอย่างถูกต้องหรือไม่?



43
มีเหตุผลที่คุณไม่ทำx /= 100;หรือไม่?
Mark Ingram

คำตอบ:


190

หากคุณใช้doubleหรือfloatคุณควรใช้การปัดเศษหรือคาดว่าจะเห็นข้อผิดพลาดในการปัดเศษ หากคุณไม่สามารถทำได้ให้ใช้BigDecimal.

ปัญหาที่คุณมีคือ 0.1 ไม่ใช่การแสดงที่แน่นอนและการคำนวณสองครั้งแสดงว่าคุณกำลังรวมข้อผิดพลาด

อย่างไรก็ตาม 100 สามารถแสดงได้อย่างถูกต้องดังนั้นลอง:

double x = 1234;
x /= 100;
System.out.println(x);

ซึ่งพิมพ์:

12.34

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

System.out.println(new BigDecimal(0.1));
System.out.println(new BigDecimal(x));

พิมพ์:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

ในระยะสั้นการปัดเศษเป็นสิ่งที่หลีกเลี่ยงไม่ได้สำหรับคำตอบที่สมเหตุสมผลในจุดลอยตัวไม่ว่าคุณจะทำสิ่งนี้อย่างชัดเจนหรือไม่ก็ตาม


หมายเหตุ: x / 100และx * 0.01ไม่เหมือนกันทุกประการเมื่อพูดถึงข้อผิดพลาดในการปัดเศษ เนื่องจากข้อผิดพลาดรอบสำหรับนิพจน์แรกขึ้นอยู่กับค่าของ x ในขณะที่0.01ในวินาทีมีข้อผิดพลาดรอบคงที่

for(int i=0;i<200;i++) {
    double d1 = (double) i / 100;
    double d2 = i * 0.01;
    if (d1 != d2)
        System.out.println(d1 + " != "+d2);
}

พิมพ์

0.35 != 0.35000000000000003
0.41 != 0.41000000000000003
0.47 != 0.47000000000000003
0.57 != 0.5700000000000001
0.69 != 0.6900000000000001
0.7 != 0.7000000000000001
0.82 != 0.8200000000000001
0.83 != 0.8300000000000001
0.94 != 0.9400000000000001
0.95 != 0.9500000000000001
1.13 != 1.1300000000000001
1.14 != 1.1400000000000001
1.15 != 1.1500000000000001
1.38 != 1.3800000000000001
1.39 != 1.3900000000000001
1.4 != 1.4000000000000001
1.63 != 1.6300000000000001
1.64 != 1.6400000000000001
1.65 != 1.6500000000000001
1.66 != 1.6600000000000001
1.88 != 1.8800000000000001
1.89 != 1.8900000000000001
1.9 != 1.9000000000000001
1.91 != 1.9100000000000001

26
ไม่อยากจะเชื่อเลยว่าฉันไม่คิดจะทำแบบนั้นตั้งแต่แรก! Thanks :-P
BlackCow

6
แม้ว่า 100 จะสามารถแทนค่าได้ทั้งหมดในรูปแบบไบนารี แต่การหารด้วย 100 ไม่สามารถแทนค่าได้อย่างแน่นอน ดังนั้นการเขียน1234/100อย่างที่คุณเคยทำจึงไม่ได้ทำอะไรเกี่ยวกับปัญหาพื้นฐาน - มันควรจะเท่ากับการเขียน1234 * 0.01ทุกประการ
Brooks Moses

1
@Peter Lawrey: คุณสามารถอธิบายเพิ่มเติมได้หรือไม่ว่าเหตุใดจำนวนจึงเป็นเลขคี่หรือแม้กระทั่งจะมีผลต่อการปัดเศษ? ฉันคิดว่า / = 100 และ * = 01 จะเหมือนกันเพราะถึงแม้ว่า 100 จะเป็น int แต่ก็จะถูกแปลงเป็น 100.0 ก็ตามเนื่องจากการบังคับประเภท
eremzeit

1
/100และ*0.01เทียบเท่ากับแต่ละอื่น ๆ แต่ไม่ถึงของ *0.1*0.1OP
Amadan

1
ทั้งหมดที่ฉันพูดก็คือการคูณด้วย 0.1 สองครั้งโดยเฉลี่ยจะทำให้เกิดข้อผิดพลาดมากกว่าการคูณด้วย 0.01 ครั้งเดียว แต่ฉันยินดีที่จะยอมรับจุดของ @ JasperBekkers เกี่ยวกับ 100 ที่แตกต่างกันโดยเป็นตัวแทนแบบไบนารี
Amadan

52

ไม่มี - ถ้าคุณต้องการที่จะเก็บค่าทศนิยมได้อย่างถูกต้อง, BigDecimalการใช้งาน doubleเพียงแค่ไม่สามารถแทนค่าเช่น 0.1 เป๊ะ ๆ ได้มากกว่าที่คุณสามารถเขียนค่าของสามด้วยจำนวนทศนิยมที่แน่นอนได้


46

หากเป็นเพียงการจัดรูปแบบให้ลอง printf

double x = 1234;
for(int i=1;i<=2;i++)
{
  x = x*.1;
}
System.out.printf("%.2f",x);

เอาท์พุท

12.34

8
คำตอบที่ได้รับการจัดอันดับสูงกว่านั้นมีข้อมูลเชิงลึกทางเทคนิคมากกว่า แต่นี่เป็นคำตอบที่ถูกต้องสำหรับปัญหาของ OP โดยทั่วไปเราไม่สนใจเกี่ยวกับความไม่ถูกต้องเล็กน้อยของ double ดังนั้น BigDecimal จึงมากเกินไป แต่เมื่อแสดงผลเรามักต้องการให้แน่ใจว่าผลลัพธ์ของเราตรงกับสัญชาตญาณของเราดังนั้นจึงSystem.out.printf()เป็นวิธีที่ถูกต้อง
dimo414

28

ในซอฟต์แวร์ทางการเงินมักใช้จำนวนเต็มสำหรับเพนนี ในโรงเรียนเราได้รับการสอนวิธีใช้จุดคงที่แทนการลอยตัว แต่โดยปกติจะเป็นพลังสอง การจัดเก็บเพนนีเป็นจำนวนเต็มอาจเรียกว่า "จุดคงที่" เช่นกัน

int i=1234;
printf("%d.%02d\r\n",i/100,i%100);

ในชั้นเรียนเราถูกถามโดยทั่วไปว่าตัวเลขใดที่สามารถแทนค่าได้อย่างแน่นอน

สำหรับbase=p1^n1*p2^n2... คุณสามารถแทนค่า N ใดก็ได้โดยที่ N = n * p1 ^ m1 * p2 ^ m2

ให้base=14=2^1*7^1... คุณสามารถแทน 1/7 1/14 1/28 1/49 แต่ไม่ใช่ 1/3

ฉันรู้เกี่ยวกับซอฟต์แวร์ทางการเงิน - ฉันแปลงรายงานทางการเงินของ Ticketmaster จาก VAX asm เป็น PASCAL พวกเขามี formatln () ของตัวเองพร้อมรหัสสำหรับเพนนี สาเหตุของการแปลงเป็นจำนวนเต็ม 32 บิตนั้นไม่เพียงพออีกต่อไป +/- 2 พันล้านเพนนีเท่ากับ 20 ล้านเหรียญและล้นไปสำหรับฟุตบอลโลกหรือโอลิมปิกฉันลืมไป

ฉันสาบานว่าจะเป็นความลับ โอ้ดี. ในทางวิชาการถ้าเป็นเรื่องดีคุณก็เผยแพร่ ในอุตสาหกรรมคุณเก็บเป็นความลับ


12

คุณสามารถลองแทนตัวเลขจำนวนเต็ม

int i =1234;
int q = i /100;
int r = i % 100;

System.out.printf("%d.%02d",q, r);

5
@ แดน: ทำไม? นี่เป็นแนวทางที่ถูกต้องสำหรับแอพทางการเงิน (หรือแอพอื่น ๆ ที่ยอมรับแม้แต่ข้อผิดพลาดในการปัดเศษเล็ก ๆ น้อย ๆ ) ในขณะที่ยังคงรักษาความเร็วระดับฮาร์ดแวร์ (แน่นอนว่าจะห่อในชั้นเรียนปกติไม่ได้เขียนออกมาทุกครั้ง)
Amadan

7
มีปัญหาเล็กน้อยในการแก้ปัญหานี้ - หากส่วนที่เหลือrน้อยกว่า 10 จะไม่มีช่องว่าง 0 เกิดขึ้นและ 1204 จะให้ผลลัพธ์เป็น 12.4 สตริงการจัดรูปแบบที่ถูกต้องคล้ายกับ "% d.% 02d" มากกว่า
jakebman

10

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


อ๊ะฉันแค่เขียนคำอธิบายที่เชื่อมโยงไปยังสถานที่เดียวกัน +1.
Pops

@ ท่านลอร์ดฮ่า ๆ ขอโทษครับ ฉันรู้สึกสงสัย แต่อย่างใด :-)
CanSpice

ฉันคิดว่าเป็นเพราะเหตุนี้ แต่ฉันสงสัยว่ามีวิธีที่สร้างสรรค์ในการย้ายตำแหน่งทศนิยมไปไว้ที่อื่นหรือไม่? เนื่องจากสามารถจัดเก็บ 12.34 ได้อย่างหมดจดในสองเท่าจึงไม่ชอบการคูณด้วย. 1
BlackCow

1
ถ้าเป็นไปได้ที่จะจัดเก็บ 12.34 อย่างหมดจดในสองเท่าคุณไม่คิดว่า Java จะทำหรือไม่? มันไม่ใช่. คุณจะต้องใช้ประเภทข้อมูลอื่น ๆ (เช่น BigDecimal) แล้วทำไมคุณไม่หารด้วย 100 แทนที่จะทำแบบวนซ้ำล่ะ?
CanSpice

Do'h ... ใช่หารด้วย 100 ผลลัพธ์ใน 12.34 ... ขอบคุณ :-P
BlackCow

9

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

String numstr = "1234";
System.out.println(new BigDecimal(numstr).movePointLeft(2));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal(0.01)));
System.out.println(new BigDecimal(numstr).multiply(new BigDecimal("0.01")));

ให้ผลลัพธ์นี้

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

ตัวสร้าง BigDecimal กล่าวโดยเฉพาะว่าควรใช้ตัวสร้างสตริงดีกว่าตัวสร้างตัวเลข ความแม่นยำขั้นสูงสุดยังได้รับอิทธิพลจาก MathContext ที่เป็นทางเลือก

ตามที่ BigDecimal Javadoc มันเป็นไปได้ที่จะสร้าง BigDecimal ซึ่งเป็นว่าเท่ากับ 0.1, ให้คุณใช้ตัวสร้างสตริง


5

ใช่มี. ในการดำเนินการสองครั้งแต่ละครั้งคุณอาจสูญเสียความแม่นยำ แต่จำนวนความแม่นยำจะแตกต่างกันไปในแต่ละการดำเนินการและสามารถลดขนาดได้โดยเลือกลำดับการดำเนินการที่เหมาะสม ตัวอย่างเช่นเมื่อคูณชุดของตัวเลขควรจัดเรียงชุดด้วยเลขชี้กำลังก่อนคูณ

หนังสือที่ดีเกี่ยวกับการกระทืบตัวเลขใด ๆ อธิบายถึงเรื่องนี้ ตัวอย่างเช่น http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

และเพื่อตอบคำถามของคุณ:

ใช้การหารแทนการคูณวิธีนี้คุณจะได้ผลลัพธ์ที่ถูกต้อง

double x = 1234;
for(int i=1;i<=2;i++)
{
  x =  x / 10.0;
}
System.out.println(x);

3

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

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