เงินไม่ว่าจะอยู่ในสกุลเงินใดก็ตาม ดูเหมือนเป็นตัวเลขทศนิยม แต่การใช้โฟลตสำหรับสกุลเงินถือเป็นความผิดพลาด
ตัวเลขทศนิยม (ด้วยเหตุนี้ วัตถุลอย) เป็นจำนวนจริงที่ไม่แน่นอนซึ่งใช้ประโยชน์จากคุณลักษณะการแสดงจุดทศนิยมแบบ double-precision ของสถาปัตยกรรมดั้งเดิม ตัวเลขที่ไม่แน่นอนทำให้นักบัญชีไม่มีความสุข
ในบทความนี้ คุณจะได้รับคำแนะนำเกี่ยวกับตัวอย่างสั้นๆ ที่จะช่วยให้คุณระบุตัวเลือกที่มีในการจัดการข้อมูลเงินใน Ruby and Rails
โฟลตคืออะไร
อย่างที่เราบอก float
วัตถุมีเลขคณิตต่างกัน นี่คือเหตุผลหลักที่พวกมันเป็นตัวเลขที่ไม่แน่นอน โดยเฉพาะอย่างยิ่งเนื่องจาก Ruby (เช่นเดียวกับภาษาส่วนใหญ่) ใช้เลขฐานสองจำนวนคงที่เพื่อแสดงจำนวนทศนิยม กล่าวอีกนัยหนึ่ง Ruby จะแปลงตัวเลขลอยตัวจากทศนิยมเป็นเลขฐานสองและในทางกลับกัน
เมื่อคุณเจาะลึกลงไปในการแสดงเลขฐานสองของจำนวนจริง (หรือที่เรียกว่าเลขฐานสิบ) ตัวเลขบางตัวจะไม่สามารถแสดงได้ทั้งหมด ดังนั้นตัวเลือกเดียวที่เหลือคือให้ระบบปัดเศษออก
หากคุณคิดล่วงหน้าและพิจารณาโครงสร้างทางคณิตศาสตร์ทั่วไป เช่น ส่วนสิบเป็นงวด คุณอาจเข้าใจว่าไม่สามารถแสดงทั้งหมดภายในจำนวนคงที่ได้ตั้งแต่ pi จำนวนตัวอย่างเช่นเป็นอนันต์ โฟลตมักจะเก็บความแม่นยำได้ถึง 32 หรือ 64 บิต ซึ่งหมายความว่าตัวเลขจะถูกตัดออกเมื่อถึงขีดจำกัด
มาวิเคราะห์ตัวอย่างคลาสสิกกัน:
1200 * (14.0/100)
นี่เป็นวิธีที่ตรงไปตรงมาในการคำนวณเปอร์เซ็นต์ของตัวเลข สิบสี่เปอร์เซ็นต์ของ 1200 ควรเป็น 168; อย่างไรก็ตาม ผลลัพธ์ของการดำเนินการนี้ภายใน Ruby จะเป็น
1200 * (14.0/100)
=> 168.00000000000003
อย่างไรก็ตาม หากคุณเพิ่มเพียง 0.1% ลงในสูตร คุณจะได้ผลลัพธ์ที่แตกต่าง:
1200 * (14.1/100)
=> 169.2
หรือคุณสามารถ round
ค่าที่ใกล้เคียงที่สุด กำหนดจำนวนตำแหน่งทศนิยมที่ต้องการ:
(my_calculation).round(2)
อันที่จริง ไม่รับประกันว่าจะมีการคำนวณที่ซับซ้อนมากขึ้น โดยเฉพาะอย่างยิ่งหากคุณทำการเปรียบเทียบค่าเหล่านี้
หากคุณสนใจที่จะเข้าใจวิทยาศาสตร์ที่แท้จริงเบื้องหลัง ขอแนะนำให้อ่านภาคผนวกของ Oracle:สิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ทุกคนควรรู้เกี่ยวกับเลขคณิตจุดลอยตัว โดยจะอธิบายรายละเอียดถึงสาเหตุที่อยู่เบื้องหลังลักษณะที่ไม่ถูกต้องของจำนวนทศนิยม
ทศนิยมที่น่าเชื่อถือ
พิจารณาข้อมูลโค้ดต่อไปนี้:
require "bigdecimal"
BigDecimal("45.99")
รหัสนี้สามารถแสดงถึงตรรกะที่แท้จริงที่โอบรับจำนวนรถเข็นอีคอมเมิร์ซได้อย่างง่ายดาย ในท้ายที่สุด มูลค่าที่แท้จริงที่ถูกจัดการจะเป็น 45.99 แทนที่จะเป็น 45.9989 หรือ 45.99000009 เป็นต้น
นี่คือลักษณะเฉพาะของ BigDecimal
. สำหรับการคำนวณเลขคณิตปกติ float
จะดำเนินการในลักษณะเดียวกัน แต่คาดเดาไม่ได้ซึ่งเป็นอันตรายจากการใช้งาน
เมื่อรันด้วย BigDecimal
การคำนวณเปอร์เซ็นต์เดียวกันกับที่เราทำในส่วนก่อนหน้าส่งผลให้
require "bigdecimal"
(BigDecimal(1200) * (BigDecimal(14)/BigDecimal(100))).to_s("F")
=> 168.0
นี่เป็นเพียงเวอร์ชันสั้นเพื่อให้ดำเนินการอย่างรวดเร็วใน irb คอนโซล
เดิมทีเมื่อคุณพิมพ์ BigDecimal
. โดยตรง คุณจะได้สัญกรณ์ทางวิทยาศาสตร์ ซึ่งไม่ใช่สิ่งที่เราต้องการที่นี่ to_s
เมธอดได้รับอาร์กิวเมนต์ที่กำหนดเนื่องจากการตั้งค่าการจัดรูปแบบและแสดงค่าลอยตัวที่เทียบเท่าของ BigDecimal สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับหัวข้อนี้ โปรดดูเอกสาร Ruby
ในกรณีที่คุณจำเป็นต้องกำหนดขีด จำกัด ของตำแหน่งทศนิยม จะมี truncate
วิธีที่จะทำงานให้คุณ:
(BigDecimal(1200) * (BigDecimal("14.12")/BigDecimal(100))).truncate(2).to_s("F")
=> 169.44
โครงการ RubyMoney
RubyMoney ถูกสร้างขึ้นหลังจากคิดถึงปัญหาเหล่านี้ เป็นชุมชนโอเพ่นซอร์สของนักพัฒนา Ruby ที่มุ่งอำนวยความสะดวกให้กับชีวิตของนักพัฒนาโดยการจัดหาห้องสมุดที่ยอดเยี่ยมเพื่อจัดการข้อมูลเงินในระบบนิเวศของ Ruby
โครงการนี้ประกอบด้วยห้องสมุด 7 แห่ง โดยสามแห่งมีความโดดเด่น:
- เงิน:ห้องสมุด Ruby สำหรับจัดการกับเงินและการแปลงสกุลเงิน มีตัวเลือกเชิงวัตถุหลายตัวในการจัดการเงินในแอปพลิเคชันที่มีประสิทธิภาพและทันสมัย ไม่ว่าจะใช้สำหรับเว็บหรือไม่
- Money-rails:การรวม RubyMoney สำหรับ Ruby on Rails โดยผสม
money
ทั้งหมด พลังของห้องสมุดที่มีความยืดหยุ่นของ Rails - สร้างรายได้:ห้องสมุดสำหรับแปลงวัตถุต่างๆ เป็น
money
วัตถุ มันทำงานเหมือนไลบรารีเสริมสำหรับแอปพลิเคชันที่จัดการกับการแยกวิเคราะห์สตริงจำนวนมาก เป็นต้น
โครงการนี้มีห้องสมุดที่น่าสนใจอีกสี่แห่ง:
- EU_central_bank:ห้องสมุดที่ช่วยคำนวณอัตราแลกเปลี่ยนโดยใช้อัตราที่เผยแพร่จากธนาคารกลางยุโรป
- Google_currency:ห้องสมุดที่น่าสนใจสำหรับการแปลงสกุลเงินโดยใช้อัตราแลกเปลี่ยนของ Google Currency
- การเก็บเงิน:ห้องสมุดเสริมสำหรับการคำนวณผลรวม/นาที/สูงสุดของ
money
อย่างแม่นยำ วัตถุ - Money-heuristics:โมดูลสำหรับการวิเคราะห์ heuristic ของการป้อนสตริงสำหรับ money gem
อัญมณี “เงิน”
เริ่มจากสิ่งที่มีชื่อเสียงที่สุด:อัญมณีเงิน ในบรรดาคุณสมบัติหลักดังต่อไปนี้:
- A
money
คลาสที่มีข้อมูลทางการเงินที่เกี่ยวข้อง เช่น ค่า สกุลเงิน และเครื่องหมายทศนิยม - อีกคลาสหนึ่งเรียกว่า
Money::Currency
ที่ห่อหุ้มข้อมูลเกี่ยวกับหน่วยการเงินที่นักพัฒนาใช้ - โดยค่าเริ่มต้น จะทำงานกับจำนวนเต็มแทนที่จะเป็นตัวเลขทศนิยมเพื่อหลีกเลี่ยงข้อผิดพลาดดังกล่าว
- ความสามารถในการแลกเปลี่ยนเงินจากสกุลเงินหนึ่งไปยังอีกสกุลเงินหนึ่งซึ่งยอดเยี่ยมมาก
นอกจากนั้น เรายังได้รับความยืดหยุ่นสูงจากโครงสร้างที่สอดคล้องกันและเชิงวัตถุเพื่อจัดการข้อมูลเงิน เช่นเดียวกับโมเดลอื่นๆ ภายในโครงการของคุณ
การใช้งานนั้นค่อนข้างง่าย เพียงแค่ติดตั้งอัญมณีที่เหมาะสม:
gem install money
ตัวอย่างสั้นๆ เกี่ยวกับจำนวนเงินที่แน่นอนคือ
my_money = Money.new(1200, "USD")
my_money.cents #=> 1200
my_money.currency #=> Currency.new("USD")
อย่างที่คุณเห็น เงินจะแสดงตามเซ็นต์ สิบสองร้อยเซ็นต์เท่ากับ 12 ดอลลาร์
เช่นเดียวกับที่คุณทำกับ BigDecimal คุณยังสามารถเล่นและทำคณิตศาสตร์พื้นฐานกับวัตถุเหล่านี้ได้ ตัวอย่างเช่น
cart_amount = Money.new(10000, "USD") #=> 100 USD
discount = Money.new(1000, "USD") #=> 10 USD
cart_amount - discount == Money.new(9000, "USD") #=> 90 USD
น่าสนใจใช่ไหม นั่นคือธรรมชาติของวัตถุที่เรากล่าวถึง เมื่อเขียนโค้ด คุณจะรู้สึกเหมือนกำลังจัดการค่าเงินมากกว่าตัวเลขธรรมดาๆ ที่ไม่แสดงออก
การแปลงสกุลเงิน
หากคุณมีระบบอัตราแลกเปลี่ยนของคุณเอง คุณสามารถแปลงสกุลเงินผ่านออบเจกต์ธนาคารแลกเปลี่ยนได้ พิจารณาสิ่งต่อไปนี้:
Money.add_rate("USD", "BRL", 5.23995)
Money.add_rate("BRL", "USD", 0.19111)
เมื่อใดก็ตามที่คุณต้องการแลกเปลี่ยนค่าระหว่างกัน คุณสามารถเรียกใช้รหัสต่อไปนี้:
Money.us_dollar(100).exchange_to("BRL") #=> Money.new(523, "BRL")
เช่นเดียวกับการประเมินทางคณิตศาสตร์และการเปรียบเทียบใดๆ ที่คุณอาจต้องการดำเนินการ
อย่าลืมอ้างอิงเอกสารสำหรับแอตทริบิวต์สกุลเงินที่ให้ไว้เพิ่มเติม เช่น iso_code
(ซึ่งส่งคืนรหัสสามหลักสากลของสกุลเงินนั้น) และ decimal_mark
(อักขระระหว่างค่าและเศษส่วนของข้อมูลเงิน) เป็นต้น
เกือบลืมไปเลย เมื่อคุณติดตั้ง Money gem แล้ว คุณจะสามารถเข้าถึง BigDecimal
วิธีการที่เรียกว่า to_money
ที่ทำการแปลงให้คุณโดยอัตโนมัติ
อัญมณี “สร้างรายได้”
สิ่งสำคัญคือต้องเข้าใจบทบาทของห้องสมุดแต่ละแห่งในโครงการ RubyMoney เมื่อใดก็ตามที่คุณต้องการแปลงวัตถุ Ruby อื่น (a String
เช่น) เป็น money
แล้ว monetize
คือสิ่งที่คุณกำลังมองหา
ขั้นแรก ตรวจสอบให้แน่ใจว่าได้ติดตั้งการพึ่งพาอัญมณีหรือเพิ่มลงใน Gemfile . ของคุณ :
gem install monetize
แน่นอน money
ต้องติดตั้งด้วย
parse
วิธีการยังมีประโยชน์มากเมื่อคุณได้รับข้อมูลเงินในรูปแบบอื่น ตัวอย่างเช่น
Money.parse("£100") == Money.new(100, "GBP") #=> true
แม้ว่าสถานการณ์ที่คุณใช้วิธีการแยกวิเคราะห์นี้จะถูกจำกัด แต่ก็มีประโยชน์มากเมื่อคุณได้รับค่าที่จัดรูปแบบควบคู่ไปกับรหัสสกุลเงินจากคำขอ HTTP บนเว็บ ทุกอย่างเป็นข้อความ ดังนั้นให้แปลงจากสตริงเป็น money
มีประโยชน์มาก
อย่างไรก็ตาม โปรดใช้ความระมัดระวังว่าระบบของคุณจัดการกับค่าต่างๆ อย่างไร และอาจถูกแฮ็กได้หรือไม่ ระบบการเงินมีชั้นความปลอดภัยหลายชั้นครอบคลุมเสมอ เพื่อให้แน่ใจว่ามูลค่าที่คุณได้รับนั้นเป็นมูลค่าที่แท้จริงของธุรกรรมนั้น
อัญมณี “monetize-rails”
นี่คือห้องสมุดที่เกี่ยวข้องกับการดำเนินการจัดการเงินแบบเดียวกัน แต่อยู่ในแอป Rails
เหตุใดเราจึงต้องมีห้องสมุดที่สองเพื่อให้ทำงานร่วมกับ Rails ได้ คุณสามารถใช้ money
. ได้อย่างแน่นอน gem เพียงอย่างเดียวภายในโครงการ Rails สำหรับการดำเนินการทางคณิตศาสตร์ทั่วไป อย่างไรก็ตาม มันจะทำงานไม่ถูกต้องเมื่อโครงสร้าง Rails ของคุณต้องสื่อสารกับmoney
คุณสมบัติของ
ลองพิจารณาตัวอย่างต่อไปนี้:
class Cart < ActiveRecord::Base
monetize :amount_cents
end
นี่คืออ็อบเจ็กต์โมเดล Rails ที่ใช้งานได้จริง คุณสามารถใช้ร่วมกับฐานข้อมูล (รวมถึงชื่อแทนเมื่อคุณต้องการชื่อแอตทริบิวต์รุ่นอื่น), Mongoid, บริการเว็บ REST ฯลฯ
ฟีเจอร์ทั้งหมดที่เราเคยติดต่อมาก็มีผลกับอัญมณีนี้ด้วย โดยปกติ จำเป็นต้องตั้งค่าเพิ่มเติมเท่านั้นเพื่อให้ทำงาน ซึ่งควรวางไว้ใน config/initializers/money.rb ไฟล์:
MoneyRails.configure do |config|
# set the default currency
config.default_currency = :usd
end
การดำเนินการนี้จะตั้งค่าสกุลเงินเริ่มต้นเป็นสกุลเงินที่คุณระบุ อย่างไรก็ตาม ในระหว่างการพัฒนา มีโอกาสที่คุณอาจต้องดำเนินการแปลงการแลกเปลี่ยนหรือจัดการมากกว่าหนึ่งสกุลเงินตลอดทั้งรูปแบบ
ถ้าใช่ money-rails
ช่วยให้เราสามารถกำหนดค่าการกำหนดสกุลเงินระดับแบบจำลอง:
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
โปรดทราบว่าเมื่อตั้งค่าทุกอย่างเรียบร้อยแล้ว คุณจะใช้ประเภทเงินร่วมกับโครงการได้ง่ายมากๆ
สรุปผล
ในบล็อกโพสต์นี้ เราได้สำรวจตัวเลือกที่มีอยู่เพื่อจัดการกับมูลค่าเงินภายในระบบนิเวศของ Ruby และ Rails สรุปประเด็นสำคัญบางส่วนไว้ด้านล่าง:
- หากคุณกำลังจัดการกับการคำนวณเลขทศนิยม โดยเฉพาะอย่างยิ่งถ้าเป็นข้อมูลเงิน ให้ไปที่
BigDecimal
หรือmoney
ตัวอย่าง - พยายามยึดติดกับระบบเดียวเท่านั้นเพื่อหลีกเลี่ยงความไม่สอดคล้องกันเพิ่มเติมควบคู่ไปกับการพัฒนาของคุณ
- The
money
ห้องสมุดเป็นแกนหลักของระบบ RubyMoney ทั้งหมด และมีความแข็งแกร่งและตรงไปตรงมามากmoney-rails
เป็นเวอร์ชันเทียบเท่าสำหรับแอปพลิเคชัน Rails และmonetize
จำเป็นเมื่อใดก็ตามที่คุณต้องการแยกค่าจากค่าใดๆ เป็นMoney
วัตถุ - หลีกเลี่ยงการใช้
Float
. แม้ว่าแอปของคุณไม่จำเป็นต้องคำนวณอะไรในตอนนี้ แต่โอกาสที่นักพัฒนาซอฟต์แวร์ที่ไม่ได้รับคำแนะนำจะทำได้ในอนาคต คุณอาจจะไม่ได้อยู่ที่นั่นเพื่อหยุดมัน
โปรดจำไว้ว่าเอกสารที่เป็นทางการควรเป็นสิ่งที่ต้องมีเสมอ BigDecimal เต็มไปด้วยคำอธิบายและตัวอย่างการใช้งานที่ยอดเยี่ยม และโครงการอัญมณี RubyMoney ก็เช่นเดียวกัน