# accounting: money and currency formatting for Go

> accounting is a small Go library that turns a number into a currency string, with configurable symbol, precision, thousand and decimal separators, and separate format strings for negative and zero values, plus a locale table for the common currencies. It accepts float64 for convenience but the documentation is explicit that you should not count money in floating point, and offers big.Rat, big.Float and decimal inputs for the cases where it matters.

**leekchan/accounting** — money and currency formatting for golang

- Repository: https://github.com/leekchan/accounting
- Stars: 908 · Forks: 75
- Language: Go
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/leekchan-accounting

## A small formatter, inspired by a JavaScript library

accounting is a library for money and currency formatting in Go, and the README says it was inspired by accounting.js, the JavaScript library of the same name.

That is the scope. This is not a money type, not an arithmetic library and not a currency conversion service. It is a formatter: it takes a number and a configuration, and it produces the string you would print on an invoice, a receipt or a balance.

That distinction makes the library easy to evaluate. The question is not whether you can represent money, it is whether the output matches the conventions your users expect, and the answer is that you can express essentially every convention, because the configuration covers the symbol, the precision, the two separators, and separate format strings for ordinary, negative and zero values.

The package is MIT licensed, the module path is github.com/leekchan/accounting, and it declares Go 1.13 in its go.mod. The dependencies are small and mostly optional in practice: cockroachdb/apd for arbitrary precision decimals, shopspring/decimal for the decimal input type, and lib/pq and pkg/errors as indirect requirements. The last push was on 2026-09-27, so the code is still being touched.

Installing it is the standard single line.

```bash
go get github.com/leekchan/accounting
```

## The struct that holds everything

Everything is configured through one struct, and its fields are the whole API surface for a currency.

```Go
type Accounting struct {
    Symbol         string // currency symbol (required)
    Precision      int    // currency precision (decimal places) (optional / default: 0)
    Thousand       string // thousand separator (optional / default: ,)
    Decimal        string // decimal separator (optional / default: .)
    Format         string // simple format string allows control of symbol position (%v = value, %s = symbol) (default: %s%v)
    FormatNegative string // format string for negative values
    FormatZero     string // format string for zero values (optional / default: Format)
}
```

The format strings use two verbs, %s for the symbol and %v for the value, so the position of the currency symbol is yours to decide. The default is %s%v, which puts the symbol immediately before the number.

The negative format has a default that is derived rather than fixed. It is computed by removing the minus sign from the Format string and turning %v into -%v, so a format of %s %v produces a negative of %s -%v, and a format with the symbol after the number keeps the symbol after it. That derivation is why you usually do not need to set FormatNegative yourself.

The zero format defaults to the ordinary format, which means a zero renders like any other value until you override it.

Precision defaults to zero, which is worth noticing. A zero precision means no decimal part at all, so the default configuration formats 500000 as $500,000 rather than $500,000.00. If you want cents, you set Precision to 2.

## Three ways to give it a number, and the float64 warning

The library offers four formatting entry points, and the difference between them is the most important thing to understand.

There is FormatMoney for a float64, FormatMoneyBigFloat for a big.Float, FormatMoneyBigRat for a big.Rat, and FormatMoneyDecimal for a shopspring decimal value.

The README is blunt about the first one. Please do not use float64 to count money, because floats can have errors when you perform operations on them, and it is highly recommended to use big.Rat, which the documentation notes is for versions before Go 1.5, or big.Float for Go 1.5 and later. The library supports float64, but that is stated as a convenience rather than an endorsement.

The examples show the difference in practice. A float64 of 123456789.213123 formats as $123,456,789.21, which looks right until you remember that the underlying value was never exactly that. A big.Rat of 77777777 over 3 formats as $25,925,925.67, and the negative version puts the sign before the symbol, which is the convention the default format produces.

The rounding is what you would expect for a formatter with a precision setting, and the point is not the arithmetic but the input. A library cannot recover precision that a float64 has already lost, so the fix belongs at the point where the number is created, not at the point where it is printed.

