Ondalık yerine ne zaman çift kullanmalıyım?


265

Bunun yerine double(veya float) kullanmanın üç avantajını adlandırabilirim decimal:

  1. Daha az bellek kullanır.
  2. Daha hızlı çünkü kayan nokta matematik işlemleri, işlemciler tarafından yerel olarak desteklenir.
  3. Daha geniş bir sayı aralığını temsil edebilir.

Ancak bu avantajlar sadece modelleme yazılımında bulunanlar gibi hesaplama yoğun operasyonlar için geçerli görünmektedir. Elbette, finansal hesaplamalar gibi hassasiyet gerektiğinde çiftler kullanılmamalıdır. Öyleyse, "normal" uygulamalar yerine hiç double(veya float) seçmenin pratik nedenleri var decimalmı?

Eklemek için düzenlendi: Tüm harika yanıtlar için teşekkürler, onlardan öğrendim.

Başka bir soru: Birkaç kişi iki katına çıkmanın gerçek sayıları daha kesin bir şekilde temsil edebileceğini belirtti. Bildirildiğinde, genellikle onları daha doğru bir şekilde temsil ettiklerini düşünürdüm. Ancak kayan nokta işlemleri yapıldığında doğruluğun (bazen önemli ölçüde) düşebileceği doğru bir ifade midir?



5
Bu oldukça düzenli olarak yükseliyor ve hala bununla mücadele ediyorum. Örneğin, finansal hesaplamalar yapan bir uygulama üzerinde çalışıyorum, bu yüzden ondalık kullanıyorum. Ancak Math ve VisualBasic.Finansal fonksiyonlar çift kullanır, bu yüzden sürekli olarak ondalık kullanımını tahmin eden ikinci şey olan bir sürü dönüştürme vardır.
Jamie Ide

@JamieIde Finansal fonksiyonlar iki katına çıkar, para her zaman ondalık olmalıdır.
Chris Marisic

@ChrisMarisic Peki jamie Ide çift kullanarak eski saçmalıklarla ne yapabilir? O zaman çok fazla dönüşüm kullanmanız gerekir, birçok dönüşüm yuvarlama hatalarına neden olur ... hiç merak etmedi VisualBasic pfffhh bahsetti .....
Elisabeth

@Elisabeth büyük olasılıkla ondalığı düzgün destekleyen farklı bir kütüphane kullanırdım. VisualBasic.Financial ne olursa olsun bugün birçok kütüphanede büyük olasılıkla var
Chris Marisic

Yanıtlar:


306

Bence avantajları oldukça iyi özetlediniz. Ancak bir noktayı kaçırıyorsunuz. decimalTipi temsil sadece daha doğrudur taban 10 numara (örneğin para / finans hesaplamalarda kullanılan olanlar). Genel olarak, doubletür en azından büyük bir hassasiyet (biri yanlışsam beni düzeltir) ve keyfi gerçek sayılar için kesinlikle daha yüksek hız sunacaktır. Basit sonuç şudur: Hangisini kullanacağınızı düşünürken, doublesunulan base 10doğruluğa ihtiyacınız olmadığı sürece her zaman kullanın decimal.

Düzenle:

Operasyonlardan sonra kayan nokta sayılarının doğruluğundaki azalmayla ilgili ek sorunuzla ilgili olarak, bu biraz daha ince bir konudur. Aslında, her işlem yapıldıktan sonra hassasiyet (burada doğruluk için birbirinin yerine kullanılabilir ifadesini kullanıyorum) sürekli olarak azalacaktır. Bunun iki nedeni vardır:

  1. belirli sayıların (en açık olarak ondalık sayılar) gerçekte kayan nokta biçiminde temsil edilememesi
  2. yuvarlama hataları, sanki hesaplamayı elle yapıyormuşsunuz gibi meydana gelir. Bununla birlikte, bu hataların çok fazla düşünmeyi gerektirecek kadar önemli olup olmadığına büyük ölçüde bağlama (kaç işlem gerçekleştirdiğinize) bağlıdır.

Her durumda, teoride eşdeğer olması gereken (ancak farklı hesaplamalar kullanılarak ulaşılan) iki kayan nokta sayısını karşılaştırmak istiyorsanız, belirli bir tolerans derecesine izin vermeniz gerekir (ne kadar değişir, ancak genellikle çok küçüktür) .

