Introduction
This guide will teach you everything you need to know to effectively use simple_decimal in your Eiffel applications. We'll start with the basics and progressively cover more advanced topics.
Why Decimal Arithmetic Matters
If you've ever wondered why your financial calculations are "close but not quite right," binary floating-point is likely the culprit. Computers store numbers in binary, and many common decimal fractions (like 0.1) cannot be represented exactly in binary.
The Classic Example
In virtually every programming language using binary floating-point:
0.1 + 0.2 = 0.30000000000000004
This isn't a bug - it's fundamental to how binary floating-point works. For financial applications, this is unacceptable.
Getting Started
Installation
- Set the environment variable pointing to the library:
# Unix/Linux/macOS export SIMPLE_EIFFEL=/d/prod # Windows set SIMPLE_EIFFEL=D:\prod - Add to your ECF configuration file:
<library name="simple_decimal" location="$SIMPLE_EIFFEL/simple_decimal/simple_decimal.ecf"/>
Your First Decimal
local
price: SIMPLE_DECIMAL
do
-- Create from string (most precise method)
create price.make ("19.99")
-- Display it
print (price.out) -- "19.99"
print (price.to_currency_string) -- "$19.99"
end
Creating Decimals
SIMPLE_DECIMAL offers several creation methods for different scenarios:
From String (Recommended)
Creating from strings is the most precise method because the value is parsed exactly as written:
create price.make ("19.99")
create tax_rate.make ("0.0825") -- 8.25% as decimal
create negative.make ("-42.50")
create large.make ("1234567890.123456789")
Smart String Parsing
The parser automatically handles currency symbols and thousand separators:
create price.make ("$1,234.56") -- Creates 1234.56
create price.make ("-$99.99") -- Creates -99.99
create price.make ("1,000,000") -- Creates 1000000
From Integer
create count.make_from_integer (42)
create negative.make_from_integer (-100)
From Dollars and Cents
Convenient for currency applications:
create amount.make_currency (19, 99) -- Creates 19.99
create amount.make_currency (0, 50) -- Creates 0.50
create amount.make_currency (-5, 25) -- Creates -5.25
Special Values
create zero.make_zero -- Creates 0
create one.make_one -- Creates 1
Avoid: Creating from DOUBLE
While make_from_double exists, avoid it when precision matters:
-- NOT recommended for financial work:
create d.make_from_double (0.1)
-- The imprecision already exists in the DOUBLE literal!
Use string creation instead: create d.make ("0.1")
Arithmetic Operations
All arithmetic operations return new SIMPLE_DECIMAL objects. The original values are never modified (immutable pattern).
Basic Operations
local
a, b, result: SIMPLE_DECIMAL
do
create a.make ("100.00")
create b.make ("25.50")
result := a + b -- Addition: 125.50
result := a - b -- Subtraction: 74.50
result := a * b -- Multiplication: 2550.00
result := a / b -- Division: 3.921568...
end
Integer Division and Modulo
create a.make ("100")
create b.make ("30")
result := a // b -- Integer division: 3
result := a \\ b -- Modulo (remainder): 10
Other Operations
result := price.negate -- Flip sign: 19.99 -> -19.99
result := price.absolute -- Absolute value: -19.99 -> 19.99
result := price.power (2) -- Exponentiation: 10^2 = 100
Chaining Operations
Because operations return new decimals, you can chain them:
-- Calculate: (price * quantity) - discount
total := (price * quantity) - discount
-- Calculate: subtotal + tax, rounded to cents
total := (subtotal + tax).round_cents
Comparison
SIMPLE_DECIMAL implements COMPARABLE, so you can use standard comparison operators:
if price < budget then
print ("Affordable")
end
if balance >= minimum then
print ("Account in good standing")
end
if a.is_equal (b) then
print ("Values are equal")
end
Available Comparisons
<- Less than<=- Less than or equal>- Greater than>=- Greater than or equalis_equal- Equality test
Status Queries
if amount.is_zero then ...
if amount.is_negative then ...
if amount.is_positive then ...
if amount.is_integer then ...
if amount.is_nan then ... -- Not a Number
if amount.is_infinity then ...
Rounding
Financial applications often require specific rounding behavior. SIMPLE_DECIMAL provides multiple rounding modes:
Round to N Decimal Places
-- Default rounding (banker's rounding / half-even)
result := price.round (2) -- 19.995 -> 20.00
result := price.round (0) -- 19.5 -> 20 (rounds to even)
-- Shortcut for currency (2 decimal places)
result := price.round_cents -- Same as round(2)
Rounding Modes
| Method | Description | 2.5 becomes | -2.5 becomes |
|---|---|---|---|
round(n) |
Banker's rounding (half to even) | 2 | -2 |
round_up(n) |
Away from zero | 3 | -3 |
round_down(n) |
Toward zero (truncate) | 2 | -2 |
round_ceiling(n) |
Toward positive infinity | 3 | -2 |
round_floor(n) |
Toward negative infinity | 2 | -3 |
truncate |
Remove fractional part | 2 | -2 |
When to Use Which
- Banker's rounding (default) - General financial calculations, statistical neutrality
- Round up - When you must ensure a minimum (e.g., fees, taxes)
- Round down - When you must not exceed (e.g., available balance)
- Ceiling/Floor - When direction matters more than magnitude
Output Formatting
Basic Output
create amount.make ("1234.567")
print (amount.out) -- "1234.567"
print (amount.to_string) -- "1234.567"
Currency Formatting
create amount.make ("1234.56")
print (amount.to_currency_string) -- "$1,234.56"
create negative.make ("-99.99")
print (negative.to_currency_string) -- "-$99.99"
Extracting Parts
create price.make ("19.95")
print (price.dollars) -- 19 (INTEGER)
print (price.cents) -- 95 (INTEGER)
Type Conversions
-- To integer (truncates)
i := price.to_integer
-- To double (may lose precision)
d := price.to_double
Financial Operations
SIMPLE_DECIMAL includes operations specifically designed for financial calculations:
Percentage Calculations
local
price, tax_rate, discount, result: SIMPLE_DECIMAL
do
create price.make ("100.00")
create tax_rate.make ("8.25") -- 8.25%
create discount.make ("15.0") -- 15%
-- Add 8.25% tax to price
result := price.add_percent (tax_rate) -- 108.25
-- Subtract 15% discount from price
result := price.subtract_percent (discount) -- 85.00
end
Percentage Conversions
create rate.make ("8.25")
-- Convert percentage to decimal (for multiplication)
decimal_rate := rate.from_percentage -- 0.0825
-- Convert decimal to percentage (for display)
create decimal_rate.make ("0.0825")
percent := decimal_rate.as_percentage -- 8.25
Calculate What Percent One Value Is of Another
create tip.make ("15.00")
create bill.make ("75.00")
percentage := tip.percent_of (bill) -- 20.0 (tip is 20% of bill)
Splitting Bills
The split feature divides an amount into equal parts while handling the remainder correctly:
create bill.make ("100.00")
parts := bill.split (3)
-- Result: [$33.34, $33.33, $33.33]
-- Sum equals exactly $100.00
The extra penny goes to the first parts, ensuring the total always equals the original amount exactly.
Best Practices
1. Always Create from Strings
When precision matters, always create decimals from strings:
-- GOOD: Exact representation
create price.make ("0.1")
-- AVOID: Imprecision may already exist
create price.make_from_double (0.1)
2. Round at the End
Perform calculations first, then round the final result:
-- GOOD: Maximum precision during calculation
total := (subtotal * tax_rate).round_cents
-- AVOID: Rounding too early loses precision
tax := (subtotal * tax_rate).round_cents
total := subtotal + tax -- May have accumulated error
3. Use Appropriate Rounding
Choose rounding mode based on business requirements:
-- Tax calculations: often round up (in favor of tax authority)
tax := calculated_tax.round_up (2)
-- Available balance: round down (don't overspend)
available := balance.round_down (2)
4. Handle Edge Cases
-- Check before division
if not divisor.is_zero then
result := amount / divisor
end
-- Check for special values
if not amount.is_nan and not amount.is_infinity then
i := amount.to_integer
end
5. Use Contracts
SIMPLE_DECIMAL has extensive contracts. Enable assertions during development:
-- In your ECF:
<option>
<assertions precondition="true" postcondition="true"/>
</option>
Next Steps
- API Reference - Complete documentation of all features
- Cookbook - Real-world examples and patterns
- Architecture - Understand the internal design