## Negative, zero and symbol placement in practice

The example code walks through the conventions that matter, and they are worth reading as a set of decisions rather than as features.

A plain configuration with a dollar symbol and precision two gives the familiar $123,456,789.21. Switching to a euro with European separators gives €4.999,99, which is the same number with the separators swapped, showing that the separators are configuration rather than a locale lookup.

```Go
ac = accounting.Accounting{Symbol: "€", Precision: 2, Thousand: ".", Decimal: ","}
fmt.Println(ac.FormatMoney(4999.99))  // "€4.999,99"
```

A symbol with a trailing space and precision zero gives £ 500,000, so the space is part of the symbol string and the precision controls the decimal part independently of the symbol.

The three format strings then cover the cases a plain formatter gets wrong. With Format as %s %v, FormatNegative as %s (%v) and FormatZero as %s --, a million renders as GBP 1,000,000, a negative value as GBP (5,000) with the value in parentheses rather than a minus sign, and zero as GBP -- rather than as a bare zero. That last one is an accounting convention, where a dash or blank means nil, and it is the kind of detail that makes a printed statement look like it belongs to a finance team rather than a developer.

The same three outputs are produced by DefaultAccounting and by NewAccounting with the same arguments, which is the subject of the next section.

## DefaultAccounting fills in the blanks, NewAccounting does not

There are two constructors, and the difference between them is easy to miss until a zero renders as an empty string.

DefaultAccounting takes a symbol and a precision and fills in every other field with its default. NewAccounting takes the full set, the symbol, the precision, the thousand separator, the decimal separator, the format, the negative format and the zero format, and does not replace blank values with the defaults above.

So a NewAccounting call with an empty formatZero formats zero as an empty string, because there is no default to fall back on. The documentation names DefaultAccounting and the Accounting struct as the two ways to get the defaults.

There is one exception, and it is worth knowing about because it is a side effect. FormatMoneyDecimal and FormatMoneyBigFloat fill in the defaults, and in doing so they also change the Accounting for later calls. The two functions therefore mutate the configuration as a side effect of formatting, which means a later FormatMoney call on the same value can produce different output than one that ran before it.

That is a sharp edge rather than a bug in the usual sense, and it is documented, but it is the kind of behaviour that produces a bug report later if nobody read the note. If you are formatting with the big float or decimal variants, build a fresh Accounting for each call or accept that the configuration changes.

There are also setters for the individual fields, SetThousandSeparator, SetDecimalSeparator, SetFormat, SetFormatNegative and SetFormatZero, which mutate in place and are convenient when the currency is decided at runtime.

## Locales and the currency table

The package ships a locale table, and the Locale struct is what it holds.

```Go
type Locale struct {
    Name           string // currency name
    FractionLength int    // default decimal length
    ThouSep        string // thousands seperator
    DecSep         string // decimal seperator
    SpaceSep       string // space seperator
    UTFSymbol      string // UTF symbol
    HTMLSymbol     string // HTML symbol
    ComSymbol      string // Common symbol
    Pre            bool   // symbol before or after currency
}
```

Three symbol fields is unusual, and it exists because a currency symbol is not one thing. UTFSymbol is the symbol as a Unicode code point, HTMLSymbol is the entity form for output into HTML, and ComSymbol is the plain text form. A receipt rendered in HTML and one printed to a terminal need different symbol strings, and the table carries all three.

The Pre field records whether the symbol goes before or after the amount, which is the difference between $5 and 5 $.

Usage is by looking the currency up and feeding the fields into an Accounting.

```Go
lc := LocaleInfo["USD"]
ac = accounting.Accounting{Symbol: lc.ComSymbol, Precision: 2, Thousand: lc.ThouSep, Decimal: lc.DecSep}
fmt.Println(ac.FormatMoney(500000)) // "$500,000.00"
```