Kesinliklerdeki hataların ortaya çıkabileceği belirli durumlara daha ayrıntılı bir genel bakış için Wikipedia makalesinin Doğruluk bölümüne bakın . Son olarak, makine düzeyinde kayan nokta sayıları / işlemleri hakkında ciddi bir derinlemesine (ve matematiksel) tartışma istiyorsanız, her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler başlıklı makaleyi okumaya çalışın .


1
Baz 2'ye dönüştürürken hassasiyetin kaybolduğu bir baz 10 numarasından bir örnek verebilir misiniz?
Mark Cidade

@ Mark: 1.000001 en azından Jon Skeet'e göre bir örnek. (Bu sayfanın 3. soruya bakın: yoda.arachsys.com/csharp/teasers-answers.html )
Noldorin

25
@ Mark: çok basit bir örnek: 0.1, baz 2'deki periyodik bir kesirdir, bu nedenle a double. Modern bilgisayarlar hala doğru değeri yazdıracaklar, ancak sonuçta “tahmin ettikleri” için - gerçekten doğru ifade edildiği için değil.
Konrad Rudolph

1
DecimalTip 52 için yaklaşık kıyasla mantis hassasiyet 93-bite sahip double. Keşke Microsoft, IEEE 80 bit formatını desteklese de, 16 bayta kadar doldurulmuş olsa bile; aşkın operasyonlar (ör. sin (x), log (x), vb.) ve hassasiyetten çok daha iyi olmamakla birlikte daha geniş bir aralığa doubleveya Decimaldaha iyi bir hıza izin verecekti . DecimalDecimaldouble
supercat

@charlotte: Yazımın tamamını okuduysan bunun açıklandığını göreceksin.
Noldorin

59

Kayan nokta türü kullanmanın yararlarına dikkat çekiyorsunuz. Her durumda ondalıklar için tasarım yapmaya eğilimliyim ve ondalık işlemlerin darboğazlara veya yavaşlamalara neden olup olmadığını bana bildirmek için bir profillere güveniyorum. Bu durumlarda, ikiye katlamak veya yüzmek için "aşağı dökeceğim", ancak sadece dahili olarak yapacağım ve yapılan matematiksel işlemdeki önemli basamakların sayısını sınırlayarak hassas kaybı yönetmeye dikkatle çalışacağım.

Genel olarak, değeriniz geçici ise (tekrar kullanılmıyorsa) bir kayan nokta türü kullanmak güvenlidir. Kayan nokta türleri ile ilgili asıl sorun aşağıdaki üç senaryodur.

  1. Kayan nokta değerlerini topluyorsunuz (bu durumda hassas hatalar bileşiği)
  2. Kayan nokta değerine dayalı değerler oluşturursunuz (örneğin, özyinelemeli algoritmada)
  3. Çok sayıda önemli basamakla matematik yapıyorsunuz (örneğin, 123456789.1 * .000000000000000987654321)

DÜZENLE

C # ondalık sayılara ilişkin referans belgelerine göre :

Ondalık anahtar 128 bitlik veri türünü gösterir. Kayan noktalı türlerle karşılaştırıldığında, ondalık tür daha büyük bir hassasiyete ve daha küçük bir aralığa sahiptir, bu da onu finansal ve parasal hesaplamalar için uygun hale getirir.

Yukarıdaki açıklamamı netleştirmek için:

Her durumda ondalıklar için tasarım yapmaya eğilimliyim ve ondalık işlemlerin darboğazlara veya yavaşlamalara neden olup olmadığını bana bildirmek için bir profillere güveniyorum.

Sadece ondalık sayıların elverişli olduğu sektörlerde çalıştım. Fizik veya grafik motorları üzerinde çalışıyorsanız, kayan nokta tipi (float veya double) için tasarlamak çok daha yararlıdır.

Ondalık sınırsız derecede kesin değildir (ilkel bir veri türünde integral olmayan için sonsuz hassasiyeti temsil etmek imkansızdır), ancak çiftten çok daha kesindir:

  • ondalık = 28-29 anlamlı basamak
  • çift ​​= 15-16 anlamlı basamak
  • kayan nokta = 7 anlamlı basamak

DÜZENLEME 2

Konrad Rudolph'un yorumuna cevaben , 1. madde (yukarıda) kesinlikle doğrudur. Tutarsızlığın bir araya gelmesi gerçekten bileşiktir. Bir örnek için aşağıdaki koda bakın:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Bu, aşağıdakileri çıkarır:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

