Kayan nokta sayıları neden yanlış?


198

Kayan nokta sayıları olarak saklandığında bazı sayılar neden doğruluk kaybediyor?

Örneğin, ondalık sayı 9.2tam olarak iki ondalık tam sayı ( 92/10) oranı olarak ifade edilebilir, her ikisi de tam olarak ikili ( 0b1011100/0b1010) olarak ifade edilebilir . Ancak, kayan nokta sayısı olarak depolanan aynı oran asla tam olarak eşit değildir 9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

Görünüşte bu kadar basit bir sayı 64 bitlik bellekte ifade etmek için nasıl "çok büyük" olabilir ?




Yanıtlar:


242

Çoğu programlama dilinde, kayan nokta sayıları bilimsel gösterime çok benzer şekilde temsil edilir : üs ve mantis (anlamlı olarak da adlandırılır). Çok basit bir sayı, diyelim ki 9.2, aslında bu kesir:

5179139571476070 * 2 -49

Üslü -49ve mantis nerede 5179139571476070. Bazı ondalık sayıları bu şekilde göstermenin imkansız olmasının nedeni , üs ve mantisin tamsayı olması gerektiğidir. Diğer bir deyişle, tüm kayan noktalar 2 tamsayısı ile çarpılan bir tam sayı olmalıdır .

9.2sadece olabilir 92/10, ama 10 olarak ifade edilemez 2 n ise n tam sayı değerleri ile sınırlıdır.


Verileri Görme

