Ondalık basamakları çift olarak taşıma


97

Yani 1234'e eşit bir çift kümem var, 12.34 yapmak için ondalık bir basamak taşımak istiyorum.

Bunu yapmak için 0,1 ile 1234'ü iki kez çarpıyorum, bunun gibi

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

Bu, "12.340000000000002" sonucunu yazdıracaktır.

İki ondalık basamağa biçimlendirmeden, ikili 12.34'ü doğru şekilde depolamanın bir yolu var mı?



43
Yapmamanın bir sebebi var mı x /= 100;?
Mark Ingram

Yanıtlar:


189

Eğer kullanıyorsanız doubleveya float, sen yuvarlama kullanabilir veya bazı yuvarlama hataları görmek için beklemek gerekir. Bunu yapamazsanız kullanın BigDecimal.

Sahip olduğunuz problem, 0.1'in tam bir temsil olmaması ve hesaplamayı iki kez yaparak bu hatayı birleştiriyorsunuz.

Bununla birlikte, 100 doğru şekilde temsil edilebilir, bu yüzden şunu deneyin:

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

hangi yazdırır:

12.34

Bu işe yarıyor çünkü Double.toString(d)sizin adınıza az miktarda yuvarlama yapıyor, ancak çok fazla değil. Yuvarlamadan nasıl görünebileceğini merak ediyorsanız:

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

baskılar:

0.100000000000000005551115123125782702118158340454101562
12.339999999999999857891452847979962825775146484375

Kısacası, bunu açıkça yapsanız da yapmasanız da kayan noktada mantıklı yanıtlar için yuvarlama kaçınılmazdır.


Not: x / 100ve x * 0.01yuvarlama hatası söz konusu olduğunda tam olarak aynı değildir. Bunun nedeni, birinci ifadenin yuvarlama hatasının x değerlerine bağlı 0.01olması, ikincinin ise sabit bir yuvarlak hatasına sahip olmasıdır.

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

baskılar

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
İlk başta bunu yapmayı düşünmediğime inanamıyorum! Teşekkürler :-P
BlackCow

6
100 tam olarak ikili biçimde gösterilebilse de, 100'e bölme tam olarak gösterilemez. Bu nedenle, 1234/100sizin yaptığınız gibi yazmak , altta yatan sorun hakkında gerçekten hiçbir şey yapmaz - tamamen yazmaya eşit olmalıdır 1234 * 0.01.
Brooks Moses

1
@Peter Lawrey: Sayının tek mi çift mi yuvarlamayı etkilediğini daha fazla açıklayabilir misiniz? / = 100 ve * =. 01'in aynı olacağını düşünüyorum, çünkü 100 bir int olsa bile, tür zorlamasının bir sonucu olarak yine de 100.0'a dönüştürülecektir.
eremzeit

1
/100ve *0.01birbirine eşdeğerdir, ancak OP'lere eşdeğer değildir *0.1*0.1.
Amadan

1
Tek söylediğim, 0,1 ile iki kez çarpmanın ortalama olarak 0,01 ile çarpmaktan daha büyük bir hata oluşturacağıdır; ancak @ JasperBekkers'ın 100'ünün farklı olduğunu, tam olarak ikiliyle temsil edilebilir olduğunu memnuniyetle kabul edeceğim.
Amadan

52

Hayır - ondalık değerleri doğru bir şekilde saklamak istiyorsanız kullanın BigDecimal. doublebasitçe olamaz artık tam olarak ondalık basamak sonlu sayı ile üçüncü değerini yazabilirsiniz daha tam 0,1 gibi bir sayı temsil eder.


46

o eğer sadece biçimlendirme, printf deneyin

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

çıktı

12.34

8
Daha yüksek puan alan cevaplar teknik olarak daha anlayışlı, ancak bu OP'nin problemine doğru cevaptır. Genellikle ikiye katlamanın ufak bir yanlışlığını umursamıyoruz , bu yüzden BigDecimal aşırıdır, ancak görüntülerken genellikle çıktımızın sezgilerimizle eşleştiğinden emin olmak isteriz, bu yüzden System.out.printf()doğru yol da öyle .
dimo414

28

Finansal yazılımda, pennies için tamsayı kullanmak yaygındır. Okulda, kayan nokta yerine sabit noktayı nasıl kullanacağımız öğretildi, ancak bu genellikle ikinin gücüdür. Kuruşları tam sayı olarak saklamak "sabit nokta" olarak da adlandırılabilir.

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

Sınıfta, genel olarak, bir tabanda tam olarak hangi sayıların temsil edilebileceği soruldu.

