numeric

package module
v0.1.0-beta Latest Latest
Warning

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

Go to latest
Published: Jun 9, 2025 License: MIT Imports: 8 Imported by: 0

README

numeric

Build Go Reference Go Report Card codecov License status

⚠️ Beta Release: This library is currently under active development. Its components and behavior are subject to change. Feedback and issues are welcome.

numeric is a high-performance Go package for fixed-precision decimal math with explicit handling of overflow, underflow, and inexact values. It is designed for scenarios where precision, determinism, and performance are critical—such as financial, scientific, or blockchain applications.

✨ Features

  • ✅ Fixed 54-digit decimal representation
    • 18 digits above the decimal point
    • 36 digits below the decimal point
  • ✅ Zero allocations for maths operations and parsing.
  • ✅ Correct overflow and underflow detection
  • ✅ Fast, deterministic arithmetic
  • ✅ Human-readable decimal formatting with symbolic extensions:
    • ~: underflow/inexact
    • <...: overflow
    • NaN: invalid or undefined
  • ✅ JSON and text marshalling/unmarshalling
  • ✅ Compatible formatting float64 and int

📦 Installation

go get github.com/nehemming/numeric

Import it:

import "github.com/nehemming/numeric"

🚀 Basic Usage

Creating Numbers
n1 := numeric.FromInt(42)
n2, _ := numeric.FromString("1.2345")
n3 := numeric.FromFloat64(3.14159)
Arithmetic
sum := n1.Add(n2)
diff := n1.Sub(n2)
product := n1.Mul(n2)
quotient := n1.Div(n2)
neg := n2.Neg()
abs := neg.Abs()
Rounding and Truncation
rounded := n2.Round(2, numeric.RoundHalfUp)
truncated := n2.Truncate(numeric.FromInt(0)) // Equivalent to Round(0, RoundTowards)
Conversion
s := n2.String()  // "1.2345"
f := n2.Float64() // Approximate float64
i := n2.Int()     // Truncated integer
Comparison
if n1.IsGreaterThan(n2) {
    fmt.Println("n1 > n2")
}

switch n1.Cmp(n2) {
case -1:
    fmt.Println("n1 < n2")
case 0:
    fmt.Println("n1 == n2")
case 1:
    fmt.Println("n1 > n2")
}
Underflow and Overflow Detection
n, _ := numeric.FromString("1e-37")
fmt.Println(n.HasUnderflow()) // true
fmt.Println(n.String())       // "~0"

🔢 Special String Formatting Rules

The numeric package accepts and produces special formats for edge-case numeric states:

Input Description Output
"" Invalid/empty NaN
"~1.23" Underflow, inexact ~1.23
"<1.23" Overflow <999999999999999999.999999999999999999999999999999999999
"NaN" Not-a-Number NaN
"1e-37" Too small ~0
"1e18" Too large <999999999999999999.999999999999999999999999999999999999
"~0" Inexact zero ~0
"~-<1" Underflow + overflow ~-<999999999999999999.999999999999999999999999999999999999
"-1e-37" Negative underflow ~-0
"-1e20" Negative overflow -<999999999999999999.999999999999999999999999999999999999
"+1.23" Explicit positive 1.23
"999999999999999999" Max exact value 999999999999999999

These representations are used in both input parsing and output formatting, and provide clear visibility into rounding or representational limits.

Numeric Type Formatting

The Numeric type implements Go's fmt.Formatter interface to provide custom formatting behavior using format verbs. Below is a summary of the supported verbs and their corresponding output behavior.

Verb Description Output Example
v Default format. If # flag is set, prints as Numeric(value) 123.45, Numeric(123.45)
f Decimal point format (float64) 123.45
e Scientific notation (float64, lower-case e) 1.234500e+02
E Scientific notation (float64, upper-case E) 1.234500E+02
g Compact format (float64) 123.45
G Compact format (float64, upper-case exponent if needed) 123.45
d Decimal integer format (uses Int() method) 123
s String format (uses String() method) 123.45
q Quoted string format (uses String() method) "123.45"
any other Unsupported verb; prints a Go-style format error %!x(Numeric=123.45) (example)
Notes
  • The output respects additional formatting flags (e.g., width, precision) passed to fmt.
  • Verb v with the # flag gives a Go-style representation helpful for debugging.
  • For unsupported verbs, the formatter prints a placeholder with the %!verb(Type=value) pattern.
