CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/golang-github-com-shopspring--decimal

Arbitrary-precision fixed-point decimal numbers for Go, avoiding floating-point precision issues with support for arithmetic, rounding, serialization, and database integration.

Overview
Eval results
Files

creating-decimals.mddocs/guides/

Creating Decimals - Decision Guide

Complete guide to choosing the right constructor and creating Decimal values.

Decision Tree

Need to create a Decimal?
│
├─ From exact string value? → NewFromString() ✓ RECOMMENDED
│  ├─ Want panic on error? → RequireFromString()
│  └─ Has formatting ($, commas)? → NewFromFormattedString()
│
├─ From integer? → NewFromInt(), NewFromInt32(), NewFromUint64()
│
├─ From float? → NewFromFloat(), NewFromFloat32()
│  ├─ Want specific precision? → NewFromFloatWithExponent()
│  └─ WARNING: May lose precision, prefer NewFromString()
│
├─ From big.Int? → NewFromBigInt(value, exp)
│
├─ From big.Rat? → NewFromBigRat(value, precision)
│
├─ Manual value + exponent? → New(value, exp)
│
├─ Need nullable for database? → NewNullDecimal()
│
└─ Zero value? → var d decimal.Decimal (equals 0, safe to use)

Best Practices

1. Prefer NewFromString for Exact Values

✓ RECOMMENDED:

price, err := decimal.NewFromString("19.99")
if err != nil {
    return err
}

Why:

  • Exact representation, no precision loss
  • Best for financial values, prices, amounts
  • No floating-point rounding errors

2. Use Float Constructors with Caution

⚠ CAUTION:

d := decimal.NewFromFloat(0.1)
// May not be exactly 0.1 due to float64 representation

When OK:

  • Converting existing float64 values
  • Precision loss is acceptable
  • Values are already approximate

When NOT OK:

  • Financial calculations
  • Need exact decimal values
  • Converting string constants (use NewFromString instead)

3. Integer Constructors are Safe

✓ SAFE:

quantity := decimal.NewFromInt(10)
multiplier := decimal.NewFromInt32(100)

Use for:

  • Whole numbers
  • Quantities
  • Multipliers without fractional parts

4. Manual Construction

New(value, exp):

// 19.99 = 1999 * 10^-2
price := decimal.New(1999, -2)

// 1000000 = 1 * 10^6
million := decimal.New(1, 6)

Use when:

  • You understand the representation
  • Building from components
  • Performance critical (faster than string parsing)

Complete Constructor Reference

From String - RECOMMENDED

NewFromString - Safest Option

func NewFromString(value string) (Decimal, error)

Supported formats:

  • Integers: "123", "-456"
  • Decimals: "123.45", ".001", "123."
  • Scientific: "1.5e-3", "1.5E+2"
  • Preserves trailing zeros: "1.500"1.500

Examples:

d1, err := decimal.NewFromString("19.99")        // 19.99
d2, err := decimal.NewFromString("-123.456")     // -123.456
d3, err := decimal.NewFromString(".0001")        // 0.0001
d4, err := decimal.NewFromString("1.5e-3")       // 0.0015
d5, err := decimal.NewFromString("1.47000")      // 1.47000 (trailing zeros kept)

// Error handling
d, err := decimal.NewFromString("invalid")
if err != nil {
    // handle error
}

RequireFromString - Panic on Error

func RequireFromString(value string) Decimal

Use when:

  • Parsing constants known to be valid
  • In test code
  • Failure should panic

Examples:

// OK for constants
const TAX_RATE = "0.08"
taxRate := decimal.RequireFromString(TAX_RATE)

// In tests
func TestPrice(t *testing.T) {
    price := decimal.RequireFromString("99.99")
    // ...
}

// DON'T use with user input
userInput := getUserInput()  // may be invalid!
d := decimal.RequireFromString(userInput)  // BAD: could panic

NewFromFormattedString - Strip Formatting

func NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)

Use for:

  • Currency symbols: $, , £
  • Thousands separators: ,, _, .
  • Other formatting characters

Examples:

import "regexp"

// Strip currency symbols and commas
r := regexp.MustCompile(`[$,]`)
d1, err := decimal.NewFromFormattedString("$5,125.99", r)  // 5125.99

// Strip underscores (Go number literals style)
r2 := regexp.MustCompile(`[_]`)
d2, err := decimal.NewFromFormattedString("1_000_000", r2)  // 1000000

// Strip currency code
r3 := regexp.MustCompile(`[A-Z\s]`)
d3, err := decimal.NewFromFormattedString("5000 USD", r3)   // 5000

From Integer - Safe

NewFromInt - From int64

func NewFromInt(value int64) Decimal

Examples:

decimal.NewFromInt(123)      // 123
decimal.NewFromInt(-10)      // -10
decimal.NewFromInt(0)        // 0

NewFromInt32 - From int32

func NewFromInt32(value int32) Decimal

Examples:

decimal.NewFromInt32(42)     // 42

NewFromUint64 - From uint64

func NewFromUint64(value uint64) Decimal

Examples:

decimal.NewFromUint64(123)   // 123

From Float - Use with Caution

NewFromFloat - From float64

func NewFromFloat(value float64) Decimal
// Panics on NaN, +Inf, -Inf

Precision:

  • Typically 15 significant digits
  • May have representation errors

Examples:

decimal.NewFromFloat(123.456)           // 123.456
decimal.NewFromFloat(0.1)                // May be 0.1000000000000000055...

// Panics on special values
decimal.NewFromFloat(math.NaN())        // PANIC
decimal.NewFromFloat(math.Inf(1))       // PANIC
decimal.NewFromFloat(math.Inf(-1))      // PANIC

Safe Pattern:

if math.IsNaN(f) || math.IsInf(f, 0) {
    return errors.New("invalid float")
}
d := decimal.NewFromFloat(f)

NewFromFloat32 - From float32

func NewFromFloat32(value float32) Decimal
// Panics on NaN, +Inf, -Inf

Precision:

  • Typically 6-8 significant digits
  • Less precise than NewFromFloat

Examples:

decimal.NewFromFloat32(float32(123.456))  // ~123.456 (less precise)

NewFromFloatWithExponent - Float with Precision

func NewFromFloatWithExponent(value float64, exp int32) Decimal

Rounds to specified exponent precision.

Examples:

decimal.NewFromFloatWithExponent(123.456, -2)      // 123.46 (2 decimal places)
decimal.NewFromFloatWithExponent(123.456789, -4)   // 123.4568 (4 decimal places)
decimal.NewFromFloatWithExponent(123.456, 0)       // 123 (no decimal places)

Manual Construction

New - Value and Exponent

func New(value int64, exp int32) Decimal
// Returns: value * 10^exp

Formula: result = value * 10^exp

Examples:

// Positive exponent (multiply by powers of 10)
decimal.New(1, 0)           // 1 * 10^0 = 1
decimal.New(1, 6)           // 1 * 10^6 = 1000000
decimal.New(123, 2)         // 123 * 10^2 = 12300

// Negative exponent (divide by powers of 10)
decimal.New(12345, -3)      // 12345 * 10^-3 = 12.345
decimal.New(-12345, -3)     // -12345 * 10^-3 = -12.345
decimal.New(1999, -2)       // 1999 * 10^-2 = 19.99

// Zero
decimal.New(0, 0)           // 0

From big.Int

func NewFromBigInt(value *big.Int, exp int32) Decimal
// Returns: value * 10^exp

Examples:

import "math/big"

bigVal := big.NewInt(123456789)
d := decimal.NewFromBigInt(bigVal, -3)  // 123456.789

// Large numbers
large := new(big.Int)
large.SetString("999999999999999999999999", 10)
d2 := decimal.NewFromBigInt(large, 0)

From big.Rat

func NewFromBigRat(value *big.Rat, precision int32) Decimal
// Rounds to precision decimal places

Examples:

import "math/big"

decimal.NewFromBigRat(big.NewRat(0, 1), 0)     // 0
decimal.NewFromBigRat(big.NewRat(4, 5), 1)     // 0.8
decimal.NewFromBigRat(big.NewRat(1000, 3), 3)  // 333.333
decimal.NewFromBigRat(big.NewRat(2, 7), 4)     // 0.2857
decimal.NewFromBigRat(big.NewRat(1, 3), 10)    // 0.3333333333

Nullable Decimal

func NewNullDecimal(d Decimal) NullDecimal
// Returns NullDecimal with Valid=true

Use for:

  • Database columns that allow NULL
  • Optional values in APIs

Examples:

// Create valid NullDecimal
price := decimal.NewFromString("99.99")
nullPrice := decimal.NewNullDecimal(price)
// nullPrice.Decimal = 99.99, nullPrice.Valid = true

// Create NULL value (zero value)
var nullDiscount decimal.NullDecimal
// nullDiscount.Valid = false (represents NULL)

See NullDecimal API →

Zero Value

var d decimal.Decimal
// d equals 0, safe to use immediately

Zero value is valid and equals 0:

var total decimal.Decimal
total.IsZero()                 // true
total.Equal(decimal.Zero)       // true
total = total.Add(decimal.NewFromInt(10))  // OK, adds to 0

Common Patterns

Configuration Constants

var (
    TaxRate      = decimal.RequireFromString("0.08")
    MinimumOrder = decimal.RequireFromString("10.00")
    MaxDiscount  = decimal.RequireFromString("100.00")
)

User Input Parsing

func ParsePrice(input string) (decimal.Decimal, error) {
    // Strip currency symbols and commas
    r := regexp.MustCompile(`[$,]`)
    return decimal.NewFromFormattedString(input, r)
}

// Usage
price, err := ParsePrice("$1,234.56")
if err != nil {
    return fmt.Errorf("invalid price: %w", err)
}

Database Scanning

var price decimal.Decimal
err := db.QueryRow("SELECT price FROM products WHERE id = ?", id).
    Scan(&price)
// Decimal.Scan() automatically handles conversion

API Response Parsing

type ProductResponse struct {
    Price decimal.Decimal `json:"price"`
}

var response ProductResponse
err := json.Unmarshal(data, &response)
// Decimal.UnmarshalJSON() handles both "19.99" and 19.99

Error Handling

NewFromString Errors

d, err := decimal.NewFromString(input)
if err != nil {
    // Input was not a valid decimal string
    return fmt.Errorf("invalid decimal: %w", err)
}

Common invalid inputs:

  • Empty string: ""
  • Non-numeric: "abc", "$$$"
  • Multiple decimals: "1.2.3"
  • Invalid scientific: "1e"

Float Panics

// BAD: Could panic
d := decimal.NewFromFloat(userFloat)

// GOOD: Check first
if math.IsNaN(userFloat) || math.IsInf(userFloat, 0) {
    return errors.New("invalid float value")
}
d := decimal.NewFromFloat(userFloat)

Performance Notes

Fastest to slowest:

  1. New(value, exp) - Direct construction
  2. NewFromInt family - Integer conversion
  3. NewFromFloat family - Float conversion
  4. NewFromString - String parsing (slower but most accurate)
  5. NewFromBigInt / NewFromBigRat - Big number conversion

Recommendation:

  • Use NewFromString unless performance is critical
  • Profile before optimizing
  • Correctness > speed for financial calculations

Install with Tessl CLI

npx tessl i tessl/golang-github-com-shopspring--decimal

docs

index.md

README.md

tile.json