money

package module
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: BSD-3-Clause Imports: 8 Imported by: 0

README

money

The one exact money type for Go — an Amount in a Currency, backed by hanzoai/decimal (big.Int fixed-point).

Martin Fowler's Money pattern (never floats, integer-exact, Add/Sub/Split/Allocate, same-currency safety) without the precision ceiling. int64/uint64 money libraries cap around 19 digits (~$9.20 at 18 decimals); this holds a fiat cent and an 18-decimal on-chain balance exactly.

price, _ := money.Parse("6.60", money.USD)
cost := price.Mul(decimal.New(200, 0)).Mul(decimal.MustParse("0.000001")) // "0.00132", never 0
bal, _ := money.FromCents(100).Sub(cost)                                  // "0.99868"

money.FromCents(123456).Display()             // "$1,234.56"
parts, _ := money.FromCents(10000).Allocate(50, 30, 20) // $50 / $30 / $20, nothing lost
a, _ := money.ParseAmount("$1,234.56")        // symbol/code/separator aware
money.FromHUSD(wei)                            // 18-dec on-chain stablecoin

Features

  • Exact, uncapped: big.Int core — fiat cents to 18-decimal crypto wei, no float, no ceiling
  • Arithmetic: Add Sub Mul Neg Cmp Sign, same-currency safety (zero-value agnostic)
  • Allocation: Split(n) and Allocate(ratios…) — no sub-unit ever lost
  • Aggregation: Sum Min Max Avg
  • Exchange: rate table + Convert / ConvertWithRate
  • Parsing: ParseAmount ($1,234.56, EUR 250,75, £99.99), European decimal-comma, ParseDecimal
  • Formatting: Display (symbol + thousands separators), AsMajorUnits
  • Currencies: ISO-4217 set (symbol, numeric code) + crypto (USDC/USDT/BTC/ETH/HUSD) + Register custom
  • Integration: json.Marshaler/Unmarshaler, sql.Scanner/driver.Valuer

Packages

Single package github.com/hanzoai/money (Amount, Currency, Exchange, parse/format/aggregate), on github.com/hanzoai/decimal.

Original work (big.Int core written from scratch, not a fork). BSD-3-Clause.

Documentation

Overview

Package money is the ONE exact money value for the Hanzo stack: an amount in a currency, backed by github.com/hanzoai/decimal (big.Int fixed-point). Unlike the int64/uint64 money libraries (which cap around 19 digits — about $9.20 at 18 decimals), this holds a fiat cent AND an 18-decimal on-chain balance with equal exactness, because the value is a big.Int decimal. No float64 ever touches money.

The design is the community-standard money pattern (never floats, integer-exact, Amount + Currency, Split/Allocate so a division loses no sub-unit) with the precision ceiling removed. An Amount is IMMUTABLE. Arithmetic between two Amounts requires the SAME currency, so you can never silently add USD to EUR.

Index

Constants

This section is empty.

Variables

View Source
var (
	USD = reg(Currency{"USD", 2, "$", "840", "US Dollar"})
	EUR = reg(Currency{"EUR", 2, "€", "978", "Euro"})
	GBP = reg(Currency{"GBP", 2, "£", "826", "Pound Sterling"})
	JPY = reg(Currency{"JPY", 0, "¥", "392", "Yen"})
	CNY = reg(Currency{"CNY", 2, "¥", "156", "Yuan Renminbi"})
	CHF = reg(Currency{"CHF", 2, "Fr", "756", "Swiss Franc"})
	CAD = reg(Currency{"CAD", 2, "$", "124", "Canadian Dollar"})
	AUD = reg(Currency{"AUD", 2, "$", "036", "Australian Dollar"})
	NZD = reg(Currency{"NZD", 2, "$", "554", "New Zealand Dollar"})
	HKD = reg(Currency{"HKD", 2, "$", "344", "Hong Kong Dollar"})
	SGD = reg(Currency{"SGD", 2, "$", "702", "Singapore Dollar"})
	INR = reg(Currency{"INR", 2, "₹", "356", "Indian Rupee"})
	KRW = reg(Currency{"KRW", 0, "₩", "410", "Won"})
	BRL = reg(Currency{"BRL", 2, "R$", "986", "Brazilian Real"})
	MXN = reg(Currency{"MXN", 2, "$", "484", "Mexican Peso"})
	ZAR = reg(Currency{"ZAR", 2, "R", "710", "Rand"})
	SEK = reg(Currency{"SEK", 2, "kr", "752", "Swedish Krona"})
	NOK = reg(Currency{"NOK", 2, "kr", "578", "Norwegian Krone"})
	DKK = reg(Currency{"DKK", 2, "kr", "208", "Danish Krone"})
	PLN = reg(Currency{"PLN", 2, "zł", "985", "Zloty"})
	AED = reg(Currency{"AED", 2, "د.إ", "784", "UAE Dirham"})
	SAR = reg(Currency{"SAR", 2, "﷼", "682", "Saudi Riyal"})
	TRY = reg(Currency{"TRY", 2, "₺", "949", "Turkish Lira"})
	RUB = reg(Currency{"RUB", 2, "₽", "643", "Russian Ruble"})
)

