Ruby'de tam sayılar Integer, ondalıklı sayılar Float, kesirler Rational, karmaşık sayılar Complex sınıfıyla temsil edilir. İki tam sayıyı böldüğünüzde sonuç da tam sayıdır ve aşağı yuvarlanır (7 / 2 sonucu 3); ondalıklı sonuç için fdiv veya işlenenlerden birini Float yapmanız gerekir. Float ikili kayan nokta kullandığı için 0.1 + 0.2 tam olarak 0.3 etmez; kesin hesap gereken yerlerde Rational veya BigDecimal tercih edilir.

Bu yazıda tam sayı bölmesi ve kalan, yuvarlama modları, kayan nokta hassasiyeti, kesirli ve karmaşık sayılar, metinden sayıya dönüşüm ve Integer#digits gibi yardımcıları Ruby 4.0 belgelerine dayanarak, çalıştırılmış örneklerle ele alıyoruz. Sayı literallerinin yazım biçimleri (1_000, 0x1F, 3r, 2i) için Ruby veri tipleri ve literaller, operatör önceliği için Ruby operatörler yazısına, genel harita için Ruby öğrenme rehberi sayfasına bakabilirsiniz.

Tam sayı bölmesi, kalan ve fdiv

Tam sayı bölmesi, kalan ve fdiv

Belgelere göre Integer#/ tam sayı bir bölenle çağrıldığında sonucu tam sayıya indirger; negatif sayılarda bu indirgeme eksi sonsuza doğrudur, yani -7 / 2 sonucu -4 olur, -3 değil. % (takma adı modulo) bu bölmeyle tutarlı kalanı, remainder ise bölünenin işaretini taşıyan kalanı verir. divmod bölüm ve kalanı birlikte, fdiv her zaman Float sonucu, ceildiv ise yukarı yuvarlanmış bölümü döndürür.

ruby
p 7 / 2
p(-7 / 2)
p 7 % 3
p(-7 % 3)
p(-7.remainder(3))
p 7.divmod(2)
p 7.fdiv(2)
p 7 / 2.0
p 7.ceildiv(2)
# çıktı: 3
# çıktı: -4
# çıktı: 1
# çıktı: 2
# çıktı: -1
# çıktı: [3, 1]
# çıktı: 3.5
# çıktı: 3.5
# çıktı: 4

Sayfalama gibi işlerde ceildiv kullanışlıdır: 45 kayıt sayfa başına 10 kayıtla 45.ceildiv(10), yani 5 sayfa eder. Tam sayıyı sıfıra bölmek ZeroDivisionError fırlatır; kayan noktada ise sonuç sonsuz olur.

ruby
begin
  10 / 0
rescue ZeroDivisionError => e
  puts e.message
end
p 10.0 / 0
p(-10.0 / 0)
p (0.0 / 0).nan?
# çıktı: divided by 0
# çıktı: Infinity
# çıktı: -Infinity
# çıktı: true

Büyük tam sayılar ve yardımcı metotlar

Integer çok büyük değerleri de tutabilir; aşağıdaki 2 ** 100 örneği ek bir tür veya kütüphane gerektirmeden hesaplanıyor. Rakamları okunur kılmak için literal içinde alt çizgi kullanabilirsiniz. Belgelere göre digits sayının basamaklarını en düşük basamaktan başlayarak bir dizi olarak verir ve negatif sayılarda hata fırlatır. to_s(taban) başka tabanda yazar, Integer.sqrt tam sayı karekökünü, pow(us, mod) modüler üs almayı yapar.

ruby
p 2 ** 100
p 1_000_000 * 1_000_000
p 12345.digits
p 12345.digits.sum
p 255.to_s(2)
p 255.to_s(16)
p Integer.sqrt(50)
p [12.gcd(18), 12.lcm(18)]
p 3.pow(100, 7)
# çıktı: 1267650600228229401496703205376
# çıktı: 1000000000000
# çıktı: [5, 4, 3, 2, 1]
# çıktı: 15
# çıktı: "11111111"
# çıktı: "ff"
# çıktı: 7
# çıktı: [6, 36]
# çıktı: 4

