Maths API

Piko's maths package provides three numeric types for applications that cannot tolerate IEEE-754 rounding. BigInt handles arbitrary-precision integers, Decimal handles 34-digit fixed-point numbers, and Money adds currency awareness on top of Decimal. All three types are immutable. Fluent operations return new values. Each value carries an optional error that propagates through the chain and is observable through Err(). For the rationale see about maths. For task recipes see how to maths. Source: wdk/maths/.

Constructors

BigInt

func NewBigIntFromString(s string) BigInt
func NewBigIntFromInt(value int64) BigInt
func NewBigIntFromApd(value apd.BigInt) BigInt
func ZeroBigInt() BigInt
func OneBigInt() BigInt
func TenBigInt() BigInt
func HundredBigInt() BigInt
func ZeroBigIntWithError(err error) BigInt

Decimal

func NewDecimalFromString(s string) Decimal
func NewDecimalFromInt(value int64) Decimal
func NewDecimalFromFloat(value float64) Decimal
func NewDecimalFromApd(value apd.Decimal) Decimal
func ZeroDecimal() Decimal
func OneDecimal() Decimal
func TenDecimal() Decimal
func HundredDecimal() Decimal
func ZeroDecimalWithError(err error) Decimal

Money

func NewMoneyFromDecimal(amount Decimal, code string) Money
func NewMoneyFromString(amount string, code string) Money
func NewMoneyFromInt(amount int64, code string) Money
func NewMoneyFromMinorInt(amount int64, code string) Money
func NewMoneyFromFloat(amount float64, code string) Money
func ZeroMoney(code string) Money
func OneMoney(code string) Money
func HundredMoney(code string) Money
func ZeroMoneyWithError(code string, err error) Money

NewMoneyFromMinorInt accepts minor units (pence for GBP, cents for USD). RegisterCurrency(code string, definition CurrencyDefinition) registers a currency outside the built-in ISO 4217 list.

CurrencyDefinition is a type alias for github.com/bojanz/currency.Definition:

FieldTypePurpose
NumericCodestringThe three-digit ISO 4217 numeric code (for example "999").
Digitsuint8The number of fraction digits used by the currency (for example 2 for GBP).
DefaultSymbolstringThe default symbol used across all locales. Leave empty when overriding an existing currency to retain the built-in locale-specific symbols.

Arithmetic

Each type exposes the same family of arithmetic operations. The Int, String, and Float variants accept the operand as that scalar type. The base form takes the same type as the receiver.

MethodAlso available as
Add, Subtract, Multiply, Divide*Int, *String, *Float
Modulus, Remainder*Int, *String, *Float
Power*Int, *String, *Float

Cross-type arithmetic

BigInt and Decimal can operate on each other directly. Money operations accept Decimal amounts through *Decimal variants.

ReceiverAvailable cross-type variants
BigIntAddDecimal, AddFloat, SubtractDecimal, SubtractFloat, MultiplyDecimal, MultiplyFloat, DivideDecimal, DivideFloat, PowerDecimal, PowerFloat
DecimalAddDecimal, AddBigInt, SubtractDecimal, SubtractBigInt, MultiplyDecimal, MultiplyBigInt, DivideDecimal, DivideBigInt (plus Modulus*, Remainder*, Power* variants)
MoneyAddDecimal, AddBigInt, SubtractDecimal, SubtractBigInt, MultiplyBigInt, DivideBigInt (plus *Int, *Float, *String, *MinorInt for scalars; Multiply(factor Decimal) and Divide(factor Decimal) take a unitless Decimal factor; mixing currencies in addition or subtraction raises an error)

Logic and comparison

