liveBook · Manning

Monetary amount

As explained in Learn Go with Pocket-Sized Projects

An amount is a monetary value whose numeric representation is interpreted with a currency and subject to supported precision constraints.

A monetary amount pairs a decimal quantity with a currency. In a currency converter, that pair is the input to Convert and the form of its result: 34.98 USD can become an amount in EUR. Keeping Amount in a reusable money package separates this domain logic from the command-line interface. section 6.1 section 6.1.1

Represent the quantity with fixed precision rather than defaulting to floating-point arithmetic. Decimal stores an int64 count of subunits and a precision: 1.52 is stored as 152 with precision 2. ParseDecimal accepts a string, avoiding floating-point imprecision at input. Amount holds that Decimal alongside a Currency; its fields are unexported so callers cannot change a constructed value directly. section 6.2.2 section 6.2.4 section 6.1.1

Reject incompatible precision

Canadian dollars have cent precision. A quantity of 19.8875 has four decimal places, so NewAmount must return ErrTooPrecise rather than create that amount. When the quantity fits, the constructor sets its precision to the currency’s precision. section 6.2.6

Listing 6.15 amount.go: NewAmount function
const (
    // ErrTooPrecise is returned if the number is 
    // too precise for the currency.
    ErrTooPrecise = Error("quantity is too precise")
)

// NewAmount returns an Amount of money.
func NewAmount(quantity Decimal, currency Currency) (Amount, error) {
    if quantity.precision > currency.precision { #1
        // In order to avoid converting 0.00001 cent, let's exit now.
        return Amount{}, ErrTooPrecise
    }
    quantity.precision = currency.precision #2

    return Amount{quantity: quantity, currency: currency}, nil
}

The constructor checks currency precision before returning an Amount. section 6.2.6

Keep exchange-rate application outside Amount: an amount describes money, while conversion needs a rate and a target currency. Applying the rate creates a new amount without changing the input. The result uses the target currency’s precision; the shown implementation discards excess fractional subunits or adds subunits when more precision is needed. Convert then validates the result against its supported size and precision limits. section 6.3.1 section 6.3.2

Test the boundaries as well as ordinary conversions: reject an overly precise amount, check outputs at different target precisions, and include large values and very small rates. Test helpers should stop on parsing or construction errors instead of silently using zero values. Fixed precision avoids some floating-point problems, but the implementation still imposes a bounded range of supported values. section 6.2.6 section 6.3.1 section 6.3.2

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through representation, construction, conversion, validation, and tests, while retaining explicit supported-range limitations.
Examples
Uses 19.8875 Canadian dollars as an invalid amount and conversion tests covering target precision, large values, and small rates.
What it assumes
Assumes familiarity with Go packages, structs, and tests while developing fixed-precision Decimal parsing and Amount construction step by step.
What you can do after
Build it from scratch as a validated Go Amount type and test its construction and conversion boundaries.
How it connects
Connects Amount to Decimal, Currency, fixed precision, exchange rates, and immutable structs.

Also explained in

Compare how 2 books explain monetary amount →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Go with Pocket-Sized Projects. The section links point to the parts of the book it draws on.