simple_decimal

Cookbook - Real-World Recipes

Recipe Collection

This cookbook contains ready-to-use code patterns for common financial and decimal arithmetic scenarios. Each recipe is complete and can be adapted to your needs.

Recipe: Invoice Calculator

Calculate invoice totals with line items, discounts, and tax.

class
    INVOICE_CALCULATOR

feature -- Access

    subtotal: SIMPLE_DECIMAL
            -- Running subtotal
        attribute
            create Result.make_zero
        end

feature -- Operations

    add_line_item (unit_price_str: STRING; quantity: INTEGER)
            -- Add a line item to the invoice
        local
            unit_price, qty, line_total: SIMPLE_DECIMAL
        do
            create unit_price.make (unit_price_str)
            create qty.make_from_integer (quantity)
            line_total := unit_price * qty
            subtotal := subtotal + line_total
        end

    apply_discount (percent_str: STRING)
            -- Apply percentage discount to subtotal
        local
            discount_rate: SIMPLE_DECIMAL
        do
            create discount_rate.make (percent_str)
            subtotal := subtotal.subtract_percent (discount_rate)
        end

    calculate_total (tax_rate_str: STRING): STRING
            -- Calculate final total with tax, return formatted
        local
            tax_rate, tax, total: SIMPLE_DECIMAL
        do
            create tax_rate.make (tax_rate_str)
            tax := (subtotal * tax_rate.from_percentage).round_cents
            total := subtotal + tax
            Result := total.to_currency_string
        end

end

-- Usage:
local
    calc: INVOICE_CALCULATOR
do
    create calc
    calc.add_line_item ("29.99", 3)    -- 3 items @ $29.99
    calc.add_line_item ("149.95", 1)   -- 1 item @ $149.95
    calc.apply_discount ("10")         -- 10% off
    print (calc.calculate_total ("8.25")) -- Add 8.25% tax
    -- Output: "$227.84"
end

Recipe: Restaurant Tip Calculator

Calculate tip and split the bill among multiple people.

class
    TIP_CALCULATOR

feature -- Calculation

    calculate_split (bill_str: STRING; tip_percent_str: STRING;
                     num_people: INTEGER): TUPLE [
                         tip: STRING;
                         total: STRING;
                         per_person: ARRAYED_LIST [STRING]]
            -- Calculate tip, total, and split per person
        local
            bill, tip_rate, tip, total: SIMPLE_DECIMAL
            splits: ARRAYED_LIST [SIMPLE_DECIMAL]
            amounts: ARRAYED_LIST [STRING]
        do
            create bill.make (bill_str)
            create tip_rate.make (tip_percent_str)

            -- Calculate tip and total
            tip := (bill * tip_rate.from_percentage).round_cents
            total := bill + tip

            -- Split among people
            splits := total.split (num_people)

            -- Convert to strings
            create amounts.make (num_people)
            across splits as s loop
                amounts.extend (s.to_currency_string)
            end

            Result := [tip.to_currency_string, total.to_currency_string, amounts]
        end

end

-- Usage:
local
    calc: TIP_CALCULATOR
    result: TUPLE [tip, total: STRING; per_person: ARRAYED_LIST [STRING]]
do
    create calc
    result := calc.calculate_split ("127.50", "20", 4)
    print ("Tip: " + result.tip)           -- Tip: $25.50
    print ("Total: " + result.total)       -- Total: $153.00
    print ("Per person: ")                 -- Per person:
    across result.per_person as p loop
        print (p)                            -- $38.25 each (4 people)
    end
end

Recipe: Compound Interest Calculator

Calculate compound interest with monthly compounding.

class
    INTEREST_CALCULATOR

