liveBook · Manning

Decimal

As explained in Learn Go with Pocket-Sized Projects

Decimal is the book's custom Go type for representing the numeric quantity of a monetary amount independently of its currency.

A money converter needs a numeric quantity as well as a currency. In Go, an Amount holds a Decimal quantity and a Currency. Keeping Decimal as a custom type avoids exposing its storage choice and leaves room to change that choice without changing the public API. section 6.1.1 section 6.2.4

Why not use a float? Floating-point values can introduce imprecision, and a currency may have a smallest meaningful subunit: 86.32456 CAD, for example, is finer than a Canadian cent. Decimal addresses the storage problem, but the implementation shown here does not enforce currency-specific subunits. section 6.2 section 6.2.4

Listing 6.6 decimal.go: Decimal struct implementation
// Decimal can represent a floating-point number with a fixed precision.
// example: 
// 1.52 = 152 * 10^(-2) will be stored as {152, 2} #1
type Decimal struct {
    // subunits is the amount of subunits. 
    // Multiply it by the precision to get the real value
    subunits int64 #2
    // Number of "subunits" in a unit, expressed as a power of 10.
    precision byte #3
}

The private fields store an integer count of subunits and a base-10 precision. Thus 1.52 is stored as 152 subunits with precision 2. section 6.2.4

The precision is a byte, allowing different numbers of fractional digits without fixing Decimal to cents. A value such as 0.00000001 BTC needs more digits than a currency commonly divided into hundredths. Keeping the fields private also means callers cannot construct a Decimal by depending on those fields. section 6.2.4

Listing 6.9 decimal.go: ParseDecimal function
// maxDecimal value is a thousand billion, using the short scale -- 10^12.
const maxDecimal = 1e12

// ParseDecimal converts a string into its Decimal representation.
// It assumes there is up to one decimal separator, 
// and that the separator is '.' (full stop character).
func ParseDecimal(value string) (Decimal, error) {
    intPart, fracPart, _ := strings.Cut(value, ".") #1

    subunits, err := strconv.ParseInt(
        intPart+fracPart, 10, 64) #2
    if err != nil {
        return Decimal{}, fmt.Errorf("%w: %s", 
            ErrInvalidDecimal, err.Error()) #3
    }

    if subunits > maxDecimal {
        return Decimal{}, ErrTooLarge
    }

    precision := byte(len(fracPart))#4
     return Decimal{subunits: subunits, precision: precision}, nil
}

ParseDecimal accepts a string rather than a float. It separates the integer and fractional text at '.', parses their joined digits as an int64, and uses the fractional text’s length as the precision. It returns errors for invalid integers or quantities above 10^12. section 6.2.4

Parsing 1.52

ParseDecimal("1.52") produces Decimal{subunits: 152, precision: 2}. An internal table-driven test checks that case alongside values with no fractional digits, invalid input, and an oversized quantity. The private simplify method can remove trailing fractional zeroes by reducing both subunits and precision. section 6.2.4

The parser assumes at most one '.' separator; its shown code does not separately validate that assumption. Its precision follows the input string, not the currency. Keep those limits in mind when using Decimal to represent money. section 6.2.4 section 6.2

How Learn Go with Pocket-Sized Projects covers it

How deep it goes
Works through private storage, string parsing, errors, tests, and simplification; does not show enforcement of currency-specific fractional limits.
Examples
Uses 1.52 as 152 subunits at precision 2, contrasts Canadian cents with finer input, and tests invalid and oversized strings.
What it assumes
Assumes familiarity with Go structs, package visibility, and tests while explaining why a monetary quantity needs a custom type.
What you can do after
Build it from scratch as a private Go representation parsed from strings, while recognizing its stated parsing and currency-precision limits.
How it connects
Connects Decimal to Amount, Currency, floating-point precision, encapsulation, and table-driven testing.

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.