Hassasiyeti kaybetmeden şamandırayı ikiye dönüştürün


97

İlkel bir şamandıram var ve ilkel bir çift olarak ihtiyacım var. Şamandırayı ikiye katlamak bana garip bir ekstra hassasiyet veriyor. Örneğin:

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

Bununla birlikte, döküm yerine, float'ı bir dizge olarak çıktısı alırsam ve dizeyi bir çift olarak ayrıştırırsam, istediğimi elde ederim:

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

String'e gidip gelmekten daha iyi bir yol var mı?

Yanıtlar:


125

Aslında ekstra hassasiyet elde ettiğinizden değil - şamandıranın başlangıçta hedeflediğiniz sayıyı doğru bir şekilde temsil etmemesidir. Çift , orijinal kayan noktayı doğru bir şekilde temsil ediyor; toStringzaten mevcut olan "ekstra" verileri gösteriyor.

Örneğin (ve bu sayılar doğru değil, ben sadece bir şeyler uyduruyorum) varsayalım ki:

float f = 0.1F;
double d = f;

O zaman değeri ftam olarak 0.100000234523 olabilir. dtam olarak aynı değere sahip olacaktır, ancak onu bir dizeye dönüştürdüğünüzde, daha yüksek bir hassasiyete doğru olduğuna "güvenecektir", bu nedenle erken yuvarlama yapmayacaktır ve önceden olan "ekstra rakamları" göreceksiniz orada, ama senden gizli.

Eğer bir dizeye dönüştürmek ve geri daha yakın orijinal şamandıra olduğundan daha dize değeri olan bir çift değer ile biten bittiğinde - ama bu sadece iyi olmadığını gerçekten dize değeri gerçekten ne istediğini olduğuna inanıyoruz.

Float / double'ın burada kullanmak yerine uygun türler olduğundan emin misiniz BigDecimal? Kesin ondalık değerlere (örneğin para) sahip sayılar kullanmaya çalışıyorsanız, o zaman BigDecimaldaha uygun bir IMO türüdür.


40

Bu sorunu anlamak için ikili gösterime dönüştürmeyi daha kolay buluyorum.

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

Şamandıranın sonuna 0 ekleyerek iki katına genişletildiğini görebilirsiniz, ancak 0.27'nin çift temsilinin 'daha doğru' olduğunu, dolayısıyla problemin ortaya çıktığını görebilirsiniz.

   111110100010100011110101110001
11111111010001010001111010111000100000000000000000000000000000
11111111010001010001111010111000010100011110101110000101001000

25

Bu sözleşmesinin kaynaklanmaktadır Float.toString(float), kısmen diyor ki:

Kesirli kısım […] için kaç rakam basılmalıdır? Kesirli bölümü temsil etmek için en az bir rakam ve bunun ötesinde , argüman değerini float türünün bitişik değerlerinden benzersiz bir şekilde ayırt etmek için gerektiği kadar , ancak gerektiği kadar çok, daha fazla rakam olmalıdır. Yani, x'in sıfırdan farklı bir sonlu bağımsız değişken f için bu yöntem tarafından üretilen ondalık gösterimle temsil edilen tam matematiksel değer olduğunu varsayalım. O halde f, x'e en yakın float değeri olmalıdır; veya iki float değeri eşit derecede x'e yakınsa, f bunlardan biri olmalı ve f'nin anlamının en az anlamlı biti 0 olmalıdır.


13

Bugün bu sorunla karşılaştım ve BigDecimal için refactor'ı kullanamadım, çünkü proje gerçekten çok büyük. Ancak kullanarak çözüm buldum

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

Ve bu işe yarar.

Result.doubleValue () çağırmanın 5623.22998046875 döndürdüğünü unutmayın.

Ancak doubleResult.doubleValue () öğesini çağırmak doğru bir şekilde 5623.23 döndürür

Ancak bunun doğru bir çözüm olup olmadığından tam olarak emin değilim.


1
Benim için çalıştı. Ayrıca çok hızlıdır. Bunun neden bir cevap olarak işaretlenmediğinden emin değilim.
Sameer

