it-swarm-tr.com

Kayan nokta tuzaklarından kaçınmak için programlama dillerine ne yapılabilir?

Kayan nokta aritmetiği ve kısa gelişmelerin yanlış anlaşılması, programlamada sürpriz ve karışıklığın önemli bir nedenidir (Yığın Taşması ile ilgili "doğru eklenmeyen sayılar" ile ilgili soru sayısını dikkate alın). Birçok programcının etkilerini henüz anlamadığı düşünüldüğünde, birçok ince hata (özellikle finansal yazılımlara) dahil etme potansiyeline sahiptir. Programlama dilleri, kavramlara aşina olmayanlar için tuzaklarından kaçınmak için ne yapabilirken, doğruluk kritik olmadığında hızını sunarken do kavramları anlayanlar için ne yapabilir?

28
Adam Paynter

Evcil hayvanımdan birini ortaya çıkaran "özellikle finansal yazılımlar için" diyorsunuz: para bir kayan nokta değil, bir int .

Tabii, bir şamandıra gibi görünüyor. İçinde ondalık bir nokta var. Ancak bunun nedeni, sorunu karıştıran birimlere alışmanızdır. Para her zaman tamsayı olarak gelir. Amerika'da, sent. (Sanırım bazı bağlamlarda değirmen olabilir, ama şimdilik görmezden gel.)

Yani 1.23 dolar dediğinizde, bu gerçekten 123 sent. Her zaman, her zaman, her zaman matematiğinizi bu terimlerle yapın ve iyi olacaksınız. Daha fazla bilgi için, bkz:

Soruyu doğrudan cevaplamak için, programlama dilleri sadece makul bir ilkel olarak bir Para türünü içermelidir.

güncelleme

Tamam, üç kez değil, sadece iki kez "hep" demeliydim. Para aslında her zaman bir int'tir; aksini düşünenler bana 0,3 sent göndermeyi ve sonucu banka ekstrenizde göstermeyi deneyebilirler. Ancak yorumcuların belirttiği gibi, para benzeri sayılar üzerinde kayan nokta matematiği yapmanız gerektiğinde nadir istisnalar vardır. Örneğin, belirli türdeki fiyatlar veya faiz hesaplamaları. O zaman bile, bunlara istisnalar gibi davranılmalıdır. Para gelir ve tamsayı miktarlar olarak çıkar, bu nedenle sisteminiz ona ne kadar yakınsa, akıl almaz.

47
William Pietri

Ondalık tip için destek sağlamak birçok durumda yardımcı olur. Birçok dilin ondalık türü vardır, ancak az kullanılır.

Reel sayıların temsili ile çalışırken ortaya çıkan yaklaşımı anlamak önemlidir. Hem ondalık hem de kayan nokta türlerini kullanmak 9 * (1/9) != 1 doğru bir ifadedir. Sabitler sabitlendiğinde, optimize edici hesaplamayı doğru olacak şekilde optimize edebilir.

Yaklaşık bir operatör sağlamak yardımcı olacaktır. Ancak, bu tür karşılaştırmalar sorunludur. .9999 trilyon dolar yaklaşık olarak 1 trilyon dolara eşittir. Farkı banka hesabımla yatırır mısınız?

15
BillThor

Üniversiteye gittiğimde bilgisayar bilimlerinde ilk yıl (ikinci sınıf) dersinde ne yapmamız gerektiği söylendi (bu ders çoğu fen dersi için de bir ön koşuldur)

"Kayan nokta sayıları yaklaşık değerlerdir. Para için tamsayı türleri kullanın. Doğru hesaplama için FORTRAN veya BCD numaralarına sahip başka bir dil kullanın" diyerek hatırlatıyorum. (ve sonra ikili kayan noktada doğru bir şekilde temsil edilmesi mümkün olmayan bu 0.2 örneğini kullanarak yaklaşımı işaret etti). Bu aynı zamanda laboratuvar egzersizlerinde de ortaya çıktı.