Examples
n := NumericFromString("123.45")

fmt.Printf("%v\n", n)       // 123.45
fmt.Printf("%#v\n", n)      // Numeric(123.45)
fmt.Printf("%.2f\n", n)     // 123.45
fmt.Printf("%e\n", n)       // 1.234500e+02
fmt.Printf("%d\n", n)       // 123
fmt.Printf("%q\n", n)       // "123.45"
fmt.Printf("%x\n", n)       // %!x(Numeric=123.45)

📤 JSON and Text Marshalling

The Numeric type implements json.Marshaler and json.Unmarshaler interfaces:

n := numeric.FromInt(123)
jsonBytes, _ := json.Marshal(n)
fmt.Println(string(jsonBytes)) // Output: "123"

To parse from JSON:

var parsed numeric.Numeric
_ = json.Unmarshal([]byte("\"1.23\""), &parsed)
fmt.Println(parsed.String()) // "1.23"

Special values like "NaN" and symbolic strings like "~1.23" and "<1.23" are supported.

Text marshalling is also supported via MarshalText and UnmarshalText.


⏱️ Benchmark Results

Op Input A & B Numeric ns/op Numeric B/op Numeric allocs/op
add 1, 2 18.74 0 0
sub 1, 2 28.46 0 0
mul 1, 2 22.76 0 0
div 1, 2 150.2 0 0
sub 123.456, -654.321 22.72 0 0
mul 123.456, -654.321 32.44 0 0
div 123.456, -654.321 394.4 0 0
add 123.456, -654.321 18.94 0 0
add 9999999.9999999999, -9999999.9999999999 10.27 0 0
sub 9999999.9999999999, -9999999.9999999999 23.10 0 0
mul 9999999.9999999999, -9999999.9999999999 48.41 0 0
div 9999999.9999999999, -9999999.9999999999 120.1 0 0
div 0.0000000001, 0.0000000002 150.2 0 0
add 0.0000000001, 0.0000000002 16.72 0 0
sub 0.0000000001, 0.0000000002 24.92 0 0
mul 0.0000000001, 0.0000000002 23.24 0 0
add 100, 0 16.09 0 0
sub 100, 0 24.18 0 0
mul 100, 0 20.87 0 0

Test run on

goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8850H CPU @ 2.60GHz

📜 License

This project is licensed under the MIT License.


🙋 Contributing

Contributions are welcome!

Please ensure your changes:

  • Maintain the zero-allocation design philosophy
  • Respect the fixed-precision arithmetic model
  • Include test coverage for:
    • Overflow and underflow scenarios
    • Symbolic formatting logic
    • Arithmetic and rounding correctness

