Tôi có một confession. Tôi khá ghét floating-point numbers. Chắc chắn, chúng hữu ích nếu bạn là máy tính, nhưng nếu bạn là con người, bạn sẽ băn khoan trước tình huống như này:
129.95 * 100 # => 12994.999999999998
Không chỉ vi phạm sự hài hòa toán học, nó còn bad UX.
Nếu công thức nấu ăn nói bạn đo 0.37211927843 cốc bột mì, bạn sẽ cười về tác giả rồi lấy một phần ba cốc.
Hầu hết mọi người nghĩ về phân số dễ dàng hơn số thập phân tùy tiện. Vì vậy nếu app của bạn cố gắng truyền đạt số cho con người, có thể nên khám phá cách biểu diễn chúng dưới dạng phân số.
Class Rational của Ruby là công cụ tuyệt vời cho số hữu tỉ. Nó không chỉ cho phép toán học hữu tỉ, mà còn cho tìm phân số đơn giản xấp xỉ số floating-point rắc rối. Hãy xem!
Rationals là gì?
Với mục đích của chúng ta, "số hữu tỉ" chỉ là cách nói đẹp cho "phân số". Chúng có hai phần: tử số và mẫu số.
1/2 # Tử số là 1. Mẫu số là 2. 5 # Tử số là 5. Mẫu số là 1.
Trong Ruby, số hữu tỉ có kiểu dữ liệu riêng như integer và float. Có vài cách tạo rational mới:
3/2r # Cú pháp này giới thiệu từ Ruby 2.1 1.5.to_r # Float chuyển sang rational qua `to_r` "3/2".to_r # ...string cũng được Rational('3/2') # Cách cũ ngày xưa Rational(3, 2) # ...nhìn đời khổ nhé?
Toán học đơn giản
Khi cộng, trừ, nhân, chia hai số hữu tỉ, kết quả cũng là hữu tỉ.
2/3r + 1/3r # => (1/1) 2/3r - 1/3r # => (1/3) 2/3r * 1/3r # => (2/9) (2/3r) / (1/3r) # Cần ngoặc để tránh nhầm lẫn interpreter # => (2/1)
Tất cả toán tử khác cũng hoạt động như mong đợi: **, >, <, v.v..
Quy tắc chung là cả hai input phải là phân số để kết quả là phân số. Ngoại lệ duy nhất tôi tìm thấy là với integer. Vì mọi integer đều là hữu tỉ, Ruby làm điều thông minh và假定 output hữu tỉ:
2/3r + 2 # => (8/3)
Xấp xỉ (Approximations)
Một trong những điều hữu ích nhất về số hữu tỉ là cho phép xấp xỉ và tính toán dễ dàng trong đầu. Để tận dụng, chúng ta cần giữ phân số đơn giản. 3/2 thay vì 3320774221237909/2251799813685248.
May mắn thay, Ruby cho cách dễ dàng chuyển số chính xác-nhưng-xấu thành số gần đúng-nhưng-đẹp. Tôi nói về phương thức rationalize.
Đây là giao diện:
# Chính xác nhưng xấu (1.47472).to_r => (3320774221237909/2251799813685248) # Kém chính xác hơn, nhưng đẹp (1.47472).to_r.rationalize(0.05) => (3/2)
Phương thức rationalize có một đối số. Nó chỉ định tolerance - lượng độ chính xác bạn sẵn sàng đánh đổi cho sự đơn giản.
Phương thức tìm số có mẫu số thấp nhất trong tolerance của bạn. Đây là ý tôi:
# Số nào có mẫu số thấp nhất giữa 5/10 và 7/10? (6/10r).rationalize(1/10r) # => (1/2) # Số nào có mẫu số thấp nhất giữa 11/20 và 13/20? (6/10r).rationalize(1/20r) # => (3/5) # ..và giữa 1/10 và 11/10? (6/10r).rationalize(1/2r) # => (1/1)
Xấp xỉ thô hơn
Nếu bạn chỉ cần tìm integer hoặc float tương ứng với phân số, bạn có vài lựa chọn.
# Trả về integer gần nhất. (6/10r).round # => 1 # Làm tròn xuống (12/10r).to_i # => 1
Hạn chế của Rationals
Có vài hạn chế cần lưu ý khi làm việc với số hữu tỉ và Ruby. Tất nhiên không thể chia cho zero:
4/0r ZeroDivisionError: divided by 0
Và bạn sẽ gặp hành vi lạ nếu cố coi số vô tỉ như hữu tỉ.
# Umm, căn bậc 2 của 2 không phải vô tỉ sao? Rational(Math.sqrt(2)) # => (6369051672525773/4503599627370496) # Và tôi chắc PI cũng vô tỉ. Rational(Math::PI) # => (884279719003555/281474976710656)
Chúng ta có thể mong đợi yêu cầu Ruby coi số vô tỉ là hữu tỉ sẽ raise exception. Nhưng tiếc thay Ruby không đủ thông minh. Thay vào đó, nó chuyển xấp xỉ float của các số vô tỉ thành rational. Không phải vấn đề lớn, nhưng cần biết.