Float Exposed
(float.exposed)- Bu yazı, kayan noktalı (float) değerlerin bellekte nasıl saklandığını ve temsil edildiğini açıklar
- Değerlerin onaltılık ve ondalık biçimleri ile gerçek sayısal değere nasıl dönüştürüldüğüne odaklanır
- İşaret(Sign), üs(Exponent), anlamlı kısım(Significand) alanlarının tanımı ve her birinin rolü açıklanır
- Belirli bir float değerinin tam olarak hangi ikili ve ondalık değeri temsil ettiğini yorumlama örnekleri içerir
- Temsil edilebilir değerler arasındaki farkın (Delta) hesaplanmasına da değinilir
Kayan noktalı değerlerin saklama yapısının analizi
half,bfloat,float,doublegibi çeşitli kayan noktalı biçimler vardır- Her değer, bellekteki saklanan değer olarak Raw Hexadecimal Integer Value (ham onaltılık tamsayı değeri) ve Raw Decimal Integer Value (ham ondalık tamsayı değeri) şeklinde incelenebilir
- Onaltılık veri, Hexadecimal Form (
%a) üzerinden gerçek kayan noktalı gösterimle ilişkilendirilir - Her değerin konumu, Significand–Exponent Range (anlamlı kısım–üs aralığındaki konum) olarak gösterilir
İkili ve ondalık değerleri yorumlama yöntemi
- Kayan noktalı sayılar Base-2 (ikilik değerlendirme ifadesi) ile şu şekilde gösterilebilir:
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
→ ikili ifade üzerinden sayısal değerlendirme yapılır
- (−12)02×102(100010012 − 011111112)×1.011111110010100000000002
- Base-10 (ondalık değerlendirme ifadesi) tarafında biçim şu şekildedir:
- 1×210×1.4967041015625
→ 2'nin 10. kuvveti ile kesirli kısmın çarpımı olarak ifade edilir
- 1×210×1.4967041015625
- Dönüştürme sırasında elde edilen tam ondalık değer de gösterilir:
- 1.532625×103 gibi bir ifade ile sunulur
Bitişik değerlerle mesafenin (Delta) hesaplanması
- Temsil edilebilir değerler arasındaki Delta (aralık) önemli bir anlam taşır
- Sonraki (Next) veya önceki (Previous) temsil edilebilir değere olan mesafe (Delta to Next/Previous Representable Value) ayrı ayrı verilir
- Örnek: ±1.220703125×10-4
- Bu aralık, kayan noktalı değerin anlamlı basamak sayısı/hassasiyeti ile ilişkilidir
Özet
- Kayan noktanın bellek temsili ile ikili ve ondalık dönüşüm mantığı
- sign, exponent, significand yapısının açıklaması
- Temsil aralığı ve bitişik değerlerle olan aralık bilgisi de birlikte düzenlenir
1 yorum
Hacker News görüşleri
Bu konu hakkında en iyi açıklama bence şu: https://fabiensanglard.net/floating_point_visually_explained/ Hacker News kullanmaya başladığımda bu yazıyla karşılaşmıştım ve bu tür içeriklerin platformda kalmaya devam etmesini istememin nedenlerinden biri de buydu: https://news.ycombinator.com/item?id=29368529
Bana biraz fazla matematik ağırlıklı gelmiş olabilir ama o açıklama o kadar da kolay değildi Kayan nokta için gerçekten basit bir açıklama istenirse: ölçekten bağımsız olarak yaklaşık aynı sayıda bitlik hassasiyet sağlar Yani 1'den çok küçük bir sayı da olsa, 1 civarında da olsa, çok büyük bir sayı da olsa, en anlamlı bitlerde neredeyse aynı doğruluk düzeyini beklersiniz Asıl temel özellik bu ama bunu içselleştirmek kolay değil
Son zamanlarda TM araştırma ekibinin yazdığı blogla bağlamı çok iyi örtüşüyor https://news.ycombinator.com/item?id=45200925
Bunu bu kadar iyi açıklanmış halde daha önce görmemiştim, paylaştığın için teşekkürler
Uzun süre kafa yorduğum sorunlardan biri, bir
floatdeğerini en kısa ama yine de açık bir ondalık string olarak nasıl ifade edeceğimdi Örneğin tek duyarlıklıfloatkullanırken, birfloat'ı benzersiz biçimde tanımlamak için en fazla 9 basamak ondalık hassasiyet gerekir Bu yüzden%.9ggibi birprintfkalıbı kullanmanız gerekir Ama bu durumda0.1,0.100000001gibi çirkin bir değer olarak yazdırılır O yüzden genelde 6 basamağa yuvarlayarak gösteririz;%.6gkullanıldığında 6 basamağa kadar girilmiş ondalık değerler, saklanan değerle aynı şekilde geri yazdırılabilir Ancak hesaplama sonucu elde edilen değerlerde round-trip artık güvenli olmaz Özelliklefloatdeğerlerini tam olarak karşılaştırmak gerektiğinde bu önemli olur (örneğin verinin değişip değişmediğini kontrol ederken) Aklıma gelen fikir şuydu: önce 6 basamakla yazdırmak, parse edildiğinde aynı ikili değer çıkarsa onu kullanmak, çıkmazsa 7, 8, 9 basamağa kadar deneyip en kısa ondalık gösterimi bulmak Algoritmam şöyleydiprintf/scanftekrarına girmeden daha verimli biçimde en kısa gösterimi bulmanın bir yolu olup olmadığını merak ediyorumBu problem gerçekten önemli Bunu, belirli bir
floatiçin "kanonik" bir string üretme problemi olarak görebilirsiniz (en yakın gösterim olması şartıyla) Bu yüzden Dragon4, Grisu3, Ryu, Dragonbox gibi çeşitli verimli algoritmalar var Google'ındouble-conversionkütüphanesi de ilk ikisini uyguluyorprintf/scanfdöngüsü olmadan yapmanın daha iyi bir yolu varprintf("%f", ...)tek başına bunun için yetmezfloattan string'e dönüşümde kullanılan gerçek algoritma epey karmaşıktır Son dönemdeki iyi algoritmalardan biri https://github.com/ulfjack/ryu Bundan da verimli yeni bir yöntem çıktığını biliyorum ama adını hatırlamıyorumOlumsuz yorumlara fazla takılmayın; en iyi yöntem olmasa bile (hata yoksa) çoğu zaman gayet yeterli çalışır Benim de benzer bir deneyimim olmuştu: bir keresinde Euler dönüşü (5°, 5°, 0) sonrası aynı vektör olacak bir vektör bulmak istiyordum, bu yüzden rastgele vektörleri hafifçe oynatıp referans vektöre yaklaşıp yaklaşmadıklarına baktım Milyonlarca kez döngü çalıştırdım ve Python'da birkaç saniye içinde sonuç aldım Kütüphane düzeyinde verimsiz olurdu ama benim kullanım amacım için son derece tatmin ediciydi
std::numeric_limits<float>::max_digits10'a bakmak faydalı olabilir https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10.htmlAnlamsız, ayrıca asla
sscanf()kullanmamalısınız İşaretsiz tamsayıya çevirip serileştirip geri yüklerseniz bilgi kaybı olmadan tersinir olurDaha kısa bir gösterim gerekiyorsa, tam geri dönüşü mümkün kılan bir sezgisel yöntem kullanın; yeter ki orijinal hassasiyeti garanti etsin (ör. idempotans)
FP ile ilgili en sevdiğim ipuçlarından biri,
floatkarşılaştırmasının neredeyse tamsayı karşılaştırması gibi kullanılabilmesia > biçin,avebyi işaretli tamsayı gibi yorumlayıp doğrudan karşılaştırabilirsiniz Bu yöntem (neredeyse) işe yarar Yani bir sonraki daha büyükfloatdeğeri, bit desenini tamsayıya çevirip 1 eklediğiniz değerdir Örneğin0.0floatdeğeriyle başlayıp tamsayı toplamasıyla 1 eklerseniz, bu bir sonrakifloatdeğeridir (denormal, en küçük pozitif değer)nextafterda bu ilkeye dayanarak uygulanırfloatdeğerlerinin sıralamasının tamsayı karşılaştırma sırasıyla aynı olduğunu bilince her şey çok daha doğal gelir Tabii istisnalar var:NaN, sonsuzluk, negatif sıfır vb. farklıdır Bazı açılardan kullanışlı ama her şey için geçerli değilBu söylenen teknik olarak tam doğru değil Pozitifler için ya da pozitif-negatif karşılaştırmalarında doğru, ama negatiflerin kendi aralarındaki karşılaştırmada farklı Standart kayan nokta (
float) gösterimi sign-magnitude'dur, modern işaretli tamsayılar ise iki'nin tümleyeni kullanır Negatiflerde büyüklük karşılaştırmasının yönü ikisi arasında tersine dönerfloatdeğeriniintgibi 1 artırırsanız, genelde aynı işaret içinde "büyüklüğü" daha yüksek olan değere gidersiniz Yani pozitifler yukarı çıkar, negatifler ise daha küçük negatif sayılara doğru iner Tamsayılarda ise her zaman yukarı çıkarsınız ya da taşma olur Daha doğru ifade etmek gerekirse, bu sign-magnitude tamsayı karşılaştırmasına benzer Elbette bahsedilencaveatler yine geçerliBilgi olsun diye, Rust standart kütüphanesinde
NaNdahil karşılaştırılabilir total-order kayan nokta sıralama algoritması şöyle (IEEE 751 önerisi)Algoritmanın tamamına bakın
Bu konuyla, OMSCS oyun AI dersimde oyun nesnelerinin konumlarını kayan noktayla ifade ederken dikkat edilmesi gerekenleri anlatan bir örnek üzerinden karşılaşmıştım Çünkü orijinden ya da referans noktasından uzaklaştıkça,
floatdaha büyük değerleri saklamak zorunda kaldığı için hassasiyet azalır ve bu tehlikelidirBunun Minecraft efsanesindeki Far Lands olarak kültürel bir yere sahip olması ilginç Yani dünya orijininden uzaklaştıkça arazi üretimi ya da fizik yavaş yavaş bozulmaya başlıyor, çok daha uzağa gidince de tamamen dağılıyor Biraz okült bir havası da var; sanki gerçekliğin kuralları yavaş yavaş çözülüyormuş gibi Ve bütün bunların sebebi
floathassasiyet sınırıfloatile 0 ile 1 arasındaki birçok sayıyı toplarken, bunları tek tek sırayla toplamakla ikişer ikişer toplayıp sonra sonuçları birleştirmek arasında büyük fark vardır; ikincisi çok daha doğrudur Bu, birikenfloathatasının ne kadar ciddi olabileceğine iyi bir örnek Gerçekten de bu tür kayan nokta hatalarının göz ardı edilmesi sorunlara yol açtı Donald Knuth'un "The Art of Computer Programming" kitabı,a + (b + c) ≠ (a + b) + cgibi kayan noktanın temel gerçeklerini anlatır Gerçek dünyada da bunun problem çıkardığı örnekler oldu; Patriot füze sistemi zamanı kayan nokta olarak biriktirdiği için hata giderek büyümüş, hedeften tamamen sapmış ve yeniden başlatılması gerekmişti Her 24 saatte bir yeniden başlatılması gerekiyordu, sonunda sistem yazılımı düzeltildi Kayan nokta hatası yüzünden büyük yapıların çökmesi gibi olaylar da yaşandı (kalınlık değeri fazla ince hesaplandığı için)Önce sınır koşullarını tanımlayıp ne kadar hassasiyet gerektiğine dair bir ölçüt belirlemek gerekir Böylece minimum ve maksimum mesafeler de önceden hesaplanabilir Dünya fazla büyürse sektörlere ayırmak ya da global/lokal koordinatları ayrı yönetmek gerekir (ör. No Man's Sky) Oyun sonuçta bir sahne illüzyonudur Double-precision çoğu durumda fazlasıyla yeterlidir Esas önemli olan, küçük ve büyük değerleri birlikte toplamamayı unutmamaktır
Kerbal Space Program, yalnızca 32 bit
floatile bir güneş sistemini modellemek için oldukça akıllı mühendislik çözümleri kullandı Bununla ilgili çok sayıda makale ve video var; şiddetle tavsiye ederimBu görselleştirme eğlenceli ve zamanında ağ aralıklarını anlamaya yardımcı olmak için yaptığım CIDR range calculator ile görsel olarak benzerlik taşıdığı için ilgimi çekti Bu tür görselleştirmeler çok faydalı
Eskiden
floatgösterimini kurcalarken https://www.h-schmidt.net/FloatConverter/IEEE754.html kullanırdım Bu sitenin avantajı dönüşüm hatasını da göstermesi ama double precision desteklemiyorfloata zaten aşina olanlara bariz gelebilir ama ilk kez öğrenen biri için ek açıklama gerektiren bir noktaBu yorum dizisinde henüz paylaşılmadı sanırım ama
floatkonusunda en sevdiğim site https://0.30000000000000004.com/32 bit
floatiçin "en ilginç tamsayı" ödülü 16777217'ye gider (64 bit için 9007199254740992) Testlerde böyle edge case'leri bilmek eğlenceli olur64 bit
floattarafında 9007199254740991, JavaScript'teNumber.MAX_SAFE_INTEGERBu değer tek sayıdır ve bir sonraki değer olan 9007199254740992 de kendi başına güvenlidir, ama 9007199254740993 gibi açıkça güvenli olmayan değerler yuvarlandığı için ayırt edilemez hale gelir64 bit
floatta tam olarak ±9,007,199,254,740,993.0 :-) Bilgi olsun diye, bu tür değerlerfloatın "tam olarak" temsil edebildiği en büyük tamsayının hemen sonrasındaki değeri ifade eder Örneğin 32 bitfloatta ±16,777,216.0'dan sonra temsil edilebilen bir sonraki değer ±16,777,218.0'dır ±16,777,217.0 temsil edilemez, bu yüzden genelde sıfıra doğru ya da benzeri bir yöne yuvarlanır Bu hassasiyet sınırları ve yuvarlama meseleleri sık sık gözden kaçarIEEE754'ün var olmasına seviniyorum ama IEEE754 kusursuz değil; donanım desteği hesaba katılmadığında posit gibi değer biçimlerinin daha iyi olduğunu düşünüyorum BigNum rational (rasyonel sayılar) ise ikisinden de üstün ama en yavaş olanı
Son dönemde GPU'larda ortaya çıkan çeşitli
fp8formatlarını da desteklese gerçekten harika olurdu