Menu

JavaScript Number ve BigInt: Ondalık Hassasiyet ve Büyük Sayılar

JavaScript'te Number tipi nasıl çalışır? Floating-point hassasiyet sorunları, MAX_SAFE_INTEGER ve büyük tam sayılar için BigInt'e ne zaman geçmeli?

JavaScript'te Tek Sayı Tipi (Neredeyse)

Çoğu dilde tam sayılar ve ondalıklı sayılar için ayrı tipler vardır. JavaScript ise tarihsel olarak tek bir tip sundu: Number. İster 42 yazın, ister 3.14 ya da -0.001 — hepsi aynı ilkel tipe karşılık geliyor: 64-bit IEEE 754 çift hassasiyetli ondalıklı sayı (double).

index.js
Output
Click Run to see the output here.

İşin güzel tarafı şu: int ve float arasında cast yapmaya gerek yok, 2^31'de overflow derdi yok. Ama Number'ın float tabanlı temsili bazı sürprizler getiriyor ve yeni başlayanlar bu sürprizlere sık sık takılıyor. Tam da Number'ın yetersiz kaldığı durumlar için 2020'de ikinci bir sayısal tip olan BigInt dile eklendi.

Floating-point sürprizi: 0.1 + 0.2 neden 0.3 değil?

Şunu bir çalıştır:

index.js
Output
Click Run to see the output here.

İlk satır 0.30000000000000004 yazdırır. İkincisi ise false döndürür. Bu, JavaScript'e özgü bir tuhaflık değil — Python, Java, C ve IEEE 754 kayan nokta standardını kullanan her dil aynı şekilde davranır.

Sebebi şu: 1/3'ün ondalık sistemde tam olarak yazılamaması gibi, 0.1 ve 0.2 de ikili sistemde tam olarak temsil edilemez. En yakın ikili yaklaşım saklanır ve küçük hatalar birikir. Zihinsel modeli şöyle kurun: ondalık içeren Number değerlerini, yazdığınız sayıya çok yakın yaklaşık değerler olarak düşünün.

Para işlemlerinde 19,99 TL'yi 19.99 olarak saklamayın. Onun yerine kuruş cinsinden tam sayı olarak — yani 1999 şeklinde — saklayın ve ekrana basarken biçimlendirin. Kayan nokta hatalarından kaçınmanın en iyi alışkanlığı budur.

Kayan Noktalı Sayıları Güvenli Karşılaştırma

Eşitlik kontrolü güvenilir olmadığı için, gerektiğinde belirli bir tolerans payıyla karşılaştırın:

index.js
Output
Click Run to see the output here.

Number.EPSILON, 1 ile ondan sonra temsil edilebilen sayı arasındaki en küçük farktır — yani 1 civarındaki değerler için makul bir varsayılan tolerans. Çok büyük veya çok küçük büyüklüklerde ise giriş değerleriyle orantılı ölçeklenen bir tolerans kullanmak isteyeceksin.

Güvenli tam sayı aralığı

Belli bir büyüklüğe kadar olan tam sayılar 64-bit float içinde tam olarak temsil edilir. Bu sınırı geçtiğinde hassasiyeti bit bit kaybetmeye başlarsın:

index.js
Output
Click Run to see the output here.

2^53 - 1, altındaki her tam sayının eksiksiz temsil edilebildiği son sayıdır. Bu eşiğin ötesinde, bazı tam sayılar Number tipinde var bile değildir — en yakın komşularına yuvarlanırlar. Veritabanından gelen 64-bit ID'leri düz JSON sayısı olarak parse ediyorsanız, sessiz sedasız veri bozulmasına davetiye çıkarmış olursunuz.

BigInt devreye giriyor

BigInt, keyfi hassasiyette tam sayılar için ayrı bir primitive tiptir. Oluşturmanın iki yolu var: ya tam sayı literal'inin sonuna n eklersiniz ya da BigInt(...) fonksiyonunu çağırırsınız:

index.js
Output
Click Run to see the output here.

BigInt'in bellekten başka bir üst sınırı yok. Şu durumlarda tam aradığınız şey:

  • 2^53 değerini aşan veritabanı ID'leri veya Twitter/X snowflake ID'leri.
  • Kriptografik hesaplamalar.
  • Hızdan çok kesin sonucun önemli olduğu tamsayı aritmetiği.

Ama gündelik sayaçlar, dizi indeksleri ya da parayı kuruş cinsinden tutmak için BigInt'e gerek yok — bu tip işlerde normal Number hem daha hızlı hem de dildeki neredeyse her API ile uyumlu çalışır.

BigInt ile aritmetik işlemler

Her iki operand da BigInt olduğu sürece alışılmış operatörlerin hepsi çalışır:

index.js
Output
Click Run to see the output here.

Bölme işlemi sıfıra doğru kırpılır — yani kesirli BigInt diye bir şey yok. Kesir gerekiyorsa yine Number dünyasına dönmen lazım (ya da decimal desteği veren bir kütüphane kullanman gerek).

Tipleri Karıştırma

İnsanların en çok takıldığı kural şu: aynı ifade içinde Number ve BigInt tiplerini bir arada kullanamazsın.

index.js
Output
Click Run to see the output here.

