Memindahkan tempat desimal menjadi ganda

97

Jadi saya memiliki himpunan ganda sama dengan 1234, saya ingin memindahkan tempat desimal menjadi 12,34

Jadi untuk melakukan ini saya mengalikan 0,1 sampai 1234 dua kali, seperti ini

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

Ini akan mencetak hasilnya, "12.340000000000002"

Adakah cara, tanpa hanya memformatnya menjadi dua tempat desimal, agar penyimpanan ganda 12,34 benar?

BlackCow
sumber
43
Apakah ada alasan mengapa Anda tidak melakukannya x /= 100;?
Mark Ingram

Jawaban:

189

Jika Anda menggunakan doubleatau float, Anda harus menggunakan pembulatan atau akan melihat beberapa kesalahan pembulatan. Jika Anda tidak dapat melakukan ini, gunakan BigDecimal.

Masalah yang Anda hadapi adalah bahwa 0,1 bukanlah representasi yang tepat, dan dengan melakukan kalkulasi dua kali, Anda menambah kesalahan itu.

Namun, 100 dapat direpresentasikan secara akurat, jadi cobalah:

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

yang mencetak:

12.34

Ini berfungsi karena Double.toString(d)melakukan sejumlah kecil pembulatan atas nama Anda, tetapi tidak banyak. Jika Anda bertanya-tanya bagaimana tampilannya tanpa pembulatan:

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

cetakan:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

Singkatnya, pembulatan tidak dapat dihindari untuk jawaban yang masuk akal dalam floating point apakah Anda melakukannya secara eksplisit atau tidak.


Catatan: x / 100dan x * 0.01tidak persis sama dalam hal kesalahan pembulatan. Ini karena kesalahan putaran untuk ekspresi pertama bergantung pada nilai x, sedangkan 0.01kesalahan putaran kedua memiliki kesalahan putaran tetap.

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);
}

cetakan

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
Peter Lawrey
sumber
26
Aku tidak percaya aku tidak berpikir untuk melakukan itu sejak awal! Terima kasih :-P
BlackCow
6
Meskipun 100 dapat direpresentasikan dengan tepat dalam format biner, pembagian dengan 100 tidak dapat direpresentasikan dengan tepat. Jadi, menulis 1234/100, seperti yang telah Anda lakukan, tidak benar-benar melakukan apa pun tentang masalah yang mendasarinya - itu harus sama persis dengan menulis 1234 * 0.01.
Brooks Moses
1
@ Peter Lawrey: Bisakah Anda menjelaskan lebih lanjut mengapa bilangan ganjil atau genap mempengaruhi pembulatan? Saya akan berpikir bahwa / = 100 dan * =. 01 akan sama karena meskipun 100 adalah int, itu akan diubah menjadi 100.0 sebagai hasil dari paksaan tipe.
eremzeit
1
/100dan *0.01setara satu sama lain, tetapi tidak dengan OP *0.1*0.1.
Amadan
1
Yang saya katakan adalah bahwa mengalikan dengan 0,1 dua kali rata-rata akan menghasilkan kesalahan yang lebih besar daripada mengalikan dengan 0,01 satu kali; tapi dengan senang hati saya akan mengakui pendapat @JasperBekkers tentang 100 berbeda, persis dapat direpresentasikan secara biner.
Amadan
52

Tidak - jika Anda ingin menyimpan nilai desimal secara akurat, gunakan BigDecimal. doubletidak dapat mewakili angka seperti 0,1 dengan tepat, sama seperti Anda dapat menulis nilai sepertiga persis dengan jumlah digit desimal yang terbatas.

Jon Skeet
sumber
46

jika itu hanya pemformatan, coba printf

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

keluaran

12.34
Augusto
sumber
8
Jawaban yang berperingkat lebih tinggi lebih berwawasan teknis, tetapi ini adalah jawaban yang benar untuk masalah OP. Kami umumnya tidak peduli dengan sedikit ketidakakuratan ganda, jadi BigDecimal berlebihan, tetapi ketika menampilkan kami sering ingin memastikan keluaran kami cocok dengan intuisi kami, begitu System.out.printf()juga cara yang tepat untuk melakukannya.
dimo414
28

Dalam perangkat lunak keuangan, umum menggunakan bilangan bulat untuk uang. Di sekolah, kami diajari cara menggunakan fixed-point alih-alih floating, tapi itu biasanya kekuatan dua. Menyimpan uang dalam bilangan bulat bisa disebut "titik tetap" juga.

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

Di kelas, kami ditanyai secara umum angka apa yang bisa direpresentasikan dengan tepat dalam basis.

Untuk base=p1^n1*p2^n2... Anda dapat mewakili N apa pun di mana N = n * p1 ^ m1 * p2 ^ m2.

