Ubah float menjadi double tanpa kehilangan presisi

97

Saya memiliki pelampung primitif dan saya perlu sebagai primitif ganda. Cukup melemparkan pelampung menjadi dua kali lipat memberi saya presisi ekstra yang aneh. Sebagai contoh:

float temp = 14009.35F;
System.out.println(Float.toString(temp)); // Prints 14009.35
System.out.println(Double.toString((double)temp)); // Prints 14009.349609375

Namun, jika alih-alih melakukan casting, saya mengeluarkan float sebagai string, dan mengurai string sebagai double, saya mendapatkan apa yang saya inginkan:

System.out.println(Double.toString(Double.parseDouble(Float.toString(temp))));
// Prints 14009.35

Apakah ada cara yang lebih baik daripada pergi ke String dan kembali?

Steve Armstrong
sumber

Jawaban:

124

Ini bukan karena Anda benar - benar mendapatkan presisi ekstra - pelampung tidak secara akurat mewakili angka yang Anda tuju pada awalnya. Ganda adalah mewakili mengapung asli akurat; toStringmenampilkan data "ekstra" yang sudah ada.

Misalnya (dan angka-angka ini tidak benar, saya hanya mengada-ada) seandainya Anda memiliki:

float f = 0.1F;
double d = f;

Maka nilai fmungkin persis 0,100000234523. dakan memiliki nilai yang persis sama, tetapi saat Anda mengonversinya menjadi string, ia akan "percaya" bahwa itu akurat ke presisi yang lebih tinggi, jadi tidak akan dibulatkan lebih awal, dan Anda akan melihat "digit ekstra" yang sudah di sana, tapi tersembunyi darimu.

Saat Anda mengonversi menjadi string dan kembali, Anda berakhir dengan nilai ganda yang lebih dekat ke nilai string daripada float asli - tetapi itu hanya bagus jika Anda benar-benar yakin bahwa nilai string adalah yang Anda inginkan.

Apakah Anda yakin bahwa float / double adalah tipe yang sesuai untuk digunakan di sini, bukan BigDecimal? Jika Anda mencoba menggunakan angka yang memiliki nilai desimal yang tepat (misalnya uang), maka BigDecimaljenis IMO yang lebih tepat.

Jon Skeet
sumber
40

Saya menemukan konversi ke representasi biner lebih mudah untuk memahami masalah ini.

float f = 0.27f;
double d2 = (double) f;
double d3 = 0.27d;

System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(f)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d2)));
System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(d3)));

Anda dapat melihat float diperluas menjadi double dengan menambahkan 0s di akhir, tetapi representasi ganda 0.27 adalah 'lebih akurat', itulah masalahnya.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000
Brecht Yperman
sumber
25

Ini karena kontrak Float.toString(float), yang sebagian menyatakan:

Berapa digit yang harus dicetak untuk bagian pecahan […]? Harus ada setidaknya satu digit untuk mewakili bagian pecahan, dan di luar itu sebanyak itu, tetapi hanya sebanyak, lebih banyak digit yang diperlukan untuk secara unik membedakan nilai argumen dari nilai yang berdekatan dari tipe float. Artinya, anggaplah x adalah nilai matematika yang tepat yang diwakili oleh representasi desimal yang dihasilkan oleh metode ini untuk argumen tak terbatas hingga f. Maka f harus menjadi nilai float yang paling dekat dengan x; atau, jika dua nilai float sama dekat dengan x, maka f harus salah satunya dan bit signifikansi terkecil dari f harus 0.

erickson
sumber
13

Saya mengalami masalah ini hari ini dan tidak dapat menggunakan refactor ke BigDecimal, karena proyeknya sangat besar. Namun saya menemukan solusi menggunakan

Float result = new Float(5623.23)
Double doubleResult = new FloatingDecimal(result.floatValue()).doubleValue()

Dan ini berhasil.

Perhatikan bahwa memanggil result.doubleValue () akan mengembalikan 5623.22998046875

Tapi memanggil doubleResult.doubleValue () akan mengembalikan 5623.23 dengan benar

Tetapi saya tidak sepenuhnya yakin apakah ini solusi yang tepat.

