Bagaimana cara mengetahui apakah BigDecimal dapat dikonversi menjadi float atau double?

10

Kelas BigDecimalmemiliki beberapa metode yang berguna untuk menjamin konversi lossless:

  • byteValueExact()
  • shortValueExact()
  • intValueExact()
  • longValueExact()

Namun, metode floatValueExact()dan doubleValueExact()tidak ada.

Saya membaca kode sumber OpenJDK untuk metode floatValue()dan doubleValue(). Keduanya tampaknya mundur ke Float.parseFloat()dan Double.parseDouble(), masing-masing, yang dapat mengembalikan infinity positif atau negatif. Misalnya, parsing string 10.000 9s akan mengembalikan tak terhingga positif. Seperti yang saya pahami, BigDecimaltidak memiliki konsep internal infinity. Selanjutnya, mengurai string 1009s sebagai doublememberi 1.0E100, yang bukan tanpa batas, tetapi kehilangan presisi.

Apa itu implementasi yang masuk akal floatValueExact()dan doubleValueExact()?

Aku berpikir tentang doublesolusi dengan menggabungkan BigDecimal.doubleValue(), BigDecial.toString(), Double.parseDouble(String)dan Double.toString(double), tapi tampaknya berantakan. Saya ingin bertanya di sini karena mungkin ada (harus!) Solusi yang lebih sederhana.

Untuk lebih jelasnya, saya tidak perlu solusi kinerja tinggi.

kevinarpe
sumber
7
Saya kira Anda bisa mengubah ke ganda, kemudian mengubah kembali ganda ke BigDecimal, dan lihat apakah Anda memperoleh nilai yang sama. Tidak yakin apa gunanya. Jika Anda menggunakan ganda, Anda sudah menerima memiliki nilai yang tidak tepat. Jika Anda menginginkan nilai yang tepat, maka tetap dengan BigDecimal.
JB Nizet

Jawaban:

6

Dari membaca yang docs , semua hal ini dengan numTypeValueExactvarian adalah untuk memeriksa keberadaan sebagian kecil atau jika nilai terlalu besar untuk jenis numerik dan melemparkan pengecualian.

Adapun floatValue()dan doubleValue(), pemeriksaan overflow serupa sedang dilakukan, tetapi alih-alih melempar pengecualian, malah mengembalikan Double.POSITIVE_INFINITYatau Double.NEGATIVE_INFINITYuntuk ganda dan Float.POSITIVE_INFINITYatau Float.NEGATIVE_INFINITYuntuk mengapung.

Karenanya implementasi exactmetode yang paling masuk akal (dan paling sederhana) untuk float dan double, harus cukup memeriksa apakah konversi kembali POSITIVE_INFINITYatau NEGATIVE_INFINITY.


Selain itu , ingatlah bahwa BigDecimalitu dirancang untuk menangani kurangnya presisi yang berasal dari menggunakan floatatau doubleuntuk irasional besar, oleh karena itu seperti @JB Nizet berkomentar , cek lain yang dapat Anda tambahkan di atas adalah untuk mengubah doubleatau floatkembali BigDecimaluntuk melihat apakah Anda masih mendapatkan nilai yang sama. Ini harus membuktikan konversi itu benar.

Inilah yang akan terlihat seperti metode untuk floatValueExact():

public static float floatValueExact(BigDecimal decimal) {
    float result = decimal.floatValue();
    if (!Float.isInfinite(result)) {
        if (new BigDecimal(String.valueOf(result)).compareTo(decimal) == 0) {
            return result;
        }
    }
    throw new ArithmeticException(String.format("%s: Cannot be represented as float", decimal));
}

Penggunaan compareTobukannya di equalsatas disengaja agar tidak menjadi terlalu ketat dengan cek. equalshanya akan mengevaluasi true ketika dua BigDecimalobjek memiliki nilai dan skala yang sama (ukuran bagian pecahan desimal), sedangkan compareToakan mengabaikan perbedaan ini ketika tidak penting. Misalnya 2.0vs 2.00.

smac89
sumber
Sangat licik. Apa yang saya butuhkan! Catatan: Anda mungkin juga ingin menggunakan Float.isFinite()atau Float.isInfinite(), tetapi ini opsional. :)
kevinarpe
3
Anda juga harus memilih compareTo daripada equals, karena equals () di BigDecimal akan menganggap 2.0 dan 2.00 sebagai nilai yang berbeda.
JB Nizet
Nilai yang tidak bisa direpresentasikan secara tepat sebagai pelampung tidak akan berfungsi. Contoh: 123.456f. Saya kira ini disebabkan oleh perbedaan ukuran signifikansi (mantissa) antara float 32-bit dan 64-bit. Dalam kode di atas Anda, saya mendapatkan hasil yang lebih baik dengan: if (new BigDecimal(result, MathContext.DECIMAL32).equals(decimal)) {. Apakah ini perubahan yang masuk akal ... atau apakah saya kehilangan case sudut lain dari nilai floating point?
kevinarpe
1
@ kevinarpe - 123.456fsecara konseptual sama dengan contoh 1.0E100 Anda. Itu mengejutkan saya bahwa jika Anda perlu memeriksa bahwa konversi dari BigDecimal -> floating point biner adalah tepat maka yang perlu adalah masalahnya; yaitu konversi tidak boleh direnungkan.
Stephen C