Gördüğünüz gibi, aynı kaynak sabitinden eklememize rağmen, çiftin sonuçları daha az kesindir (muhtemelen doğru bir şekilde yuvarlansa da) ve şamandıra çok daha az hassastır, sadece azaltıldığı noktaya kadar iki önemli basamak.


1
Nokta 1 yanlış. Hassasiyet / yuvarlama hataları hesaplamalarda değil, yalnızca dökümde ortaya çıkar. İse çoğu matematiksel işlemler dolayısıyla hata çarparak, kararsız olduklarını elbette doğru. Ancak bu başka bir konudur ve sınırlı hassasiyetteki tüm veri türleri için de geçerlidir, özellikle ondalık için.
Konrad Rudolph

1
@Konrad Rudolph, "DÜZENLEME 2" deki örneği 1. maddede belirtmeye çalıştığım noktanın kanıtı olarak görüyorum. Genellikle bu sorun kendini göstermez çünkü pozitif tutarsızlık olumsuz belirsizlikle dengelenir ve toplama, ancak aynı örnekte toplama (örnekte yaptığım gibi) sorunu vurgular.
Michael Meadows

Harika bir örnek. Yeni geliştiricilerime gösterdim, çocuklar şaşırdı.
Machado

Şimdi aynı şeyi 3/5 yerine 2/3 ile yapabilirsiniz ... 2 / 3'leri mükemmel şekilde işleyen asgari sayı sistemi hakkında bilgi edinmelisiniz.
gnasher729

1
@ gnasher729, 3/5 yerine 2/3 kullanarak farklı türler için mükemmel bir şekilde ele alınmamıştır . İlginçtir, şamandıra değeri Single: 667660.400000000000, ondalık değer verirken çıktı Decimal: 666666.7000000000. Şamandıra değeri, doğru değerin binden azdır.
jhenninger

25

Diğerlerinin önerdiği gibi, temel 10 değerleri, örneğin finansal hesaplamalar için ondalık sayı kullanın.

Ancak çift, genellikle rastgele hesaplanan değerler için daha doğrudur.

Örneğin, bir portföydeki her bir satırın ağırlığını hesaplamak istiyorsanız, sonuç neredeyse% 100'e kadar çıkacağı için iki kat kullanın.

Aşağıdaki örnekte doubleResult, ondalık sayıdan 1'e yakındır:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Bir portföy örneğini tekrar ele alalım:

  • Portföydeki her bir satırın piyasa değeri parasal bir değerdir ve muhtemelen en iyi ondalık sayı olarak temsil edilir.

  • Portföydeki her bir satırın ağırlığı (= Pazar Değeri / TOPLA (Pazar Değeri)) genellikle çift olarak daha iyi temsil edilir.


6

Hassaslığa ihtiyacınız olmadığında bir çift veya bir şamandıra kullanın, örneğin yazdığım bir platform oyununda, oyuncu hızlarını saklamak için bir şamandıra kullandım. Açıkçası burada süper hassasiyete ihtiyacım yok çünkü sonunda ekranda çizim için bir Int'ye yuvarlanıyorum.


3
Hassasiyet, ondalık sayıların SADECE avantajıdır, bu doğrudur. Ondalık sayılar üzerinde kayan nokta sayılarını ne zaman kullanmanız gerektiğini sormamalısınız. Bu ilk düşüncen olmalı. O zaman soru, ondalık basamakları ne zaman kullanmanız gerektiğidir (ve cevap tam burada ... hassasiyet önemli olduğunda).
Instance Hunter

3
@Daniel Straight, Komik ama tam tersi bir fikrim var. Performans özellikleri nedeniyle daha az hassas bir tip kullanmak bir ön optimizasyon anlamına geliyor. Avantajını fark etmeden önce potansiyel olarak bu ön optimizasyon için birçok kez ödeme yapmanız gerekecektir.
Michael Meadows

3
@Michael Meadows, bu iddiayı anlayabiliyorum. Ancak dikkat edilmesi gereken bir şey, erken optimizasyon ile ilgili ana şikayetlerden birinin programcıların neyin yavaş olacağını bilmeme eğiliminde olmasıdır. Kuşkusuz, ondalık sayıların iki kattan daha yavaş olduğunu biliyoruz. Yine de, çoğu durumda, performans iyileştirmesi yine de kullanıcı tarafından fark edilmeyecektir. Tabii ki, çoğu durumda, hassasiyet de gerekli değildir. Heh.
Örneği Avcısı