MethodPurpose
BigInt.Cmp(other) (int, error)Three-way compare for BigInt only. Returns -1, 0, or 1 plus a chain error. Decimal and Money do not expose Cmp; use the Check* predicates below.
Equals, LessThan, GreaterThanBoolean comparisons returning (bool, error). Available on BigInt, Decimal, and Money. *Int / *String / *Float variants exist where cross-type comparison makes sense.
LessThanOrEqual, GreaterThanOrEqualAvailable on Decimal only. Returns (bool, error). BigInt and Money do not expose the OrEqual pair.
CheckEquals, CheckLessThan, CheckGreaterThanPlain bool form on all three types. A chain error folds to false.
MustEquals, MustLessThan, MustGreaterThanPlain bool form. Panics if the chain carries an error.
IsZero, IsPositive, IsNegativeSign predicates.
IsIntegerChecks whether the value has no fractional part. Available on BigInt, Decimal, and Money.
IsBetween(min, max)Inclusive range check.
IsCloseTo(target, tolerance)Tolerance-based equality: returns true when `
IsEven, IsOdd, IsMultipleOf(divisor)Integer-style predicates (available on BigInt, Decimal, and Money for whole values).

The Check* family returns plain bool with chain errors folded to false. The bare-named methods return (bool, error). The Must* family panics on chain error.

Fluent transformations

Returns a new value of the same type. The chain retains any prior error state.

MethodPurpose
Abs()Absolute value.
Negate()Sign flip.
Decimal.Round(places int32)Round to places decimal places using banker's rounding.
Money.RoundToStandard(), Money.RoundTo(places uint8, mode currency.RoundingMode)Round to the currency's standard precision, or to a chosen precision and rounding mode.
Ceil(), Floor(), Truncate()Rounding modes.
AddPercent(p), SubtractPercent(p), GetPercent(p)Percentage helpers. GetPercent returns value * p / 100. Variants: *Int, *String, *Float.

Aggregates

Aggregates take either a variadic list or a slice. Each type ships matching helpers.

SumBigInts(...BigInt) BigInt           AverageBigInts(...BigInt) Decimal     MinBigInt(b1, ...others) BigInt
MaxBigInt(b1, ...others) BigInt        AbsSumBigInts(...BigInt) BigInt       SortBigInts(slice)
SortBigIntsReverse(slice)
// b.Allocate(ratios ...int64) ([]BigInt, error): method on BigInt; distributes b across ratios with fair rounding

SumDecimals(...Decimal) Decimal        AverageDecimals(...Decimal) Decimal   MinDecimal(d1, ...others) Decimal
MaxDecimal(d1, ...others) Decimal      AbsSumDecimals(...Decimal) Decimal    SortDecimals(slice)
SortDecimalsReverse(slice)

SumMoney(...Money) Money               AverageMoney(...Money) Money          MinMoney(m1, ...others) Money
MaxMoney(m1, ...others) Money          AbsSumMoney(...Money) Money           SortMoney(slice)
SortMoneyReverse(slice)

Note: AverageBigInts returns a Decimal (not a BigInt) because the average of integers can have a fractional part.

Allocate is a method on each numeric type (BigInt.Allocate, Decimal.Allocate, Money.Allocate) that splits the receiver across a set of integer ratios while ensuring the parts sum to the original. This is the right primitive for dividing an invoice total across line items without rounding drift.

In-place mutations

The *InPlace methods mutate the receiver instead of returning a new value. They are available on BigInt, Decimal, and Money.

AddInPlace(other)
SubtractInPlace(other)
MultiplyInPlace(other)

Conversion and extraction

MethodPurpose
BigInt.ToDecimal()Widens a BigInt to a Decimal.
Decimal.ToBigInt()Narrows a Decimal to a BigInt, truncating the fractional part.
Money.Amount() (Decimal, error)Extracts the Decimal amount.
Money.CurrencyCode() (string, error)Returns the ISO 4217 (or custom-registered) currency code, propagating any chain error.
String() (string, error)Canonical string form. Returns the chain error alongside the rendered text.
MustString() stringCanonical string form. Panics on chain error.
Err() errorReturns any error accumulated through the fluent chain.
Must()Returns the value if no error; panics otherwise.

Currency conversion

func NewExchangeRates(baseCurrency string, rates map[string]Decimal) (ExchangeRates, error)
func InvertRates(original ExchangeRates, newBaseCurrency string) (ExchangeRates, error)
func NewConverter(rates ExchangeRates) *Converter
func (c *Converter) Convert(source Money, targetCode string) Money

Converter handles single-base conversions. InvertRates rebuilds an ExchangeRates set against a new base currency without losing precision.

For cross-rate accuracy (EUR to JPY without routing through USD), use the matrix form. RateMatrix stores a full source-to-target rate map, so the converter handles any pair of supported currencies directly:

type RateMatrix struct {
    Rates        map[string]map[string]Decimal
    BaseCurrency string
}

func NewRateMatrix(baseCurrency string, baseRates map[string]Decimal, overrides map[string]map[string]Decimal) (RateMatrix, error)
func NewMatrixConverter(matrix RateMatrix) *MatrixConverter
func (c *MatrixConverter) Convert(source Money, targetCode string) Money
func (c *MatrixConverter) ConvertAll(sources []Money, targetCode string) ([]Money, error)
func (c *MatrixConverter) Supports(code string) bool
func (c *MatrixConverter) CanConvert(from, to string) bool

NewRateMatrix initialises identity rates for every currency it sees, populates the matrix from baseRates (and their inverses), then applies any direct overrides so cross-rates can bypass routing through the base currency.

Number formatting

type NumberLocale struct { /* ... */ }
func GetNumberLocale(locale string) NumberLocale
func FormatNumberString(s string, locale NumberLocale) string

NumberLocale is a struct describing the formatting rules for a locale. GetNumberLocale is the constructor that resolves a locale tag to a populated NumberLocale value. Unrecognised locales fall back to LocaleEnGB.

Decimal formatting

func (d Decimal) Format(locale string) (string, error)
func (d Decimal) MustFormat(locale string) string

Format keeps full precision while applying the locale's separators. MustFormat panics on chain error.

Money formatting

func (m Money) RoundedNumber() (string, error)        // Currency-rounded amount, plain digits
func (m Money) FormattedNumber() (string, error)      // Default-locale grouped number
func (m Money) MustFormattedNumber() string           // Panic on error
func (m Money) Format(localeID string) (string, error)        // Currency string with symbol for the locale
func (m Money) MustFormat(localeID string) string             // Panic on error
func (m Money) DefaultFormat() string                         // Currency string in en-GB
func (m Money) RoundedDefaultFormat() string                  // Currency-rounded amount in en-GB

Money.Format emits a fully localised currency string (symbol position, separators, fraction digits) while RoundedNumber and FormattedNumber return digit-only renderings useful for embedding in custom layouts.

Currency registration

func RegisterCurrency(code string, definition CurrencyDefinition)

Registers a non-standard currency (cryptocurrency, test currency, legacy unit).

Error propagation

Every BigInt, Decimal, and Money carries an optional error. When an operation in a fluent chain fails, subsequent operations become no-ops and the error is observable through Err().

total := maths.NewDecimalFromString("19.99").
    Multiply(quantity).
    AddPercent(taxPercent).
    Round(2)

if err := total.Err(); err != nil {
    return err
}

The Check* family of predicates folds chain errors to false. The Must* family panics on chain error.

Constants

ConstantValue
Base10Common base for formatting.
Value10Decimal literal 10.
Value100Decimal literal 100.

See also

  • About maths for IEEE-754 pitfalls and design rationale.
  • How to maths for tax calculation, invoice splitting, and multi-currency recipes.
  • doc.go for allocation and precision rationale in source form.