Aynı ders: "Kayan noktadan daha fazla doğruluk almanız gerekiyorsa, terimlerinizi sıralayın. Büyük sayılara değil, küçük sayıları birlikte ekleyin." Bu aklımda kaldı.

Birkaç yıl önce, çok doğru ve hala hızlı olması gereken bazı küresel geometrim vardı. PC'lerde 80 bit çift kesmiyordu, bu yüzden değişmeli işlemleri gerçekleştirmeden önce terimleri sıralayan programa bazı türler ekledim. Sorun çözüldü.

Gitarın kalitesi hakkında şikayet etmeden önce çalmayı öğrenin.

Dört yıl önce JPL için çalışan bir iş arkadaşım vardı. Bazı şeyler için FORTRAN'ı kullandığımıza olan inançsızlığını dile getirdi. (Çevrimdışı hesaplanan süper hassas sayısal simülasyonlara ihtiyacımız vardı.) "Tüm bu FORTRAN'ı C++ ile değiştirdik" dedi gururla. Neden bir gezegeni kaçırdıklarını merak ettim.

9
Tim Williscroft

Uyarı: Kayan nokta tipi System.Double, doğrudan eşitlik testi için kesinliğe sahip değildir.

double x = CalculateX();
if (x == 0.1)
{
    // ............
}

Herhangi bir şeyin dil düzeyinde yapılabileceğine veya yapılması gerektiğine inanmıyorum.

8
ChaosPandion

Varsayılan olarak, diller tamsayı olmayan sayılar için rasgele duyarlıklı gerekçeler kullanmalıdır.

Optimize etmek isteyenler her zaman şamandıra isteyebilirler. Bunları varsayılan olarak kullanmak C ve diğer sistem programlama dillerinde anlamlıdır, ancak günümüzde popüler olan çoğu dilde değildir.

7
Waquo

Kayan nokta sayılarını içeren en büyük iki sorun şunlardır:

  • hesaplamalara uygulanan tutarsız birimler (bunun aynı zamanda tamsayı aritmetiğini de etkilediğini unutmayın)
  • FP sayılarının bir yaklaşık olduğunu ve yuvarlama ile akıllıca nasıl başa çıkılacağını anlamama.

İlk hata tipi, yalnızca değer ve birim bilgilerini içeren bileşik bir tür sağlayarak giderilebilir. Örneğin, birimi içeren bir length veya area değeri (sırasıyla metre veya metrekare veya fit ve feet kare). Aksi takdirde, her zaman bir tür ölçü birimi ile çalışmak ve sadece bir insanla cevap paylaştığımızda diğerine dönüştürmek konusunda gayretli olmalısınız.

İkinci başarısızlık türü kavramsal bir başarısızlıktır. Başarısızlıklar, insanlar onları mutlak sayı olarak düşündüklerinde kendini gösterir. Eşitlik işlemlerini, kümülatif yuvarlama hatalarını vb. Etkiler. Örneğin, bir sistem için iki ölçümün belirli bir hata payı içinde eşdeğer olması doğru olabilir. Yani .999 ve 1.001, +/- .1'den küçük farkları umursamadığınızda kabaca 1.0 ile aynıdır. Ancak, tüm sistemler o kadar yumuşak değildir.

Gerekli herhangi bir dil seviyesi tesis varsa, o zaman eşitlik hassasiyeti olarak adlandırırdım. NUnit, JUnit ve benzer şekilde oluşturulmuş test çerçevelerinde doğru kabul edilen hassasiyeti kontrol edebilirsiniz. Örneğin:

Assert.That(.999, Is.EqualTo(1.001).Within(10).Percent);
// -- or --
Assert.That(.999, Is.EqualTo(1.001).Within(.1));

Örneğin, C # veya Java bir hassas işleç içerecek şekilde değiştirildiyse, şöyle görünebilir:

if(.999 == 1.001 within .1) { /* do something */ }

Ancak, böyle bir özellik sağlarsanız, +/- tarafları aynı değilse eşitliğin iyi olduğu durumu da dikkate almanız gerekir. Örneğin + 1/-10, biri 1'den fazla veya ilk sayıdan 10 daha azsa iki sayıyı eşdeğer kabul eder. Bu durumu ele almak için bir range anahtar kelimesi de eklemeniz gerekebilir:

if(.999 == 1.001 within range(.001, -.1)) { /* do something */ }
4
Berin Loritsch

Görmek istediğim bir şey, double - float 'nin genişleyen dönüşüm olarak kabul edilmesi gerektiğinde, float - double daralıyorsa ( *). Bu sezgisel görünebilir, ancak türlerin gerçekte ne anlama geldiğini düşünün:

  • 0.1f "13.421.773.5/134.217.728, artı ya da eksi 1/268.435.456 ya da" anlamına gelir.
  • 0.1 gerçekten 3.602.879.701.896.397/36.028.797.018.963.968, artı veya eksi 1/72.057.594.037.927.936 veya daha fazla "

Eğer biri "onda biri" miktarının en iyi temsilini tutan ve bunu double değerine dönüştüren bir float değerine sahipse, sonuç "13,421,773,5/134,217,728 artı veya eksi 1/268,435,456 veya yani ", değerin doğru açıklamasıdır.

Buna karşılık, eğer biri "onda biri" miktarının en iyi temsilini tutan ve float olarak dönüştüren bir double varsa, sonuç "13,421,773.5/134,217,728 artı veya eksi 1 olacaktır./72,057,594,037,927,936 ya da öylesine "- 53 milyondan fazla bir faktörle yanlış olan zımni doğruluk seviyesi.

IEEE-744 standardı kayan nokta matematiğinin yapılmasını gerektirse de sanki her bir kayan nokta sayısı, aralığının tam ortasındaki tam sayısal miktarı temsil eder; bu kayan nokta değerlerinin gerçekte bu tam sayısal miktarları temsil ettiği anlamına gelmemelidir. Aksine, değerlerin aralıklarının merkezinde olduğu varsayımı üç olgudan kaynaklanmaktadır: (1) hesaplamalar, işlenenler belirli kesin değerlere sahipmiş gibi yapılmalıdır; (2) tutarlı ve belgelenmiş varsayımlar tutarsız veya belgelenmemiş varsayımlardan daha yararlıdır; (3) eğer kişi tutarlı bir varsayım yapacaksa, başka hiçbir tutarlı varsayım, bir miktarın kendi aralığının merkezini temsil ettiğini varsaymaktan daha iyi olmayacaktır.

Bu arada, yaklaşık 25 yıl önce hatırlıyorum, birisi C için "her biri 128 bitlik float içeren" aralık türleri "kullanılan sayısal bir paket buldu; tüm hesaplamalar, her bir sonuç için mümkün olan minimum ve maksimum değeri hesaplayacak şekilde yapılacaktır. Eğer biri büyük bir uzun yinelemeli hesaplama yaptıysa ve [12.53401391134 12.53902812673] değerine ulaştıysa, bir çok kesinlik hanesinin yuvarlama hatalarına karşı kaybedilmesine rağmen, sonucun yine de makul olarak 12.54 olarak ifade edilebileceğinden emin olabilirsiniz. t gerçekten 12.9 veya 53.2). Özellikle bu tür diller için herhangi bir destek görmediğime şaşırdım, özellikle de paralel olarak birden çok değer üzerinde çalışabilen matematik birimleriyle iyi bir uyum içinde göründüklerinden.