feature -- Calculation

    compound_interest (principal_str: STRING;
                       annual_rate_str: STRING;
                       years: INTEGER): STRING
            -- Calculate compound interest (monthly compounding)
            -- Formula: A = P(1 + r/n)^(nt)
            -- P = principal, r = rate, n = 12 (monthly), t = years
        local
            principal, rate, monthly_rate: SIMPLE_DECIMAL
            base, factor, final_amount: SIMPLE_DECIMAL
            one, twelve: SIMPLE_DECIMAL
            total_periods: INTEGER
            i: INTEGER
        do
            create principal.make (principal_str)
            create rate.make (annual_rate_str)
            create one.make_one
            create twelve.make_from_integer (12)

            -- Monthly rate = annual rate / 12
            monthly_rate := rate.from_percentage / twelve

            -- Base = 1 + monthly_rate
            base := one + monthly_rate

            -- Calculate (1 + r/n)^(nt) iteratively
            total_periods := years * 12
            create factor.make_one
            from i := 1 until i > total_periods loop
                factor := factor * base
                i := i + 1
            end

            -- Final = principal * factor
            final_amount := (principal * factor).round_cents

            Result := final_amount.to_currency_string
        end

    interest_earned (principal_str, annual_rate_str: STRING;
                     years: INTEGER): STRING
            -- Calculate just the interest portion
        local
            principal, final_amount, interest: SIMPLE_DECIMAL
        do
            create principal.make (principal_str)
            create final_amount.make (compound_interest (principal_str,
                                                        annual_rate_str, years))
            interest := final_amount - principal
            Result := interest.to_currency_string
        end

end

-- Usage: $10,000 at 5% for 10 years
local
    calc: INTEREST_CALCULATOR
do
    create calc
    print (calc.compound_interest ("10000", "5", 10))
    -- Output: "$16,470.09"
    print (calc.interest_earned ("10000", "5", 10))
    -- Output: "$6,470.09"
end

Recipe: Currency Converter

Convert between currencies with precise exchange rates.

class
    CURRENCY_CONVERTER

feature -- Access

    rates: HASH_TABLE [SIMPLE_DECIMAL, STRING]
            -- Exchange rates relative to USD
        attribute
            create Result.make (10)
        end

feature -- Setup

    set_rate (currency: STRING; rate_str: STRING)
            -- Set exchange rate (how many of currency = 1 USD)
        local
            rate: SIMPLE_DECIMAL
        do
            create rate.make (rate_str)
            rates.force (rate, currency.as_upper)
        end

feature -- Conversion

    convert (amount_str: STRING; from_currency, to_currency: STRING): STRING
            -- Convert amount from one currency to another
        require
            valid_from: rates.has (from_currency.as_upper) or
                        from_currency.as_upper.is_equal ("USD")
            valid_to: rates.has (to_currency.as_upper) or
                      to_currency.as_upper.is_equal ("USD")
        local
            amount, usd_amount, result_amount: SIMPLE_DECIMAL
            from_rate, to_rate: SIMPLE_DECIMAL
            one: SIMPLE_DECIMAL
        do
            create amount.make (amount_str)
            create one.make_one

            -- Get rates (USD = 1.0)
            if from_currency.as_upper.is_equal ("USD") then
                from_rate := one
            else
                from_rate := rates [from_currency.as_upper]
            end

            if to_currency.as_upper.is_equal ("USD") then
                to_rate := one
            else
                to_rate := rates [to_currency.as_upper]
            end

            -- Convert: from -> USD -> to
            usd_amount := amount / from_rate
            result_amount := (usd_amount * to_rate).round_cents

            Result := result_amount.to_string
        end

end

-- Usage:
local
    conv: CURRENCY_CONVERTER
do
    create conv
    conv.set_rate ("EUR", "0.92")    -- 1 USD = 0.92 EUR
    conv.set_rate ("GBP", "0.79")    -- 1 USD = 0.79 GBP
    conv.set_rate ("JPY", "149.50")  -- 1 USD = 149.50 JPY

    print (conv.convert ("100", "USD", "EUR"))  -- "92.00"
    print (conv.convert ("100", "EUR", "USD"))  -- "108.70"
    print (conv.convert ("100", "EUR", "JPY"))  -- "16249.46"
end

Recipe: Payroll Calculator

Calculate gross pay, deductions, and net pay.

class
    PAYROLL_CALCULATOR

