Aşağıdaki kodu göz önünde bulundurun:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Bu yanlışlıklar neden oluyor?
Aşağıdaki kodu göz önünde bulundurun:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Bu yanlışlıklar neden oluyor?
Yanıtlar:
İkili kayan nokta matematiği böyledir. Çoğu programlama dilinde, IEEE 754 standardını temel alır . Sorunun temel noktası, sayıların bu formatta bir tam sayı olarak iki kat bir güç olarak temsil edilmesidir; paydası ikisinin gücü olmayan rasyonel sayılar (örneğin 0.1
, 1/10
) tam olarak temsil edilemez.
İçin 0.1
standart içinde binary64
formatında, temsili tam olarak yazılabilir
0.1000000000000000055511151231257827021181583404541015625
ondalık olarak veya0x1.999999999999ap-4
içinde C99 hexfloat gösterimde .Aksine, rasyonel sayı 0.1
, 1/10
tam olarak şu şekilde yazılabilir:
0.1
ondalık olarak veya0x1.99999999999999...p-4
C99 hekzfloat gösteriminin bir analogunda, burada 9'ların ...
bitmeyen bir sekansını temsil eder.Sabitler 0.2
ve 0.3
programınızdaki gerçek değerleri de yaklaşık olacaktır. O en yakın olur double
üzere 0.2
rasyonel sayısından daha fazla 0.2
ama en yakın olduğu double
için 0.3
rasyonel sayısından daha küçüktür 0.3
. Toplamı 0.1
ve 0.2
rüzgarları rasyonel sayıdan daha büyük olmak 0.3
ve böylece kodunuzdaki sabit ile katılmıyorum.
Kayan nokta aritmetik konularının oldukça kapsamlı bir tedavisi, Her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenlerdir . Sindirimi daha kolay bir açıklama için, bkz. Floating-point-gui.de .
Yan Not: Tüm konumsal (temel-N) sayı sistemleri bu sorunu hassas bir şekilde paylaşır
Düz eski ondalık (temel 10) sayılar aynı sorunlara sahiptir, bu nedenle 1/3 gibi sayılar 0.333333333 olarak sonuçlanır ...
Ondalık sistemle temsil edilmesi kolay olan, ancak ikili sisteme uymayan bir sayı (3/10) üzerinde tökezlediniz. Her iki yöne de (bir dereceye kadar) gider: 1/16, ondalık (0.0625) değerinde çirkin bir sayıdır, ancak ikilikte ondalık (0.0001) olarak 10.000'inci kadar temiz görünür ** - eğer günlük yaşamlarımızda bir taban-2 sayı sistemi kullanma alışkanlığı, bu sayıya bile bakabilir ve içgüdüsel olarak bir şeyi yarıya indirerek, tekrar tekrar yarıya vurarak oraya gelebileceğinizi anlayabilirsiniz.
** Tabii ki, kayan nokta sayıları bellekte tam olarak bu şekilde saklanmaz (bir tür bilimsel gösterim kullanırlar). Bununla birlikte, ikili kayar nokta hassaslık hatalarının ortaya çıkmaya meyilli olduğu noktasını göstermektedir, çünkü genellikle birlikte çalışmakla ilgilendiğimiz "gerçek dünya" sayıları genellikle on güçtür - ancak yalnızca ondalık sayı sistemi kullandığımız için- bugün. Bu nedenle "her 7'de 5" yerine% 71 gibi şeyleri söyleyeceğiz (% 71 bir yaklaşımdır, çünkü 5/7 tam olarak ondalık sayı ile temsil edilemez).
Yani hayır: ikili kayan nokta sayıları kırılmaz, sadece diğer tüm baz-N sayı sistemleri kadar kusurlu olurlar :)
Yan Taraf Not: Programlamada Floatlarla Çalışma
Pratikte, bu hassasiyet sorunu kayan nokta sayılarınızı görüntülemeden önce ilgilendiğiniz ondalık basamağa yuvarlamak için yuvarlama işlevlerini kullanmanız gerektiği anlamına gelir.
Eşitlik testlerini, bir miktar toleransa izin veren karşılaştırmalarla değiştirmeniz gerekir, yani:
Do not doif (x == y) { ... }
Bunun yerine yapın if (abs(x - y) < myToleranceValue) { ... }
.
abs
mutlak değer nerede . myToleranceValue
özel uygulamanız için seçilmesi gerekir - ve ne kadar "kıpır kama odasına" izin vermeye hazır olduğunuz ve en büyük sayının ne olacağı ile ilgili çok şey olacaktır (hassasiyet sorunları nedeniyle) ). Seçtiğiniz dilde "epsilon" tarzı sabitlere dikkat edin. Bunlar olup tolerans değerleri olarak kullanılır.
Kayan noktalı donanım tasarladığım ve kurduğum için buna bir donanım tasarımcısı bakış açısı eklemem gerektiğine inanıyorum. Hatanın kaynağını bilmek, yazılımda neler olduğunu anlamaya yardımcı olabilir ve sonuçta, bunun kayan nokta hatalarının neden ortaya çıktığını ve zamanla biriktiğini açıklamaya yardımcı olacağını umuyorum.
Mühendislik açısından bakıldığında, kayan nokta işlemlerinin çoğunu yapan donanımın en sonda yalnızca bir birimin yarısından daha az bir hataya sahip olması gerektiğinden, kayan nokta işlemlerinin çoğunda bir hata öğesi olacaktır. Bu nedenle, çok sayıda donanım , özellikle kayan nokta bölümünde özellikle sorunlu olan tek bir işlem için son yerde bir ünitenin yarısından daha az bir hata vermek için gerekli olan bir hassasiyetle duracaktır . Tek bir işlemi neyin oluşturduğu, ünitenin kaç işlenen aldığına bağlıdır. Çoğu için, iki, ancak bazı birimler 3 veya daha fazla işlenen alır. Bu nedenle, hatalar zaman içinde toplandığından tekrarlanan işlemlerin istenen bir hataya neden olacağının garantisi yoktur.
Çoğu işlemci IEEE-754 standardını takip eder, ancak bazıları normalleştirilmemiş veya farklı standartlar kullanır. Örneğin, IEEE-754'te çok küçük kayan nokta sayılarının hassasiyet pahasına gösterilmesine izin veren denormalize bir mod vardır. Bununla birlikte, aşağıdakiler, tipik çalışma modu olan normalleştirilmiş IEEE-754 modunu kapsayacaktır.
IEEE-754 standardında, donanım tasarımcılarına en sonda bir birimin yarısından daha az olduğu sürece herhangi bir hata / epsilon değerine izin verilir ve sonuç son olarak bir birimin yarısından daha az olmalıdır bir operasyon için yer. Bu, tekrarlanan işlemler olduğunda hataların neden toplandığını açıklar. IEEE-754 çift duyarlık için, bu 54. bittir, çünkü kayan nokta numarasının mantis olarak da adlandırılan sayısal kısmı (normalize edilmiş) temsil etmek için 53 bit kullanılır (örn. 5.3e5'teki 5.3). Sonraki bölümler, çeşitli kayan nokta işlemlerinde donanım hatasının nedenleri hakkında daha ayrıntılı bilgi vermektedir.
Kayan nokta bölmesindeki hatanın ana nedeni, bölümü hesaplamak için kullanılan bölme algoritmalarıdır. Birçok bilgisayar sistemi esas olarak, bir ters ile çarpma ile bölme hesaplamak Z=X/Y
,Z = X * (1/Y)
. Bir bölüm yinelemeli olarak hesaplanır, yani her döngü, istenen hassasiyete ulaşılana kadar bölümün bazı bitlerini hesaplar; bu, IEEE-754 için en sonda bir birimden daha az hataya sahip herhangi bir şeydir. Y karşılıklılık tablosu (1 / Y) yavaş bölmede bölüm seçim tablosu (QST) olarak bilinir ve bölüm seçim tablosunun bit cinsinden boyutu genellikle yarıçapın genişliği veya bir dizi bittir. her bir yinelemede hesaplanan bölüm artı birkaç koruma biti. IEEE-754 standardı, çift kesinlikli (64 bit) için, bölücünün yarıçapının boyutu, artı birkaç koruyucu bit k olacaktır k>=2
. Bu nedenle, örneğin, bir seferde bölümün 2 bitini (sayı 4) hesaplayan bir bölücü için tipik bir Bölüm Seçim Tablosu 2+2= 4
bit (artı birkaç isteğe bağlı bit) olacaktır.
3.1 Bölüm Yuvarlama Hatası: Karşılıklı Yaklaşım
Bölüm seçim tablosunda hangi karşılıklılıklar bölme yöntemine bağlıdır : SRT bölümü gibi yavaş bölüm veya Goldschmidt bölümü gibi hızlı bölüm; her giriş olası en düşük hatayı vermek amacıyla bölme algoritmasına göre değiştirilir. Her durumda, tüm karşılıklılıklar yaklaşıktırgerçek karşılıklılık ve hata bazı unsurları tanıtmak. Hem yavaş bölme hem de hızlı bölme yöntemleri, bölümü yinelemeli olarak hesaplar, yani her adımın bir miktar biti hesaplanır, daha sonra sonuç bölünmeden çıkarılır ve bölücü, hata bir yarısından daha az olana kadar adımları tekrarlar. birim. Yavaş bölme yöntemleri, her adımda bölümün sabit sayıda basamağını hesaplar ve genellikle inşa edilmesi daha ucuzdur ve hızlı bölme yöntemleri, adım başına değişken sayıda basamağı hesaplar ve genellikle inşa edilmesi daha pahalıdır. Bölme yöntemlerinin en önemli kısmı, çoğunun karşılıklı bir yaklaşımla tekrarlanan çarpmaya dayanmasıdır , bu nedenle hataya eğilimlidirler.
Tüm operasyonlardaki yuvarlama hatalarının bir başka nedeni, IEEE-754'ün izin verdiği son cevabın farklı kesilme modlarıdır. Kesik, sıfıra yuvarlama , en yakın yuvarlama (varsayılan), yuvarlama ve yuvarlama vardır. Tüm yöntemler, tek bir işlem için son yerde birden fazla üniteden daha az bir hata unsuru sunar. Zamanla ve tekrarlanan işlemler sırasında, kesilme aynı zamanda oluşan hataya kümülatif olarak ekler. Bu kesme hatası, bir tür tekrarlanan çarpmayı içeren üs almada özellikle problemlidir.
Kayan nokta hesaplarını yapan donanımın, tek bir işlem için en son yerde bir birimin yarısından daha az bir hata ile bir sonuç vermesi gerektiğinden, hata izlenmezse tekrarlanan işlemler üzerinde artacaktır. Bu, sınırlı bir hata gerektiren hesaplamalarda, matematikçilerin IEEE-754'ün son yerine en yakın yuvarlak rakamı kullanma gibi yöntemleri kullanmasının nedenidir , çünkü zamanla hataların birbirini iptal etme olasılığı daha yüksektir ve Aralık Aritmetiği , IEEE 754 yuvarlama modlarının varyasyonlarıyla birleştirildiYuvarlama hatalarını tahmin etmek ve düzeltmek için. Diğer yuvarlama modlarına kıyasla düşük göreli hatası nedeniyle, en yakın çift basamağa (son sırada) yuvarlama, IEEE-754'ün varsayılan yuvarlama modudur.
Son yuvarlamadan en yakın çift basamağa kadar olan varsayılan yuvarlama modunun, bir işlem için son konumda bir birimin yarısından daha az bir hata garanti ettiğini unutmayın. Kesmeyi, yuvarlamayı ve yuvarlamayı tek başına kullanmak, son yerde bir birimin yarısından daha büyük, ancak son yerde bir birimden daha az bir hataya neden olabilir, bu nedenle bu modlar Aralık Aritmetiğinde kullanılır.
Kısacası, kayan nokta işlemlerindeki hataların temel nedeni, donanımdaki kesilmenin ve bölünme durumunda karşılıklı kesmenin birleşimidir. IEEE-754 standardı, tek bir işlem için son yerde yalnızca bir ünitenin yarısından daha az bir hata gerektirdiğinden, tekrarlanmayan işlemler üzerindeki kayan nokta hataları düzeltilmedikçe toplanır.
.1 veya 1/10'u temel 2'ye (ikili) dönüştürdüğünüzde, tıpkı 10'da 1/3'ü temsil etmeye çalıştığınız gibi, ondalık noktadan sonra yinelenen bir desen elde edersiniz. Değer tam değildir ve bu nedenle yapamazsınız normal kayan nokta yöntemlerini kullanarak tam matematik.
Buradaki cevapların çoğu bu soruyu çok kuru, teknik terimlerle ele almaktadır. Bunu normal insanların anlayabileceği açılardan ele almak istiyorum.
Pizzaları dilimlemeye çalıştığınızı düşünün. Pizza dilimlerini tam olarak yarıya indirebilen robotik bir pizza kesiciniz var . Bütün bir pizzayı yarıya indirebilir veya mevcut bir dilimi yarıya indirebilir, ancak her durumda yarıya her zaman kesindir.
Bu pizza kesicinin çok ince hareketleri var ve eğer bütün bir pizzayla başlıyorsanız, bunu yarıya indirip her seferinde en küçük dilimi yarıya indirmeye devam ederseniz , dilim yüksek hassasiyetli yetenekleri için bile çok küçük olmadan 53 kez yarıya yapabilirsiniz. . Bu noktada, artık çok ince dilimi yarıya indiremezsiniz, ancak onu olduğu gibi dahil etmeli veya hariç tutmalısınız.
Şimdi, tüm dilimleri bir pizzanın onda birine (0,1) veya beşte birine (0,2) kadar ekleyecek şekilde nasıl parçalara ayırırsınız? Gerçekten düşünün ve üzerinde çalışmayı deneyin. Elinizde efsanevi bir hassas pizza kesiciniz varsa, gerçek bir pizza kullanmayı bile deneyebilirsiniz. :-)
En deneyimli programcılar, tabii ki, birlikte parçasına hiçbir şekilde bir olmasıdır gerçek yanıtı, biliyorum tam onuncu ya da beşinci pizza olursa olsun onları dilim nasıl ince, bu dilimleri kullanarak. Oldukça iyi bir yaklaşım yapabilirsiniz ve 0,1 yaklaşımını 0,2 yaklaşımıyla toplarsanız, 0,3'lük oldukça iyi bir yaklaşım elde edersiniz, ancak yine de bu sadece bir yaklaşımdır.
Çift kesinlikli sayılar için (bu, pizzanızı 53 kez yarıya indirmenize izin veren kesinliktir), hemen 0.1'den az ve daha büyük sayılar 0.09999999999999999167332731531132594682276248931884765625 ve 0.100000000000000005551115123125782702118158340454101562525'dir. İkincisi, öncekinden 0.1'e biraz daha yakındır, bu nedenle sayısal bir ayrıştırıcı, 0.1 girişi verildiğinde ikincisini tercih eder.
(Bu iki sayı arasındaki fark, aşağı yönlü bir önyargı oluşturan ya da aşağı yönde bir önyargı oluşturan dışlama dahil etmeye karar vermemiz gereken "en küçük dilim" dir. En küçük dilim için teknik terim bir ulp'dir .)
0.2 durumunda, sayılar aynıdır, sadece 2 katına kadar büyütülmüştür. Yine, 0.2'den biraz daha yüksek olan değeri tercih ediyoruz.
Her iki durumda da, 0,1 ve 0,2 için yaklaşımların hafif bir yukarı yönlü sapmaya sahip olduğuna dikkat edin. Eğer bu önyargıları yeterince eklersek, sayıyı istediğimizden daha fazla ve uzağa itecekler ve aslında, 0.1 + 0.2 durumunda, önyargı, elde edilen sayının artık en yakın sayı olmayacak kadar yüksektir. 0.3'e kadar.
Özellikle, 0,1 + 0,2 gerçekten 0,1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.30000000000004440892098599999999999999999999999999999999999999999
Not: Bazı programlama dilleri, dilimleri tam onda birine bölen pizza kesiciler de sağlar . Bu tür pizza kesiciler nadir olsa da, birine erişiminiz varsa, bir dilimin tam olarak onda biri veya beşte birini elde edebilmek önemli olduğunda kullanmalısınız.
Kayan nokta yuvarlama hataları. 0.1, 5'in eksik ana faktörü nedeniyle base-2'de base-10'da olduğu gibi doğru bir şekilde temsil edilemez. 1/3'ün ondalık olarak temsil etmek için sonsuz sayıda basamak alması gibi, base-3'te "0.1" olması gibi, 0.1 taban-2'de taban-10'da bulunmayan sonsuz sayıda basamak alır. Ve bilgisayarların sonsuz miktarda belleği yoktur.
Diğer doğru cevaplara ek olarak, kayan noktalı aritmetik ile ilgili sorunları önlemek için değerlerinizi ölçeklendirmeyi düşünebilirsiniz.
Örneğin:
var result = 1.0 + 2.0; // result === 3.0 returns true
... onun yerine:
var result = 0.1 + 0.2; // result === 0.3 returns false
İfade JavaScript'te 0.1 + 0.2 === 0.3
döner false
, ancak neyse ki kayan noktadaki tamsayı aritmetiği tamdır, bu nedenle ondalık gösterim hataları ölçekleme ile önlenebilir.
Pratik bir örnek olarak, doğruluk şeyden önemlidir kayan nokta sorunları önlemek için, bu tavsiye edilir 1 : cent sayısını temsil eden bir tamsayı olarak sap para 2550
yerine sent 25.50
dolar.
1 Douglas Crockford: JavaScript: İyi Parçalar : Ek A - Korkunç Parçalar (sayfa 105) .
Cevabım oldukça uzun, bu yüzden üç bölüme ayırdım. Soru kayan nokta matematiği ile ilgili olduğundan, makinenin gerçekte ne yaptığına vurgu yaptım. Ayrıca çift (64 bit) hassasiyete özel yaptım, ancak argüman herhangi bir kayan nokta aritmetiği için de geçerlidir.
önsöz
Bir IEEE 754 çift kesinlikli ikili kayan noktalı biçim (binary64) sayısı, formun bir sayısını temsil eder
değer = (-1) ^ s * (1.m 51 m 50 ... m 2 m 1 m 0 ) 2 * 2 e-1023
64 bit'te:
1
sayı negatifse, 0
aksi takdirde 1 .1.
her zaman 2 herhangi bir ikili değerin en önemli bit olduğundan ihmal 1
.1 - IEEE 754, işaretli bir sıfır kavramına izin verir - +0
ve -0
farklı muamele görür: 1 / (+0)
pozitif sonsuzdur; 1 / (-0)
negatif sonsuzdur. Sıfır değerleri için, mantis ve üs bitleri sıfırdır. Not: sıfır değerleri (+0 ve -0) açıkça denormal 2 olarak sınıflandırılmaz .
2 - Bu, ofset üssü sıfır (ve zımni ) olan denormal sayılar için geçerli değildir 0.
. Denormal çift duyarlıklı sayıların aralığı d min ≤ | x | ≤ d maksimum d, burada en az (en küçük sıfır olmayan Temsil sayısı) 2 -1.023-51 (≈ 4.94 x 10 -324 ) ve d maks (mantis tamamen oluştuğu için en büyük denormal numarası 1
s) 2 -1023 +1 - 2 -1.023-51 (* 10 ≈ 2.225 -308 ).
Çift kesinlikli sayıyı ikiliye dönüştürme
Birçok çevrimiçi dönüştürücü, çift kesinlikli kayar nokta sayısını ikiliye dönüştürmek için mevcuttur (örneğin, binaryconvert.com'da ), ancak çift kesinlikli bir sayı için IEEE 754 temsilini elde etmek için bazı örnek C # kodu (Üç parçayı iki nokta ile ayırıyorum ( :
) :
public static string BinaryRepresentation(double value)
{
long valueInLongType = BitConverter.DoubleToInt64Bits(value);
string bits = Convert.ToString(valueInLongType, 2);
string leadingZeros = new string('0', 64 - bits.Length);
string binaryRepresentation = leadingZeros + bits;
string sign = binaryRepresentation[0].ToString();
string exponent = binaryRepresentation.Substring(1, 11);
string mantissa = binaryRepresentation.Substring(12);
return string.Format("{0}:{1}:{2}", sign, exponent, mantissa);
}
Konuya ulaşmak: orijinal soru
(TL; DR sürümü için en alta atla)
Cato Johnston (soru asker) neden 0.1 + 0.2! = 0.3 olduğunu sordu.
İkili olarak yazılır (üç parçayı ayıran kolonlarla), değerlerin IEEE 754 gösterimleri şunlardır:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
Mantis'in tekrarlayan rakamlarından oluştuğunu unutmayın 0011
. Bu anahtar 0.1, 0.2 ve 0.3, ikili temsil edilemez - hesaplamalar için bir hata vardır neden tam bir de sonlu tam temsil edilebilir herhangi bir ikili bit sayısı fazla 1/9, 1/3 veya 1/7 ondalık basamak .
Ayrıca üssündeki gücü 52 oranında azaltabildiğimizi ve ikili gösterimdeki noktayı 52 yer sağa kaydırabileceğimizi unutmayın (10 -3 * 1.23 == 10 -5 * 123 gibi). Bu daha sonra ikili temsili a * 2 p biçiminde temsil ettiği tam değer olarak göstermemizi sağlar . burada 'a' bir tamsayıdır.
Üsleri ondalık sayıya dönüştürme, ofseti kaldırma ve ima edilen 1
(köşeli parantezler içinde) 0,1 ve 0,2'yi yeniden ekleme :
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
İki sayı eklemek için üssün aynı olması gerekir, yani:
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
Toplam 2 n * 1 biçiminde olmadığından {bbb} üssü bir arttırır ve ondalık ( ikili ) noktayı elde etmek için kaydırırız :
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
Artık mantiste 53 bit var (53'üncü yukarıdaki satırda köşeli parantez içinde). IEEE 754 için varsayılan yuvarlama modu ' En Yakın Yuvarla ' şeklindedir - yani x sayısı iki a ve b değeri arasına düşerse , en az anlamlı bitin sıfır olduğu değer seçilir.
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
Not Bir ve b sadece son bit farklı; ...0011
+ 1
= ...0100
. Bu durumda, en az anlamlı biti sıfır olan değer b'dir , bu nedenle toplam:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
0.3'ün ikili gösterimi:
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
bu sadece 0.1 ve 0.2 toplamının 2 -54 ile ikili gösteriminden farklıdır .
0.1 ve 0.2'nin ikili gösterimi, IEEE 754 tarafından izin verilen sayıların en doğru temsilidir. Varsayılan gösterme modu nedeniyle bu gösterimin eklenmesi, yalnızca en az anlamlı bitte farklılık gösteren bir değerle sonuçlanır.
TL; DR
0.1 + 0.2
Bir IEEE 754 ikili gösterimde (üç parçayı ayıran iki nokta işaretiyle) yazma ve bunu karşılaştırma ile 0.3
, bu (farklı bitleri köşeli parantez içine koydum):
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
Ondalık biçime geri dönüştürüldüğünde, bu değerler:
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
Fark tam olarak 2 -54 5,5511151231258 x 10 ~ olan -17 orijinal değerlerine göre anlamlı (bir çok uygulama için) -.
Bir kayan nokta sayısının son birkaç bitini karşılaştırmak, " Her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler " (bu cevabın tüm önemli kısımlarını kapsayan) bilen herkesin bileceği gibi, doğası gereği tehlikelidir .
Çoğu hesap makinesi bu sorunun üstesinden gelmek için ek koruma basamakları kullanır, bu nasıl 0.1 + 0.2
olur 0.3
: son birkaç bit yuvarlanır.
Bilgisayarda saklanan kayan nokta sayıları, bir tamsayı ve tabanın tamsayı kısmına alındığı ve bu sayı ile çarpıldığı bir üs olmak üzere iki bölümden oluşur.
Bilgisayar temel 10'da çalışıyor 0.1
olsaydı 1 x 10⁻¹
, 0.2
olurdu , olurdu 2 x 10⁻¹
ve 0.3
olurdu 3 x 10⁻¹
. Tamsayı matematik kolay ve kesindir, bu yüzden ekleme 0.1 + 0.2
açıkça sonuç verecektir 0.3
.
Bilgisayarlar genellikle taban 10'da çalışmazlar, taban 2'de çalışırlar. Örneğin 0.5
, olduğu 1 x 2⁻¹
ve 0.25
olduğu gibi bazı değerler için kesin sonuçlar elde edersiniz 1 x 2⁻²
ve bunları 3 x 2⁻²
veya 0.75
. Kesinlikle.
Sorun, tam olarak taban 10'da temsil edilebilecek, ancak taban 2'de gösterilemeyen sayılarla birlikte gelir. Bu sayıların en yakın eşdeğerine yuvarlanması gerekir. Çok yaygın IEEE 64-bit kayan noktalı biçimi, en yakın numarayı varsayarsak 0.1
olduğunu 3602879701896397 x 2⁻⁵⁵
ve en yakın sayı 0.2
olduğu 7205759403792794 x 2⁻⁵⁵
; Bunları bir araya getirmek 10808639105689191 x 2⁻⁵⁵
, değerinin tam ondalık değerini verir 0.3000000000000000444089209850062616169452667236328125
. Kayan nokta sayıları genellikle gösterim için yuvarlanır.
Kayan nokta yuvarlama hatası. Gönderen Ne Her Bilgisayar Mühendisi gerektiğini biliyorum Kayan Noktalı Aritmetik Hakkında :
Sınırsız sayıda bitin sonsuz sayıda bite sıkıştırılması yaklaşık bir gösterim gerektirir. Sonsuz sayıda tamsayı olmasına rağmen, çoğu programda tamsayı hesaplamalarının sonuçları 32 bitte saklanabilir. Buna karşılık, sabit sayıda bit verildiğinde, gerçek sayılarla yapılan hesaplamaların çoğu, o kadar bit kullanılarak tam olarak temsil edilemeyen miktarlar üretecektir. Bu nedenle, bir kayan noktalı hesaplamanın sonucu, sonlu gösterimine uyması için genellikle yuvarlatılmalıdır. Bu yuvarlama hatası, kayan nokta hesaplamasının karakteristik özelliğidir.
Birçok iyi yanıt gönderildi, ancak bir tane daha eklemek istiyorum.
Tüm sayılar şamandıralar / çiftler aracılığıyla gösterilemez. Örneğin, "0.2" sayısı, IEEE754 kayan nokta standardında tek kesinlikte "0.200000003" olarak temsil edilecektir.
Kaputun altındaki gerçek sayıları saklamak için model,
0.2
Kolayca yazabilmenize rağmen FLT_RADIX
ve DBL_RADIX
2; "İkili Kayan Nokta Aritmetiği için IEEE Standardı (ISO / IEEE Std 754-1985)" kullanan FPU'lu bir bilgisayar için 10 değil.
Bu yüzden bu sayıları tam olarak temsil etmek biraz zor. Bu değişkeni herhangi bir ara hesaplama olmadan açıkça belirtmiş olsanız bile.
Bu ünlü çift kesinlik sorusuyla ilgili bazı istatistikler.
Tüm değerleri ( a + b ) 0.1 adımı (0.1'den 100'e kadar) kullanarak eklerken ~% 15 hassasiyet hatası şansımız vardır . Hatanın biraz daha büyük veya daha küçük değerlerle sonuçlanabileceğini unutmayın. İşte bazı örnekler:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
0.1 (100'den 0.1'e kadar) adımını kullanarak tüm değerleri ( a - b burada a> b ) çıkarırken,% ~ 34 hassasiyet hatası şansımız vardır . İşte bazı örnekler:
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
*% 15 ve% 34 gerçekten çok büyük, bu nedenle hassasiyet büyük önem taşıyorsa her zaman BigDecimal kullanın. 2 ondalık basamakla (adım 0.01) durum biraz daha kötüleşir (% 18 ve% 36).
özet
Kayan noktalı aritmetik olduğunu sık sık bunu biraz biz ne yazdı gelen kapalı girdi veriyoruz dışarı o dönmesini sağlayın kesin, ne yazık ki, bizim her zamanki 10 tabanlı sayı gösterimi ile iyi yukarıya uyuşmuyor.
0.01, 0.02, 0.03, 0.04 ... 0.24 gibi basit sayılar bile tam olarak ikili kesirler olarak gösterilemez. Eğer 0,01, .02, .03 ... sayarsanız, 0.25'e ulaşana kadar değil, temel 2'de temsil edilebilen ilk kesri elde edemezsiniz . FP kullanarak bunu denediyseniz, 0.01'iniz biraz kapalı olurdu, bu yüzden 25'i güzel bir tam 0.25'e eklemenin tek yolu, koruma bitleri ve yuvarlama içeren uzun bir nedensellik zinciri gerektiriyordu. Tahmin etmek zor, bu yüzden ellerimizi fırlatıp "FP yanlış " diyoruz , ama bu gerçekten doğru değil.
FP donanımına sürekli olarak taban 10'da basit görünen ama taban 2'de tekrar eden bir kesir veriyoruz.
Bu nasıl oldu?
Ondalık olarak yazdığımızda, her kesir (özellikle her sonlanan ondalık sayı) formun rasyonel bir sayısıdır
a / (2 n x 5 m )
İkili olarak, sadece 2 n terimi alırız , yani:
a / 2 n
Yani ondalık olarak, 1 / 3'ü temsil edemeyiz . Taban 10 birinci sınıf bir faktör olarak 2 içerdiğinden, bir ikili kesir olarak yazabilir her sayı da bir taban 10 kesir olarak yazılabilir. Bununla birlikte, baz 10 fraksiyonu olarak yazdığımız hemen hemen hiçbir şey ikili olarak temsil edilemez. 0.01, 0.02, 0.03 ... 0.99 aralığında, FP formatımızda sadece üç sayı temsil edilebilir: 0.25, 0.50 ve 0.75, çünkü 1/4, 1/2 ve 3/4, tüm sayılar yalnızca 2 n terimini kullanan asal bir faktör .
Temel 10'da 1 / 3'ü temsil edemeyiz . Ama ikili, biz yapamayız 1 / 10 veya 1 / 3 .
Bu nedenle, her ikili kesir ondalık olarak yazılabilirken, tersi doğru değildir. Ve aslında ondalık kesirlerin çoğu ikili olarak tekrarlanır.
Onunla başa çıkmak
Geliştiricilere genellikle <epsilon karşılaştırmaları yapmaları söylenir, integral değerlere (C kütüphanesinde: round () ve roundf (), yani FP formatında kalmak) ve daha sonra karşılaştırmak daha iyi bir tavsiye olabilir. Belirli bir ondalık kesir uzunluğuna yuvarlama çıktı ile ilgili çoğu sorunu çözer.
Ayrıca, gerçek sayı çatırdama problemlerinde (FP'nin erken, korkutucu pahalı bilgisayarlarda icat edildiği problemler) evrenin fiziksel sabitleri ve diğer tüm ölçümler sadece nispeten az sayıda önemli figür tarafından bilinir, bu yüzden tüm problem alanı zaten "hatalı" idi. FP "doğruluğu" bu tür uygulamalarda sorun oluşturmaz.
Bütün mesele, insanlar fasulye sayımı için FP kullanmaya çalıştıklarında ortaya çıkıyor. Bunun için işe yarıyor, ama sadece integral değerlere bağlı kalırsanız, hangi tür onu kullanma noktasını yener. Bu yüzden tüm ondalık kesir yazılım kütüphanelerine sahibiz.
Ben sadece "yanlışlık" hakkında her zamanki handwaving değil, gerçek sorunu açıklar, çünkü Chris Pizza cevap seviyorum . FP basitçe "yanlış" olsaydı, bunu düzeltebilirdik ve onlarca yıl önce yapardı. Nedeni, FP formatının kompakt ve hızlı olması ve çok sayıda sayıyı düzeltmenin en iyi yolu olmasıdır. Ayrıca, uzay çağı ve silahlanma yarışı bir miras ve küçük bellek sistemleri kullanan çok yavaş bilgisayarlarda büyük sorunları çözmek için erken girişimler. (Bazen, 1 bitlik depolama için ayrı manyetik çekirdekler , ancak bu başka bir hikaye. )
Sonuç
Bir bankada sadece fasulye sayıyorsanız, ilk başta ondalık dize temsillerini kullanan yazılım çözümleri mükemmel bir şekilde çalışır. Ancak kuantum kromodinamiği veya aerodinamiği bu şekilde yapamazsınız.
nextafter()
bir IEEE şamandırasının ikili temsili üzerinde bir tamsayı artışı veya azalması uygulayabilirsiniz . Ayrıca, kayan sayıları tamsayı olarak karşılaştırabilir ve her ikisinin de negatif olduğu durumlar dışında doğru cevabı alabilirsiniz (işaret büyüklüğü ve 2'nin tamamlayıcısı nedeniyle).
Koli bandı çözümünü denediniz mi?
Hataların ne zaman ortaya çıktığını belirlemeye çalışın ve bunları kısa if ifadeleriyle düzeltin, güzel değil, ancak bazı problemler için tek çözümdür ve bu da bunlardan biridir.
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
Aynı sorunu c # 'daki bilimsel simülasyon projesinde yaşadım ve size kelebek etkisini görmezden gelirseniz büyük bir şişman ejderhaya döneceğini ve sizi a **
En iyi çözümü sunmak için aşağıdaki yöntemi keşfettiğimi söyleyebilirim:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
Neden en iyi çözüm olduğunu açıklayayım. Yukarıdaki yanıtlarda belirtildiği gibi, sorunu çözmek için Javascript toFixed () işlevini kullanmaya hazır kullanmak iyi bir fikirdir. Ancak büyük olasılıkla bazı sorunlarla karşılaşacaksınız.
Eğer gibi iki şamandıra sayıları toplamak için gidiyoruz düşünün 0.2
ve 0.7
işte burada: 0.2 + 0.7 = 0.8999999999999999
.
Beklediğiniz sonuç, 0.9
bu durumda 1 basamaklı bir sonuca ihtiyacınız olduğu anlamına geliyordu. Bu yüzden kullanmalısınız, (0.2 + 0.7).tofixed(1)
ancak verilen sayıya bağlı olduğu için toFixed () öğesine belirli bir parametre veremezsiniz, örneğin
`0.22 + 0.7 = 0.9199999999999999`
Bu örnekte, 2 basamak hassasiyetine ihtiyacınız vardır toFixed(2)
, bu yüzden, her float numarasına uyması için parametre ne olmalıdır?
O zaman her durumda 10 olsun diyebilirsiniz:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
Lanet olsun! 9'dan sonra istenmeyen sıfırlarla ne yapacaksın? İstediğiniz gibi yapmak için şamandıraya dönüştürme zamanı:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
Şimdi çözümü bulduğunuza göre, bunu böyle bir işlev olarak sunmak daha iyidir:
function floatify(number){
return parseFloat((number).toFixed(10));
}
Hadi kendiniz deneyelim:
function floatify(number){
return parseFloat((number).toFixed(10));
}
function addUp(){
var number1 = +$("#number1").val();
var number2 = +$("#number2").val();
var unexpectedResult = number1 + number2;
var expectedResult = floatify(number1 + number2);
$("#unexpectedResult").text(unexpectedResult);
$("#expectedResult").text(expectedResult);
}
addUp();
input{
width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>
Bu şekilde kullanabilirsiniz:
var x = 0.2 + 0.7;
floatify(x); => Result: 0.9
W3SCHOOLS'un başka bir çözüm olduğunu öne sürdüğü gibi , yukarıdaki sorunu çözmek için çarpabilir ve bölebilirsiniz:
var x = (0.2 * 10 + 0.1 * 10) / 10; // x will be 0.3
(0.2 + 0.1) * 10 / 10
Aynı görünse de hiç işe yaramayacağını unutmayın ! İlk çözümü tercih ediyorum, çünkü bunu girdi şamandıraını doğru çıktı şamandırasına dönüştüren bir işlev olarak uygulayabiliyorum.
Bu garip sayılar, hesaplama amacıyla ikili (taban 2) sayı sistemini kullandığımız halde, ondalık (taban 10) kullandığımız için bu garip sayılar ortaya çıkar.
İkili veya ondalık olarak veya her ikisinde tam olarak gösterilemeyen kesirli sayıların çoğunluğu vardır. Sonuç - Yuvarlanmış (ama kesin) bir sayı ortaya çıkar.
Bu sorunun birçok kopyası, kayan nokta yuvarlamasının belirli sayılar üzerindeki etkilerini soruyor. Uygulamada, sadece okumadan ziyade, ilgi hesaplamalarının kesin sonuçlarına bakarak nasıl çalıştığını hissetmek daha kolaydır. Böyle bir dönüştürme olarak - Bazı dillerde bu yapmanın yollarını sağlamak float
veya double
karşı BigDecimal
Java.
Bu, dile agnostik bir soru olduğundan, Ondalıktan Kayan Noktalı Dönüştürücü gibi dile agnostik araçlara ihtiyaç duyar .
Bunu sorudaki sayılara uygulamak, çiftler olarak kabul edilir:
0.1, 0.1000000000000000055511151231257827021181583404541015625'e dönüştürür,
0.2, 0.200000000000000011102230246251565404236316680908203125'e dönüştürür,
0.3, 0.299999999999999988897769753748434595763683319091796875 olarak dönüştürülür ve
0.30000000000000004, 0.3000000000000000444089209850062616169452667236328125'e dönüştürür.
İlk iki sayıyı manuel olarak veya Tam Hassasiyetli Hesap Makinesi gibi bir ondalık hesap makinesine eklemek, gerçek girişlerin tam toplamını 0.3000000000000000166533453693773481063544750213623046875 olduğunu gösterir.
0.3 eşdeğerine yuvarlanırsa yuvarlama hatası 0.0000000000000000277555756156289135105907917022705078125 olur. 0.30000000000000004 eşdeğerine yuvarlama da 0.0000000000000000277555756156289135105907917022705078125 yuvarlama hatası verir. Yuvarlak-eşit kravat kırıcı geçerlidir.
Kayan nokta dönüştürücüye dönersek, 0.30000000000000004 için ham onaltılık sayı, 3fd3333333333334'tür ve bu da çift haneli olarak biter ve bu nedenle doğru sonuçtur.
Kimsenin bundan bahsetmediği göz önüne alındığında ...
Python ve Java gibi bazı üst düzey diller, ikili kayan nokta sınırlamalarının üstesinden gelmek için araçlar ile birlikte gelir. Örneğin:
Python'un decimal
modülü ve Java BigDecimal
sınıfı , ondalık gösterimle dahili sayıları temsil eder (ikili gösterimin aksine). Her ikisinin de sınırlı bir hassasiyeti vardır, bu yüzden hala hataya eğilimlidirler, ancak ikili kayan nokta aritmetiğindeki en yaygın sorunları çözerler.
Ondalık para ile uğraşırken çok güzel: on sent artı yirmi sent her zaman tam otuz senttir:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Python'un decimal
modülü IEEE 854-1987 standardını temel alır .
Python'un fractions
modülü ve Apache Common'un BigFraction
sınıfı . Her ikisi de rasyonel sayıları (numerator, denominator)
çift olarak temsil eder ve ondalık kayan nokta aritmetiğinden daha doğru sonuçlar verebilir.
Bu çözümlerin hiçbiri mükemmel değildir (özellikle performanslara bakarsak veya çok yüksek bir hassasiyete ihtiyacımız varsa), ancak yine de ikili kayan nokta aritmetiği ile çok sayıda sorunu çözerler.
Sadece ekleyebilir miyim; insanlar her zaman bunun bir bilgisayar sorunu olduğunu varsayarlar, ancak ellerinizle (temel 10) sayarsanız, (1/3+1/3=2/3)=true
0.333 ... 0.333'e sonsuzluk eklemediğiniz sürece elde edemezsiniz ... tıpkı temeldeki (1/10+2/10)!==3/10
problemde olduğu gibi 2, 0.333 + 0.333 = 0.666'ya keser ve muhtemelen teknik olarak yanlış olan 0.667'ye yuvarlarsınız.
Üçlü sayın ve üçte biri sorun değil - belki her eldeki 15 parmaklı bir yarış ondalık matematiğinizin neden kırıldığını sorar ...
Dijital bir bilgisayarda uygulanabilen kayan nokta matematiği türü, gerçek sayılar ve bunların üzerindeki işlemlerin yaklaşık bir değerini kullanır. ( Standart sürüm elli sayfadan fazla dokümantasyona sahiptir ve errata ve daha fazla ayrıntılandırma ile ilgili bir komiteye sahiptir.)
Bu yaklaşım, her biri ya doğruluktan sapma biçimi nedeniyle göz ardı edilebilir ya da dikkatle açıklanabilecek farklı türlerdeki yaklaşımların bir karışımıdır. Ayrıca, çoğu insanın fark etmiyormuş gibi davranırken, geçmişte yürüdüğü hem donanım hem de yazılım seviyelerinde bir dizi istisnai durum içerir.
Sonsuz hassasiyete ihtiyacınız varsa (örneğin, daha kısa stand-in'larından biri yerine π sayısını kullanarak), bunun yerine sembolik bir matematik programı yazmalı veya kullanmalısınız.
Ancak bazen kayan nokta matematiğinin değer ve mantıkta bulanık olduğu ve hataların hızlı bir şekilde birikebileceği fikriniz varsa ve buna izin vermek için gereksinimlerinizi ve testlerinizi yazabilirsiniz, o zaman kodunuz sık sık FPU'nuz.
Sadece eğlence için, Standart C99'un tanımlarını takip ederek şamandıraların temsili ile oynadım ve aşağıdaki kodu yazdım.
Kod, 3 ayrı gruptaki float'ların ikili temsilini yazdırır
SIGN EXPONENT FRACTION
ve bundan sonra, yeterli hassasiyetle toplandığında, donanımda gerçekten var olan değeri gösterecek bir miktar yazdırır.
Bu nedenle, yazarken float x = 999...
, derleyici bu sayıyı işlev tarafından yazdırılan bir bit gösteriminde, işlev xx
tarafından yazdırılan toplamın yy
verilen sayıya eşit olacak şekilde dönüştürür.
Gerçekte, bu toplam sadece bir yaklaşımdır. 999,999,999 sayısı için derleyici şamandıranın bit gösterimine 1.000.000.000 sayısını ekleyecektir
Koddan sonra bir konsol oturumu ekliyorum, burada donanımda bulunan her iki sabit (eksi PI ve 999999999) için terimlerin toplamını hesaplıyorum, derleyici tarafından buraya ekleniyor.
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
Burada, donanımda bulunan şamandıranın gerçek değerini hesapladığım bir konsol oturumu var. Kullandığım bc
ana programda tarafından çıkış terimlerin toplamını yazdırmak için. Bu toplamı python repl
veya benzeri bir şeye ekleyebilir .
-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872
Bu kadar. 999999999 değeri aslında
999999999.999999446351872
Ayrıca bc
-3.14'ün de bozulduğunu kontrol edebilirsiniz . Bir scale
faktör ayarlamayı unutmayın bc
.
Görüntülenen toplam, donanımın içindekidir. Hesaplayarak elde ettiğiniz değer, ayarladığınız ölçeğe bağlıdır. scale
Faktörü 15'e ayarladım . Matematiksel olarak, sonsuz bir hassasiyetle, 1.000.000.000 gibi görünüyor.
Buna bakmanın başka bir yolu: Kullanılan sayıları temsil etmek için 64 bittir. Sonuç olarak 2 ** 64 = 18,446,744,073,709,551,616'dan fazla farklı sayının tam olarak temsil edilebileceği bir yol yoktur.
Bununla birlikte, Math, 0 ile 1 arasında sonsuz sayıda ondalık sayı olduğunu söylüyor. IEE 754, bu 64 biti çok daha büyük bir sayı alanı artı NaN ve +/- Infinity için verimli bir şekilde kullanmak için bir kodlama tanımlar, bu nedenle doğru şekilde temsil edilen sayılar arasında boşluklar vardır. sadece yaklaşık rakamlar.
Ne yazık ki 0.3 bir boşlukta oturuyor.
On basamakta, örneğin 8 basamaklı bir doğrulukla çalıştığınızı düşünün. Siz
1/3 + 2 / 3 == 1
ve bunun geri döndüğünü öğren false
. Neden? Gerçek sayılar olarak
1/3 = 0.333 .... ve 2/3 = 0.666 ....
Sekiz ondalık basamaktan kesiliyor,
0.33333333 + 0.66666666 = 0.99999999
elbette ki 1.00000000
tam olarak farklıdır 0.00000001
.
Sabit sayıda bit içeren ikili sayıların durumu tam olarak benzerdir. Gerçek sayılar olarak,
1/10 = 0.0001100110011001100 ... (taban 2)
ve
1/5 = 0.0011001100110011001 ... (taban 2)
Bunları diyelim ki yedi bite kesersek,
0.0001100 + 0.0011001 = 0.0100101
öte yandan,
3/10 = 0.01001100110011 ... (taban 2)
yedi bite kesilmiş 0.0100110
ve bunlar tam olarak farklılık gösterir 0.0000001
.
Kesin durum biraz daha incedir çünkü bu sayılar genellikle bilimsel gösterimde saklanır. Dolayısıyla, örneğin, yerine 1/10 depolama olarak 0.0001100
biz böyle bir şey olarak saklayabilir1.10011 * 2^-4
üs ve mantis için kaç bit ayırdığımıza bağlı . Bu, hesaplamalarınız için kaç basamak hassasiyet elde edeceğinizi etkiler.
Sonuç olarak, bu yuvarlama hataları nedeniyle esasen kayan nokta sayılarında == kullanmak istemezsiniz. Bunun yerine, farklarının mutlak değerinin sabit bir küçük sayıdan daha küçük olup olmadığını kontrol edebilirsiniz.
Python 3.5'ten berimath.isclose()
yaklaşık eşitliği test etmek için işlevi kullanabilirsiniz :
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
Bu iş parçacığı mevcut kayan nokta uygulamaları hakkında genel bir tartışmaya biraz dalmış olduğundan, sorunlarını gidermek için projeler olduğunu ekleyeceğim.
Örneğin, daha az bitle daha iyi doğruluk sunmayı vaat eden posit (ve önceki sene unum) adlı bir sayı türünü gösteren https://posithub.org/ adresine bir göz atın . Anlayışım doğruysa, aynı zamanda sorudaki sorunları da düzeltir. Oldukça ilginç bir proje, arkasındaki kişi bir matematikçi olan Dr. John Gustafson . Her şey açık kaynak, C / C ++, Python, Julia ve C # ( https://hastlayer.com/arithmetics ) birçok gerçek uygulama ile .
Aslında oldukça basit. Bir taban 10 sisteminiz varsa (bizimki gibi), yalnızca tabanın ana faktörünü kullanan kesirleri ifade edebilir. 10'un asal faktörleri 2 ve 5'tir. Böylece, paydaların hepsi 10 asal faktörleri kullandığından 1/2, 1/4, 1/5, 1/8 ve 1/10 temiz bir şekilde ifade edilebilir. / 3, 1/6 ve 1/7 yinelenen ondalık sayılardır, çünkü paydaları 3 veya 7'lik bir asal çarpan kullanır. İkili (veya taban 2), tek asal çarpan 2'dir. asal faktör olarak yalnızca 2 içerir. İkili olarak, 1/2, 1/4, 1/8 ondalık sayı olarak temiz bir şekilde ifade edilir. 1/5 veya 1/10 ondalık sayıları tekrar eder. Yani 0.1 ve 0.2 (1/10 ve 1/5) bir taban 10 sistemindeki temiz ondalık sayıları, bilgisayarın çalıştığı taban 2 sisteminde ondalık sayıları tekrarlar. Bu yinelenen ondalık sayılar için matematik yaptığınızda,
Gönderen https://0.30000000000000004.com/
Gibi Ondalık sayılar 0.1
, 0.2
ve 0.3
tam olarak ikili temsil kayan nokta türlerini kodlanmamış. İçin sadece yaklaşık toplamı 0.1
ve 0.2
kullanılan yaklaşım değişmesidir 0.3
, dolayısıyla yalan 0.1 + 0.2 == 0.3
daha açık bir şekilde buradan görülebileceği gibi:
#include <stdio.h>
int main() {
printf("0.1 + 0.2 == 0.3 is %s\n", 0.1 + 0.2 == 0.3 ? "true" : "false");
printf("0.1 is %.23f\n", 0.1);
printf("0.2 is %.23f\n", 0.2);
printf("0.1 + 0.2 is %.23f\n", 0.1 + 0.2);
printf("0.3 is %.23f\n", 0.3);
printf("0.3 - (0.1 + 0.2) is %g\n", 0.3 - (0.1 + 0.2));
return 0;
}
Çıktı:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
Bu hesaplamaların daha güvenilir bir şekilde değerlendirilmesi için, kayan nokta değerleri için ondalık tabanlı bir gösterim kullanmanız gerekir. C Standardı bu tür türleri varsayılan olarak değil, teknik Raporda açıklanan bir uzantı olarak belirtir .
_Decimal32
, _Decimal64
Ve _Decimal128
türleri sisteminizde mevcut olabilir (örneğin, GCC onları destekleyen seçilmiş hedeflere ancak Clang bunları desteklemez OS X ).
Math.sum (javascript) .... tür operatör değiştirme
.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
sign: {
value: function (x) {
return x ? x < 0 ? -1 : 1 : 0;
}
},
precision: {
value: function (value, precision, type) {
var v = parseFloat(value),
p = Math.max(precision, 0) || 0,
t = type || 'round';
return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
}
},
scientific_to_num: { // this is from https://gist.github.com/jiggzson
value: function (num) {
//if the number is in scientific notation remove it
if (/e/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(), //store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e / l,
coeff_array = parts[0].split('.');
if (sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
} else {
var dec = coeff_array[1];
if (dec)
l = l - dec.length;
num = coeff_array.join('') + new Array(l + 1).join(zero);
}
}
return num;
}
}
get_precision: {
value: function (number) {
var arr = Math.scientific_to_num((number + "")).split(".");
return arr[1] ? arr[1].length : 0;
}
},
sum: {
value: function () {
var prec = 0, sum = 0;
for (var i = 0; i < arguments.length; i++) {
prec = this.max(prec, this.get_precision(arguments[i]));
sum += +arguments[i]; // force float to convert strings to number
}
return Math.precision(sum, prec);
}
}
});
fikir float hatalarını önlemek için matematik yerine operatörler kullanmaktır
Math.sum, kullanılacak hassasiyeti otomatik olarak algılar
Math.sum herhangi bir sayıda argümanı kabul eder
Aşağıdaki sonuçları göz önünde bulundurun:
error = (2**53+1) - int(float(2**53+1))
>>> (2**53+1) - int(float(2**53+1))
1
Ne zaman bir kırılma noktası olduğunu açıkça görebiliriz 2**53+1
- hepsi iyi çalışır 2**53
.
>>> (2**53) - int(float(2**53))
0
Bunun nedeni çift kesinlikli ikili: IEEE 754 çift kesinlikli ikili kayan noktalı biçim: binary64
Çift kesinlikli kayar nokta biçimi için Wikipedia sayfasından :
Çift hassasiyetli ikili kayan nokta, performansına ve bant genişliği maliyetine rağmen, tek duyarlıklı kayan nokta üzerindeki geniş aralığı nedeniyle PC'lerde yaygın olarak kullanılan bir formattır. Tek kesinlikli kayar nokta biçiminde olduğu gibi, aynı boyuttaki bir tamsayı biçimiyle karşılaştırıldığında tamsayı sayılarında kesinlik yoktur. Genellikle basitçe çift olarak bilinir. IEEE 754 standardı bir binary64'ü şu şekilde belirtir:
- İşaret biti: 1 bit
- Üs: 11 bit
- Önemli hassasiyet: 53 bit (52 açıkça depolanmış)
Belirli bir önyargılı üs ve 52 bitlik bir kesite sahip belirli bir 64 bit çift kesinlikli veri tarafından varsayılan gerçek değer
veya
@A_guest'e bunu gösterdiğin için teşekkürler.
Farklı bir soru, bu sorunun kopyası olarak adlandırıldı:
C ++ 'da, neden cout << x
bir hata ayıklayıcının gösterdiği değerden farklıdır x
?
Söz x
konusu olan bir float
değişkendir.
Bir örnek,
float x = 9.9F;
Hata ayıklayıcı, işlemin 9.89999962
çıktısını gösterir .cout
9.9
Cevap, bunun cout
varsayılan hassasiyeti float
6 olduğu için 6 ondalık basamağa yuvarlanıyor.
Referans için buraya bakınız