Fiat (ISO 4217) — the common set; extend via Register for the long tail.

View Source
var (
	USDC = reg(Currency{"USDC", 6, "$", "", "USD Coin"})
	USDT = reg(Currency{"USDT", 6, "$", "", "Tether"})
	BTC  = reg(Currency{"BTC", 8, "₿", "", "Bitcoin"})
	ETH  = reg(Currency{"ETH", 18, "Ξ", "", "Ether"})
	HUSD = reg(Currency{"HUSD", 18, "$", "", "Hanzo USD"}) // Hanzo USD stablecoin, ERC-20
)

Crypto / stablecoins — decimals are the token's on-chain decimals so an off-chain Amount and an on-chain balance share one integer scale.

View Source
var (
	// ErrCurrencyMismatch is returned when an operation combines two different
	// (non-zero) currencies.
	ErrCurrencyMismatch = errors.New("money: currencies do not match")
	// ErrUnknownCurrency is returned when a currency code/number is not in the registry.
	ErrUnknownCurrency = errors.New("money: unknown currency")
	// ErrInvalidAmount is returned when an amount string cannot be parsed.
	ErrInvalidAmount = errors.New("money: invalid amount")
	// ErrNoRate is returned when an exchange rate is missing.
	ErrNoRate = errors.New("money: no exchange rate")
	// ErrNonPositive is returned when a positive value was required (split parts, ratios).
	ErrNonPositive = errors.New("money: value must be positive")
)

Sentinel errors, matched with errors.Is.

Functions

func ParseDecimal added in v0.2.0

func ParseDecimal(s string) (decimal.Decimal, error)

ParseDecimal parses just the numeric part (separators handled) — no currency.

Types

type Amount

type Amount struct {
	// contains filtered or unexported fields
}

Amount is an exact money value: a decimal in a currency. (Currency and the currency registry live in currency.go.)

func Avg added in v0.2.0

func Avg(amounts ...Amount) (Amount, error)

Avg returns the mean, rounded to the currency's decimals (half-away-from-zero). Errors on an empty list or a currency mismatch.

func ConvertWithRate added in v0.2.0

func ConvertWithRate(amount Amount, to Currency, rate decimal.Decimal) Amount