Ondalık kayan nokta aslında aynı sayıda bit kullanan ikili kayan noktadan daha az hassastır. Decimal'in avantajı, finansal hesaplamada yaygın olan 0.01 gibi DECIMAL kesirleri tam olarak temsil edebilmektir.
dan04

Peki, bu doğru değil :) - birçok oyunda kayan nokta sayıları tutarlı olmadıkları için istenemez. Buraya
BlueRaja - Danny Pflughoeft

4

Bazı Muhasebe'de, bunun yerine veya birlikte integral türler kullanma olasılığını göz önünde bulundurun. Örneğin, altında çalıştığınız kuralların her hesaplama sonucunun en az 6 ondalık basamakla devam etmesini gerektirdiğini ve nihai sonucun en yakın kuruşa yuvarlanacağını varsayalım.

100 $ 'ın 1 / 6'sı bir hesaplama 16.66666666666666 ... verir, bu nedenle bir çalışma sayfasında taşınan değer 16.666667 $ olacaktır. Hem çift hem de ondalık, 6 ondalık basamağa doğru sonuç verir. Ancak, sonucu 16666667 tamsayı olarak ileri taşıyarak herhangi bir kümülatif hatayı önleyebiliriz. Sonraki her hesaplama aynı hassasiyetle yapılabilir ve benzer şekilde ileriye taşınabilir. Örneğe devam ederken, bu tutar üzerinden Teksas satış vergisini hesaplıyorum (16666667 * .0825 = 1375000). İkisini eklemek (kısa bir çalışma sayfasıdır) 1666667 + 1375000 = 18041667. Ondalık noktayı geri taşımak bize 18.041667 veya 18.04 dolar kazandırır.

Bu kısa örnek, çift veya ondalık kullanarak kümülatif bir hata vermese de, yalnızca çift veya ondalık hesaplamanın ve ileri taşınmanın önemli hata biriktireceği durumları göstermek oldukça kolaydır. Altında çalıştığınız kurallar sınırlı sayıda ondalık basamak gerektiriyorsa, gerçek değeri elde etmek için her değeri 10 ^ (çarpı ondalık basamak sayısı) ile çarpıp 10 ^ (gerekli ondalık basamak sayısı) ile bölerek tamsayı olarak saklayın. değeri herhangi bir kümülatif hatayı önleyecektir.

Kuruş parçalarının meydana gelmediği durumlarda (örneğin, bir otomat), entegre olmayan tipleri kullanmak için hiçbir neden yoktur. Basitçe dolar yerine pennies saymak gibi düşünün. Her hesaplamanın sadece bütün kuruşları içerdiği kodu gördüm, ancak çift kullanımı hatalara yol açtı! Tamsayı sadece matematik sorunu kaldırdı. Yani benim alışılmadık cevabım, mümkün olduğunda, hem çift hem de ondalık sayıdan vazgeçmektir.


3

Diğer diller veya platformlarla ikili interrop kullanmanız gerekiyorsa, standartlaştırılmış float veya double kullanmanız gerekebilir.


2

Not: Bu yazı, ondalık türün http://csharpindepth.com/Articles/General/Decimal.aspx adresindeki yeteneklerine ve bunun ne anlama geldiğine dair kendi yorumuma dayanmaktadır. Double'in normal IEEE çift kesinlik olduğunu varsayacağım.

Not2: bu yazıdaki en küçük ve en büyük sayı büyüklüğüne karşılık gelir.

"Ondalık" artıları.

  • "ondalık" tam olarak (yeterince kısa) ondalık kesir olarak yazılabilen sayıları temsil edebilir, çift olamaz. Bu, finansal defterlerde ve benzerlerinde, sonuçların hesaplamaları yapan bir insanın vereceği şeyle tam olarak eşleşmesinin önemli olduğu yerlerde önemlidir.
  • "ondalık", "çift" ten çok daha büyük bir mantis içerir. Bu, normalleştirilmiş aralığı içindeki değerler için "ondalık" değerinin çiftten çok daha yüksek bir hassasiyete sahip olacağı anlamına gelir.

