decimal

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Mar 24, 2026 License: BSL-1.0 Imports: 8 Imported by: 0

README

decimal

decimal is a small Go package for fixed-layout decimal values with value semantics and no heap allocation in the common paths.

It is designed for high performance, compact storage, straightforward formatting, and predictable behavior that stays close to primitive integer operations in Go.

It is not an arbitrary-precision decimal library.

Representation

A Decimal stores:

  • a sign bit
  • an unsigned 64-bit integer part
  • an unsigned 64-bit fractional part
  • a decimal scale in Digits
  • as a 24 byte struct

The supported range is:

  • integer part: 0 to math.MaxUint64
  • fractional precision: 0 to 19 decimal digits

That means the type can represent values such as:

  • 123
  • -123.45
  • 0.000000000000000001

but not arbitrary precision or more than 19 digits after the decimal point.

Installation

go get github.com/fossoreslp/decimal

Quick Start

package main

import (
	"fmt"

	"github.com/fossoreslp/decimal"
)

func main() {
	a, _ := decimal.NewFromString("123.4500")
	b := decimal.New(2)

	sum := decimal.Add(a, b)
	expected, _ := decimal.NewFromString("125.45")

	fmt.Println(a.String())   // 123.4500
	fmt.Println(sum.String()) // 125.4500
	fmt.Println(decimal.Equal(sum, expected)) // true
}

Constructors And Parsing

New

New is a generic constructor that accepts Go builtin numeric types:

  • uint, uint8, uint16, uint32, uint64
  • int, int8, int16, int32, int64
  • float32, float64

Behavior:

  • integer inputs are represented exactly
  • float32 values are converted with 10 fractional digits
  • float64 values are converted with 18 fractional digits
  • trailing fractional zeros are removed after float conversion
  • NaN, +Inf, and -Inf are converted to zero
  • finite floats larger than the uint64 range inherit Go's float-to-uint64 overflow behavior
NewFromString

NewFromString parses a strict decimal string:

d, err := decimal.NewFromString("-123.4500")

Notes:

  • accepts an optional leading -
  • accepts a leading decimal point such as .25
  • accepts a trailing decimal point such as 123.
  • rejects extra surrounding characters
  • allows at most 19 digits after the decimal point
  • accepts any integer length whose numeric value still fits in uint64
  • leading zeros are allowed and do not by themselves cause integer overflow
NewFromStringFuzzy

NewFromStringFuzzy scans the first decimal-looking token from a larger string:

d, err := decimal.NewFromStringFuzzy("price: 123.45 EUR")

This is intended for loose extraction, not strict validation.

Zero

Zero creates a zero-valued decimal value.

Arithmetic

The package currently provides:

  • Add(a, b) Decimal
  • Subtract(a, b) Decimal
  • Multiply(a, b) Decimal
  • Divide(a, b) Decimal
  • Negate(n) Decimal
  • Absolute(n) Decimal
Arithmetic Semantics

Operations are designed to behave like primitive integer operations in Go with respect to overflow and division-by-zero semantics. They will wrap on overflow while divide-by-zero panics.

Arithmetic operations will expand the number of decimal digits as far as necessary (up to the limit of 19 digits) to represent the result accurately.

Operations that require more than 19 digits after the decimal point may lose precision.

All arithmetic operations accept Decimal values as well as any of the primitive Go numeric types, converting to Decimal as described by New.

Equality / Comparison

Equal compares values after canonical trailing-zero truncation.

These compare equal:

a, _ := decimal.NewFromString("0.1")
b, _ := decimal.NewFromString("0.10")
decimal.Equal(a, b) // true

Compare compares to values and returns -1 if a < b, 0 if a = b and 1 if a > b.

a := decimal.New(1)
b := decimal.New(-2)
decimal.Compare(a, b) // 1

IsZero checks if a decimal is exactly zero.

decimal.New(0).IsZero() // true

Precision

Creating decimal values from strings adheres to the precision on the input. Conversion of numeric types uses the least amount of fractional digits possible to represent the exact converted value.

Arithmetic operations extend precision as necessary to represent the resulting value exactly unless it overflows the limits.

To adjust precision manually, there are three options:

  • ToDigits(uint8) Decimal
  • Round(uint8) Decimal
  • Truncate() Decimal

ToDigits extends precision by adding trailing zeros, or reduces precision by truncation. It truncates toward zero and does not round. Round extends precision by adding trailing zeros, or reduces precision by rounding to nearest, ties away from zero. Truncate removes unnecessary trailing zeros while ensuring to never change the value.

Example:

d, _ := decimal.NewFromString("1.999")

d.ToDigits(2) // 1.99
d.Round(2)    // 2.00