(*) Pratikte, tek kesinlikli sayılarla çalışırken ara hesaplamaları tutmak için çift kesinlikli değerlerin kullanılması genellikle yararlıdır, bu nedenle bu tür tüm işlemler için bir daktilo kullanmak zorunda olmak can sıkıcı olabilir. Diller, hesaplamaları çift olarak gerçekleştirecek ve tek başına ve tek başına serbestçe kullanılabilecek bir "bulanık çift" tipine sahip olarak yardımcı olabilir; double türündeki parametreleri alan ve double parametrelerini alan işlevler otomatik olarak "bulanık çift" kabul eden ve döndüren bir aşırı yük oluşturacak şekilde işaretlenebiliyorsa bu özellikle yararlı olacaktır.

3
supercat

Dillerin yapabileceği tek şey - NAN değerleriyle doğrudan karşılaştırma dışında kayan nokta türlerinden eşitlik karşılaştırmasını kaldırmak.

Eşitlik testi yalnızca iki değeri ve bir deltayı alan işlev çağrısı olarak veya türlerin diğer değeri ve deltayı alan bir EqualsTo yöntemine sahip olmasına izin veren C # gibi diller içindir.

3
Loren Pechtel

Hiç kimsenin LISP ailesinin rasyonel numara numarasına dikkat çekmediğini garip buluyorum.

Cidden, sbcl dosyasını açın ve şunu yapın: (+ 1 3) ve 4 elde edersiniz. *( 3 2) yaparsanız, 6 elde edersiniz. Şimdi (/ 5 3) deneyin ve 5/3 elde ederseniz, veya üçte ikisi.

Bu bazı durumlarda biraz yardımcı olmalı, değil mi?

3
Haakon Løtveit

Programlama dilleri ne yapabilir? Bu sorunun bir cevabı olup olmadığını bilmiyorum, çünkü derleyicinin/tercümanın programcı adına hayatını kolaylaştırmak için yaptığı her şey genellikle performans, netlik ve okunabilirliğe karşı çalışır. Hem C++ yolu (sadece ihtiyacınız olanı öde) hem de Perl yolu (en az sürpriz ilkesi) hem de geçerli olduğunu düşünüyorum, ancak uygulamaya bağlı.

Programcıların hala dil ile çalışmaları ve kayan noktaları nasıl ele aldıklarını anlamaları gerekir, çünkü eğer yapmazlarsa varsayımlar yaparlar ve bir gün zulüm gören davranış varsayımlarıyla eşleşmez.

Programcının bilmesi gereken şeyleri ele alalım:

  • Sistemde ve dilde hangi kayan nokta türleri bulunur?
  • Hangi tip gerekli?
  • Kodda gerekli olan türün niyetleri nasıl ifade edilir
  • Doğruluğu korurken netliği ve verimliliği dengelemek için herhangi bir otomatik tip tanıtımından doğru şekilde nasıl yararlanılabilir
3
John

Dil düzeyinde yapacak bir şey olmadığını kabul ediyorum. Programcılar, bilgisayarların ayrık ve sınırlı olduğunu ve bu bilgisayarlarda temsil edilen matematiksel kavramların çoğunun yalnızca yaklaşımlar olduğunu anlamalıdır.

Kayan noktayı boş verin. Bit modellerinin yarısının negatif sayılar için kullanıldığını ve tamsayı aritmetiğindeki tipik problemlerden kaçınmak için aslında 2 ^ 64'ün oldukça küçük olduğunu anlamak gerekir.

3
Apalala

Programlama dilleri [kayan nokta] tuzaklarından kaçınmak için ne yapabilir?

Hassas varsayılanları kullanın, ör. decmials için yerleşik destek.

Groovy bunu oldukça güzel yapıyor, ancak biraz çaba ile hala kayan nokta belirsizliğini tanıtmak için kod yazabilirsiniz.

3
Armand