Float ve 0.1 + 0.2 sorunu

Float nesneleri makinenin çift duyarlıklı kayan nokta biçimini kullanır. Belgelerin de vurguladığı gibi bazı ondalık değerler bu biçimde tam olarak gösterilemez ve basit bir toplama bile küçük bir sapma üretebilir. Bu Ruby'ye özgü bir hata değil, ikili kayan nokta aritmetiğinin doğasıdır.

ruby
p 0.1 + 0.2
p 0.1 + 0.2 == 0.3
p((0.1 + 0.2 - 0.3).abs < Float::EPSILON)
p (0.3 / 0.1).to_i
p 10.times.inject(0) { |t| t + 0.1 }
# çıktı: 0.30000000000000004
# çıktı: false
# çıktı: true
# çıktı: 2
# çıktı: 0.9999999999999999

Dördüncü satırdaki 2 sonucu belgelerdeki örnekle aynıdır: 0.3 / 0.1 işlemi 3'ün çok az altında bir değer verir ve to_i ondalık kısmı kestiği için 2 kalır. Kayan noktalı değerleri == ile karşılaştırmak yerine aralarındaki farkın küçük bir eşikten az olup olmadığına bakın. Float::EPSILON, 1 ile ondan büyük en küçük kayan nokta sayısı arasındaki farktır; büyük değerlerle çalışırken eşiği değerin büyüklüğüne göre seçmeniz gerekir.

Yuvarlama: round, floor, ceil ve truncate

Yuvarlama: round, floor, ceil ve truncate: half: :up (varsayılan), half: :down, half: :even

round en yakın değere yuvarlar; argüman olarak ondalık basamak sayısı alır. Negatif bir sayı verirseniz onlar, yüzler basamağına yuvarlar. Belgelere göre tam ortada kalan değerler için half: anahtar kelimesi üç mod sunar: :up (varsayılan, sıfırdan uzağa), :down (sıfıra doğru) ve :even (son basamağı çift olana, bankacı yuvarlaması). floor aşağı, ceil yukarı yuvarlar, truncate ise ondalık kısmı keser.

ruby
p 2.5.round
p 2.5.round(half: :even)
p 2.5.round(half: :down)
p(-2.5.round)
p 3.14159.round(2)
p 1234.5678.floor(2)
p 1234.5678.ceil(1)
p(-7.9.truncate)
p 1234.round(-2)
p 25.round(-1, half: :even)
# çıktı: 3
# çıktı: 2
# çıktı: 2
# çıktı: -3
# çıktı: 3.14
# çıktı: 1234.56
# çıktı: 1234.6
# çıktı: -7
# çıktı: 1200
# çıktı: 20
Mod2.53.5-2.5
half: :up (varsayılan)34-3
half: :down23-2
half: :even24-2

Rational: kesin kesirli hesap

Rational bir sayıyı pay ve payda olarak saklar, bu yüzden yuvarlama hatası üretmez. 3r gibi literallerle, Rational(1, 3) ile ya da metinden Rational("0.1") ile oluşturabilirsiniz. Belgeler önemli bir ayrıntıya dikkat çeker: 0.3.to_r kayan nokta değerinin gerçek ikili karşılığını verir ve 3/10 değildir; "0.3".to_r ise tam olarak 3/10'dur. rationalize kayan nokta değerine yakın en basit kesri bulur.

ruby
p 1/3r
p Rational(1, 3) + Rational(1, 6)
p Rational("0.1") + Rational("0.2") == Rational("0.3")
p 10.times.inject(0) { |t| t + Rational("0.1") }
p 0.3.to_r
p "0.3".to_r
p 0.3.rationalize
p Rational(7, 2).to_f
p Rational(7, 2).round
p Rational(10) / 3.0
# çıktı: (1/3)
# çıktı: (1/2)
# çıktı: true
# çıktı: (1/1)
# çıktı: (5404319552844595/18014398509481984)
# çıktı: (3/10)
# çıktı: (3/10)
# çıktı: 3.5
# çıktı: 4
# çıktı: 3.3333333333333335