feature -- Calculation

    calculate_paycheck (
        hourly_rate_str: STRING;
        regular_hours, overtime_hours: INTEGER;
        federal_tax_rate_str, state_tax_rate_str: STRING;
        insurance_deduction_str: STRING
    ): TUPLE [gross, federal_tax, state_tax, insurance, net: STRING]
            -- Calculate complete paycheck breakdown
        local
            hourly_rate, regular_pay, overtime_pay, gross_pay: SIMPLE_DECIMAL
            federal_rate, state_rate, federal_tax, state_tax: SIMPLE_DECIMAL
            insurance, total_deductions, net_pay: SIMPLE_DECIMAL
            overtime_multiplier: SIMPLE_DECIMAL
        do
            create hourly_rate.make (hourly_rate_str)
            create overtime_multiplier.make ("1.5")

            -- Calculate gross pay
            regular_pay := hourly_rate *
                           create {SIMPLE_DECIMAL}.make_from_integer (regular_hours)
            overtime_pay := (hourly_rate * overtime_multiplier) *
                            create {SIMPLE_DECIMAL}.make_from_integer (overtime_hours)
            gross_pay := regular_pay + overtime_pay

            -- Calculate taxes
            create federal_rate.make (federal_tax_rate_str)
            create state_rate.make (state_tax_rate_str)
            federal_tax := (gross_pay * federal_rate.from_percentage).round_cents
            state_tax := (gross_pay * state_rate.from_percentage).round_cents

            -- Insurance deduction
            create insurance.make (insurance_deduction_str)

            -- Calculate net
            total_deductions := federal_tax + state_tax + insurance
            net_pay := gross_pay - total_deductions

            Result := [
                gross_pay.to_currency_string,
                federal_tax.to_currency_string,
                state_tax.to_currency_string,
                insurance.to_currency_string,
                net_pay.to_currency_string
            ]
        end

end

-- Usage: $25/hour, 40 regular + 10 overtime
local
    payroll: PAYROLL_CALCULATOR
    check: TUPLE [gross, federal_tax, state_tax, insurance, net: STRING]
do
    create payroll
    check := payroll.calculate_paycheck (
        "25.00",      -- hourly rate
        40, 10,        -- regular hours, overtime hours
        "22",         -- 22% federal tax
        "5",          -- 5% state tax
        "125.00"      -- insurance per period
    )
    print ("Gross: " + check.gross)           -- Gross: $1,375.00
    print ("Federal Tax: " + check.federal_tax) -- Federal Tax: $302.50
    print ("State Tax: " + check.state_tax)     -- State Tax: $68.75
    print ("Insurance: " + check.insurance)     -- Insurance: $125.00
    print ("Net Pay: " + check.net)             -- Net Pay: $878.75
end

Recipe: Shopping Cart with Discounts

A shopping cart that supports different discount types.

class
    SHOPPING_CART

feature -- Access

    items: ARRAYED_LIST [TUPLE [name: STRING; price: SIMPLE_DECIMAL; qty: INTEGER]]
        attribute
            create Result.make (10)
        end

    discount_code: detachable STRING

feature -- Modification

    add_item (name, price_str: STRING; quantity: INTEGER)
        local
            price: SIMPLE_DECIMAL
        do
            create price.make (price_str)
            items.extend ([name, price, quantity])
        end

    apply_code (code: STRING)
        do
            discount_code := code.as_upper
        end

