Tiền, bất kể đơn vị tiền tệ được sử dụng, có vẻ giống như một số dấu phẩy động. Nhưng thật sai lầm khi sử dụng float cho tiền tệ.
Số float (do đó, đối tượng float), theo định nghĩa, là số thực không chính xác sử dụng đặc tính biểu diễn dấu phẩy động chính xác kép của kiến trúc gốc. Những con số không liên hệ khiến kế toán không hài lòng.
Trong bài viết này, bạn sẽ được hướng dẫn qua một số ví dụ nhanh sẽ giúp bạn giải quyết các tùy chọn có sẵn để xử lý dữ liệu tiền trong Ruby và Rails.
Float là gì?
Như chúng tôi đã nói, float
các đối tượng có số học khác nhau. Đây là lý do chính khiến chúng là số không chính xác, đặc biệt là vì Ruby (giống như hầu hết các ngôn ngữ khác) sử dụng một số lượng cố định các chữ số nhị phân để biểu diễn số thực. Nói cách khác, Ruby chuyển đổi số thực từ thập phân sang nhị phân và ngược lại.
Khi bạn đi sâu vào biểu diễn nhị phân của số thực (hay còn gọi là số thập phân), một số trong số chúng không thể được biểu diễn chính xác, vì vậy lựa chọn còn lại duy nhất là hệ thống làm tròn chúng.
Nếu bạn suy nghĩ trước và xem xét các cấu trúc toán học phổ biến, chẳng hạn như phần mười tuần hoàn, bạn có thể hiểu rằng chúng không thể được biểu diễn hoàn toàn trong một số cố định vì pi số, chẳng hạn, là vô hạn. Số nổi thường có thể có độ chính xác lên đến 32 hoặc 64 bit, có nghĩa là số này sẽ bị cắt khi đạt đến giới hạn.
Hãy phân tích một ví dụ cổ điển:
1200 * (14.0/100)
Đây là một cách đơn giản để tính toán tỷ lệ phần trăm của một số. Mười bốn phần trăm của 1200 phải là 168; tuy nhiên, kết quả của việc thực thi này trong Ruby sẽ là
1200 * (14.0/100)
=> 168.00000000000003
Tuy nhiên, nếu bạn chỉ thêm 0,1% vào công thức, bạn sẽ nhận được một cái gì đó khác biệt:
1200 * (14.1/100)
=> 169.2
Ngoài ra, bạn có thể round
giá trị chính xác nhất có thể, xác định bao nhiêu chữ số thập phân được mong muốn:
(my_calculation).round(2)
Thật vậy, nó không được đảm bảo khi nói đến các phép tính phức tạp hơn, đặc biệt nếu bạn thực hiện so sánh các giá trị này.
Nếu bạn muốn tìm hiểu khoa học thực sự đằng sau nó, tôi thực sự khuyên bạn nên đọc phần phụ lục của Oracle:Những gì mọi nhà khoa học máy tính nên biết về số học dấu chấm động. Nó giải thích chi tiết lý do đằng sau bản chất không chính xác của số thực.
Số thập phân BigDecimal đáng tin cậy
Hãy xem xét đoạn mã sau:
require "bigdecimal"
BigDecimal("45.99")
Mã này có thể dễ dàng đại diện cho một logic thực sự bao gồm số lượng của giỏ hàng Thương mại điện tử. Cuối cùng, giá trị thực đang được thao tác sẽ luôn là 45,99 thay vì 45,9989 hoặc 45,99000009, chẳng hạn.
Đây là bản chất chính xác của BigDecimal
. Đối với các phép tính số học thông thường, float
sẽ thực hiện theo cùng một cách; tuy nhiên, nó không thể đoán trước được, đó là mối nguy hiểm khi sử dụng nó.
Khi nó chạy với BigDecimal
, cách tính tỷ lệ phần trăm tương tự mà chúng tôi đã thực hiện trong phần trước cho kết quả
require "bigdecimal"
(BigDecimal(1200) * (BigDecimal(14)/BigDecimal(100))).to_s("F")
=> 168.0
Đây chỉ là một phiên bản ngắn để cho phép thực thi nhanh chóng trong irb bảng điều khiển.
Ban đầu, khi bạn in BigDecimal
trực tiếp đối tượng, bạn sẽ nhận được ký hiệu khoa học của nó, đó không phải là những gì chúng tôi muốn ở đây. to_s
phương thức nhận đối số đã cho do cài đặt định dạng và hiển thị giá trị động tương đương của BigDecimal. Để biết thêm chi tiết về chủ đề này, hãy tham khảo tài liệu Ruby.
Trong trường hợp bạn cần xác định giới hạn cho các vị trí thập phân, nó có truncate
phương pháp này sẽ thực hiện công việc cho bạn:
(BigDecimal(1200) * (BigDecimal("14.12")/BigDecimal(100))).truncate(2).to_s("F")
=> 169.44
Dự án RubyMoney
RubyMoney được tạo ra sau khi suy nghĩ về những vấn đề này. Đây là một cộng đồng mã nguồn mở của các nhà phát triển Ruby nhằm tạo điều kiện thuận lợi cho cuộc sống của các nhà phát triển bằng cách cung cấp các thư viện tuyệt vời để thao túng dữ liệu tiền trong hệ sinh thái Ruby.
Dự án bao gồm bảy thư viện, ba trong số đó nổi bật về tầm quan trọng:
- Tiền:Một thư viện Ruby để xử lý tiền và chuyển đổi tiền tệ. Nó cung cấp một số tùy chọn hướng đối tượng để xử lý tiền trong các ứng dụng mạnh mẽ và hiện đại, bất kể chúng có dành cho web hay không.
- Money-rails:Tích hợp RubyMoney cho Ruby on Rails, trộn tất cả
money
Sức mạnh thư viện của Rails với tính linh hoạt. - Kiếm tiền:Một thư viện để chuyển đổi các đối tượng khác nhau thành
money
các đối tượng. Nó hoạt động giống như một thư viện phụ trợ cho các ứng dụng xử lý nhiều phân tích cú pháp chuỗi chẳng hạn.
Dự án có bốn thư viện thú vị khác:
- EU_central_bank:Thư viện giúp tính toán tỷ giá hối đoái bằng cách sử dụng tỷ giá được công bố từ Ngân hàng Trung ương Châu Âu.
- Google_currency:Một thư viện thú vị để chuyển đổi tiền tệ bằng cách sử dụng tỷ giá Google Currency làm tài liệu tham khảo.
- Thu tiền:Một thư viện phụ trợ để tính toán chính xác tổng / tối thiểu / tối đa của
money
đồ vật. - Money-heuristics:Một mô-đun dành cho các phân tích heuristic về chuỗi đầu vào cho money gem.
Viên ngọc "Tiền"
Hãy bắt đầu với thứ nổi tiếng nhất:viên ngọc tiền. Trong số các tính năng chính của nó là:
- Một
money
lớp chứa thông tin tiền tệ có liên quan, chẳng hạn như giá trị, đơn vị tiền tệ và dấu thập phân. - Một lớp khác được gọi là
Money::Currency
bao gồm thông tin liên quan đến đơn vị tiền tệ đang được nhà phát triển sử dụng. - Theo mặc định, nó hoạt động với số nguyên thay vì số dấu phẩy động để tránh các lỗi nói trên.
- Khả năng trao đổi tiền từ đơn vị tiền tệ này sang đơn vị tiền tệ khác, thật tuyệt vời.
Ngoài ra, chúng tôi cũng có được tính linh hoạt cao được cung cấp bởi cấu trúc hướng đối tượng và nhất quán để thao tác dữ liệu tiền tệ, giống như bất kỳ mô hình nào khác trong các dự án của bạn.
Cách sử dụng nó khá đơn giản, chỉ cần cài đặt đá quý thích hợp:
gem install money
Một ví dụ nhanh liên quan đến một số tiền cố định sẽ là
my_money = Money.new(1200, "USD")
my_money.cents #=> 1200
my_money.currency #=> Currency.new("USD")
Như bạn có thể thấy, tiền được biểu thị dựa trên xu. Mười hai trăm xu tương đương với 12 đô la.
Giống như bạn đã làm với BigDecimal, bạn cũng có thể chơi xung quanh và làm một số phép toán cơ bản với các đối tượng này. Ví dụ:
cart_amount = Money.new(10000, "USD") #=> 100 USD
discount = Money.new(1000, "USD") #=> 10 USD
cart_amount - discount == Money.new(9000, "USD") #=> 90 USD
Thật thú vị, phải không? Đó là bản chất của các đối tượng mà chúng tôi đã đề cập. Khi viết mã, bạn thực sự có cảm giác như đang thao túng các giá trị tiền tệ hơn là các con số thông thường và không thể diễn tả được.
Chuyển đổi tiền tệ
Nếu bạn đã có hệ thống tỷ giá hối đoái của riêng mình, bạn có thể thực hiện chuyển đổi tiền tệ thông qua một đối tượng ngân hàng hối đoái. Hãy xem xét những điều sau:
Money.add_rate("USD", "BRL", 5.23995)
Money.add_rate("BRL", "USD", 0.19111)
Bất cứ khi nào bạn cần trao đổi các giá trị giữa chúng, bạn có thể chạy mã sau:
Money.us_dollar(100).exchange_to("BRL") #=> Money.new(523, "BRL")
Điều tương tự cũng áp dụng cho bất kỳ đánh giá số học và so sánh nào mà bạn có thể muốn thực hiện.
Đảm bảo tham khảo tài liệu để biết thêm các thuộc tính tiền tệ được cung cấp, chẳng hạn như iso_code
(trả về mã ba chữ số quốc tế của đơn vị tiền tệ đó) và decimal_mark
(biểu đồ giữa giá trị và phần dữ liệu tiền), trong số những thứ khác.
Ồ, tôi gần như quên mất; khi bạn đã cài đặt money gem, bạn có thể truy cập vào BigDecimal
phương thức được gọi là to_money
tự động thực hiện chuyển đổi cho bạn.
Đá quý "kiếm tiền"
Điều quan trọng là phải hiểu vai trò của mỗi thư viện trong dự án RubyMoney. Bất cứ khi nào bạn cần chuyển đổi một đối tượng Ruby khác (một String
, chẳng hạn) thành Money
, sau đó monetize
là những gì bạn đang tìm kiếm.
Trước tiên, hãy đảm bảo cài đặt phụ thuộc đá quý hoặc thêm nó vào Gemfile của bạn :
gem install monetize
Rõ ràng, money
cũng cần được cài đặt.
parse
phương pháp này cũng rất hữu ích khi bạn nhận dữ liệu tiền ở một định dạng khác; ví dụ,
Money.parse("£100") == Money.new(100, "GBP") #=> true
Mặc dù các trường hợp bạn sử dụng phương pháp phân tích cú pháp này bị hạn chế, nhưng nó có thể rất hữu ích khi bạn nhận được một giá trị được định dạng cùng với mã đơn vị tiền tệ của nó từ một yêu cầu HTTP. Trên web, mọi thứ đều là văn bản, do đó, chuyển đổi từ chuỗi thành money
có thể rất hữu ích.
Tuy nhiên, hãy cẩn thận với cách hệ thống của bạn thao tác các giá trị và nếu chúng có thể bị tấn công bằng cách nào đó. Hệ thống tài chính luôn được bao phủ bởi nhiều lớp bảo mật để đảm bảo rằng giá trị bạn nhận được là giá trị thực của giao dịch đó.
Đá quý “kiếm tiền từ rails”
Đây là thư viện xử lý các hoạt động thao túng tiền tương tự, nhưng trong ứng dụng Rails.
Tại sao chúng ta cần một thư viện thứ hai chỉ để làm cho nó hoạt động cùng với Rails? Chà, bạn chắc chắn có thể tận dụng money
gem một mình trong các dự án Rails cho các phép toán thông thường. Tuy nhiên, nó sẽ không hoạt động bình thường khi cấu trúc Rails của bạn cần giao tiếp với money
Các tính năng của.
Hãy xem xét ví dụ sau:
class Cart < ActiveRecord::Base
monetize :amount_cents
end
Đây là một đối tượng mô hình Rails thực sự có chức năng. Bạn có thể sử dụng nó cùng với cơ sở dữ liệu (thậm chí bao gồm cả bí danh khi bạn muốn có một tên thuộc tính mô hình khác), dịch vụ web Mongoid, REST, v.v.
Tất cả các tính năng mà chúng tôi đã tiếp xúc cho đến nay cũng áp dụng cho viên ngọc này. Thông thường, chỉ các cài đặt bổ sung là cần thiết để chạy, cài đặt này sẽ được đặt vào config / initializers / money.rb tệp:
MoneyRails.configure do |config|
# set the default currency
config.default_currency = :usd
end
Thao tác này sẽ đặt đơn vị tiền tệ mặc định thành đơn vị tiền tệ bạn cung cấp. Tuy nhiên, trong quá trình phát triển, rất có thể bạn cần thực hiện chuyển đổi trao đổi hoặc xử lý nhiều loại tiền tệ trong suốt các mô hình.
Nếu vậy, money-rails
cho phép chúng tôi định cấu hình định nghĩa tiền tệ cấp mô hình:
class Cart < ActiveRecord::Base
# Use GPB as model level currency
register_currency :eur
monetize :amount_cents, as "amount"
monetize :discount_cents, with_currency: :eur
monetize :converted_value, with_currency: :usd
end
Lưu ý rằng khi mọi thứ đã được thiết lập xong, bạn sẽ thực sự dễ dàng sử dụng các loại tiền cùng với các dự án của mình.
Kết thúc
Trong bài đăng trên blog này, chúng tôi đã khám phá một số tùy chọn có sẵn để xử lý các giá trị tiền trong hệ sinh thái Ruby và Rails. Một số điểm quan trọng được tóm tắt dưới đây:
- Nếu bạn đang xử lý việc tính toán các số thực, đặc biệt nếu chúng đại diện cho dữ liệu tiền, hãy chuyển sang
BigDecimal
hoặcMoney
phiên bản. - Cố gắng chỉ bám vào một hệ thống để tránh những mâu thuẫn tiếp theo trong quá trình phát triển của bạn.
-
money
thư viện là cốt lõi của toàn bộ hệ thống RubyMoney, và nó rất mạnh mẽ và đơn giản.Money-rails
là phiên bản tương đương cho các ứng dụng Rails vàmonetize
là cần thiết bất cứ khi nào bạn cần phân tích cú pháp từ bất kỳ giá trị nào thànhMoney
đồ vật. - Tránh sử dụng
Float
. Ngay cả khi ứng dụng của bạn không cần phải tính toán bất cứ điều gì bây giờ, thì rất có thể một nhà phát triển không có kinh nghiệm sẽ làm điều đó trong tương lai. Bạn có thể không ở đó để ngăn chặn nó.
Hãy nhớ rằng, các tài liệu chính thức phải luôn là tài liệu bắt buộc. BigDecimal chứa đầy những lời giải thích và ví dụ tuyệt vời về cách sử dụng của nó và điều này cũng đúng với các dự án đá quý RubyMoney.