Son satır belgelerdeki uyarıyı gösterir: bir ifadede kesin olmayan bir bileşen (burada 3.0) varsa sonuç da kesin olmaz ve Float döner. Kesinliği korumak için tüm işlenenleri Integer veya Rational tutun.

Complex: karmaşık sayılar

Complex gerçel ve sanal kısımdan oluşan sayıları temsil eder; 2 + 3i literaliyle veya Complex(3, 4) ile oluşturulur. abs mutlak değeri, real ve imaginary parçaları verir. Math.sqrt negatif bir sayıyla çağrıldığında karmaşık sonuç üretmez, Math::DomainError fırlatır.

ruby
z = Complex(3, 4)
p z
p z.abs
p z * Complex(1, -1)
p 2 + 3i
p 1i ** 2
begin
  Math.sqrt(-1)
rescue Math::DomainError => e
  puts e.message
end
# çıktı: (3+4i)
# çıktı: 5.0
# çıktı: (7+1i)
# çıktı: (2+3i)
# çıktı: (-1+0i)
# çıktı: Numerical argument is out of domain - sqrt

Türler karışınca ne olur?

Farklı sayı türlerini bir işlemde birleştirdiğinizde Ruby sonucu daha "geniş" türe yükseltir: Integer ile Float toplamı Float, Integer ile Rational toplamı Rational olur; ama Rational ile Float birleşince kesinlik kaybolur ve sonuç Float olur. Tüm bu sınıflar Numeric sınıfından türer.

ruby
p (1 + 2.0).class
p (1 + 2r).class
p (2r + 0.5).class
p (1 + 2i).class
p [Integer, Float, Rational, Complex].all? { it < Numeric }
# çıktı: Float
# çıktı: Rational
# çıktı: Float
# çıktı: Complex
# çıktı: true

Metinden sayıya: Integer() ve to_i farkı

"42abc".to_i baştaki sayısal kısmı alır ve gerisini sessizce yok sayar; hiç sayı yoksa 0 döndürür. Kernel#Integer ve Kernel#Float ise katıdır: metnin tamamı geçerli bir sayı değilse ArgumentError fırlatır. Belgelere göre exception: false verildiğinde hata yerine nil dönerler, bu da kullanıcı girdisi doğrularken çok kullanışlıdır. Integer() ayrıca 0x, 0b ve baştaki 0 gibi taban öneklerini tanır; baştaki sıfır sekizlik taban anlamına geldiği için kullanıcı girdisinde tabanı açıkça vermek daha güvenlidir.

ruby
p "42abc".to_i
p "abc".to_i
begin
  Integer("42abc")
rescue ArgumentError => e
  puts e.message
end
p Integer("42abc", exception: false)
p Integer("0x1F")
p Integer("010")
p Integer("010", 10)
p "3,5".to_f
p Float("3,5", exception: false)
p "3,5".tr(",", ".").to_f
# çıktı: 42
# çıktı: 0
# çıktı: invalid value for Integer(): "42abc"
# çıktı: nil
# çıktı: 31
# çıktı: 8
# çıktı: 10
# çıktı: 3.0
# çıktı: nil
# çıktı: 3.5

Türkçede ondalık ayırıcı virgül olduğu için kullanıcıdan gelen "3,5" gibi değerler to_f ile sessizce 3.0 olur. Girdiyi önce noktaya çevirip ardından katı dönüşümle doğrulamak bu hatayı önler. Çıktıda Türkçe biçim istiyorsanız format ile ürettiğiniz metni düzenleyebilirsiniz:

ruby
tutar = 1234567.891
tam, kesir = format("%.2f", tutar).split(".")
tam = tam.reverse.scan(/\d{1,3}/).join(".").reverse
puts "#{tam},#{kesir} TL"
# çıktı: 1.234.567,89 TL

BigDecimal ve Ruby 4.0 notları