feature -- Calculation

    subtotal: SIMPLE_DECIMAL
        local
            item_total: SIMPLE_DECIMAL
        do
            create Result.make_zero
            across items as item loop
                item_total := item.price *
                    create {SIMPLE_DECIMAL}.make_from_integer (item.qty)
                Result := Result + item_total
            end
        end

    discount_amount: SIMPLE_DECIMAL
        local
            sub: SIMPLE_DECIMAL
            discount_rate, flat_discount: SIMPLE_DECIMAL
        do
            create Result.make_zero
            sub := subtotal

            if attached discount_code as code then
                if code.is_equal ("SAVE10") then
                    -- 10% off
                    create discount_rate.make ("10")
                    Result := (sub * discount_rate.from_percentage).round_cents
                elseif code.is_equal ("SAVE20") then
                    -- 20% off
                    create discount_rate.make ("20")
                    Result := (sub * discount_rate.from_percentage).round_cents
                elseif code.is_equal ("FLAT25") then
                    -- $25 off if subtotal >= $100
                    create flat_discount.make ("25.00")
                    if sub >= create {SIMPLE_DECIMAL}.make ("100") then
                        Result := flat_discount
                    end
                end
            end
        end

    total: SIMPLE_DECIMAL
        do
            Result := (subtotal - discount_amount).round_cents
        end

    print_receipt
        do
            print ("=== RECEIPT ===%N")
            across items as item loop
                print (item.name + " x" + item.qty.out +
                       ": " + (item.price *
                       create {SIMPLE_DECIMAL}.make_from_integer (item.qty)
                       ).to_currency_string + "%N")
            end
            print ("Subtotal: " + subtotal.to_currency_string + "%N")
            if not discount_amount.is_zero then
                print ("Discount: -" + discount_amount.to_currency_string + "%N")
            end
            print ("TOTAL: " + total.to_currency_string + "%N")
        end

end

-- Usage:
local
    cart: SHOPPING_CART
do
    create cart
    cart.add_item ("Widget", "29.99", 2)
    cart.add_item ("Gadget", "49.99", 1)
    cart.apply_code ("SAVE10")
    cart.print_receipt
    -- === RECEIPT ===
    -- Widget x2: $59.98
    -- Gadget x1: $49.99
    -- Subtotal: $109.97
    -- Discount: -$11.00
    -- TOTAL: $98.97
end

Recipe: Loan Payment Calculator

Calculate monthly payments and amortization schedule.

class
    LOAN_CALCULATOR

feature -- Calculation

    monthly_payment (principal_str, annual_rate_str: STRING;
                     term_months: INTEGER): STRING
            -- Calculate monthly payment using standard formula:
            -- M = P * (r(1+r)^n) / ((1+r)^n - 1)
            -- P = principal, r = monthly rate, n = number of payments
        local
            principal, annual_rate, monthly_rate: SIMPLE_DECIMAL
            one, twelve: SIMPLE_DECIMAL
            one_plus_r, one_plus_r_n: SIMPLE_DECIMAL
            numerator, denominator, payment: SIMPLE_DECIMAL
            i: INTEGER
        do
            create principal.make (principal_str)
            create annual_rate.make (annual_rate_str)
            create one.make_one
            create twelve.make_from_integer (12)

            -- Monthly rate = annual rate / 12 / 100
            monthly_rate := annual_rate / twelve /
                            create {SIMPLE_DECIMAL}.make_from_integer (100)

            -- (1 + r)
            one_plus_r := one + monthly_rate

            -- (1 + r)^n
            create one_plus_r_n.make_one
            from i := 1 until i > term_months loop
                one_plus_r_n := one_plus_r_n * one_plus_r
                i := i + 1
            end

            -- numerator = r * (1+r)^n
            numerator := monthly_rate * one_plus_r_n

            -- denominator = (1+r)^n - 1
            denominator := one_plus_r_n - one

            -- payment = P * numerator / denominator
            payment := (principal * numerator / denominator).round_cents

            Result := payment.to_currency_string
        end

    total_cost (principal_str, annual_rate_str: STRING;
                term_months: INTEGER): STRING
            -- Total amount paid over loan lifetime
        local
            payment, total: SIMPLE_DECIMAL
        do
            create payment.make (monthly_payment (principal_str, annual_rate_str,
                                                  term_months))
            total := payment * create {SIMPLE_DECIMAL}.make_from_integer (term_months)
            Result := total.to_currency_string
        end

    total_interest (principal_str, annual_rate_str: STRING;
                    term_months: INTEGER): STRING
            -- Total interest paid
        local
            principal, total, interest: SIMPLE_DECIMAL
        do
            create principal.make (principal_str)
            create total.make (total_cost (principal_str, annual_rate_str, term_months))
            interest := total - principal
            Result := interest.to_currency_string
        end

end

-- Usage: $250,000 mortgage at 6.5% for 30 years
local
    loan: LOAN_CALCULATOR