İçin base=p1^n1*p2^n2... herhangi bir N'yi temsil edebilirsiniz, burada N = n * p1 ^ m1 * p2 ^ m2.

İzin verin base=14=2^1*7^1... 1/7 1/14 1/28 1/49 temsil edebilirsiniz ama 1/3 olamaz

Finansal yazılım hakkında bilgim var - Ticketmaster'ın finansal raporlarını VAX asm'den PASCAL'a dönüştürdüm. Kuruş kodları ile kendi formatln () 'larına sahiplerdi. Dönüşümün nedeni 32 bitlik tamsayılar artık yeterli değildi. +/- 2 milyar kuruş 20 milyon dolar ve bu Dünya Kupası veya Olimpiyatlar için doldu, unuttum.

Gizlilik yemini ettim. Oh iyi. Academea'da eğer iyiyse yayınlayın; endüstride bunu gizli tutarsınız.


12

tam sayı gösterimini deneyebilirsiniz

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

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

5
@Dan: Neden? Bu, donanım düzeyinde hızı korurken finansal uygulamalar (veya küçük bir yuvarlama hatasının bile kabul edilemez olduğu diğer uygulamalar) için doğru yaklaşımdır. (Elbette, normalde her seferinde yazılmayan bir sınıfa
sarılırdı

7
Bu çözümde ufak bir sorun var - eğer kalan r10'dan küçükse, 0 doldurma oluşmaz ve 1204, 12.4 sonucunu verir. Doğru biçimlendirme dizesi "% d.% 02d" ye daha çok benziyor
jakebman

10

Bu, bilgisayarların kayan noktalı sayıları saklama biçiminden kaynaklanır. Bunu tam olarak yapmıyorlar. Bir programcı olarak, kayan noktalı sayılarla ilgili denemeler ve sıkıntılar hakkında bilgi sahibi olmak için bu kayan nokta kılavuzunu okumalısınız .


Argh, tam olarak aynı yerle bağlantılı bir açıklama yazıyordum. +1.
Pops

@ Lord Haha, üzgünüm. Nasıl olsa Skeeted'i aldım. :-)
CanSpice

Nedeninin bu olduğunu anladım, ancak ondalık basamağı kaydırmanın yaratıcı bir yolu olup olmadığını merak ediyorum. Bir duble
12.34'ü

1
Bir çifte 12.34'ü temiz bir şekilde saklamak mümkün olsaydı, Java'nın yapacağını düşünmüyor musun? Değil. Başka bir veri türü (BigDecimal gibi) kullanmanız gerekecek. Ayrıca, neden bir döngüde yapmak yerine 100'e bölmüyorsunuz?
CanSpice

Do'h ... evet, 100'e bölerek temiz bir 12.34 ile sonuçlanır ... teşekkürler :-P
BlackCow

9

Çok sayıda gönderinin BigDecimal'i kullanmaktan bahsetmesi komik, ancak kimse BigDecimal'e göre doğru cevabı vermeyi rahatsız etmiyor mu? Çünkü BigDecimal ile bile, bu kodda gösterildiği gibi hala yanlış gidebilirsiniz.

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

Bu çıktıyı verir

12.34
12.34000000000000025687785232264559454051777720451354980468750
12.34

BigDecimal yapıcısı, özellikle String yapıcısı kullanmanın sayısal bir oluşturucuya göre daha iyi olduğunu belirtir. Nihai hassasiyet, isteğe bağlı MathContext'ten de etkilenir.

BigDecimal Javadoc'a göre , String yapıcısını kullanmanız koşuluyla , tam olarak 0.1'e eşit bir BigDecimal oluşturmak mümkündür .


5

Evet var. Her çift işlemle doğruluğu kaybedebilirsiniz ancak doğruluk miktarı her işlem için farklılık gösterir ve doğru işlem sırasını seçerek en aza indirilebilir. Örneğin, sayı kümesini çarparken, en iyisi kümeyi çarpmadan önce üsse göre sıralamaktır.

Sayı hesaplaması üzerine herhangi bir düzgün kitap bunu açıklar. Örneğin: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Ve sorunuzu cevaplamak için:

Çarpma yerine bölme kullanın, bu şekilde doğru sonucu elde edersiniz.

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

3

Hayır, Java kayan nokta türleri (aslında tüm kayan nokta türleri) boyut ve hassasiyet arasında bir değiş tokuş olduğundan hayır . Pek çok görev için çok yararlı olsalar da, keyfi bir hassasiyete ihtiyacınız varsa kullanmalısınız BigDecimal.

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.