Misalkan base=14=2^1*7^1... Anda dapat mewakili 1/7 1/14 1/28 1/49 tetapi tidak 1/3

Saya tahu tentang perangkat lunak keuangan - Saya mengubah laporan keuangan Ticketmaster dari VAX asm menjadi PASCAL. Mereka memiliki formatln () sendiri dengan kode untuk uang. Alasan untuk konversi adalah 32 bit integer tidak lagi cukup. +/- 2 miliar sen adalah $ 20 juta dan itu melimpah untuk Piala Dunia atau Olimpiade, saya lupa.

Saya disumpah untuk merahasiakan. Baiklah. Di akademi, jika ada baiknya Anda mempublikasikan; dalam industri, Anda merahasiakannya.

Terrence Andrew Davis
sumber
12

Anda dapat mencoba representasi bilangan bulat

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

System.out.printf("%d.%02d",q, r);
Angel Koh
sumber
5
@Dan: Mengapa? Ini adalah pendekatan yang tepat untuk aplikasi keuangan (atau aplikasi lain di mana bahkan kesalahan pembulatan kecil tidak dapat diterima), sambil tetap mempertahankan kecepatan tingkat perangkat keras. (Tentu saja, itu akan dibungkus dalam kelas, biasanya, tidak ditulis setiap waktu)
Amadan
7
Ada sedikit masalah dengan solusi ini - jika sisanya rkurang dari 10, tidak ada bantalan 0 yang terjadi dan 1204 akan menghasilkan hasil 12,4. String pemformatan yang benar lebih mirip dengan "% d.% 02d"
jakebman
10

Ini disebabkan oleh cara komputer menyimpan bilangan floating-point. Mereka tidak melakukannya dengan tepat. Sebagai seorang programmer, Anda harus membaca panduan floating-point ini untuk membiasakan diri dengan cobaan dan kesengsaraan dalam menangani bilangan floating-point.

CanSpice
sumber
Argh, saya baru saja menulis penjelasan yang menghubungkan ke tempat yang sama persis. +1.
Muncul
@Tuhan Haha, maaf. Bagaimanapun juga aku mendapat Skeet. :-)
CanSpice
Saya pikir itu sebabnya, tetapi saya ingin tahu apakah ada cara kreatif untuk memindahkan tempat desimal? Karena dimungkinkan untuk menyimpan 12,34 dengan rapi dalam dua kali lipat, itu tidak suka mengalikan dengan 0,1
BlackCow
1
Jika dimungkinkan untuk menyimpan 12,34 dengan rapi dalam format ganda, tidakkah menurut Anda Java akan melakukannya? Ini bukan. Anda harus menggunakan beberapa tipe data lain (seperti BigDecimal). Juga, mengapa Anda tidak membagi dengan 100 daripada melakukannya dalam satu lingkaran?
CanSpice
Do'h ... ya, membaginya dengan 100 menghasilkan 12.34 bersih ... terima kasih :-P
BlackCow
9

Lucu bahwa banyak posting menyebutkan untuk menggunakan BigDecimal tetapi tidak ada yang mau memberikan jawaban yang benar berdasarkan BigDecimal? Karena bahkan dengan BigDecimal, Anda masih bisa salah, seperti yang ditunjukkan oleh kode ini

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")));

Memberikan keluaran ini

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

Konstruktor BigDecimal secara khusus menyebutkan bahwa lebih baik menggunakan konstruktor String daripada konstruktor numerik. Presisi tertinggi juga dipengaruhi oleh MathContext opsional.

Menurut Javadoc BigDecimal dimungkinkan untuk membuat BigDecimal yang sama persis dengan 0.1, asalkan Anda menggunakan konstruktor String.

Justin Rowe
sumber
5

Ya ada. Dengan setiap operasi ganda Anda mungkin kehilangan akurasi tetapi jumlah akurasi berbeda untuk setiap operasi dan dapat diminimalkan dengan memilih urutan operasi yang tepat. Misalnya saat mengalikan kumpulan bilangan, yang terbaik adalah mengurutkan himpunan berdasarkan eksponen sebelum mengalikan.

Setiap buku yang layak tentang angka-angka menggambarkan hal ini. Misalnya: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Dan untuk menjawab pertanyaan Anda:

Gunakan pembagian, bukan perkalian, dengan cara ini Anda mendapatkan hasil yang benar.

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

Tidak, karena tipe floating point Java (memang semua tipe floating point) adalah trade-off antara ukuran dan presisi. Meskipun sangat berguna untuk banyak tugas, jika Anda membutuhkan ketepatan yang sewenang-wenang, Anda harus menggunakannya BigDecimal.

biziclop
sumber