ruby
43 lines · 7 steps
Handling money safely with BigDecimal in Ruby
A module that converts, rounds, taxes, and splits money using exact decimal arithmetic instead of error-prone floats.
Explained by
highlit
1require "bigdecimal"
2
3module Money
4 CENTS = BigDecimal("0.01")
5
6 module_function
7
8 def to_decimal(amount)
9 case amount
10 when BigDecimal then amount
11 when Integer then BigDecimal(amount)
12 when Float then BigDecimal(amount.to_s)
13 when String then BigDecimal(amount)
14 else
15 raise ArgumentError, "cannot convert #{amount.class} to money"
16 end
17 end
18
19 def round(amount)
20 to_decimal(amount).round(2, BigDecimal::ROUND_HALF_EVEN)
21 end
22
23 def apply_tax(subtotal, rate)
24 tax = to_decimal(subtotal) * to_decimal(rate)
25 round(tax)
26 end
27
28 def split(total, ways)
29 raise ArgumentError, "ways must be positive" unless ways.positive?
30
31 cents = (to_decimal(total) / CENTS).round
32 base, remainder = cents.divmod(ways)
33
34 Array.new(ways) do |i|
35 share = i < remainder ? base + 1 : base
36 (share * CENTS)
37 end
38 end
39
40 def format(amount)
41 format("$%.2f", round(amount))
42 end
43end
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Representing money as BigDecimal avoids the rounding errors floats introduce, especially when parsing floats via their string form.
- 2Banker's rounding (ROUND_HALF_EVEN) reduces systematic bias when rounding many monetary values.
- 3Splitting money in integer cents and distributing the remainder guarantees the shares add back to the exact total.
Related explainers
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
ruby
class LogAggregator BUCKET_FORMAT = "%Y-%m-%dT%H:%M" def initialize(entries)
Bucketing log entries by the minute in Ruby
aggregation
hashing
enumerable
Intermediate
5 steps
ruby
class WeeklySignupsReport DEFAULT_WEEKS = 12 def initialize(weeks: DEFAULT_WEEKS, source: User.all)
Building a weekly signups report in Rails
service object
aggregation
group by
Intermediate
7 steps
ruby
class ApplicationController < ActionController::Base EXPERIMENTS = { checkout_button_color: %w[control blue green], onboarding_flow: %w[control streamlined]
How A/B test cohorts are assigned in Rails
a-b-testing
cookies
hashing
Intermediate
8 steps
ruby
class SnippetHighlighter CONTEXT_RADIUS = 60 MAX_TERMS = 8
Building search-result snippets in Ruby
regular-expressions
text-processing
search
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/handling-money-safely-with-bigdecimal-in-ruby-explained-ruby-ce97/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.