Karşılaştırma tek istisna — <, >, == operatörleri iki tür arasında otomatik dönüşüm yapar:

index.js
Output
Click Run to see the output here.

== onları eşit sayar, === saymaz. Zaten her yerde === kullanıyorsan (ki kullanmalısın), iki tip arasındaki sayısal karşılaştırmaları bir tasarım kokusu olarak gör — bir tarafı seç ve dönüştür.

İki Tip Arasında Dönüşüm

İki dönüşüm yolu var, her birinin kendi tuzağı:

index.js
Output
Click Run to see the output here.

Number → BigInt dönüşümü katıdır: ondalıklı değerler ve NaN hata fırlatır. BigInt → Number yönü ise esnek ama kayıplıdır — MAX_SAFE_INTEGER'ın üzerindeki her şey yuvarlanır. Sunucudan aldığın bir BigInt'i dönüştürüyorsan, buna gerçekten ihtiyacın olup olmadığını bir kez daha düşün.

Özel Number değerleri

Hazır konu açılmışken, Number tipinde yer alan ama matematiksel anlamda "sayı" sayılmayan üç değerden bahsedelim:

index.js
Output
Click Run to see the output here.

Infinity ve -Infinity değerleri, sıfıra böldüğünüzde ya da float aralığını taşırdığınızda karşınıza çıkar. NaN ("not a number" yani "sayı değil") ise bir aritmetik işlem anlamlı bir sonuç üretmediğinde ortaya çıkar.

NaN'in meşhur bir özelliği var: kendisine bile eşit değildir. Bu bir JS bug'ı değil, IEEE 754 standardının gereği. Kontrol etmek için Number.isNaN(x) kullanın. Eski global isNaN ise argümanını önce sayıya çevirdiği için yanlış sonuçlar verir — mesela isNaN("hello") true döner. Her zaman Number.isNaN versiyonunu tercih edin.

String'den Sayıya Dönüşüm

Kullanıcı girdileri ve JSON verilerindeki sayılar çoğu zaman string olarak gelir. Dönüştürmenin üç yolu var:

index.js
Output
Click Run to see the output here.

Number() oldukça katıdır — sayısal olmayan her şey için NaN döner, yalnız boş string ve sadece boşluk içeren string 0 verir. parseInt ve parseFloat ise toleranslıdır — okuyabildiği kadar okur, takıldığı yerde durur. Amacınıza hangisi uyuyorsa onu seçin ve sonucu kullanmadan önce NaN kontrolü yapın.

String'den BigInt elde etmek için BigInt("123") kullanın — bu yöntem katıdır ve geçersiz girdide hata fırlatır.

Hızlı Kural Kitabı

  • Sayaçlar, matematiksel işlemler, koordinatlar ve günlük sayıların çoğu için: Number kullanın.
  • Para için: tam sayı cinsinden kuruşa çevirip Number kullanın veya bir decimal kütüphanesine başvurun.
  • 2^53'ten büyük tam sayılar için (veritabanı ID'leri, kriptografi, kombinatorik): n son ekiyle BigInt kullanın.
  • Float'ları karşılaştırırken === yerine bir tolerans payı kullanın.
  • Geçersiz sonuçları global sürümlerle değil, Number.isNaN ve Number.isFinite ile kontrol edin.
  • Aynı ifadede Number ile BigInt'i karıştırmayın — dönüşümü açıkça yapın.

Sırada: null vs undefined

JavaScript'te "değer yok" demenin iki yolu vardır — null ve undefined — ve bunlar birbirinin yerine kullanılamaz. Sırada: her birinin ne anlama geldiği, aralarındaki fark ve hangisini ne zaman tercih etmeniz gerektiği.

Sıkça Sorulan Sorular

JavaScript'te neden 0.1 + 0.2 sonucu 0.3 etmiyor?

Çünkü JavaScript'teki Number tipi 64-bit IEEE 754 floating-point formatında tutuluyor ve 0.1 ile 0.2 sayıları ikilik tabanda tam olarak ifade edilemiyor. Sonuç 0.30000000000000004 çıkıyor. Bu bir JavaScript bug'ı değil — Python, Java ve aynı float formatını kullanan her dilde aynı sorun var. Para hesapları için sayıyı tam sayıya ölçekleyin (örneğin kuruşa çevirin) ya da decimal bir kütüphane kullanın.

BigInt nedir ve ne zaman kullanmalıyım?

BigInt, Number.MAX_SAFE_INTEGER (2^53 - 1) sınırının ötesindeki tam sayılar için ayrı bir sayısal primitive tip. Sonuna n ekleyerek (9007199254740993n gibi) ya da BigInt(value) çağrısıyla oluşturabilirsiniz. 64-bit veritabanı ID'leri, kriptografi veya hızdan çok hassasiyetin önemli olduğu her tam sayı hesabında kullanın.

JavaScript'te Number ve BigInt'i birlikte kullanabilir miyim?

Hayır. 1n + 1 ifadesi TypeError: Cannot mix BigInt and other types hatası fırlatır. Dönüşümü elle yapmanız gerekir: BigInt(n) veya Number(b). < ve == gibi karşılaştırma operatörleri iki tip arasında çalışır ama === tipler farklı olduğu için false döner.

Coddy ile kodlamayı öğren

BAŞLA