The map is keyed by currency code, and the example uses USD to produce $500,000.00 with a precision of two. Note that the precision comes from the configuration here rather than from the locale's FractionLength, so the two are not automatically in step, and it is worth deciding which one you treat as authoritative in your own code.

## The tree, the tests and a release cadence that has slowed down

The repository layout shows a library that has been maintained across Go version changes rather than rewritten.

There are parallel implementations and parallel test files for different Go versions and word sizes: accounting.go with accounting15.go beside it, formatnumber.go with formatnumber15.go, and separate test files for 32 bit and 64 bit builds, formatnumber_32bit_test.go and formatnumber_64bit_test.go. The go.mod declares Go 1.13, which is the floor the module supports, and the version-specific files are how that floor is honoured without dropping the newer language features for older toolchains.

There is also unformatnumber.go with a test file beside it, so the package contains the reverse operation of formatting as well, although the visible documentation does not describe its behaviour.

The test tooling is Travis CI with a Coveralls coverage badge, which dates the project: both are services that were standard at the time and have since been replaced by GitHub Actions in most repositories.

The release history tells the same story. The tagged versions are v0.3.1 from 2020-08-06, v0.3 from 2019-12-24 and v0.2 from 2016-12-11, so the last published release is more than six years old at the time of writing, even though the last push was on 2026-09-27.

That combination is the practical warning. The library is small, stable and unlikely to break, but the newest tagged version predates the recent commits, so if you want the current code you should depend on the branch or a commit hash rather than on the release tag, and you should read the diff yourself.

The API documentation is on godoc, and the licence is MIT.

## Conclusion

Adopt accounting when you need currency strings in Go and you want the separators and the negative and zero conventions of a locale without hand rolling a format function per currency. Do not adopt it for the arithmetic, because it does not do arithmetic, and do not count money in float64 as the README warns, so pass big.Float, big.Rat or a decimal value. Before you start, decide which of the two constructors to use, since NewAccounting does not fill in defaults for blank values while DefaultAccounting does, and check the LocaleInfo table for the currencies you need, since a locale with the wrong separators produces plausible but wrong output.

## FAQ

### How do I use the accounting library in Go?

Run go get github.com/leekchan/accounting, then build an Accounting with a symbol and a precision, or use DefaultAccounting for the defaults, and call FormatMoney. For example, accounting.Accounting{Symbol: "$", Precision: 2} formats 123456789.213123 as $123,456,789.21.

### Should I use float64 for money with this library?

No. The README says explicitly not to use float64 to count money because floats can have errors, and recommends big.Rat for versions before Go 1.5 or big.Float from Go 1.5 onwards. The library accepts float64 for convenience, and also offers FormatMoneyBigRat, FormatMoneyBigFloat and FormatMoneyDecimal.

### What is the difference between DefaultAccounting and NewAccounting?

DefaultAccounting replaces blank values with the documented defaults, while NewAccounting does not, so a blank formatZero in NewAccounting formats zero as an empty string. The exception is FormatMoneyDecimal and FormatMoneyBigFloat, which fill in the defaults and also change the Accounting for later calls.

### How do I format negative and zero amounts differently?

Set FormatNegative and FormatZero on the Accounting struct. The README example uses Format %s %v, FormatNegative %s (%v) and FormatZero %s --, which renders a negative as GBP (5,000) with parentheses and a zero as GBP --.

### Does the accounting library include locale data for currencies?

Yes. There is a Locale struct with a name, fraction length, thousand and decimal separators, a space separator, UTF, HTML and common symbol forms, and a Pre flag for symbol placement, and a LocaleInfo map keyed by currency code such as USD.

## Sources

- [Issues](https://github.com/leekchan/accounting/issues)
- [leekchan/accounting on GitHub](https://github.com/leekchan/accounting)
- [License: MIT](https://github.com/leekchan/accounting/blob/master/LICENSE)
- [README](https://github.com/leekchan/accounting/blob/master/README.md)
- [Releases](https://github.com/leekchan/accounting/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/leekchan-accounting