Kullandığım yöntem bu ve yeni Float parçasına ihtiyacınız yok ... Sadece FloatingDecimal'i ilkel ile oluşturun. Otomatik olarak kutulanacak ... Ve hızlı da çalışıyor ... Tek bir dezavantaj var, FloatingDecimal değişmez, bu yüzden her bir kayan nokta için bir tane oluşturmanız gerekiyor ... O (1e10) hesaplama kodunu hayal edin! !! Tabii ki BigDecimal de aynı dezavantaja sahip ...
Mostafa Zeinali

6
Bu çözümün dezavantajı, sun.misc.FloatingDecimal'in JVM'nin dahili sınıfı olması ve yapıcısının imzasının Java 1.8'de değiştirilmiş olmasıdır. Hiç kimse gerçek yaşam uygulamasında iç sınıfları kullanmamalıdır.
Igor Bljahhin

8

Aşağıdaki çözümü buldum:

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

Eğer kullanırsanız şamandıra ve çift yerine Float ve Double aşağıdaki kullanın:

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

7

Bir kullan BigDecimalyerine float/ ' double. İkili kayan nokta olarak temsil edilemeyen çok sayıda sayı vardır (örneğin, 0.1). Dolayısıyla, sonucu her zaman bilinen bir kesinliğe veya kullanıma yuvarlamalısınız BigDecimal.

Daha fazla bilgi için http://en.wikipedia.org/wiki/Floating_point adresine bakın .


Önbelleğinizde 10 milyon kayan ticaret fiyatınız olduğunu varsayalım, yine de BigDecimal kullanmayı ve benzersiz izinleri örneklemeyi düşünüyor musunuz (flyweight / immutable'ı azaltmak için) .. yoksa daha fazlası var mı?
Sendi_t

1
@Sendi_t Lütfen karmaşık sorular sormak için yorumları kullanmayın :-)
Aaron Digulla

Baktığınız için teşekkürler! .. on yıl önce kararlaştırıldığı gibi şu anda şamandıra kullanıyoruz - bu durumla ilgili bakış açınızı takipte görmeye çalışıyordum -. Teşekkürler!
Sendi_t

@Sendi_t Yanlış anlaşılma. Sorunuza 512 karakterde cevap veremiyorum. Lütfen uygun bir soru sorun ve bana bir bağlantı gönderin.
Aaron Digulla

1
@GKFX Bilgisayarlarda ondalık sayılar söz konusu olduğunda "herkese uyan tek bir boyut" yoktur. Ancak çoğu insan anlamadığından, onları işaret ediyorum BigDecimalçünkü bu, yaygın hataların çoğunu yakalayacaktır. İşler çok yavaşsa, daha fazla öğrenmeleri ve sorunlarını optimize etmenin yollarını bulmaları gerekir. Erken optimizasyon tüm kötülüklerin köküdür - DE Knuth.
Aaron Digulla

1

Şamandıralar, doğaları gereği kesin değildir ve her zaman düzgün yuvarlama "sorunları" vardır. Hassasiyet önemliyse, uygulamanızı Decimal veya BigDecimal kullanacak şekilde yeniden düzenlemeyi düşünebilirsiniz.

Evet, kayan değerler, işlemci desteği nedeniyle sayısal olarak ondalık sayılardan daha hızlıdır. Ancak, hızlı mı yoksa doğru mu istiyorsunuz?


1
Ondalık aritmetik de kesin değildir. (ör. 1/3 * 3 == 0.9999999999999999999999999999) Para gibi kesin ondalık miktarları temsil etmek için elbette daha iyidir , ancak fiziksel ölçümler için hiçbir avantajı yoktur.
dan04

2
Ama 1 == 0.9999999999999999999999999999 :)
Emmanuel Bourg

0

Bilgi için bu, Joshua Bloch'un Effective Java 2. baskısının, Madde 48 - Tam değerler gerektiğinde kaymayı önleyin ve ikiye katlayın. Bu kitap, güzel şeylerle dolu ve kesinlikle görülmeye değer.


0

Bu çalışıyor mu?

float flt = 145.664454;

Double dbl = 0.0;
dbl += flt;

0

İyi çalışan basit bir çözüm, duble float'ın dize gösteriminden ayrıştırmaktır:

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

Süper verimli değil ama işe yarıyor!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.