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

index.mddocs/

shopspring/decimal

Arbitrary-precision fixed-point decimal numbers for Go. Eliminates floating-point precision errors in financial calculations, e-commerce, and any application requiring exact decimal arithmetic.

Package: github.com/shopspring/decimal | Version: v1.4.0 | Install: go get github.com/shopspring/decimal

⚡ 30-Second Quick Start

import "github.com/shopspring/decimal"

// Create decimals (ALWAYS prefer NewFromString for exact values)
price, _ := decimal.NewFromString("19.99")
quantity := decimal.NewFromInt(3)

// Calculate (immutable - returns new values)
total := price.Mul(quantity)  // 59.97

// Compare (NEVER use == operator - won't work!)
if total.GreaterThan(decimal.NewFromInt(50)) {
    discount := total.Mul(decimal.NewFromString("0.10"))
    total = total.Sub(discount)
}

// Round and display
fmt.Println(total.StringFixed(2))  // "53.97"

🚨 Critical Rules (Avoid Common Mistakes)

❌ NEVER DO THIS✅ ALWAYS DO THISWhy
if d1 == d2if d1.Equal(d2)== compares pointers, not values - will fail
result := d.Div(divisor)if !divisor.IsZero() { result = d.Div(divisor) }Division by zero causes panic
decimal.NewFromFloat(19.99)decimal.NewFromString("19.99")Float has precision loss: 19.989999...
d.Add(x) (expecting d to change)d = d.Add(x) (assign result)Decimal is immutable - operations return new values
MarshalJSONWithoutQuotes=trueKeep false (default)Unquoted JSON numbers lose precision in JavaScript
Ignoring NewFromString errorAlways check err != nilInvalid strings return errors you must handle

📋 Task Navigator - Find What You Need

Creating Decimals

  • From string (exact): NewFromString("19.99")Guide | API
  • From integer: NewFromInt(100)Guide
  • From float ⚠️: NewFromFloat(3.14)Guide (caution: precision loss)
  • For NULL in database: Use NullDecimalGuide | API
  • Decision tree: Which constructor?

Doing Math

Comparing & Checking

  • Equality/comparison: Equal(), GreaterThan(), LessThan()API
  • Check zero/sign: IsZero(), IsPositive(), IsNegative()API
  • Three-way compare: Cmp() returns -1/0/1 → API

Formatting & Display

Database & Storage

JSON & APIs

Troubleshooting

  • Common mistakes: Gotchas Guide
  • Error messages: Common Errors (below)
  • Best practices: Best Practices Guide
  • Performance tips: Performance Considerations
  • Migration from float64: Migration Guide

📊 Quick Reference Tables

Creating Decimals - Which Constructor?

ConstructorUse WhenPrecisionExample
NewFromString(s)Exact values neededPerfectNewFromString("19.99")
NewFromInt(i)Integer valuesPerfectNewFromInt(100)
NewFromInt32(i)32-bit integersPerfectNewFromInt32(42)
NewFromFloat(f)Converting existing float ⚠️May loseNewFromFloat(3.14)
New(value, exp)Manual control: value × 10^expPerfectNew(1999, -2) = 19.99
ZeroNeed zero constantPerfectdecimal.Zero

Complete Constructor Guide → | Constructor API →

Arithmetic Operations

OperationMethodReturnsExampleNotes
AdditionAdd(d2)d + d2price.Add(tax)
SubtractionSub(d2)d - d2total.Sub(discount)
MultiplicationMul(d2)d × d2price.Mul(quantity)
DivisionDiv(d2)d ÷ d2total.Div(count)⚠️ Panics if d2=0
Division (rounded)DivRound(d2, places)Rounded d ÷ d2amount.DivRound(decimal.NewFromInt(3), 2)Safer than Div
ModuloMod(d2)d % d2amount.Mod(interval)
NegationNeg()-dprofit.Neg() (make loss)
Absolute valueAbs()|d|delta.Abs()
Power of 10 shiftShift(places)d × 10^placesamount.Shift(2) (×100)Fast multiplication/division

Complete Arithmetic Guide → | Arithmetic API →

Comparison Methods