Ondalık tabanda kesin hesap için BigDecimal kullanılır. Ruby 3.4 NEWS dosyasına göre bigdecimal varsayılan gem olmaktan çıkıp bundled gem'e dönüştü; Ruby 4.0 NEWS dosyası ise bundled gem olarak bigdecimal 4.0.1 sürümünü listeler. Bundler ile çalışan bir projede kullanacaksanız gem'i Gemfile dosyanıza eklemeniz gerekir. Yerel Ruby belgelerinde BigDecimal için yalnızca JSON dönüşüm metotları yer alır; diğer metotların ayrıntısı gem'in kendi belgelerindedir.

ruby
require "bigdecimal"

a = BigDecimal("0.1")
b = BigDecimal("0.2")
p a + b == BigDecimal("0.3")
puts a + b
# çıktı: true
# çıktı: 0.3e0

Ruby 4.0 NEWS dosyası sayılar tarafında Math.log1p ve Math.expm1 metotlarının eklendiğini listeler. Belgelere göre ilki log(x + 1), ikincisi exp(x) - 1 değerini hesaplar. Çok küçük x değerlerinde doğrudan Math.log(1 + x) yazmak, 1 + x toplamı yuvarlandığı için hassasiyet kaybettirebilir; aşağıdaki karşılaştırmada fark açıkça görülüyor:

ruby
p Math.log1p(1e-10)
p Math.log(1 + 1e-10)
p Math.expm1(1e-10)
p Math.exp(1e-10) - 1
# çıktı: 9.999999999500001e-11
# çıktı: 1.000000082690371e-10
# çıktı: 1.00000000005e-10
# çıktı: 1.000000082740371e-10

Sık yapılan hatalar

Sık yapılan hatalar: Tam sayı bölmesini unutmak, Negatif bölmede kesme beklemek, Float ile == karşılaştırmak, 0.3.to_r ile kesin kesir beklemek
  • Tam sayı bölmesini unutmak: ortalama = toplam / adet iki tam sayıyla tam sayı döndürür. fdiv veya to_f kullanın.
  • Negatif bölmede kesme beklemek: -7 / 2 sonucu -4'tür; sıfıra doğru kesme istiyorsanız (-7).fdiv(2).truncate gibi açık bir yol seçin.
  • Float ile == karşılaştırmak: 0.1 + 0.2 == 0.3 yanlış döner; tolerans kullanın veya Rational ile çalışın.
  • 0.3.to_r ile kesin kesir beklemek: Kesin değer için metinden dönüştürün: "0.3".to_r veya Rational("0.3").
  • to_i ile girdi doğrulamak: "12abc".to_i 12 döndürür ve hatayı gizler; Integer(girdi, exception: false) kullanın.
  • Virgüllü ondalıkları doğrudan to_f ile okumak: "3,5".to_f 3.0 olur; önce ayırıcıyı dönüştürün.
  • digits sırasını ters okumak: digits en düşük basamakla başlar; soldan sağa basamaklar için digits.reverse kullanın.

Sık Sorulan Sorular

Ruby'de iki tam sayıyı bölünce neden ondalık sonuç gelmiyor?

Çünkü Integer#/ tam sayı bölen aldığında tam sayı döndürür. 7.fdiv(2), 7 / 2.0 veya kesin sonuç için 7 / 2r kullanabilirsiniz; sonuncusu (7/2) kesrini verir.

0.1 + 0.2 neden 0.30000000000000004 çıkıyor?

Float ikili kayan nokta biçimini kullanır ve 0.1 ile 0.2 bu biçimde tam olarak gösterilemez. Belgelerdeki örnek de aynı sonucu verir. Kesin ondalık hesap için Rational veya BigDecimal kullanın.

Bankacı yuvarlaması nasıl yapılır?

round(half: :even) ile. Örneğin 2.5.round(half: :even) 2, 3.5.round(half: :even) 4 döndürür. Aynı anahtar kelime Integer#round için de geçerlidir.

Bir sayının basamaklarını nasıl toplarım?

12345.digits.sum 15 döndürür. digits negatif sayılarda hata fırlattığı için gerekirse önce abs alın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Ruby öğrenme rehberi · Ruby veri tipleri ve literaller · Ruby operatörler · Ruby range · Ruby hata yönetimi · Ruby Enumerable · Python nedir