İlk olarak, birkaç işlevi 32 ve 64 bit yapan bileşenleri görmek için float. Yalnızca çıktıyı önemsiyorsanız bunlar üzerinde parlaklık (Python'da örnek):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

Bu işlevin arkasında çok fazla karmaşıklık var ve açıklamak oldukça teğet olurdu, ancak ilgileniyorsanız, amaçlarımız için önemli kaynak yapı modülüdür.

Python's float64-bit, çift kesinlikli bir sayıdır. C, C ++, Java ve C # gibi diğer dillerde, çift duyarlıklı doublegenellikle 64 bit olarak uygulanan ayrı bir tür vardır .

Örneğimizle bu işlevi çağırdığımızda, şunu 9.2elde ederiz:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

Verileri Yorumlama

Dönüş değerini üç bileşene böldüğümü göreceksiniz. Bu bileşenler:

  • İşaret
  • üs
  • Mantissa (Anlamlı veya Kesir olarak da adlandırılır)

İşaret

İşaret, ilk bileşende tek bir bit olarak saklanır. Açıklamak kolaydır: 0şamandıranın pozitif bir sayı olduğu anlamına gelir; 1negatif olduğu anlamına gelir. Çünkü 9.2olumlu, bizim işareti değeridir 0.

üs

Üs, orta bileşende 11 bit olarak depolanır. Bizim durumumuzda 0b10000000010,. Ondalık olarak, bu değeri temsil eder 1026. Bu bileşenin tuhaflığı , gerçek üssü elde etmek için 2 (bit sayısı) - 1 - 1'e eşit bir sayı çıkarmanız gerektiğidir ; bizim durumumuzda bu , gerçek üssü elde etmek için 0b1111111111(ondalık sayı 1023) çıkarma (ondalık sayı 3 ) anlamına gelir 0b00000000011.

mantis

Mantis üçüncü bileşende 52 bit olarak depolanır. Ancak, bu bileşene de bir tuhaflık var. Bu tuhaflığı anlamak için bilimsel gösterimdeki bir sayıyı şöyle düşünün:

6.0221413x10 23

Mantis olurdu 6.0221413. Bilimsel gösterimdeki mantisin her zaman sıfır olmayan bir rakamla başladığını hatırlayın. Aynısı ikili dosya için de geçerlidir, ancak ikili dosyada yalnızca iki basamak vardır: 0ve 1. Böylece ikili mantis her zaman ile başlar 1! Bir şamandıra depolandığında, 1ikili mantis önündeki yerden tasarruf etmek için atlanır; gerçek mantis elde etmek için onu üçüncü elementimizin önüne yerleştirmeliyiz :

1,0010011001100110011001100110011001100110011001100110

Bu, basit bir eklemeden daha fazlasını içerir, çünkü üçüncü bileşenimizde depolanan bitler aslında mantisin, yarıçap noktasının sağındaki kesirli kısmını temsil eder .

Ondalık sayılarla uğraşırken, 10'luk güçlerle çarparak veya bölerek "ondalık noktasını hareket ettiririz". İkili olarak, 2'nin güçleriyle çarparak veya bölerek de aynı şeyi yapabiliriz. Üçüncü elemanımız 52 bit olduğu için böleriz tarafından o 2 52 o sağa 52 yerlere taşımak için:

0,0010011001100110011001100110011001100110011001100110

Ondalık gösterimde, o bölünmesi aynı şey 675539944105574tarafından 4503599627370496olsun 0.1499999999999999. (Bu, tam olarak ikili, ancak yaklaşık olarak ondalık olarak ifade edilebilen bir oranın bir örneğidir; daha fazla ayrıntı için bkz: 675539944105574/4503599627370496 .)

Şimdi üçüncü bileşeni kesirli bir sayıya dönüştürdüğümüze göre 1, gerçek mantis verir.

Bileşenleri Yeniden Kapatma

  • İşaret (birinci bileşen): 0pozitif 1için, negatif için
  • Üs (orta bileşen): Gerçek üssü elde etmek için 2 (bit sayısı) - 1 - 1'i çıkarın
  • Mantissa (son bileşen): 2'ye bölün ( bit sayısı) ve 1gerçek mantis elde etmek için ekleyin

Sayının Hesaplanması

Her üç parçayı bir araya getirdiğimizde, bu ikili sayı bize verilir:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Daha sonra ikiliden ondalığa dönüştürebiliriz:

1.1499999999999999 x 2 3 (hatalı!)

Ve 9.2kayan nokta değeri olarak saklandıktan sonra ( ) ile başladığımız sayının son temsilini ortaya çıkarmak için çarpın :

9,1999999999999993


Kesir olarak temsil etme

9.2

Şimdi sayıyı inşa ettiğimize göre, basit bir kesite yeniden yapılandırmak mümkün:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Mantisayı bir tam sayıya kaydır:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

Ondalık biçime dönüştür:

5179139571476070 x 2 3-52

Üs çıkarın:

5179139571476070 x 2 -49

Negatif üssü bölünmeye dönüştürün:

5179139571476070/2 49

Üs değerini çarp:

5179139571476070/562949953421312

Hangisi:

9,1999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

Zaten mantis sadece 4 basamak ve bir sürü sıfır olduğunu görebilirsiniz. Ama adım adım ilerleyelim.

İkili bilimsel gösterimi birleştirin:

1.0011 x 10 11

Ondalık noktasını kaydır:

10011 x 10 11-100

Üs çıkarın:

10011 x 10 -1

İkiliden ondalığa:

19 x 2 -1

Bölümleme için negatif üs:

19/2 1

Üs değerini çarp:

19/2

Eşittir:

9.5



daha fazla okuma


1
Başka bir yolla nasıl gidileceğini gösteren güzel bir öğretici de var - bir sayının ondalık bir temsili verildiğinde, kayan nokta eşdeğerini nasıl oluşturursunuz. "Uzun bölüm" yaklaşımı, sayıyı temsil etmeye çalıştıktan sonra nasıl bir "kalan" ile sonuçlandığınızı çok net bir şekilde gösterir. Cevabınızla gerçekten "kanonik" olmak istiyorsanız eklenmelidir.
Floris

1
Python ve kayan nokta hakkında konuşuyorsanız, en azından bağlantılarınıza Python öğreticisini dahil etmenizi öneririm : docs.python.org/3.4/tutorial/floatingpoint.html Bu tek duraklı olması gerekiyordu programcıları için kayan nokta sorunları için kaynak. Bir şekilde eksikse (ve neredeyse şüphesiz), lütfen güncellemeler veya değişiklikler için Python hata izleyicide bir sorun açın.
Mark Dickinson

@mhlester Bu topluluk wikisine dönüşürse, cevabımı sizinkine dahil etmekten çekinmeyin.
Nicu Stiurca

5
Bu cevap kesinlikle yeni başlayanlar için en iyi giriş olduğundan kayan-point-gui.de adresine de bağlanmalıdır . IMO, hatta her bilgisayar bilim insanının bilmesi gerekenler ... 'in üstüne çıkmalı - bugünlerde Goldberg'in gazetesini makul bir şekilde anlayabilen insanlar genellikle bunun farkındalar.
Daniel Pryden

1
Msgstr "Bu, tam olarak ikili olarak ancak yaklaşık olarak ondalık olarak ifade edilebilen bir oranın bir örneğidir". Bu doğru değil. Tüm bu 'iki güç üzerinden sayı' oranları ondalık olarak kesindir. Herhangi bir tahmin, kolaylık sağlamak için sadece ondalık sayıyı kısaltmaktır.
Rick Regan

29

Bu tam bir cevap değil ( mhlester zaten çoğaltmayacağım çok iyi bir zemin kapladı), ancak bir sayının temsilinin çalıştığınız tabana ne kadar bağlı olduğunu vurgulamak istiyorum.

2/3 bölümünü düşünün

Good-ol 'base 10'da, tipik olarak böyle bir şey olarak yazıyoruz

  • 0.666 ...
  • 0.666
  • 0.667

Bu temsillere baktığımızda, sadece ilk temsil matematiksel olarak kesire eşit olsa da, her birini 2/3 kesriyle ilişkilendirme eğilimindeyiz. İkinci ve üçüncü gösterimler / yaklaşımlar 0.001 sıralamasında bir hataya sahiptir, bu aslında 9.2 ve 9.1999999999999993 arasındaki hatadan çok daha kötüdür. Aslında, ikinci temsil doğru şekilde yuvarlanmamıştır! Bununla birlikte, 2/66 sayısının yaklaşık bir değeri olarak 0.666 ile ilgili bir sorunumuz yok, bu nedenle çoğu programda 9.2'nin yaklaşık olarak nasıl yaklaşıldığı konusunda bir sorun yaşamamalıyız .(Evet, bazı programlarda önemlidir.)

Taban sayısı

İşte burada sayı tabanları önemsizdir. 3. tabandaki 2/3'ü temsil etmeye çalışsaydık, o zaman

(2/3) 10 = 0.2 3

Başka bir deyişle, tabanları değiştirerek aynı sayı için kesin, sonlu bir temsile sahibiz! Take-away, herhangi bir sayıyı herhangi bir tabana dönüştürebilmenize rağmen, tüm rasyonel sayıların bazı bazlarda kesin sonlu temsillere sahip olmasına rağmen diğerlerinde değil .

Bu noktayı eve götürmek için 1/2'ye bakalım. Bu mükemmel derecede basit sayı, taban 10 ve 2'de kesin bir temsile sahip olsa da, taban 3'te tekrar eden bir temsili gerektirmesi sizi şaşırtabilir.

(1/2) 10 = 0,5 10 = 0,1 2 = 0,1111 ... 3

Kayan nokta sayıları neden yanlış?

Çünkü çoğu kez, temel 2'de (rakam tekrarlama) sonlu olarak temsil edilemeyen rasyonları tahmin ederler ve genel olarak, herhangi bir bazda sonlu birçok basamakta gösterilemeyen gerçek (muhtemelen irrasyonel) sayılara yaklaşırlar .


3
Başka bir deyişle, base-31/3 tıpkı base-10 için mükemmel olduğu için mükemmel olurdu 1/10. Baz-2'de
mhlester

2
@mhlester Evet. Ve genel olarak, baz-N , paydası Nveya bunun bir katı olan herhangi bir fraksiyon için mükemmeldir .
Nicu Stiurca

2
Ve bu, bazı sayısal alet kutularının "neyin neye bölündüğünü" takip etmesinin ve süreçte tüm rasyonel sayılar için "sonsuz doğruluğu" muhafaza etmesinin bir nedenidir. Tıpkı fizikçiler πvb.
Floris

3
@Floris Ayrıca, sadece temel aritmetik gerçekleştiren (yani, girdinin rasyonalitesini koruyan), girdinin rasyonel olup olmadığını belirlemek, matematiksel normal kayan nokta aritmetiği kullanarak gerçekleştirmek, daha sonra bir rasyonel tahmin tahmin Yuvarlama hatalarını düzeltmek için sonda yaklaşık. Özellikle Matlab'ın azaltılmış sıralı kademeli form algoritması bunu yapar ve sayısal istikrara muazzam bir şekilde yardımcı olur.
Nicu Stiurca

@SchighSchagh - ilginç, bunu bilmiyordum. Sayısal istikrarın bu çift çifte kesinlik günlerinde yeterince öğretilmeyen bir şey olduğunu biliyorum. Bu, birçok kişinin birçok güzel algoritmanın zarafeti hakkında öğrenmeyi kaçırdığı anlamına gelir. Kendi hatalarını hesaplayan ve düzelten algoritmaları çok seviyorum.
Floris

13

Diğer tüm cevaplar iyi olsa da, eksik olan bir şey var:

O (örn π, irrasyonel sayıları temsil etmek imkansızdır sqrt(2), log(3)doğrusu, vs.)!

Ve aslında bu yüzden irrasyonel olarak adlandırılırlar. Bunlardan birini tutmak için dünyada hiçbir miktarda bit depolama yeterli olmaz. Sadece sembolik aritmetik hassasiyetlerini koruyabilir.

Eğer matematik ihtiyaçlarınızı rasyonel sayılarla sınırlarsanız, sadece kesinlik sorunu yönetilebilir hale gelir. Sen (muhtemelen çok büyük) tamsayılar bir çift saklamak gerekir ave bkesir temsil ettiği sayıyı tutmak için a/b. Tüm aritmetik işlemlerinizin tıpkı lise matematiklerinde olduğu gibi kesirler üzerinde yapılması gerekirdi (örneğin a/b * c/d = ac/bd).

Ama tabii ki size yine sorun aynı tür içine aday olacağını pi, sqrt, log, sin, vb katılmaktadırlar.

TL; DR

Donanım hızlandırmalı aritmetik için yalnızca sınırlı sayıda rasyonel sayı temsil edilebilir. Temsil edilemeyen her sayı yaklaşıktır. Bazı sayılar (yani irrasyonel) sistem ne olursa olsun asla temsil edilemez.


4
İlginçtir, irrasyonel bazlar vardır. Örneğin , Phinary .
Veedrac

5
irrasyonel sayılar (yalnızca) tabanlarında temsil edilebilir. Örneğin, pi pi bazında
10'dur

4
Nokta geçerli kalır: Sistem ne olursa olsun bazı sayılar asla temsil edilemez. Üssünüzü değiştirerek hiçbir şey kazanmazsınız, çünkü o zaman diğer bazı sayılar artık temsil edilemez.
LumpN

4

Sonsuz sayıda gerçek sayı vardır (onları numaralandıramayacağınız kadar çok) ve sonsuz sayıda rasyonel sayı vardır (bunları numaralandırmak mümkündür).

Kayan nokta gösterimi sonludur (bilgisayardaki herhangi bir şey gibi), bu yüzden kaçınılmaz olarak çok sayıda sayıyı temsil etmek imkansızdır. Özellikle, 64 bit sadece 18,446,744,073,709,551,616 farklı değeri ayırt etmenize izin verir (bu sonsuzluğa kıyasla hiçbir şey değildir). Standart sözleşmeyle 9.2 bunlardan biri değildir. Olabilecekler m ve e bazı tamsayıları için m.2 ^ e biçimindedir.


Örneğin, 9.2'nin tam bir temsilini sağlayacağı 10 tabanlı farklı bir numaralandırma sistemi bulabilirsiniz. Ancak 1/3 diyelim diğer sayıları temsil etmek hala imkansız.


Ayrıca çift kesinlikli kayar nokta sayılarının son derece hassas olduğunu unutmayın . Çok geniş bir aralıktaki herhangi bir sayıyı 15 tam basamağa kadar temsil edebilirler. Günlük yaşam hesaplamaları için 4 veya 5 basamak yeterlidir. Hayatınızın her milisaniyesini saymak istemiyorsanız, bu 15'e asla gerçekten ihtiyacınız olmayacak.


1

İkili kayan noktada neden 9.2'yi temsil edemeyiz?

Kayan nokta sayıları (hafifçe basitleştirilir), sınırlı sayıda basamak ve hareketli bir yarıçap noktasına sahip konumsal bir numaralandırma sistemidir.

Bir pay, yalnızca paydadaki asal faktörler (fraksiyon en düşük terimlerle ifade edildiğinde) tabanın faktörleri ise, konumsal bir numaralandırma sisteminde sonlu sayıda basamak kullanılarak tam olarak ifade edilebilir.

10'un ana faktörleri 5 ve 2'dir, bu nedenle baz 10'da a / ( 2b 5 c ) formunun herhangi bir kısmını temsil edebiliriz .

Öte yandan 2'nin tek asal faktörü 2'dir, bu nedenle baz 2'de sadece a / (2 b ) formunun kısımlarını temsil edebiliriz

Bilgisayarlar neden bu temsili kullanıyor?

Çünkü çalışmak için basit bir format ve çoğu amaç için yeterince doğru. Temelde aynı neden bilim adamları "bilimsel gösterim" kullanmak ve sonuçlarını her adımda makul sayıda basamak yuvarlayın.

Kesirli bir biçim tanımlamak kesinlikle mümkün olacaktır (örneğin) 32 bitlik bir pay ve 32 bitli bir payda. IEEE çift kesinlikli kayar noktanın yapamayacağı sayıları temsil edebilecektir, fakat aynı şekilde, bu tür bir sabit boyutlu kesir biçiminde temsil edilemeyen çift kesinlikli kayar nokta ile temsil edilebilen birçok sayı olacaktır.

Ancak büyük sorun, böyle bir formatın hesaplamalar yapmak için bir acı olmasıdır. İki nedenden dolayı.

  1. Her sayının tam olarak bir temsilini almak istiyorsanız, her hesaplamadan sonra kesri en düşük terimlere indirmeniz gerekir. Bu, her işlem için temelde en büyük ortak bölen hesaplamasını yapmanız gerektiği anlamına gelir.
  2. Hesaplamanızdan sonra, pay veya payda nedeniyle temsil edilemeyen bir sonuç alırsanız, temsil edilebilir en yakın sonucu bulmanız gerekir. Bu önemsiz değil.

Bazı Diller kesir türleri sunar, ancak genellikle arbiter kesinlik ile birlikte yaparlar, bu kesirlere yaklaşmaktan endişe etmeyi önler, ancak bir sayı payda büyüklüğünde çok sayıda hesaplama adımından geçtiğinde kendi problemini yaratır ve dolayısıyla kesir için gereken depolama alanı patlayabilir.

Bazı diller de ondalık kayan nokta türleri sunar, bunlar çoğunlukla bilgisayarın sonuçları akılda tutulan önceden var olan yuvarlama kurallarıyla eşleşmesinin önemsiz olduğu senaryolarda kullanılır (başlıca finansal hesaplamalar). Bunların çalışması ikili kayan noktadan biraz daha zordur, ancak en büyük sorun çoğu bilgisayarın kendileri için donanım desteği sunmamasıdır.


-4

Bunu dene

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

" decimalValue" dönüştürülecek değerinizdir.

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.