Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Thành thạo Số trong Ruby: Integer, Float & BigDecimal giải thích chi tiết

Ruby 2.4 gộp Fixnum và Bignum vào cùng một lớp (Integer), vì vậy tôi nghĩ đây là lúc tốt để xem lại các kiểu số khác nhau trong Ruby!

Đó là những gì chúng ta sẽ nói trong bài viết này 🙂

Tổng quan các kiểu số

Hãy bắt đầu bằng cách nhìn vào hệ thống phân cấp lớp của tất cả các lớp liên quan đến số trong Ruby:

Numeric
  Integer
    Fixnum
    Bignum
  Float
  Complex
  Rational
  BigDecimal (Thư viện chuẩn)

Như bạn thấy, lớp Numeric là cha của tất cả các lớp số. Nhớ rằng bạn có thể dùng phương thức ancestors để khám phá các lớp cha cho bất kỳ lớp nào.

Ví dụ:

Fixnum.ancestors - Fixnum.included_modules
[Fixnum, Integer, Numeric, Object, BasicObject]

Giờ hãy xem các lớp này dưới dạng bảng:

Lớp Mô tả Ví dụ
Integer Lớp cha của Fixnum & Bignum 1
Fixnum Số nguyên phù hợp kiểu integer của HĐH (32 hoặc 64 bit) 1
Bignum Dùng cho số lớn hơn 111111111111
Float Số thập phân không chính xác 5.0
Complex Dùng cho toán học với số phức (1+0i)
Rational Dùng để biểu diễn phân số (2/3)
BigDecimal Số thập phân độ chính xác hoàn hảo 3.0

Sự không chính xác của Float

Lớp Float trong Ruby được mô tả là "không chính xác" trong tài liệu Ruby chính thức.

Tại sao lại như vậy?

Hãy cho tôi ví dụ:

0.2 + 0.1 == 0.3 # false

Tại sao điều này là false?

Hãy nhìn kết quả của 0.2 + 0.1:

0.30000000000000004

Chính xác! Đó là ý của sự không chính xác.

Điều này xảy ra vì cách một float được lưu trữ. Nếu bạn cần số thập phân luôn chính xác, bạn có thể dùng lớp BigDecimal.

Float vs BigDecimal

BigDecimal là lớp cho số thập phân tùy ý độ chính xác (arbitrary-precision).

Ví dụ:

require 'bigdecimal'
BigDecimal("0.2") + BigDecimal("0.1") == 0.3 # true

Tại sao chúng ta không luôn dùng BigDecimal? Vì nó chậm hơn nhiều!

Đây là benchmark:

Calculating -------------------------------------
bigdecimal 21.559k i/100ms
float 79.336k i/100ms
-------------------------------------------------
bigdecimal 311.721k (± 7.4%) i/s - 1.552M
float 3.817M (±11.7%) i/s - 18.803M
Comparison:
float: 3817207.2 i/s
bigdecimal: 311721.2 i/s - 12.25x slower

BigDecimal chậm gấp 12 lần Float, và đó là lý do nó không phải mặc định 🙂

Fixnum vs Bignum

Trong phần này, tôi muốn khám phá sự khác biệt giữa Fixnum và Bignum trước Ruby 2.4.

Hãy bắt đầu với mã:

1.class # Fixnum
100000000000.class # Bignum

Ruby tạo lớp đúng cho chúng ta, và sẽ tự động thăng cấp Fixnum thành Bignum khi cần thiết.

Lưu ý: Bạn có thể cần số lớn hơn để có đối tượng Bignum nếu bạn dùng trình thông dịch Ruby 64-bit.

Tại sao chúng ta cần các lớp khác nhau? Câu trả lời là để làm việc với số lớn hơn, bạn cần triển khai khác nhau, và làm việc với số lớn chậm hơn, nên chúng ta kết thúc với tình huống tương tự Float vs BigDecimal.

Đặc điểm đặc biệt của Fixnum

Lớp Fixnum cũng có một số đặc điểm đặc biệt. Ví dụ, object_id được tính bằng công thức.

1.object_id # 3
20.object_id # 41

Công thức là: (number * 2) + 1.

Nhưng có thêm, khi bạn dùng Fixnum, không có đối tượng nào được tạo ra cả. Không có dữ liệu để lưu trữ trong Fixnum, vì giá trị được suy ra từ chính object_id.

Đây chỉ là chi tiết triển khai, nhưng tôi nghĩ nó thú vị để biết 🙂

MRI (Matz's Ruby Interpreter) dùng hai macro này để chuyển đổi giữa giá trị và object_id:

INT2FIX(i) ((VALUE)(((SIGNED_VALUE)(i))<<1 | FIXNUM_FLAG))
FIX2LONG(x) ((long)RSHIFT((SIGNED_VALUE)(x),1))

Điều xảy ra ở đây gọi là "bit shifting" (dịch bit), di chuyển tất cả bit sang trái hoặc phải.

Dịch một vị trí sang trái tương đương nhân 2 và đó là lý do công thức là (number * 2) + 1. +1 đến từ FIXNUM_FLAG.

Ngược lại, Bignum hoạt động hơn như một lớp bình thường và dùng object_id bình thường:

111111111111111.object_id # 23885808

Tất cả điều này nghĩa là đối tượng Fixnum gần với symbol về mặt cách hoạt động ở cấp trình thông dịch, trong khi đối tượng Bignum gần với string hơn.

Integer trong 2.4

Kể từ Ruby 2.4, Fixnum và Bignum đã deprecated, nhưng bên dưới chúng vẫn hoạt động y nguyên cách cũ.

Ruby tự động chuyển từ kiểu này sang kiểu khác.

Không thay đổi lớp.

Điều này nghĩa là số Integer nhỏ vẫn hoạt động giống như Fixnum.

Tóm tắt

Trong bài viết này, bạn đã học về các lớp liên quan đến số tồn tại trong Ruby.

Bạn học được float không chính xác và bạn có thể dùng BigDecimal nếu độ chính xác quan trọng hơn hiệu năng nhiều. Bạn cũng học được đối tượng Fixnum đặc biệt ở cấp trình thông dịch, nhưng Bignum chỉ là đối tượng bình thường.

Nếu bạn thấy bài viết này thú vị, đừng quên đăng ký newsletter của tôi trong form bên dưới 🙂