MethodReturnsUse ForExample
Equal(d2)boolExact equalityprice.Equal(decimal.NewFromString("19.99"))
Cmp(d2)-1, 0, 1Three-way compare, sortingif total.Cmp(limit) > 0
GreaterThan(d2)boold > d2if balance.GreaterThan(minBalance)
GreaterThanOrEqual(d2)boold ≥ d2if amount.GreaterThanOrEqual(threshold)
LessThan(d2)boold < d2if price.LessThan(maxPrice)
LessThanOrEqual(d2)boold ≤ d2if value.LessThanOrEqual(cap)
IsZero()boold == 0if balance.IsZero()
IsPositive()boold > 0if profit.IsPositive()
IsNegative()boold < 0if loss.IsNegative()

Complete Comparison API →

Rounding Strategies - Which One?

MethodStrategy2.5 →-2.5 →Use Case
Round(n)Half-up (away from 0)3-3General purpose ⭐ Most common
RoundBank(n)Banker's (to even)2-2Minimize rounding bias in large datasets
RoundUp(n)Away from zero3-3Conservative estimates (always round up)
RoundDown(n)Toward zero2-2Aggressive estimates (always round down)
RoundCeil(n)Toward +∞3-2Ceiling function
RoundFloor(n)Toward -∞2-3Floor function
Truncate(n)Cut off digits2-2No rounding, just truncate

Rounding Decision Guide → | Rounding API →

String Formatting