Andrew
sumber
1
Bekerja untuk saya. Juga sangat cepat. Tidak yakin mengapa ini tidak ditandai sebagai jawaban.
Sameer
Ini adalah metode yang saya gunakan, dan Anda tidak memerlukan bagian Float baru ... Buat saja FloatingDecimal dengan primitif. Ini akan otomatis dimasukkan ke dalam kotak ... Dan bekerja cepat juga ... Ada satu kelemahan, FloatingDecimal tidak dapat diubah, jadi Anda perlu membuat satu untuk setiap float ... bayangkan kode komputasi O (1e10)! !! Tentu saja BigDecimal memiliki kelemahan yang sama ...
Mostafa Zeinali
6
Kelemahan dari solusi ini adalah sun.misc.FloatingDecimal adalah kelas internal JVM dan tanda tangan konstruktornya telah diubah di Java 1.8. Tidak ada yang boleh menggunakan kelas internal dalam aplikasi kehidupan nyata.
Igor Bljahhin
8

Saya menemukan solusi berikut:

public static Double getFloatAsDouble(Float fValue) {
    return Double.valueOf(fValue.toString());
}

Jika Anda menggunakan float dan double, bukan Float dan Double, gunakan yang berikut:

public static double getFloatAsDouble(float value) {
    return Double.valueOf(Float.valueOf(value).toString()).doubleValue();
}
GSD.Aaz
sumber
7

Gunakan sebagai BigDecimalpengganti float/ double. Ada banyak angka yang tidak bisa direpresentasikan sebagai floating point biner (misalnya, 0.1). Jadi, Anda harus selalu membulatkan hasil ke presisi atau penggunaan yang diketahui BigDecimal.

Lihat http://en.wikipedia.org/wiki/Floating_point untuk informasi lebih lanjut.

Aaron Digulla
sumber
Katakanlah Anda memiliki 10 juta harga perdagangan mengambang di cache Anda, apakah Anda masih mempertimbangkan untuk menggunakan BigDecimal dan membuat contoh unik, katakanlah ~ 6 juta objek (untuk mendiskon kelas terbang / tidak dapat diubah) .. atau ada lebih dari itu?
Sendi_t
1
@Sendi_t Tolong jangan gunakan komentar untuk mengajukan pertanyaan rumit :-)
Aaron Digulla
Terima kasih telah melihat! .. kami menggunakan pelampung sekarang seperti yang telah disepakati satu dekade lalu --Saya mencoba melihat sudut pandang Anda tentang situasi ini pada tindak lanjut -. Terima kasih!
Sendi_t
@SendiKesalahpahaman. Saya tidak bisa menjawab pertanyaan Anda dalam 512 karakter. Silakan ajukan pertanyaan yang tepat dan kirimkan saya tautan.
Aaron Digulla
1
@ GKFX Tidak ada "satu ukuran cocok untuk semua" saat menangani desimal di komputer. Tapi karena kebanyakan orang tidak memahaminya, saya tunjukkan BigDecimalkarena itu akan menangkap sebagian besar kesalahan umum. Jika segala sesuatunya berjalan terlalu lambat, mereka perlu belajar lebih banyak dan menemukan cara untuk mengoptimalkan masalah mereka. Optimasi prematur adalah akar dari segala kejahatan - DE Knuth.
Aaron Digulla
1

Float, pada dasarnya, tidak tepat dan selalu memiliki "masalah" pembulatan yang rapi. Jika presisi penting, Anda dapat mempertimbangkan untuk memfaktorkan ulang aplikasi Anda untuk menggunakan Desimal atau BigDecimal.

Ya, float secara komputasi lebih cepat daripada desimal karena dukungan prosesor on. Namun, apakah Anda ingin cepat atau akurat?

Bukan saya
sumber
1
Aritmatika desimal juga tidak tepat. (mis. 1/3 * 3 == 0.9999999999999999999999999999) Ini, tentu saja, lebih baik untuk merepresentasikan jumlah desimal yang tepat seperti uang, tetapi untuk pengukuran fisik, ini tidak memiliki keuntungan.
dan04
2
Tapi 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg
0

Untuk informasi, ini ada di Item 48 - Hindari float dan double ketika nilai yang tepat diperlukan, dari Effective Java 2nd edition oleh Joshua Bloch. Buku ini dikemas dengan barang-barang bagus dan pasti menarik untuk dilihat.

mR_fr0g
sumber
0

Apakah ini berhasil?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;
AesmaDiv
sumber
0

Solusi sederhana yang berfungsi dengan baik, adalah mengurai ganda dari representasi string float:

double val = Double.valueOf(String.valueOf(yourFloat));

Tidak super efisien, tapi berhasil!

Alessandro Roaro
sumber