Documentation

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrParseFormatNumeric indicates a general numeric format error.
	ErrParseFormatNumeric = errors.New("invalid numeric format")

	// ErrMultipleUnderflowSymbols is returned when multiple '~' underflow markers are present in the input.
	ErrMultipleUnderflowSymbols = errors.New("multiple underflow symbols (~) not allowed")

	// ErrMultipleOverflowSymbols is returned when multiple '<' overflow markers are present in the input.
	ErrMultipleOverflowSymbols = errors.New("multiple overflow symbols (<) not allowed")

	// ErrMultipleMinusSigns is returned when multiple '-' signs are found in the prefix, indicating conflicting negative flags.
	ErrMultipleMinusSigns = errors.New("multiple sign symbols (-) not allowed")

	// ErrMultiplePlusSigns is returned when multiple '+' signs are found in the prefix, indicating conflicting positive flags.
	ErrMultiplePlusSigns = errors.New("multiple sign symbols (+) not allowed")

	// ErrInvalidDecimalPoint is returned when an input contains more than one decimal point or uses it incorrectly with an exponent.
	ErrInvalidDecimalPoint = errors.New("invalid decimal points")

	// ErrMultipleExponents is returned when more than one exponent ('e' or 'E') is encountered in the input string.
	ErrMultipleExponents = errors.New("multiple exponents")

	// ErrMultipleExponentSigns is returned when multiple '+' or '-' signs are used in the exponent portion of the input.
	ErrMultipleExponentSigns = errors.New("multiple exponent signs")

	// ErrNoExponentValue is returned when an exponent character is present but not followed by a valid numeric value.
	ErrNoExponentValue = errors.New("no exponent value")

	// ErrNoDigitsInInput is returned when no numeric digits are found in the input.
	ErrNoDigitsInInput = errors.New("no digits in input")

	// ErrInvalidCharacter is returned when an invalid character is encountered in the input string.
	ErrInvalidCharacter = errors.New("invalid character")

	ErrIntegerOutOfRange = errors.New("integer value out of range for Numeric representation")
	ErrFloatOutOfRange   = errors.New("float value out of range for Numeric representation")
)
View Source
var Zero = Numeric{} // Zero represents the numeric zero value.

Functions

func ValidateFloatRange

func ValidateFloatRange(i float64) error

ValidateFloatRange checks if an int is within the valid range for Numeric.

func ValidateIntRange

func ValidateIntRange(i int64) error

ValidateIntRange checks if an int is within the valid range for Numeric.

Types

type Numeric

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

Numeric represents a fixed-point arbitrary-precision decimal number.

func FromFloat64

func FromFloat64(f float64) Numeric

FromFloat64 creates a Numeric from a float64. NOTE!!: Precision may be lost depending on internal representation.

Example
n := FromFloat64(3.14159)
fmt.Println(n.Round(3, RoundHalfUp).String())
Output:

3.142

func FromInt

func FromInt(i int64) Numeric

FromInt creates a Numeric from an int.

Example
n := FromInt(42)
fmt.Println(n.String())
Output:

42

func FromString

func FromString(s string) (Numeric, error)

FromString parses a string into a Numeric. Returns an error on invalid format.

Example
n, err := FromString("123.456")
if err != nil {
	panic(err)
}
fmt.Println(n.String())
Output:

123.456

func NaN

func NaN() Numeric

NaN returns a Numeric representing Not-a-Number (NaN).

func One

func One(isNeg bool) Numeric

One returns a positive or negative 1

func Sum

func Sum(vals ...Numeric) Numeric

Sum returns the sum of a variadic slice of Numerics.

func (Numeric) Abs

func (n Numeric) Abs() Numeric

Abs returns the absolute value of n.

Example
n, _ := FromString("-3.14")
fmt.Println(n.Abs().String())
Output:

3.14

func (Numeric) Add

func (n Numeric) Add(n2 Numeric) Numeric

Add returns the sum of n and n2.

Example
x, _ := FromString("1.5")
y, _ := FromString("2.25")
fmt.Println(x.Add(y).String())
Output:

3.75

func (Numeric) Cmp

func (n Numeric) Cmp(n2 Numeric) int

Cmp compares n to n2 and returns: -1 if n < n2,

0 if n == n2,
1 if n > n2.
Example
x, _ := FromString("2")
y, _ := FromString("3")
fmt.Println(x.Cmp(y)) // -1 if x < y
Output:

-1

func (Numeric) Div

func (n Numeric) Div(n2 Numeric) Numeric

Div returns the quotient of n divided by n2.

Example
x, _ := FromString("7")
y, _ := FromString("2")
fmt.Println(x.Div(y).Round(3, RoundHalfUp).String())
Output:

3.5

func (Numeric) DivRem

func (n Numeric) DivRem(n2 Numeric) (Numeric, Numeric)

DivRem returns the integer quotient and remainder of n / n2.