MethodOutputUse CaseExample
String()Full precisionDebug, logging"19.9900"
StringFixed(places)Fixed decimals (half-up)Money displayStringFixed(2)"19.99"
StringFixedBank(places)Fixed decimals (banker's)Financial reportsStringFixedBank(2)"19.98"
StringFixedCash(interval)Cash roundingPhysical currencyStringFixedCash(5)"20.00"

Complete Formatting Guide → | Conversion API →

Type Conversions

MethodTo TypePrecisionNotes
String()stringPerfectFull precision preserved
StringFixed(n)stringn decimal placesRounded to n places
Float64()(float64, bool)May lose ⚠️Returns (value, exact) - exact=false if precision lost
IntPart()int64Integer onlyDrops fractional part, may overflow
BigInt()*big.IntInteger onlyDrops fractional part, no overflow
BigFloat()*big.FloatMay loseApproximate conversion
Rat()*big.RatPerfectExact rational representation

Complete Conversion API →

💼 Copy-Paste Ready Patterns

Financial Calculation with Tax

// Calculate price with tax
price, _ := decimal.NewFromString("99.99")
taxRate, _ := decimal.NewFromString("0.08")  // 8% tax

// Calculate tax amount
tax := price.Mul(taxRate).Round(2)

// Calculate total
total := price.Add(tax)

fmt.Printf("Price: $%s\n", price.StringFixed(2))    // $99.99
fmt.Printf("Tax:   $%s\n", tax.StringFixed(2))      // $8.00
fmt.Printf("Total: $%s\n", total.StringFixed(2))    // $107.99

Percentage Discount

// Apply percentage discount
originalPrice, _ := decimal.NewFromString("200.00")
discountPercent := decimal.NewFromInt(15)  // 15% off
hundred := decimal.NewFromInt(100)

// Calculate discount amount
discountAmount := originalPrice.Mul(discountPercent).Div(hundred)

// Apply discount and round
finalPrice := originalPrice.Sub(discountAmount).Round(2)

fmt.Printf("Original: $%s\n", originalPrice.StringFixed(2))        // $200.00
fmt.Printf("Discount: $%s (15%%)\n", discountAmount.StringFixed(2)) // $30.00
fmt.Printf("Final:    $%s\n", finalPrice.StringFixed(2))           // $170.00

Safe Division with Error Handling

// Production-ready division function
func SafeDivide(numerator, denominator decimal.Decimal, places int32) (decimal.Decimal, error) {
    if denominator.IsZero() {
        return decimal.Zero, errors.New("division by zero")
    }
    return numerator.DivRound(denominator, places), nil
}

// Usage
average, err := SafeDivide(total, count, 2)
if err != nil {
    return fmt.Errorf("cannot calculate average: %w", err)
}

Database Storage (SQL)

// Product with decimal fields
type Product struct {
    ID       int64               `db:"id"`
    Name     string              `db:"name"`
    Price    decimal.Decimal     `db:"price"`      // Required field
    Discount decimal.NullDecimal `db:"discount"`   // Optional field (can be NULL)
}

// Insert product
_, err := db.Exec(
    "INSERT INTO products (id, name, price, discount) VALUES (?, ?, ?, ?)",
    product.ID,
    product.Name,
    product.Price,
    product.Discount,  // NullDecimal handles NULL automatically
)

// Query product
var product Product
err := db.QueryRow(
    "SELECT id, name, price, discount FROM products WHERE id = ?",
    productID,
).Scan(&product.ID, &product.Name, &product.Price, &product.Discount)

// Check if discount is NULL
if product.Discount.Valid {
    finalPrice := product.Price.Sub(product.Discount.Decimal)
} else {
    finalPrice := product.Price
}

JSON API Response

// API response structure
type OrderResponse struct {
    OrderID  int64               `json:"order_id"`
    Subtotal decimal.Decimal     `json:"subtotal"`    // Serializes as "19.99" (quoted)
    Tax      decimal.Decimal     `json:"tax"`
    Total    decimal.Decimal     `json:"total"`
    Discount decimal.NullDecimal `json:"discount,omitempty"` // null or omitted if not set
}

// Create response
order := OrderResponse{
    OrderID:  12345,
    Subtotal: decimal.NewFromString("89.97"),
    Tax:      decimal.NewFromString("7.20"),
    Total:    decimal.NewFromString("97.17"),
    Discount: decimal.NullDecimal{Valid: false}, // No discount
}

// Marshal to JSON (default: quoted strings for precision)
jsonData, err := json.Marshal(order)
// {"order_id":12345,"subtotal":"89.97","tax":"7.20","total":"97.17"}

// Unmarshal from JSON
var received OrderResponse
err = json.Unmarshal(jsonData, &received)

Split Amount Evenly

// Split amount among N recipients with remainder handling
func SplitAmount(total decimal.Decimal, recipients int) ([]decimal.Decimal, error) {
    if recipients <= 0 {
        return nil, errors.New("recipients must be positive")
    }

    recipientsDecimal := decimal.NewFromInt(int64(recipients))
    perPerson := total.DivRound(recipientsDecimal, 2)

    // Calculate remainder (due to rounding)
    distributed := perPerson.Mul(recipientsDecimal)
    remainder := total.Sub(distributed)

    // Create shares
    shares := make([]decimal.Decimal, recipients)
    for i := 0; i < recipients; i++ {
        shares[i] = perPerson
    }

    // Add remainder to first person (or distribute across recipients)
    if !remainder.IsZero() {
        shares[0] = shares[0].Add(remainder)
    }

    return shares, nil
}

// Usage: Split $100 among 3 people
shares, _ := SplitAmount(decimal.NewFromString("100.00"), 3)
// shares[0] = 33.34, shares[1] = 33.33, shares[2] = 33.33

❌ Common Errors & Solutions

Error/IssueCauseSolution
Comparison doesn't workUsed == or != operatorUse .Equal(d2) method instead
Panic: division by zeroDivisor is zeroCheck divisor.IsZero() before dividing
Panic: NaN or InfUsed NewFromFloat(NaN) or NewFromFloat(Inf)Validate float before converting
Lost precision in calculationUsed NewFromFloat with literalUse NewFromString("19.99") instead
Wrong rounding resultUsed wrong rounding methodSee rounding decision tree
JSON precision lossSet MarshalJSONWithoutQuotes=trueKeep default false - use quoted strings
Division not precise enoughDefault 16 digits insufficientSet decimal.DivisionPrecision or use DivRound
NULL database value errorUsed Decimal for nullable columnUse NullDecimal type instead
Decimal not changing after operationForgot immutabilityAssign result: d = d.Add(x)
Decimal comparison sorting wrongUsing wrong comparisonUse Cmp() method: returns -1/0/1

Complete Troubleshooting →

🏗️ Core Types

Decimal

// Immutable arbitrary-precision decimal number
// Internally: coefficient × 10^exponent
// Zero value is valid and equals 0
type Decimal struct { /* internal fields */ }

Key Properties:

  • Immutable: All operations return new values (never modify in place)
  • Safe zero value: var d decimal.Decimal equals 0 and is ready to use
  • Thread-safe for reads (immutable); global config vars are NOT thread-safe
  • Precision: Unlimited coefficient (uses big.Int), exponent range: -2³¹ to 2³¹-1
  • Internal representation: 1999 × 10^-2 = 19.99

NullDecimal

// Nullable decimal for database NULL and optional API fields
type NullDecimal struct {
    Decimal Decimal
    Valid   bool  // true if Decimal is valid (not NULL)
}

Use For:

  • Database columns that allow NULL (DECIMAL NULL)
  • Optional JSON fields (omitempty)
  • Distinguishing "zero" from "no value"
// Create NULL value
nullValue := decimal.NullDecimal{Valid: false}

// Create valid value
validValue := decimal.NullDecimal{
    Decimal: decimal.NewFromString("19.99"),
    Valid:   true,
}

// Check before using
if validValue.Valid {
    price := validValue.Decimal
}

Complete NullDecimal Guide → | Database Integration →

⚙️ Global Configuration

// Package-level variables (NOT thread-safe - set once at startup)
var DivisionPrecision = 16                // Decimal places for Div() operation
var PowPrecisionNegativeExponent = 16     // Precision for Pow() with negative exponent
var MarshalJSONWithoutQuotes = false      // JSON: false="19.99" (recommended), true=19.99 (loses precision)
var ExpMaxIterations = 1000               // Max iterations for ExpHullAbrham method
var Zero = New(0, 1)                      // Reusable zero constant

⚠️ Warning: These are global mutable variables. Set once during application initialization, NOT at runtime. Not thread-safe to modify.

// Example: Configure at startup
func init() {
    decimal.DivisionPrecision = 8      // Use 8 decimal places for division
    decimal.MarshalJSONWithoutQuotes = false  // Keep quoted (recommended)
}

Complete Configuration API →

🔄 Serialization Support

FormatMarshalUnmarshalDefault Encoding
JSONQuoted string: "19.99" (preserves precision)
XMLString in XML text element
BinaryCustom binary format (compact)
GobGo gob encoding
SQL✓ (Value)✓ (Scan)String or numeric (driver-dependent)

Complete Serialization Guide → | JSON Guide → | Database Guide →

✅ When to Use This Library

Use shopspring/decimal for:

  • Financial calculations - money, prices, taxes, interest rates, currency
  • E-commerce - product pricing, cart totals, discounts, shipping
  • Accounting - ledgers, balances, financial reports, reconciliation
  • Any exact decimal math - where precision matters and rounding errors are unacceptable
  • Database DECIMAL columns - DECIMAL, NUMERIC, MONEY types
  • JSON APIs with decimal values - where JavaScript number precision matters
  • Anywhere you'd use SQL DECIMAL - if it's DECIMAL in the database, use decimal.Decimal in Go

⚠️ Consider alternatives for:

  • High-performance math - use float64 if small precision loss is acceptable
  • Scientific computing - use float64, float32, or math/big.Float
  • Simple integer math - use int64 or int (faster)
  • Graphics/games - use float32 or float64 (much faster)
  • Extremely high precision math - consider math/big.Float or math/big.Rat

🎯 Quick Start by Use Case

E-commerce: CreatingArithmetic PatternsFormattingJSON

Database: Integration GuideNULL HandlingStorage Recommendations

Financial: ArithmeticFinancial PatternsRoundingBest Practices

Migration: Migration Guide - Replace types/operators, handle division by zero, testing patterns

🏛️ Architecture & Performance

Internal: coefficient × 10^exponent (e.g., 19.99 = 1999 × 10^-2). Coefficient uses *big.Int (arbitrary precision), exponent is int32. Zero value Decimal{} equals 0.

Performance: String parsing (slow, accurate) vs integer ops (fast) vs float conversion (fast, may lose precision). Immutable = new allocation per operation.

Thread Safety: Decimal/NullDecimal values are thread-safe (immutable). Global config vars are NOT thread-safe (set once at init).

Complete Performance Guide →

📚 Complete Documentation

Task-Based Guides (How-To)

Complete API Reference (What's Available)

🆘 Need Help?

Common issues: Comparison not working? Use .Equal() not ==. Panic? Check .IsZero() before division. Wrong value? Use NewFromString() not NewFromFloat(). NULL handling? Use NullDecimal.

Resources: Gotchas · Common Errors · Best Practices

Links: GitHub · Go Docs

Install with Tessl CLI

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

docs

index.md

README.md

tile.json