Ruby 2.4 đã hợp nhất Fixnum
&Bignum
vào cùng một lớp (Integer
) vì vậy tôi nghĩ đây là thời điểm tốt để xem xét các loại số khác nhau trong Ruby!
Đó là những gì chúng ta sẽ nói trong bài đăng này 🙂
Tổng quan về các loại số
Hãy bắt đầu bằng cách xem xét 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 (Standard Library)
Như bạn có thể thấy, Numeric
lớp là cha của tất cả các lớp số. Hãy nhớ rằng bạn có thể sử dụng 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]
Bây giờ chúng ta hãy xem các lớp này ở dạng bảng:
Lớp | Mô tả | Ví dụ |
---|---|---|
Số nguyên | Lớp chính của Fixnum &Bignum | 1 |
Fixnum | Các số nguyên phù hợp với kiểu số nguyên hệ điều hành (32 hoặc 64 bit) | 1 |
Bignum | Được sử dụng cho các số lớn hơn | 111111111111 |
Phao | Số thập phân không chính xác | 5.0 |
Phức tạp | Được sử dụng cho công cụ toán học với các số ảo | (1 + 0i) |
Hợp lý | 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 |
Phao không chính xác
Float
lớp 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 vậy?
Để tôi cho bạn xem một ví dụ :
0.2 + 0.1 == 0.3 # false
Tại sao điều này là sai?
Hãy xem kết quả của 0.2 + 0.1
:
0.30000000000000004
Một cách chính xác! Đó là ý của chúng tôi khi nói đến sự không chính xác.
Điều này xảy ra do cách lưu trữ float. Nếu bạn cần số thập phân luôn chính xác, bạn có thể sử dụng BigDecimal
lớp học.
Float so với BigDecimal
BigDecimal là một lớp cung cấp cho bạn các số thập phân có độ chính xác tùy ý.
Ví dụ :
require 'bigdecimal' BigDecimal("0.2") + BigDecimal("0.1") == 0.3 # true
Tại sao chúng ta không luôn sử dụng BigDecimal
sau đó? Bởi vì nó chậm hơn rất nhiều!
Đây là điểm chuẩn :
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 hơn 12 lần so với Float
và đó là lý do tại sao nó không phải là mặc định 🙂
Fixnum và 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ột số mã :
1.class # Fixnum 100000000000.class # Bignum
Ruby tạo đúng lớp cho chúng tôi và nó sẽ tự động quảng bá một Fixnum
đến một Bignum
khi cần thiết.
Lưu ý :Bạn có thể cần một số lớn hơn để nhận được
Bignum
đối tượng nếu bạn có 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 những con số lớn hơn, bạn cần một cách triển khai khác và làm việc với những con số lớn sẽ chậm hơn, vì vậy chúng ta sẽ gặp phải tình huống tương tự với Float
so với BigDecimal
.
Các thuộc tính đặc biệt của Fixnums
Fixnum
lớp cũng có một số thuộc tính đặc biệt. Ví dụ:id đối tượng đượ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òn nhiều điều hơn thế này, khi bạn sử dụng Fixnum
không có đối tượng nào được tạo ra cả. Không có dữ liệu nào để lưu trữ trong Fixnum
, bởi vì giá trị được lấy từ chính id đối tượng.
Đây chỉ là chi tiết triển khai, nhưng tôi nghĩ sẽ rất thú vị khi biết 🙂
MRI (Matz’s Ruby Interpreter) sử dụng hai macro này để chuyển đổi giữa giá trị và id đối tượng:
INT2FIX(i) ((VALUE)(((SIGNED_VALUE)(i))<<1 | FIXNUM_FLAG)) FIX2LONG(x) ((long)RSHIFT((SIGNED_VALUE)(x),1))
Điều xảy ra ở đây được gọi là "dịch chuyển bit", di chuyển tất cả các bit sang trái hoặc phải.
Di chuyển một vị trí sang trái tương đương với nhân với 2 và đó là lý do tại sao công thức là (number * 2) + 1
. +1 đến từ FIXNUM_FLAG
.
Ngược lại, Bignum
hoạt động giống như một lớp bình thường và sử dụng id đối tượng bình thường:
111111111111111.object_id # 23885808
Tất cả điều này có nghĩa là Fixnum
các đối tượng gần với các biểu tượng hơn về cách chúng hoạt động ở cấp thông dịch, trong khi Bignum
các đối tượng gần với chuỗi hơn.
Số nguyên trong 2,4
Vì Ruby 2.4 Fixnum &Bignum không được dùng nữa, nhưng đằng sau nó vẫn hoạt động theo cách tương tự.
Ruby tự động chuyển từ loại này sang loại khác.
Không thay đổi lớp học .
Điều này có nghĩa là Integer
nhỏ các số vẫn hoạt động giống như một Fixnum
.
Tóm tắt
Trong bài đăng này, bạn đã tìm hiểu về các lớp liên quan đến số khác nhau tồn tại trong Ruby.
Bạn đã biết rằng số thực không chính xác và bạn có thể sử dụng BigDecimal
nếu độ chính xác quan trọng hơn nhiều so với hiệu suất. Bạn cũng biết rằng Fixnum
các đối tượng đặc biệt ở cấp thông dịch, nhưng Bignum
s chỉ là các đối tượng thông thường.
Nếu bạn thấy bài đăng này thú vị, đừng quên đăng ký nhận bản tin của tôi ở biểu mẫu bên dưới 🙂