Example
x, _ := FromString("10")
y, _ := FromString("3")
q, r := x.DivRem(y)
fmt.Printf("q=%v r=%v\n", q.String(), r.String())
Output:

q=3 r=1

func (Numeric) Float64

func (n Numeric) Float64() float64

Float64 converts the Numeric to a float64. NOTE!!: Precision loss possible; not safe for financial calculations.

func (Numeric) Format

func (n Numeric) Format(f fmt.State, verb rune)

Format implements the fmt.Formatter interface for the Numeric type.

It supports the following format verbs:

Verb | Description
-----|-------------------------------------------------------------
  v  | Default format using String(). With '#' flag: Numeric(value)
  f  | Decimal format using Float64() (e.g., 123.45)
  e  | Scientific notation with 'e' using Float64() (e.g., 1.23e+02)
  E  | Scientific notation with 'E' using Float64() (e.g., 1.23E+02)
  g  | Compact float format using Float64()
  G  | Compact float format (upper case) using Float64()
  d  | Integer format using Int() (e.g., 123)
  s  | String format using String()
  q  | Quoted string format using String() (e.g., "123.45")
other| Unsupported verb; output will be: %!<verb>(Numeric=<value>)

The method respects width and precision flags defined by fmt.State. For example, %.2f will format to 2 decimal places.

Example usage:

n := NumericFromString("123.45")
fmt.Printf("%v\n", n)    // 123.45
fmt.Printf("%#v\n", n)   // Numeric(123.45)
fmt.Printf("%.2f\n", n)  // 123.45
fmt.Printf("%d\n", n)    // 123
fmt.Printf("%q\n", n)    // "123.45"
fmt.Printf("%x\n", n)    // %!x(Numeric=123.45)

func (Numeric) HasOverflow

func (n Numeric) HasOverflow() bool

HasOverflow returns true if the number has overflowed.

Example
n, _ := FromString("1999999999999999999.999999999999999999999999999999999999")
fmt.Println(n.HasOverflow())
Output:

true

func (Numeric) HasUnderflow

func (n Numeric) HasUnderflow() bool

HasUnderflow returns true if the number has underflow.

Example
n, _ := FromString("0.0000000000000000000000000000000000001")
fmt.Println(n.HasUnderflow())
Output:

true

func (Numeric) Int

func (n Numeric) Int() int64

Int converts the Numeric to an int, discarding any fractional part. NOTE!!: Overflows are masked to int range; no error is returned.

func (Numeric) IsEqual

func (n Numeric) IsEqual(n2 Numeric) bool

IsEqual returns true if n == n2, considering special flags.

Example
x, _ := FromString("3.00")
y, _ := FromString("3")
fmt.Println(x.IsEqual(y))
Output:

true

func (Numeric) IsGreaterEqual

func (n Numeric) IsGreaterEqual(n2 Numeric) bool

IsGreaterEqual returns true if n >= n2.

Example
x, _ := FromString("5")
y, _ := FromString("5")
fmt.Println(x.IsGreaterEqual(y))
Output:

true

func (Numeric) IsGreaterThan

func (n Numeric) IsGreaterThan(n2 Numeric) bool

IsGreaterThan returns true if n > n2.

Example
x, _ := FromString("5")
y, _ := FromString("2")
fmt.Println(x.IsGreaterThan(y))
Output:

true

func (Numeric) IsLessThan

func (n Numeric) IsLessThan(n2 Numeric) bool

IsLessThan returns true if n < n2.

Example
x, _ := FromString("2.5")
y, _ := FromString("3")
fmt.Println(x.IsLessThan(y))
Output:

true

func (Numeric) IsLessThanEqual

func (n Numeric) IsLessThanEqual(n2 Numeric) bool

IsLessThanEqual returns true if n <= n2.

Example
x, _ := FromString("2.5")
y, _ := FromString("2.5")
fmt.Println(x.IsLessThanEqual(y))
Output:

true

func (Numeric) IsNaN

func (n Numeric) IsNaN() bool

IsNaN returns true if the value is Not-a-Number.