Ondalık Eksileri

  • Çok daha yavaş olacak (Ben kriterlerim yok ama belki de en azından bir büyüklük sırasını tahmin ediyorum), ondalık herhangi bir donanım hızlandırmasından faydalanmayacak ve üzerindeki aritmetik 10 güç ile nispeten pahalı çarpma / bölme gerektirecektir ( 2) kuvvetleri ile çarpma ve bölme işleminden çok daha pahalıdır) toplama / çıkarma öncesi üssü eşleştirmek ve üssü / bölme işleminden sonra üssü tekrar aralığa getirmek.
  • ondalık daha önce taşacaktır. ondalık yalnızca ± 2 96 -1'e kadar olan sayıları temsil edebilir . Karşılaştırma ile çift yaklaşık ± 2 1024'e kadar sayıları temsil edebilir
  • ondalık daha erken taşacaktır. Ondalık olarak temsil edilebilen en küçük sayılar ± 10-28'dir . Karşılaştırılarak çift 2'ye kadar değerleri temsil edebilir -149 (yaklaşık 10 -45 subnromal numaraları desteklenir varsa) ve 2 -126 (yaklaşık 10 -38 değiller ise).
  • ondalık, iki kat daha fazla bellek kaplar.

Benim düşüncem, para çalışması ve insan hesaplamasının tam olarak eşleşmesinin önemli olduğu diğer durumlar için "ondalık" değerini varsayılan olarak kullanmanız ve geri kalan süre için varsayılan seçiminiz olarak iki kez kullanmanız gerektiğidir.


2

İhtiyacınız olan şeye bağlıdır.

Float ve double ikili veri türleri olduğundan, mermi sayılarında bazı zorluklarınız ve hatalarınız vardır, bu nedenle örneğin, çift 0.1 ila 0.100000001490116, çift de 1/3 ila 0.33333334326441 yuvarlanır. Basitçe söylemek gerekirse, tüm gerçek sayılar çift tiplerde doğru temsile sahip değildir

Neyse ki C #, sayıların ikili sistem yerine ondalık sayısal sistem aracılığıyla temsil edildiği ondalık kayan noktalı aritmetiği de destekler. Bu nedenle, ondalık kayan nokta aritmetiği kayan nokta sayılarını saklarken ve işlerken doğruluğunu kaybetmez . Bu, yüksek düzeyde doğruluk gerektiren hesaplamalara son derece uygundur.


0

Performansı doğruluğa göre değerlendiriyorsanız kayan noktalar kullanın.


6
Ondalık sayılar, bazen (hiçbir zaman her zaman önemli olmayan) bazı sınırlı durumlar dışında daha doğru değildir.
David Thornley

0

Uygulamanızın işlev türünü seçin. Finansal analizde olduğu gibi hassasiyete ihtiyacınız varsa, sorunuzu cevapladınız. Ancak uygulamanız bir tahminde bulunabiliyorsa, çift ile tamam.

Başvurunuzun hızlı bir hesaplamaya ihtiyacı var mı yoksa size cevap vermek için dünyada her zaman var mı? Gerçekten uygulamanın türüne bağlıdır.

Grafik aç mı? float veya double yeterlidir. Finansal veri analizi, meteor bir gezegene bir tür hassasiyet vurur? Bunlar biraz hassasiyete ihtiyaç duyacaklar :)


8
Ondalık sayılar da tahminidir. Finansal aritmetik kurallarına uygundurlar, ancak örneğin fizikle ilgili hesaplamalarda avantaj yoktur.
David Thornley

0

Ondalık daha geniş bayt içerir, çift CPU tarafından doğal olarak desteklenir. Ondalık taban-10'dur, bu nedenle ondalık hesaplanırken ondalık-çift dönüşüm gerçekleşir.

For accounting - decimal
For finance - double
For heavy computation - double

Unutmayın. .NET CLR yalnızca Math.Pow'u (çift, çift) destekler. Ondalık desteklenmez.

.Net çerçeve 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

Ondalık gösterimden daha kısasa, çift değerler varsayılan olarak bilimsel gösterime serileştirilir. (örneğin .00000003 3e-8 olacaktır) Ondalık değerler hiçbir zaman bilimsel gösterime serileştirilmez. Harici bir tarafça tüketilmek üzere serileştirilirken, bu dikkate alınabilir.

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.