do
    create loan
    print ("Monthly payment: " +
           loan.monthly_payment ("250000", "6.5", 360))
    -- Monthly payment: $1,580.17

    print ("Total cost: " +
           loan.total_cost ("250000", "6.5", 360))
    -- Total cost: $568,861.20

    print ("Total interest: " +
           loan.total_interest ("250000", "6.5", 360))
    -- Total interest: $318,861.20
end

Recipe: Price Formatting Patterns

Common patterns for displaying prices and numbers.

class
    PRICE_FORMATTER

feature -- Formatting

    format_price (amount_str: STRING): STRING
            -- Standard price: "$1,234.56"
        local
            amount: SIMPLE_DECIMAL
        do
            create amount.make (amount_str)
            Result := amount.to_currency_string
        end

    format_price_no_cents (amount_str: STRING): STRING
            -- Round price: "$1,235"
        local
            amount: SIMPLE_DECIMAL
        do
            create amount.make (amount_str)
            Result := "$" + amount.round (0).to_string
        end

    format_with_symbol (amount_str, symbol: STRING): STRING
            -- Custom symbol: "EUR 1,234.56"
        local
            amount: SIMPLE_DECIMAL
            formatted: STRING
        do
            create amount.make (amount_str)
            formatted := amount.to_currency_string
            -- Replace $ with custom symbol
            formatted.replace_substring_all ("$", symbol + " ")
            Result := formatted
        end

    format_percentage (decimal_str: STRING; places: INTEGER): STRING
            -- Format as percentage: "8.25%"
        local
            amount: SIMPLE_DECIMAL
        do
            create amount.make (decimal_str)
            Result := amount.as_percentage.round (places).to_string + "%%"
        end

    format_accounting (amount_str: STRING): STRING
            -- Accounting format: negative in parens "(1,234.56)"
        local
            amount: SIMPLE_DECIMAL
        do
            create amount.make (amount_str)
            if amount.is_negative then
                Result := "(" + amount.absolute.to_currency_string + ")"
            else
                Result := amount.to_currency_string
            end
        end

end

-- Usage:
local
    fmt: PRICE_FORMATTER
do
    create fmt
    print (fmt.format_price ("1234.56"))           -- $1,234.56
    print (fmt.format_price_no_cents ("1234.56"))  -- $1235
    print (fmt.format_with_symbol ("1234.56", "EUR")) -- EUR 1,234.56
    print (fmt.format_percentage ("0.0825", 2))   -- 8.25%
    print (fmt.format_accounting ("-1234.56"))    -- ($1,234.56)
end

Recipe: Input Validation

Safely parse and validate user-entered amounts.

class
    AMOUNT_VALIDATOR

feature -- Validation

    is_valid_amount (input: STRING): BOOLEAN
            -- Check if input can be parsed as a valid decimal
        local
            test: SIMPLE_DECIMAL
            retried: BOOLEAN
        do
            if not retried then
                if input /= Void and then not input.is_empty then
                    create test.make (input)
                    Result := not test.is_nan
                end
            end
        rescue
            retried := True
            Result := False
            retry
        end

    parsed_amount (input: STRING): detachable SIMPLE_DECIMAL
            -- Parse input, return Void if invalid
        local
            retried: BOOLEAN
        do
            if not retried and is_valid_amount (input) then
                create Result.make (input)
            end
        rescue
            retried := True
            retry
        end

    validated_currency (input: STRING;
                        min_str, max_str: STRING): detachable SIMPLE_DECIMAL
            -- Parse and validate within range, return Void if invalid
        local
            amount, min_val, max_val: SIMPLE_DECIMAL
        do
            if attached parsed_amount (input) as amt then
                create min_val.make (min_str)
                create max_val.make (max_str)
                if amt >= min_val and amt <= max_val then
                    Result := amt
                end
            end
        end

end

-- Usage:
local
    validator: AMOUNT_VALIDATOR
    user_input: STRING
do
    create validator
    user_input := "$1,234.56"

    if validator.is_valid_amount (user_input) then
        print ("Valid amount")
    end

    if attached validator.validated_currency (user_input, "0", "10000") as amt then
        print ("Amount in range: " + amt.to_currency_string)
    else
        print ("Amount out of range or invalid")
    end
end