Daha fazla programlama dili veritabanlarından bir sayfa aldıysa ve geliştiricilerin sayısal veri türlerinin uzunluğunu ve kesinliğini belirtmelerine izin verdiyse, kayan nokta ile ilgili hata olasılığını önemli ölçüde azaltabilirler. Bir dil, geliştiricinin bir değişkeni Kayan Nokta (2) olarak bildirmesine izin verdiyse, iki ondalık basamak hassasiyetine sahip kayan nokta numarasına ihtiyaç duyduklarını belirtirse, matematiksel işlemleri çok daha güvenli bir şekilde gerçekleştirebilir. Değişkeni dahili olarak bir tamsayı olarak göstererek ve değeri ortaya çıkarmadan önce 100'e bölerek, daha hızlı tamsayı aritmetik yollarını kullanarak hızı artırabilir. Bir Float'ın (2) semantiği de geliştiricilerin verileri çıkarmadan önce verileri yuvarlama ihtiyacından kaçınmasına izin verecektir, çünkü bir Float (2) verileri kendiliğinden iki ondalık noktaya yuvarlayacaktır.

Elbette, geliştiricinin bu hassasiyete sahip olması gerektiğinde bir geliştiricinin maksimum kesinlikli kayar nokta değeri istemesine izin vermeniz gerekir. Ve aynı matematiksel işlemin biraz farklı ifadelerinin, geliştiricilerin değişkenlerinde yeterli hassasiyet taşımadığı zaman ara yuvarlama işlemleri nedeniyle potansiyel olarak farklı sonuçlar ürettiği problemler ortaya koyacaksınız. Ama en azından veritabanı dünyasında, bu çok büyük bir anlaşma gibi görünmüyor. Çoğu insan, ara sonuçlarda çok fazla hassasiyet gerektiren bilimsel hesaplamalar yapmıyor.

2
Justin Cave

Diğer cevapların belirttiği gibi, finansal yazılımda kayan nokta tuzaklarından kaçınmanın tek gerçek yolu onu kullanmamaktır. Bu gerçekten uygun olabilir - eğer iyi tasarlanmış bir kütüphane finansal matematiğe adanmışsanız.

Kayan nokta tahminlerini içe aktarmak için tasarlanmış işlevler bu şekilde açıkça etiketlenmeli ve bu işleme uygun parametrelerle sağlanmalıdır, örn .:

Finance.importEstimate(float value, Finance roundingStep)

Genel olarak kayan nokta tuzaklarından kaçınmanın tek gerçek yolu eğitimdir - programcıların Her Programcının Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler gibi bir şeyi okuması ve anlaması gerekir.

Yine de yardımcı olabilecek birkaç şey:

  • "Kayan nokta için kesin eşitlik testi neden yasal bile?"
  • Bunun yerine, bir isNear() işlevi kullanın.
  • Kayan noktalı akümülatör nesneleri sağlayın ve kullanımını teşvik edin (kayan nokta değerlerinin dizilerini, normal bir kayan nokta değişkenine eklemekten daha kararlı bir şekilde ekler).
1
comingstorm
  • diller Ondalık tip desteğine sahiptir; Tabii ki bu gerçekten sorunu çözmez, yine de örneğin ⅓'nin kesin ve sonlu bir temsiline sahip değilsiniz;
  • bazı DB'ler ve çerçeveler Para türü desteğine sahiptir, bu temel olarak sent sayısını tamsayı olarak saklar;
  • rasyonel sayılar desteği için bazı kütüphaneler vardır; problem problemini çözer, fakat örneğin √2 problemini çözmez;

Yukarıda belirtilenler bazı durumlarda uygulanabilir, ancak kayan değerlerle başa çıkmak için gerçekten genel bir çözüm değildir. Asıl çözüm, sorunu anlamak ve bununla nasıl başa çıkacağınızı öğrenmek. Kayan nokta hesaplamaları kullanıyorsanız, algoritmalarınızın sayısal olarak kararlı olup olmadığını daima kontrol etmelisiniz. Problemle ilgili büyük bir matematik/bilgisayar bilimi alanı vardır. Buna Sayısal Analiz denir.

1
vartec