Example
n, _ := FromString("NaN")
fmt.Println(n.IsNaN())
Output:

true

func (*Numeric) IsUnderOverNaN

func (n *Numeric) IsUnderOverNaN() bool

IsUnderOverNaN returns true if the number is NaN, has overflow, or underflow.

func (Numeric) IsZero

func (n Numeric) IsZero() bool

IsZero returns true if the number is exactly zero.

Example
n1, _ := FromString("0")
n2, _ := FromString("0.00000000000000000000000000000000001")
fmt.Println(n1.IsZero(), n2.IsZero())
Output:

true false

func (Numeric) MarshalJSON

func (n Numeric) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. NaN is serialized as the string "NaN".

Example
n, _ := FromString("42.42")
json, _ := n.MarshalJSON()
fmt.Println(string(json))
Output:

"42.42"

func (Numeric) MarshalText

func (n Numeric) MarshalText() ([]byte, error)

MarshalText implements encoding.TextMarshaler for text formats.

Example
n, _ := FromString("1.23")
text, _ := n.MarshalText()
fmt.Println(string(text))
Output:

1.23

func (Numeric) Mul

func (n Numeric) Mul(n2 Numeric) Numeric

Mul returns the product of n and n2.

Example
x, _ := FromString("2")
y, _ := FromString("3.5")
fmt.Println(x.Mul(y).String())
Output:

7

func (Numeric) Neg

func (n Numeric) Neg() Numeric

Neg returns the negated value of n.

Example
n, _ := FromString("42")
fmt.Println(n.Neg().String())
Output:

-42

func (Numeric) Round

func (n Numeric) Round(places int, mode RoundMode) Numeric

Round returns a new Numeric rounded to the specified number of decimal places. 'places' is digits after the decimal point. 0 means integer rounding. Underflow is removed.

func (Numeric) Sign

func (n Numeric) Sign() int

Sign returns -1 if negative, 1 if zero or positive, 0 for NaN

Example
n1, _ := FromString("-1")
n2, _ := FromString("0")
n3, _ := FromString("3.14")
fmt.Println(n1.Sign(), n2.Sign(), n3.Sign())
Output:

-1 1 1

func (Numeric) String

func (n Numeric) String() string

String returns the decimal string representation of the number. This function allocates to the heap the return string.

func (Numeric) Sub

func (n Numeric) Sub(n2 Numeric) Numeric

Sub returns the result of subtracting n2 from n.

Example
x, _ := FromString("5")
y, _ := FromString("3.2")
fmt.Println(x.Sub(y).String())
Output:

1.8

func (Numeric) Truncate

func (n Numeric) Truncate(n2 Numeric) Numeric

TruncateTo returns n rounded down to the nearest integer.

func (*Numeric) UnmarshalJSON

func (n *Numeric) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler for Numeric. Parses quoted decimal strings. Returns error on invalid input.

Example
var n Numeric
err := n.UnmarshalJSON([]byte(`"789.01"`))
if err != nil {
	panic(err)
}
fmt.Println(n.String())
Output:

789.01

func (*Numeric) UnmarshalText

func (n *Numeric) UnmarshalText(text []byte) error

UnmarshalText implements encoding.TextUnmarshaler for text formats.

Example
var n Numeric
err := n.UnmarshalText([]byte("456.78"))
if err != nil {
	panic(err)
}
fmt.Println(n.String())
Output:

456.78

type RoundMode

type RoundMode int

RoundMode represents rounding behavior for Numeric.Round.

const (
	// RoundTowards rounds toward zero (truncates).
	RoundTowards RoundMode = iota

	// RoundAway rounds away from zero.
	RoundAway

	// RoundHalfDown rounds to nearest, but halves are rounded down.
	RoundHalfDown

	// RoundHalfUp rounds to nearest, but halves are rounded up.
	RoundHalfUp
)

func (RoundMode) String

func (rm RoundMode) String() string

String returns the string name for the RoundMode.

Directories

Path Synopsis
encoding

Jump to

Keyboard shortcuts

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