Formatting And Conversion

  • String() returns a decimal string without scientific notation
  • Float64() converts to float64

Float64() is a convenience conversion and can lose precision, just like any decimal-to-float conversion.

JSON

The type implements json.Marshaler and json.Unmarshaler.

Behavior:

  • values are marshaled as JSON numbers, not JSON strings
  • null unmarshals to zero
  • quoted JSON strings are accepted only when the content is a plain decimal string
  • escaped or otherwise non-plain JSON strings are not supported

Examples:

123.45
"123.45"
SQL

The type implements sql.Scanner and driver.Valuer.

Supported Scan inputs:

  • nil
  • []byte
  • string
  • float64
  • int64
  • uint64

Behavior:

  • nil resets the receiver to zero
  • Value() returns the decimal string form
CBOR

The type implements CBOR marshaling and unmarshaling.

Behavior:

  • integers are encoded as CBOR integers when Digits == 0
  • values with a fractional part are encoded as RFC 8949 decimal fractions (tag 4)
  • large mantissas are encoded using CBOR bignum tags when needed
  • unmarshaling also accepts CBOR integers and CBOR float16/32/64 values

Interoperability note:

  • the encoder uses preferred encoding
  • the decoder may not support every valid alternate CBOR encoding of the same semantic value

Designed Limits And Invariants

This package is intentionally low-level. The Decimal fields are exported, so callers can construct values directly. Public methods assume the following invariants are respected:

  • Digits must be between 0 and 19
  • Fraction must match the declared scale in Digits
  • negative zero is unsupported

If you construct values manually and break those invariants, behavior is undefined for this package and may include incorrect formatting, incorrect arithmetic, or panics.

Examples of supported manual values:

decimal.Decimal{Integer: 12, Fraction: 34, Digits: 2}   // 12.34
decimal.Decimal{Integer: 12, Fraction: 340, Digits: 3}  // 12.340
decimal.Decimal{Integer: 12, Fraction: 34, Digits: 3}   // 12.034

Examples of unsupported manual values:

decimal.Decimal{Fraction: 123, Digits: 2}  // Fraction value exceeds defined digits
decimal.Decimal{Fraction: 123, Digits: 20} // Digits exceeds 19
decimal.Decimal{Negative: true}            // Negative zero

Documentation

Index

Constants

View Source
const (
	CBOR_MAJOR           = 0b111_00000
	CBOR_ADDITIONAL      = 0b000_11111
	CBOR_INTPOS          = 0b000_00000
	CBOR_INTNEG          = 0b001_00000
	CBOR_BYTESTRING      = 0b010_00000
	CBOR_ARRAY_LEN2      = 0b100_00010
	CBOR_TAG             = 0b110_00000
	CBOR_TAG_BIGNUMPOS   = 0b110_00010
	CBOR_TAG_BIGNUMNEG   = 0b110_00011
	CBOR_TAG_DECIMALFRAC = 0b110_00100
	CBOR_TYPE7           = 0b111_00000
	CBOR_FLOAT16         = 0b111_11001
	CBOR_FLOAT32         = 0b111_11010
	CBOR_FLOAT64         = 0b111_11011
)

Variables

This section is empty.

Functions

func Compare added in v0.4.0

func Compare[A, B Number](a A, b B) int

Compare compares 2 values. It converts to `Decimal` before comparing which can be lossy for floating point numbers.

func Equal added in v0.4.0

func Equal[A, B Number](a A, b B) bool

Equal checks if 2 values are equal. It converts to `Decimal` before comparing which can be lossy for floating point numbers. The comparison is exact by value without any tolerance for floating point errors.

Types

type Decimal

type Decimal struct {
	Negative bool
	Digits   uint8 // Number of digits in Fraction, must be less or equal to 19
	Integer  uint64
	Fraction uint64
}

Decimal represents high precision decimal numbers. It can store numbers with up to 19 digits behind the decimal point. The integer component is constrained to the range of a 64-bit unsigned integer in both positive and negative values. When manually constructed or modified, `Digits` must represent the exact number of digits in `Fraction`. Values with more than 19 fractional digits as well as negative zero are unsupported and will trigger undefined behavior.

func Absolute added in v0.4.0

func Absolute[N Number](n N) Decimal

Absolute returns the absolute value of a given input.

func Add added in v0.4.0

func Add[A, B Number](a A, b B) Decimal

Add adds two decimals together. Its overflow behavior matches that of integers in Go.

func Divide added in v0.4.0

func Divide[A, B Number](a A, b B) Decimal

Divide divides two decimal values. The result is computed with maximum precision (19 fractional digits). Its overflow and divide-by-zero behavior match that of integers in Go.

func Multiply added in v0.4.0

