Exact Numbers, Dates and Errors
How numbers and dates load and come back, where rounding happens, and every way a blueprint or a run can fail.
After reading this guide, you will know:
- Why
0.1 + 0.2is0.3in a blueprint. - What type a number or date comes back as, from Ruby and from the CLI.
- Where each kind of failure surfaces, and what it is called.
Decimals Stay Exact
Decimals stay exact from input to output:
flowchart LR
in1["YAML 2.75"] --> bd["BigDecimal('2.75')"]
in2["JSON 7.5"] --> bd
in3["'2.75' in a definition"] --> bd
bd --> calc["arithmetic and ROUND in BigDecimal"]
calc --> outrb["Ruby API: BigDecimal"]
calc --> outcli["CLI: '20.75', whole numbers as '60'"]
A decimal in a blueprint, a cases file or JSON facts loads as a BigDecimal, so there is no float
rounding anywhere:
calculations:
money:
inputs:
price: {type: number}
qty: {type: number}
paid_on: {type: date}
steps:
- {name: subtotal, formula: price * qty}
- {name: gst, formula: 'ROUND(subtotal / 11, 2)'}
- {name: thirds, formula: subtotal / 3}
- {name: point_three, formula: 0.1 + 0.2}
- {name: due_on, formula: paid_on + 30}
outputs: [subtotal, gst, thirds, point_three, due_on]
$ echo '{"price": 19.99, "qty": 3, "paid_on": "2026-07-01"}' | rmt run money.yml --json
{
"subtotal": "59.97",
"gst": "5.45",
"thirds": "19.99",
"point_three": "0.3",
"due_on": "2026-07-31"
}
In plain Ruby floats, 0.1 + 0.2 is 0.30000000000000004. In a blueprint it is 0.3.
Rounding
Nothing is rounded unless a formula says so. ROUND(x, 2) rounds half away from zero, so 11.875 is
11.88 and -2.5 is -3. ROUNDUP and ROUNDDOWN round to whole numbers. Division is exact to 32
significant digits, so 1 / 3 is 0.33333333333333333333333333333333.
Round where the source rounds, once. A bill that shows each line to the cent and totals the rounded lines should round each line, then add them up, and not round the total again.
What Comes Back
| From | Numbers | Dates |
|---|---|---|
the reason_me_this gem in Ruby |
BigDecimal, or Integer for whole numbers |
Date |
| the CLI and the hosted API, as JSON | exact strings: "59.97", "60" |
"2026-07-31" |
| the CLI as text | the same digits, unquoted | 2026-07-31 |
a case’s expect |
compared by value, so 7, 7.00 and '7' match |
compared as dates |
IRB prints a BigDecimal as 0.5997e2. The examples in these docs write it as 59.97 to be easier to
read, but it is still a BigDecimal, not a Float.
answer = ReasonMeThis.run('money', {price: 19.99, qty: 3, paid_on: Date.new(2026, 7, 1)})
answer.to_h # => {subtotal: 59.97, gst: 5.45, thirds: 19.99, point_three: 0.3, due_on: Date(2026-07-31)}
answer.subtotal.to_s('F') # => "59.97"
Read the CLI’s strings back with BigDecimal(value), not to_f, or the exactness is lost at the last
step. The reason_me_this gem does this for you.
Dates
Dates written YYYY-MM-DD load as Date in blueprints, definitions, cases and facts, from YAML and from
JSON strings alike. Any text fact written that way is read as a date, so a string input cannot be given
"2026-07-01".
Where Failures Surface
| Failure | From rmt |
From the reason_me_this gem |
|---|---|---|
| A blueprint with problems | rmt check, exit 2 |
ReasonMeThis::Rejected from push |
| Facts that do not match the inputs | rmt run, exit 1 |
ReasonMeThis::InvalidFacts, with problems |
| A step failing, including a bad row of a list or no matching table row | "fight > rounds > round > exchanged: ...", exit 1 |
ReasonMeThis::StepFailed, with the same path |
| A case that fails | rmt test, exit 3 |
ReasonMeThis::Rejected from push or publish |
Every error the gem raises is a ReasonMeThis::Error, so one rescue covers a run:
begin
ReasonMeThis.run('bill', facts)
rescue ReasonMeThis::Error => e
e.message
end
A formula that divides by zero fails its step: d > y: divides by zero. Guard divisors that
can be zero with IF.
Next Steps
- CLI Commands and Exit Codes - Every exit code in one place.
- Formulas and Functions - Rounding and arithmetic in formulas.