3 puan yazan GN⁺ 2025-09-13 | 1 yorum | WhatsApp'ta paylaş
  • 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, double gibi ç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
  • 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
  • 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
    Reklam

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

 
GN⁺ 2025-09-13
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 float değerini en kısa ama yine de açık bir ondalık string olarak nasıl ifade edeceğimdi Örneğin tek duyarlıklı float kullanırken, bir float'ı benzersiz biçimde tanımlamak için en fazla 9 basamak ondalık hassasiyet gerekir Bu yüzden %.9g gibi bir printf kalıbı kullanmanız gerekir Ama bu durumda 0.1, 0.100000001 gibi çirkin bir değer olarak yazdırılır O yüzden genelde 6 basamağa yuvarlayarak gösteririz; %.6g kullanı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 Özellikle float değ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 şöyleydi

    int out_length;
    char buffer[32];
    for (int prec = 6; prec<=9; prec++) {
      out_length = sprintf(buffer, "%.*g", prec, floatValue);
      if (prec == 9) {
        break;
      }
      float checked_number;
      sscanf(buffer, "%g", &checked_number);
      if (checked_number == floatValue) {
        break;
      }
    }
    

    printf/scanf tekrarına girmeden daha verimli biçimde en kısa gösterimi bulmanın bir yolu olup olmadığını merak ediyorum

    • Bu problem gerçekten önemli Bunu, belirli bir float iç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'ın double-conversion kütüphanesi de ilk ikisini uyguluyor

    • printf/scanf döngüsü olmadan yapmanın daha iyi bir yolu var printf("%f", ...) tek başına bunun için yetmez floattan 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ıyorum

    • Olumsuz 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.html

    • Anlamsız, ayrıca asla sscanf() kullanmamalısınız İşaretsiz tamsayıya çevirip serileştirip geri yüklerseniz bilgi kaybı olmadan tersinir olur

      double f = 0.0/0.0; // bazı derleyicilerde soft error bayrağı gerekebilir
      double g;
      char s[9];
      
      assert(sizeof double == sizeof uint64_t);
      
      snprintf(s, 9, "%0" PRIu64, *(uint64_t *)(&f));
      
      snscanf(s, 9, "%0" SCNu64, (uint64_t *)(&g));
      

      Daha 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, float karşılaştırmasının neredeyse tamsayı karşılaştırması gibi kullanılabilmesi a > b için, a ve byi 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ük float değeri, bit desenini tamsayıya çevirip 1 eklediğiniz değerdir Örneğin 0.0 float değeriyle başlayıp tamsayı toplamasıyla 1 eklerseniz, bu bir sonraki float değeridir (denormal, en küçük pozitif değer) nextafter da bu ilkeye dayanarak uygulanır float değ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ğil

    • Bu 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öner float değerini int gibi 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 bahsedilen caveatler yine geçerli

    • Bilgi olsun diye, Rust standart kütüphanesinde NaN dahil karşılaştırılabilir total-order kayan nokta sıralama algoritması şöyle (IEEE 751 önerisi)

      let mut left = self.to_bits() as i32;
      let mut right = other.to_bits() as i32;
      
      // Negatifse, işaret dışındaki tüm bitleri tersleyerek
      // iki'nin tümleyeni tamsayı karşılaştırmasına benzer bir düzen elde edilir
      
      left ^= (((left >> 31) as u32) >> 1) as i32;
      right ^= (((right >> 31) as u32) >> 1) as i32;
      
      left.cmp(&right)
      

      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, float daha büyük değerleri saklamak zorunda kaldığı için hassasiyet azalır ve bu tehlikelidir

    • Bunun 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 float hassasiyet sınırı

    • float ile 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, biriken float hatası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) + c gibi 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 float ile 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 ederim

  • Bu 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 float gö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 desteklemiyor

    • Ben de buna daha önce değinilmiş mi diye yorumlara göz gezdirdim; gerçekten harika bir web sayfası Ama OP'nin paylaştığı site, sayısal uzayın bölünme yapısını grafikle gerçekten çok sezgisel biçimde anlatıyor Dikey eksen logaritmik ölçekte, yatay eksen ise her satırda doğrusal ama log aralığına göre normalize edilmiş floata zaten aşina olanlara bariz gelebilir ama ilk kez öğrenen biri için ek açıklama gerektiren bir nokta
  • Bu yorum dizisinde henüz paylaşılmadı sanırım ama float konusunda en sevdiğim site https://0.30000000000000004.com/

  • 32 bit float için "en ilginç tamsayı" ödülü 16777217'ye gider (64 bit için 9007199254740992) Testlerde böyle edge case'leri bilmek eğlenceli olur

    • 64 bit float tarafında 9007199254740991, JavaScript'te Number.MAX_SAFE_INTEGER Bu 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 gelir

    • 64 bit floatta tam olarak ±9,007,199,254,740,993.0 :-) Bilgi olsun diye, bu tür değerler floatın "tam olarak" temsil edebildiği en büyük tamsayının hemen sonrasındaki değeri ifade eder Örneğin 32 bit floatta ±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çar

  • IEEE754'ü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ı

    • IEEE754, birçok gereksinimi karşılamak için yapılmış bir uzlaşma Alternatif biçimlerden bazıları belli alanlarda daha iyidir ama başka alanlarda daha kötüdür
  • Son dönemde GPU'larda ortaya çıkan çeşitli fp8 formatlarını da desteklese gerçekten harika olurdu