ConvertWithRate converts amount into `to` at an explicit rate (rounded to `to`'s decimals).

func FromCents

func FromCents(cents int64) Amount

FromCents builds a USD Amount from integer cents.

func FromEUR added in v0.2.0

func FromEUR(minor int64) Amount

FromEUR builds a EUR amount from integer cents.

func FromFloat added in v0.2.0

func FromFloat(f float64, cur Currency) Amount

FromFloat builds an Amount from a float — USE ONLY for converting user input; floats are imprecise, so prefer Parse for exact decimals. The float is rendered at the currency's decimals and parsed exactly from there.

func FromGBP added in v0.2.0

func FromGBP(minor int64) Amount

FromGBP builds a GBP amount from integer pence.

func FromHUSD added in v0.2.0

func FromHUSD(wei *big.Int) Amount

FromHUSD builds a Hanzo-USD amount from integer 18-decimal wei.

func FromJPY added in v0.2.0

func FromJPY(minor int64) Amount

FromJPY builds a JPY amount from integer yen (0 decimals).

func FromMinor

func FromMinor(units int64, cur Currency) Amount

FromMinor builds an Amount from an integer count of the currency's smallest unit (e.g. cents for USD): value = units × 10^-cur.Decimals.

func FromMinorBig

func FromMinorBig(units *big.Int, cur Currency) Amount

FromMinorBig is FromMinor for a big.Int count (an on-chain wei balance).

func FromUSD added in v0.2.0

func FromUSD(minor int64) Amount

FromUSD builds a USD amount from integer cents. The FromXXX helpers mirror gocanto's convenience constructors; they take the currency's smallest unit.

func Max added in v0.2.0

func Max(amounts ...Amount) (Amount, error)

Max returns the largest amount (same currency).

func Min added in v0.2.0

func Min(amounts ...Amount) (Amount, error)

Min returns the smallest amount (same currency). Errors on an empty list or a currency mismatch.

func New

func New(v decimal.Decimal, cur Currency) Amount

New wraps an exact decimal value in a currency.

func Parse

func Parse(s string, cur Currency) (Amount, error)

Parse reads a decimal string ("6.60", "-0.00132") as an exact Amount in the currency.

func ParseAmount added in v0.2.0

func ParseAmount(s string, defaultCode ...string) (Amount, error)

ParseAmount parses a human-entered money string — "$1,234.56", "EUR 250,75", "100.50 USD", "£99.99" — into an exact Amount. The currency comes from a leading symbol, or a code token (prefix or suffix), or the optional defaultCode. Thousands separators are handled; a comma-only decimal is treated as a thousands separator (use ParseAmountEuropean for "10,50" == 10.50).

func ParseAmountEuropean added in v0.2.0

func ParseAmountEuropean(s string, defaultCode ...string) (Amount, error)

ParseAmountEuropean is ParseAmount but treats a comma-only separator as the DECIMAL point ("€10,50" == €10.50).

func Sum added in v0.2.0

func Sum(amounts ...Amount) (Amount, error)

Sum adds all amounts, which must share a currency (a zero-value operand is agnostic). The empty sum is a zero-value Amount.

func Zero

func Zero(cur Currency) Amount

Zero is 0 in the currency.

func (Amount) Add

func (a Amount) Add(b Amount) (Amount, error)

Add returns a + b. A zero-value operand adopts the other's currency; two different non-zero currencies error.

func (Amount) Allocate added in v0.2.0

func (a Amount) Allocate(ratios ...int) ([]Amount, error)

Allocate distributes the amount across the given integer ratios, summing EXACTLY to the original at the currency's minor-unit granularity — each part floors, then the leftover minor units go one-at-a-time to the leading parts. Ratios must be non-negative with a positive total.

func (Amount) AsMajorUnits added in v0.2.0

func (a Amount) AsMajorUnits() float64

AsMajorUnits returns the value as a float in major units (e.g. dollars) — for DISPLAY only; never round-trip money through it.

func (Amount) Cmp

func (a Amount) Cmp(b Amount) int

Cmp reports −1,0,+1 as a <,==,> b. A zero-value operand is currency-agnostic; two different non-zero currencies panic (comparing USD to EUR is a bug, not a runtime condition — check Currency first for untrusted inputs).

func (Amount) Currency

func (a Amount) Currency() Currency

Currency returns the amount's currency.

func (Amount) Decimal

func (a Amount) Decimal() decimal.Decimal

Decimal returns the exact underlying value.

func (Amount) Display

func (a Amount) Display() string

Display renders the value at the currency's decimals with grouped thousands and the currency symbol ("$1,234.56", "¥10000", "100,003.01 HUSD" when no symbol). Exact — derived from the integer value, not a float.

func (Amount) IsNeg

func (a Amount) IsNeg() bool

IsNeg reports whether a < 0.

func (Amount) IsZero

func (a Amount) IsZero() bool

IsZero reports whether a == 0.

func (Amount) MarshalJSON

func (a Amount) MarshalJSON() ([]byte, error)

MarshalJSON emits {"amount":"<decimal>","currency":"USD"} — exact strings, no float.

func (Amount) Minor

func (a Amount) Minor() *big.Int

Minor returns the value as a big.Int count of the currency's smallest unit (round-half-up on any sub-unit precision) — the storage/on-chain integer.

func (Amount) MinorString

func (a Amount) MinorString() string

MinorString is the minor-unit integer as a decimal string (the canonical storage form).

func (Amount) Mul

func (a Amount) Mul(factor decimal.Decimal) Amount

Mul scales the amount by an exact decimal factor (e.g. a token count or a fee rate), keeping the currency.

func (Amount) Neg

func (a Amount) Neg() Amount

Neg returns −a.

func (*Amount) Scan added in v0.2.0

func (a *Amount) Scan(src any) error

Scan implements sql.Scanner for the "amount|currency" form.

func (Amount) Sign

func (a Amount) Sign() int

Sign reports −1,0,+1 as a <,==,> 0.

func (Amount) Split

func (a Amount) Split(n int) ([]Amount, error)

Split divides the amount into n parts that sum EXACTLY to the original at the currency's minor-unit granularity — the remainder is distributed one minor unit at a time to the leading parts, so no sub-unit is created or lost. n must be positive.

func (Amount) String

func (a Amount) String() string

String renders the exact decimal value ("0.00132").

func (Amount) Sub

func (a Amount) Sub(b Amount) (Amount, error)

Sub returns a − b (same currency rule as Add).

func (*Amount) UnmarshalJSON added in v0.2.0

func (a *Amount) UnmarshalJSON(b []byte) error

UnmarshalJSON parses {"amount","currency"} exactly (a known code resolves to its full Currency; an unknown code is kept as a bare code).

func (Amount) Value added in v0.2.0

func (a Amount) Value() (driver.Value, error)

Value implements driver.Valuer — stored as "amount|currency" ("0.00132|USD"), exact and currency-carrying.

type Currency

type Currency struct {
	Code     string // ISO-4217 alpha code or custom ("USD", "HUSD")
	Decimals int32  // fractional digits of the smallest unit
	Symbol   string // display grapheme ("$", "€", "₿")
	Numeric  string // ISO-4217 numeric code ("840"); "" for custom
	Name     string
}

Currency is a currency's identity: its ISO-4217 (or custom) code, the number of decimal places in its smallest unit (Decimals — USD 2, JPY 0, an 18-decimal ERC-20 18), a display Symbol, and the ISO numeric code where applicable. Custom currencies (crypto, loyalty points) are first-class — construct one directly or Register it.

func ByCode added in v0.2.0

func ByCode(code string) (Currency, error)

ByCode looks a currency up by alpha code (case-insensitive).

func ByNumeric added in v0.2.0

func ByNumeric(num string) (Currency, error)

ByNumeric looks a currency up by ISO-4217 numeric code ("840").

func Register added in v0.2.0

func Register(c Currency) Currency

Register adds (or overrides) a currency in the registry and returns it, so custom currencies resolve by code the same as the built-ins.

type Exchange added in v0.2.0

type Exchange struct {
	// contains filtered or unexported fields
}

Exchange is an in-memory table of conversion rates. A rate from → to means one unit of `from` buys `rate` units of `to`. Not safe for concurrent writes; build it once, then read.

func NewExchange added in v0.2.0

func NewExchange() *Exchange

NewExchange returns an empty rate table.

func (*Exchange) AddRate added in v0.2.0

func (e *Exchange) AddRate(from, to Currency, rate string) error

AddRate registers (or replaces) the from→to rate from a decimal string ("0.85"). The inverse is NOT implied — add it explicitly if you need both directions.

func (*Exchange) Convert added in v0.2.0

func (e *Exchange) Convert(amount Amount, to Currency) (Amount, error)

Convert converts amount into `to`, rounded to `to`'s decimals, using the registered rate.

func (*Exchange) Rate added in v0.2.0

func (e *Exchange) Rate(from, to Currency) (decimal.Decimal, error)

Rate returns the from→to rate (identity 1 when from == to).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL