Kayan nokta matematiği kırık mı?


2983

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?


127
Kayan nokta değişkenleri genellikle bu davranışa sahiptir. Bunun nedeni donanımda nasıl depolandıklarıdır. Daha fazla bilgi için kayan nokta sayıları hakkındaki Wikipedia makalesine göz atın .
Ben S

62
JavaScript ondalık sayıları kayan nokta sayısı olarak kabul eder , yani toplama gibi işlemler yuvarlama hatasına maruz kalabilir. Bu makaleye göz atmak isteyebilirsiniz: Her Bilgisayar Bilimcisinin Kayan Nokta Aritmetiği Hakkında Bilmesi Gerekenler
matt b

4
Sadece bilgi amaçlı olarak, javascript'teki TÜM sayısal türler IEEE-754 Çiftler'dir.
Gary Willoughby

6
JavaScript, Math için IEEE 754 standardını kullandığından, 64 bit kayan sayılar kullanır. Bu, kayar nokta (ondalık) hesaplamaları yaparken, ondalık Taban 10 iken Base 2'de çalışan bilgisayarlar nedeniyle kısaca hatalara neden olur .
Pardeep Jain

Yanıtlar:


2252

İ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.1standart içinde binary64formatında, temsili tam olarak yazılabilir

  • 0.1000000000000000055511151231257827021181583404541015625 ondalık olarak veya
  • 0x1.999999999999ap-4içinde C99 hexfloat gösterimde .

Aksine, rasyonel sayı 0.1, 1/10tam olarak şu şekilde yazılabilir:

  • 0.1 ondalık olarak veya
  • 0x1.99999999999999...p-4C99 hekzfloat gösteriminin bir analogunda, burada 9'ların ...bitmeyen bir sekansını temsil eder.

Sabitler 0.2ve 0.3programınızdaki gerçek değerleri de yaklaşık olacaktır. O en yakın olur doubleüzere 0.2rasyonel sayısından daha fazla 0.2ama en yakın olduğu doubleiçin 0.3rasyonel sayısından daha küçüktür 0.3. Toplamı 0.1ve 0.2rüzgarları rasyonel sayıdan daha büyük olmak 0.3ve 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) { ... }.

absmutlak 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.


181
Bence "bazı hata sabiti" "Epsilon" dan daha doğrudur, çünkü her durumda kullanılabilecek "Epsilon" yoktur. Farklı durumlarda farklı epsilonsların kullanılması gerekir. Ve makine epsilon neredeyse hiç kullanmak için iyi bir sabit değildir.
Rotsor

34
Bütün kayan noktalı matematiğin IEEE [754] standardına dayandığı pek doğru değildir . Örneğin, hala eski IBM onaltılık FP'ye sahip bazı sistemler var ve hala IEEE-754 aritmetiğini desteklemeyen grafik kartları var. Bununla birlikte, makul bir yaklaşım için doğrudur.
Stephen Canon

19
Cray, hız için IEEE-754 uyumluluğunu ortadan kaldırdı. Java da bir optimizasyon olarak bağlılığını gevşetti.
Art Taylor

28
Bence bu cevaba, para hesaplamalarının her zaman tamsayılarda sabit nokta aritmetiği ile nasıl yapılması gerektiğine dair bir şeyler eklemelisiniz , çünkü para nicelendirilir. (Bir kuruşun en küçük kesirlerinde veya en küçük para biriminiz ne olursa olsun dahili muhasebe hesaplamaları yapmak mantıklı olabilir - bu genellikle örneğin "ayda 29,99 ABD doları" değerini günlük bir orana dönüştürürken yuvarlama hatasını azaltmaya yardımcı olur - ancak hala sabit noktalı aritmetik olabilir.)
zwol

18
İlginç gerçek: İkili kayan noktada tam olarak temsil edilmeyen bu 0.1 , ilk Irak savaşı sırasında 28 kişinin ölümüyle sonuçlanan rezil bir Patriot füze yazılımı hatasına neden oldu.
HDL

602

Bir Donanım Tasarımcının Perspektifi

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.

1. Genel Bakış

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.

2. Standartlar

Ç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.

3. Bölme Yuvarlama Hatasının Nedeni

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= 4bit (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.

4. Diğer İşlemlerde Yuvarlama Hataları: Kesme

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.

5. Tekrarlanan İşlemler

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.

6. Özet

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.


8
(3) yanlış. Bir bölümdeki yuvarlama hatası en sondaki bir birimden daha az değil , en sondaki en fazla yarım birimdir.
gnasher729

6
@ gnasher729 İyi yakaladın. Çoğu temel işlem, varsayılan IEEE yuvarlama modunu kullanarak en son yerde bir ünitenin 1 / 2'sinden daha az hataya sahiptir. Açıklamayı düzenledi ve ayrıca kullanıcı varsayılan yuvarlama modunu geçersiz kılarsa hatanın bir ulp'nin 1 / 2'sinden daha büyük, ancak 1 ulp'nin altında olabileceğini belirtti (bu özellikle gömülü sistemlerde doğrudur).
KernelPanik

39
(1) Kayan nokta numaralarında hata yoktur. Her bir kayan nokta değeri tam olarak budur. Kayan nokta işlemlerinin çoğu (hepsi değil) hatalı sonuçları verir. Örneğin, tam olarak 1.0 / 10.0'a eşit ikili kayan nokta değeri yoktur. Bazı işlemler (örneğin, 1.0 + 1.0) do diğer taraftan kesin sonuçlar verir.
Solomon Slow

19
"Kayan nokta bölümündeki hatanın ana nedeni, bölümü hesaplamak için kullanılan bölme algoritmalarıdır" çok yanıltıcı bir şeydir. IEEE-754 uyumlu bir bölme için, kayan nokta bölmesindeki hatanın tek nedeni, sonucun sonuç biçiminde tam olarak temsil edilememesidir; kullanılan algoritmadan bağımsız olarak aynı sonuç hesaplanır.
Stephen Canon

6
@Matt Geç cevap verdiğim için üzgünüm. Temel olarak kaynak / zaman sorunları ve ödünleşmeden kaynaklanmaktadır. Uzun bölüm / daha 'normal' bölüm yapmanın bir yolu vardır, buna yarıçap iki olan SRT Bölümü denir. Bununla birlikte, bu defalarca bölücüyü temettüden çıkarır ve çıkarır ve birçok saat döngüsü alır çünkü saat döngüsü başına bölümün sadece bir bitini hesaplar. Döngü başına bölümün daha fazla bitini hesaplayabilmemiz ve etkili performans / hız ödünleşimleri yapabilmemiz için karşılıklı tablolar kullanıyoruz.
KernelPanik

462

.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.


133
Harika ve kısa cevap. Yinelenen desen 0.00011001100110011001100110011001100110011001100110011 ...
Konstantin Chernov

4
Bu neden ilk etapta ikililere dönüşmeyen daha iyi bir algoritma kullanılmadığını açıklamaz.
Dmitri Zaitsev

12
Çünkü performans. İkili kullanmak birkaç bin kat daha hızlıdır, çünkü makine için yereldir.
Joel Coehoorn

7
Tam ondalık değerler veren ARE yöntemleri vardır. BCD (İkili kodlu ondalık) veya diğer çeşitli ondalık sayı biçimleri. Bununla birlikte, bunların ikisi de yavaştır (LOT yavaştır) ve ikili kayan nokta kullanmaktan daha fazla depolama alanı gerektirir. (örnek olarak, paketlenmiş BCD, bir baytta 2 ondalık hane saklar. Bu, bir baytta gerçekte 256 olası değeri saklayabilen 100 olası değer veya bir baytın olası değerlerinin yaklaşık% 60'ını harcayan 100/256'dır.)
Duncan C

16
@ Jacksonson hala baz-10'da düşünüyorsun. Bilgisayarlar temel-2'dir.
Joel Coehoorn

306

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.

(Başlangıçta Quora'da yayınlanmıştır.)


3
Tam matematiği içeren bazı diller olduğunu unutmayın. Bir örnek, örneğin GNU Guile aracılığıyla Şema'dır. Bkz. Draketo.de/english/exact-math-to-the-rescue - bunlar matematiği kesir olarak tutar ve sadece sonunda kesilir .
Arne Babenhauserheide

5
@FloatingRock Aslında, çok az ana akım programlama dili yerleşik rasyonel sayılara sahiptir. Arne bir Schemer, benim gibi, bu yüzden bunlar şımartıldığımız şeyler.
Chris Jester-Young

5
@ArneBabenhauserheide Bunun sadece rasyonel sayılarla çalışacağını eklemeye değer olduğunu düşünüyorum. Bu yüzden pi gibi irrasyonel sayılarla biraz matematik yapıyorsanız, bunu pi'nin bir katı olarak saklamanız gerekir. Elbette, pi içeren herhangi bir hesaplama kesin bir ondalık sayı olarak gösterilemez.
Aidiakapi

13
@connexo Tamam. Pizza döndürücünüzü 36 derece alacak şekilde nasıl programlarsınız? 36 derece nedir? (İpucu: Bunu tam bir şekilde tanımlayabiliyorsanız, ayrıca dilim-tam-onuncu bir pizza kesiciniz de vardır.) Başka bir deyişle, aslında 1/360 (derece) veya 1 / 10 (36 derece) sadece ikili kayan noktalı.
Chris Jester-Young

12
@connexo Ayrıca, "her salak" bir pizzayı tam 36 derece döndüremez . İnsanlar bu kadar kesin bir şey yapamayacak kadar hata yapma eğilimindedirler.
Chris Jester-Young

212

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.


133
bilgisayarlar 0,1 + 0,2 = 0,3 sağ almak için sonsuz miktarda belleğe ihtiyaç duymaz
Pacerier

23
@Pacerier Elbette, bir kesiri temsil etmek için iki sınırsız kesinlikli tamsayı kullanabilirler veya alıntı gösterimini kullanabilirler. Bunu "imkansız" veya "ondalık" kavramı, bunu imkansız kılan şeydir - bir dizi ikili / ondalık hane ve orada bir yerde bir sayı tabanı noktasına sahip olduğunuz fikri. Kesin rasyonel sonuçlar elde etmek için daha iyi bir biçime ihtiyacımız var.
Devin Jeanpierre

15
@Pacerier: İkili veya ondalık kayan nokta tam olarak 1/3 veya 1/13 depolayamaz. Ondalık kayan nokta türleri, M / 10 ^ E formunun değerlerini tam olarak temsil edebilir, ancak diğer çoğu kesri temsil ettiğinde, benzer boyutlu ikili kayan nokta sayılarından daha az hassastır . Birçok uygulamada, rastgele kesirler ile daha yüksek hassasiyete sahip olmak, birkaç "özel" ile mükemmel hassasiyete sahip olmaktan daha yararlıdır.
supercat

13
@Pacerier Sayıları ikili yüzer olarak saklıyorlarsa yaparlar , bu da cevabın noktasıydı.
Mark Amery

3
@chux: İkili ve ondalık türler arasındaki doğruluk farkı çok büyük değildir, ancak ondalık türler için en iyi durum ile en kötü durum hassasiyetindeki 10: 1 fark, ikili tiplerle 2: 1 farktan çok daha büyüktür. Herhangi birinin donanımda veya yazılımda verimli uygulama için uygun görünmeyeceğinden, ondalık türlerden herhangi birinde verimli bir şekilde çalışmak için herhangi bir donanım veya yazılı yazılım geliştirip geliştirmediğini merak ediyorum.
supercat

121

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.3dö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 2550yerine sent 25.50dolar.


1 Douglas Crockford: JavaScript: İyi Parçalar : Ek A - Korkunç Parçalar (sayfa 105) .


3
Sorun dönüşümün kendisinin yanlış olmasıdır. 16.08 * 100 = 1607.9999999999998. Numarayı bölmeye ve ayrı ayrı dönüştürmeye başvurmalıyız (16 * 100 + 08 = 1608'deki gibi)?
Jason

38
Buradaki çözüm, tüm hesaplamalarınızı tamsayı olarak yapmak ve oranınıza (bu durumda 100) bölmek ve yalnızca verileri sunarken yuvarlamaktır. Bu, hesaplamalarınızın her zaman kesin olmasını sağlayacaktır.
David Granado

15
Biraz nitpick yapmak için: tamsayı aritmetiği sadece bir noktaya kadar kayan nokta için kesindir (pun amaçlı). Sayı 0x1p53'ten büyükse (Java 7'nin onaltılık kayan nokta gösterimini kullanmak için, = 9007199254740992), bu durumda ulp 2'dir ve bu nedenle 0x1p53 + 1, 0x1p53'e yuvarlanır (ve 0x1p53 + 3, 0x1p53 + 'a yuvarlanır 4, çünkü yuvarlak-çift). :-D Ama kesinlikle, eğer sayınız 9 katrilyondan küçükse, iyi olmalısınız. :-P
Chris Jester-Young

2
Jason, sadece sonucu yuvarlaman gerekiyor (int) (16.08 * 100 + 0.5)
Mikhail Semenov

@CodyBugstein " Peki .1 + .2'yi .3'ü nasıl göstereceksiniz? " Ondalık sayıyı istediğiniz yere yerleştirmek için özel bir yazdırma işlevi yazın.
RonJohn

113

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:

  • İlk bit işaret bitidir : 1sayı negatifse, 0aksi takdirde 1 .
  • Bir sonraki 11 bit olan üs olup, ofset Diğer bir deyişle 1023 ile, bir çift duyarlıklı numarasından üstel bit okuduktan sonra 1023 iki güç elde edilmesi için çıkartılması gerekmektedir.
  • Kalan 52 bit anlamlı (veya mantis). Mantiste olarak, bir 'ima' 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 - +0ve -0farklı 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ı 1s) 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.2Bir 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.2olur 0.3: son birkaç bit yuvarlanır.


14
Cevabım gönderildikten kısa bir süre sonra oylandı. O zamandan beri birçok değişiklik yaptım (orijinalde atladığım ikili ve 0,1 ve 0,2 yazarken açıkça yinelenen bitleri not etmek dahil). Düşman seçmenin bunu görmesi ihtimaline karşı, cevabımı geliştirebilmem için lütfen bana biraz geri bildirim verebilir misiniz? IEEE 754'teki meblağın diğer cevaplarla aynı şekilde ele alınmadığından cevabımın yeni bir şey eklediğini hissediyorum. "Her bilgisayar bilimcisinin bilmesi gerekenler ..." aynı materyali içeriyor olsa da, cevabım özellikle 0.1 + 0.2 vakasıyla ilgileniyor .
Wai Ha Lee

57

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.1olsaydı 1 x 10⁻¹, 0.2olurdu , olurdu 2 x 10⁻¹ve 0.3olurdu 3 x 10⁻¹. Tamsayı matematik kolay ve kesindir, bu yüzden ekleme 0.1 + 0.2açı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.25olduğ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.1olduğunu 3602879701896397 x 2⁻⁵⁵ve en yakın sayı 0.2olduğ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.


2
@ Mark Bu Açık açıklama için teşekkür ederim, ancak daha sonra soru neden 0.1 + 0.4'ün tam olarak 0.5'e (Python 3'te en az) eklediğini ortaya çıkar. Ayrıca Python 3'te şamandıra kullanırken eşitliği kontrol etmenin en iyi yolu nedir?
pchegoor

2
@ user2417881 IEEE kayan nokta işlemlerinin her işlem için yuvarlama kuralları vardır ve bazen iki sayı biraz kapalı olsa bile yuvarlama kesin bir yanıt verebilir. Ayrıntılar yorum yapmak için çok uzun ve ben zaten bu konuda uzman değilim. Bu cevapta gördüğünüz gibi 0.5, ikili olarak temsil edilebilecek birkaç ondalık sayıdan biridir, ancak bu sadece bir tesadüf. Eşitlik testi için stackoverflow.com/questions/5595425/… adresine bakın .
Mark Ransom

1
@ user2417881 Sorunuz beni ilgilendirdi, bu yüzden tam bir soruya ve cevaba dönüştürdüm: stackoverflow.com/q/48374522/5987
Mark Ransom

47

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.


33

Geçici çözümüm:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

duyarlık , toplama sırasında ondalık noktadan sonra korumak istediğiniz basamak sayısını ifade eder.


30

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,

resim açıklamasını buraya girin

0.2Kolayca yazabilmenize rağmen FLT_RADIXve DBL_RADIX2; "İ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.


28

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


28

Hayır, kırık değil, ancak ondalık kesirlerin çoğuna yaklaşılmalıdır

ö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.


En yakın tam sayıya yuvarlama, her durumda karşılaştırma problemini çözmenin güvenli bir yolu değildir. 0.4999998 ve 0.500001 farklı tamsayılara yuvarlanır, böylece her yuvarlama kesme noktasının etrafında bir "tehlike bölgesi" vardır. (Bu ondalık dizelerin muhtemelen IEEE ikili yüzenları olarak tam olarak temsil edilemeyeceğini biliyorum.)
Peter Cordes

1
Ayrıca, kayan nokta "eski" bir format olsa da, çok iyi tasarlanmıştır. Şimdi yeniden tasarlarsa kimsenin değiştireceği hiçbir şey bilmiyorum. Ne kadar çok şey öğrenirsem, o kadar iyi tasarlanmış olduğunu düşünüyorum . örneğin önyargılı üs, ardışık ikili şamandıraların ardışık tamsayı temsillerine sahip olduğu anlamına gelir, böylece 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).
Peter Cordes

Katılıyorum, şamandıralar ikili değil ondalık olarak saklanmalı ve tüm sorunlar çözülmelidir.
Ronen Festinger

" X / (2 ^ n + 5 ^ n) " " x / (2 ^ n * 5 ^ n) " olmamalı mı?
Wai Ha Lee

@RonenFestinger - Peki ya 1/3?
Stephen C

19

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 **


19

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.2ve 0.7işte burada: 0.2 + 0.7 = 0.8999999999999999.

Beklediğiniz sonuç, 0.9bu 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 / 10Aynı 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 beni gerçek bir baş ağrısı yaptı. 12 kayan sayı toplam, sonra toplamları ve bu sayıların ortalamasını gösteririm. toFixed () kullanılması, 2 sayının toplanmasını düzeltebilir, ancak birkaç sayının toplamı sırasında sıçrama önemlidir.
Nuryagdy Mustapayev

@Nuryagdy Mustapayev Niyetinizi almadım, 12 float sayısını toplayabilmeden önce test ettikten sonra sonuçta floatify () işlevini kullanın, sonra ne istersen yap, onu kullanırken hiçbir sorun gözlemlemedim.
Mohammad Musavi

Ben sadece her formülün sonucunun başkalarına bağlı olduğu yaklaşık 20 parametre ve 20 formülün olduğu durumumda bu çözümün yardımcı olmadığını söylüyorum.
Nuryagdy Mustapayev

16

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.


İkinci paragrafınızı hiç anlamıyorum.
Nae

1
@Nae İkinci paragrafı "Kesirlerin çoğunluğu tam olarak ondalık veya ikili olarak temsil edilemez . Bu nedenle çoğu sonuç yuvarlanır - yine de, gösterimdeki doğal bit / basamak sayısına kesin olarak Kullanılan."
Steve Summit

15

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 floatveya doublekarşı BigDecimalJava.

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.


2
Düzenlemesini yeni geri aldığım kişiye: Kod alıntılarına kod alıntılarına uygun olduğunu düşünüyorum. Dilde tarafsız olan bu cevap hiç alıntılanmış kod içermez. Sayılar İngilizce cümlelerde kullanılabilir ve bu onları koda dönüştürmez.
Patricia Shanahan

Bu ancak okunabilirlik için değil, biçimlendirme için - Biri kodu olarak Numaralarınızı biçimlendirilmiş neden muhtemeldir.
Wai Ha Lee

... ayrıca, yuvarlama , ondalık gösterimi değil , ikili gösterimi ifade eder. Bkz bu , örneğin, ya da bu .
Wai Ha Lee

@WaiHaLee Herhangi bir ondalık sayıya tek / çift testi uygulamadım, sadece onaltılık. Onaltılı basamak, ikili genişlemesinin en az anlamlı biti sıfır olsa bile olur.
Patricia Shanahan

14

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 decimalmodülü ve Java BigDecimalsı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 decimalmodülü IEEE 854-1987 standardını temel alır .

  • Python'un fractionsmodülü ve Apache Common'un BigFractionsı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.


14

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)=true0.333 ... 0.333'e sonsuzluk eklemediğiniz sürece elde edemezsiniz ... tıpkı temeldeki (1/10+2/10)!==3/10problemde 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 ...


İnsanlar ondalık sayılar kullandığından, şamandıraların varsayılan olarak ondalık sayı olarak gösterilmemesinin iyi bir nedenini göremiyorum, bu yüzden doğru sonuçlara sahibiz.
Ronen Festinger

İnsanlar baz 10 (ondalık) dışında birçok üs kullanmak, ikili hesaplama için en çok kullandığımız .. 'iyi nedeni' sadece her üssünde her kesiri temsil edemez olmasıdır ..

@RonenFestinger ikili aritmetiğin bilgisayarlarda uygulanması kolaydır, çünkü basamaklı yalnızca sekiz temel işlem gerektirir: $ a $, $ b $ 0,1 $ içinde bilmeniz gereken tek şey $ \ operatorname {xor} (a, b) $ ve $ \ operatorname {cb} (a, b) $, burada xor özeldir ve cb "taşıma bitidir"; bu durumda $ a = 1 = b $ hariç tüm durumlarda 0 $ 'dır, bu durumda bir (aslında tüm operasyonların değişebilirliği size $ 2 $ tasarruf sağlar ve tek ihtiyacınız olan $ 6 $ kuralları). Ondalık genişletme için 10 $ 'lık 11 $ (ondalık gösterimde) vakaların depolanması ve her bit için 10 $' lık farklı durumların bulunması ve taşıma sırasında depolamanın boşa harcanması gerekir.
Oskar Limka

@RonenFestinger - Ondalık daha doğru DEĞİLDİR. Bu cevap bunu söylüyor. Seçtiğiniz herhangi bir taban için, sonsuz tekrar eden basamak dizileri veren rasyonel sayılar (kesirler) olacaktır. Kayıt için, ilk bilgisayarların bazı yaptılar kullanımı üssü numaraları için 10 temsillerini, ancak öncü bilgisayar donanım tasarımcıları yakında taban 2 çok daha kolay ve uygulanması daha verimli olduğu sonucuna vardı.
Stephen C

9

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.


9

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 xxtarafından yazdırılan toplamın yyverilen 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 bcana programda tarafından çıkış terimlerin toplamını yazdırmak için. Bu toplamı python replveya 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 scalefaktö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. scaleFaktörü 15'e ayarladım . Matematiksel olarak, sonsuz bir hassasiyetle, 1.000.000.000 gibi görünüyor.


5

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.


4

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.00000000tam 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.0100110ve 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.0001100biz 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.


4

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

3

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 .


3

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/


3

Gibi Ondalık sayılar 0.1, 0.2ve 0.3tam olarak ikili temsil kayan nokta türlerini kodlanmamış. İçin sadece yaklaşık toplamı 0.1ve 0.2kullanılan yaklaşım değişmesidir 0.3, dolayısıyla yalan 0.1 + 0.2 == 0.3daha 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, _Decimal64Ve _Decimal128türleri sisteminizde mevcut olabilir (örneğin, GCC onları destekleyen seçilmiş hedeflere ancak Clang bunları desteklemez OS X ).


1

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


1
Yine de , " Bu yanlışlıklar neden oluyor? " Sorusunu cevapladığınızdan emin değilim .
Wai Ha Lee

bir şekilde haklısın ama ben burada bu konu ile ilgili bir javascript garip davranış geldi ... ben sadece bir tür paylaşmak istiyorum
bortunac

Sen ediyoruz hala olsa soruya cevap değil.
Wai Ha Lee

k bununla ilgili bir sorun var ... nereye taşıyacağımı söyle yoksa ısrar
edersen

0

Bu ilginç konuyu kayan noktaların etrafında gördüm:

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

Resim açıklamasını buraya girin

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ış)

Resim açıklamasını buraya girin

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

Resim açıklamasını buraya girin

veya

Resim açıklamasını buraya girin

@A_guest'e bunu gösterdiğin için teşekkürler.


-1

Farklı bir soru, bu sorunun kopyası olarak adlandırıldı:

C ++ 'da, neden cout << xbir hata ayıklayıcının gösterdiği değerden farklıdır x?

Söz xkonusu olan bir floatdeğişkendir.

Bir örnek,

float x = 9.9F;

Hata ayıklayıcı, işlemin 9.89999962çıktısını gösterir .cout9.9

Cevap, bunun coutvarsayılan hassasiyeti float6 olduğu için 6 ondalık basamağa yuvarlanıyor.

Referans için buraya bakınız


1
IMO - bunu buraya göndermek yanlış bir yaklaşımdı. Sinir bozucu olduğunu biliyorum, ancak orijinal soruya bir cevap isteyen (görünüşte şimdi silindi!) İnsanlar burada bulamayacaklar. Eğer çalışmanızın gerçekten kurtarılmayı hak ettiğini düşünüyorsanız, şunu öneririm: 1) bunun gerçekten cevapladığı başka bir Q aramak, 2) kendi kendine cevaplanan bir soru oluşturmak.
Stephen C
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.