func Multiply[A, B Number](a A, b B) Decimal

Multiply multiplies two decimal values. Its overflow behavior matches that of integers in Go.

func Negate added in v0.4.0

func Negate[N Number](n N) Decimal

Negate returns the negation of the given value.

func New

func New[N Number](value N) Decimal

New converts any of the builtin numeric types in Go to a decimal value. It represents integer values exactly. 32-bit floating point numbers are converted with 10 digits behind the decimal point. 64-bit floating point numbers are converted with 18 digits behind the decimal point. Floating point numbers exceeding the unsigned 64-bit integer range inherit Go's float-to-uint64 overflow logic. Positive and negative infinity and NaN cannot be represented and are instead converted to zero.

func NewFromString

func NewFromString(s string) (Decimal, error)

NewFromString parses a decimal value from a string. The string must contain just the number with no additional characters around it. It will parse at most 19 digits after the decimal point. The integer component must fit into an unsigned 64-bit integer.

func NewFromStringFuzzy

func NewFromStringFuzzy(s string) (Decimal, error)

NewFromStringFuzzy parses the first decimal value it can find from a string. Leading or trailing characters are ignored. The first digit, minus or dot marks the beginning of a number. It will parse at most 19 digits after the decimal point. The integer component must fit into an unsigned 64-bit integer.

func Subtract added in v0.4.0

func Subtract[A, B Number](a A, b B) Decimal

Subtract subtracts one decimal value from another. Its overflow behavior matches that of integers in Go.

func Zero

func Zero() Decimal

Zero returns a zero value decimal

func (Decimal) Float64

func (d Decimal) Float64() float64

Float64 converts a decimal value to floating point. Precision loss is minimized but not all values can be represented exactly.

func (Decimal) Format added in v0.4.0

func (d Decimal) Format(state fmt.State, verb rune)

Format implements fmt.Formatter. It supports "%f" and "%v" but not the other formats specified for floating point values such as "%e".

func (Decimal) IsZero added in v0.3.0

func (d Decimal) IsZero() bool

IsZero checks if a decimal value is zero.

func (Decimal) MarshalCBOR

func (d Decimal) MarshalCBOR() ([]byte, error)

MarshalCBOR implements the cbor.Marshaler interface. It encodes the decimal number according to RFC 8949 Section 3.4.4 for Decimal Fractions. The format is a CBOR tag 4 containing a two-element array: [exponent, mantissa].

func (Decimal) MarshalJSON

func (d Decimal) MarshalJSON() ([]byte, error)

MarshalJSON encodes a decimal value as a JSON number.

func (Decimal) MarshalText added in v0.2.0

func (d Decimal) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler.

func (Decimal) Round added in v0.4.0

func (d Decimal) Round(digits uint8) Decimal

Round rounds a decimal value to the specified number of digits after the decimal point. It rounds to nearest, ties away from zero. The number of digits is limited to 19.

func (*Decimal) Scan

func (d *Decimal) Scan(value any) (err error)

Scan converts SQL data into a decimal value. It handles textual representation as well as floating point and integer values. Decoding follows the standards set by `New` and `NewFromString` respectively.

func (Decimal) String

func (d Decimal) String() string

String converts a decimal value into a string representation.

func (Decimal) ToDigits

func (d Decimal) ToDigits(digits uint8) Decimal

ToDigits converts a decimal value to the specified number of digits after the decimal point. The number of digits is limited to 19. Digits beyond the defined number are truncated, no rounding is performed.

func (Decimal) Truncate

func (d Decimal) Truncate() Decimal

Truncate removes trailing zeros from the decimal value.

func (*Decimal) UnmarshalCBOR

func (d *Decimal) UnmarshalCBOR(data []byte) error

UnmarshalCBOR implements the cbor.Unmarshaler interface. It supports decoding RFC 8949 Decimal Fractions, standard floats, and integers.

func (*Decimal) UnmarshalJSON

func (d *Decimal) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a JSON number or string into a decimal value. Strings must be a plain number and may not contain any escaped or non-numeric characters. `null` is decoded as zero to ensure missing values do not stop decoding entirely.

func (*Decimal) UnmarshalText added in v0.2.0

func (d *Decimal) UnmarshalText(data []byte) error

UnmarshalText implements encoding.TextUnmarshaler.

func (Decimal) Value

func (d Decimal) Value() (driver.Value, error)

Value encodes a decimal value for SQL. It uses a string representation that is widely compatible with most databases and data types.

type Number

type Number interface {
	uint | uint8 | uint16 | uint32 | uint64 | int | int8 | int16 | int32 | int64 | float32 | float64 | Decimal
}

Jump to

Keyboard shortcuts

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