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

formatting-output.mddocs/guides/

Formatting and Rounding Guide

Complete guide to formatting Decimal values as strings and applying rounding strategies.

Quick Reference - String Formatting

MethodOutputExample Input → Output
String()Full precision123.4567"123.4567"
StringFixed(2)Fixed places (half-up)123.456"123.46"
StringFixedBank(2)Fixed places (banker's)123.455"123.46"
StringFixedCash(5)Cash rounding3.43"3.45"

Quick Reference - Rounding Methods

MethodStrategy5.45 at 1 place-5.45 at 1 place
Round(1)Half-up (away from 0)5.5-5.5
RoundBank(1)Banker's (to even)5.4-5.4
RoundUp(1)Away from zero5.5-5.5
RoundDown(1)Towards zero5.4-5.4
RoundCeil(1)Towards +∞5.5-5.4
RoundFloor(1)Towards -∞5.4-5.5
Truncate(1)Cut off digits5.4-5.4

Choosing a Rounding Strategy

Need to round a decimal?
│
├─ Financial/Currency? → Round(2) or RoundBank(2)
│  └─ Fair rounding? → RoundBank(2) (banker's rounding)
│
├─ Cash transactions? → RoundCash(5, 10, 25, 50, or 100)
│
├─ Always round up (ceiling)? → RoundCeil(places)
│
├─ Always round down (floor)? → RoundFloor(places)
│
├─ Towards zero? → RoundDown(places) or Truncate(precision)
│
├─ Away from zero? → RoundUp(places)
│
└─ Standard rounding? → Round(places) (half-up)

String Formatting

String() - Full Precision

func (d Decimal) String() string
// Returns string with full precision, no rounding

Examples:

decimal.NewFromString("123.4567").String()     // "123.4567"
decimal.NewFromFloat(1234.5678).String()       // "1234.5678"
decimal.NewFromInt(100).String()               // "100"
decimal.New(-12345, -3).String()               // "-12.345"

Use when:

  • Need exact representation
  • Debug output
  • Logging
  • No rounding required

StringFixed() - Fixed Decimal Places

func (d Decimal) StringFixed(places int32) string
// Returns string with fixed places after decimal, half-up rounding
// Negative places rounds integer part

Examples:

d := decimal.NewFromFloat(1234.5678)

d.StringFixed(2)   // "1234.57"
d.StringFixed(0)   // "1235"
d.StringFixed(6)   // "1234.567800" (pads with zeros)
d.StringFixed(-1)  // "1230" (rounds to tens)
d.StringFixed(-2)  // "1200" (rounds to hundreds)

// Edge cases
decimal.NewFromFloat(5.45).StringFixed(1)   // "5.5" (half-up)
decimal.NewFromFloat(5.44).StringFixed(1)   // "5.4"

Use cases:

// Currency display (always 2 decimals)
fmt.Printf("$%s", price.StringFixed(2))  // "$19.99"

// Percentage display
fmt.Printf("%s%%", percent.StringFixed(1))  // "15.5%"

// Rounded output
fmt.Println(result.StringFixed(2))

StringFixedBank() - Banker's Rounding

func (d Decimal) StringFixedBank(places int32) string
// Returns string with fixed places, banker's rounding (half-to-even)
// Negative places rounds integer part

Banker's Rounding:

  • When digit is exactly 5, rounds to nearest even number
  • Reduces accumulated bias in repeated rounding
  • Preferred for financial applications

Examples:

// Half-to-even rounding
decimal.NewFromFloat(5.45).StringFixedBank(1)   // "5.4" (rounds to even: 4)
decimal.NewFromFloat(5.55).StringFixedBank(1)   // "5.6" (rounds to even: 6)
decimal.NewFromFloat(5.46).StringFixedBank(1)   // "5.5" (not exactly 5)

// Integer rounding
decimal.NewFromInt(545).StringFixedBank(-1)     // "540" (rounds to even: 4)
decimal.NewFromInt(555).StringFixedBank(-1)     // "560" (rounds to even: 6)

Use when:

  • Need statistically unbiased rounding
  • Financial calculations with repeated rounding
  • Compliance requirements specify banker's rounding

StringFixedCash() - Cash Rounding

func (d Decimal) StringFixedCash(interval uint8) string
// Rounds to nearest multiple of cash interval
// interval must be: 5, 10, 25, 50, or 100 (cents)
// Panics for other values

Intervals:

  • 5 = nickel (5¢) rounding
  • 10 = dime (10¢) rounding
  • 25 = quarter (25¢) rounding
  • 50 = half-dollar (50¢) rounding
  • 100 = dollar ($1) rounding

Examples:

// Nickel rounding (5 cents)
decimal.NewFromFloat(3.43).StringFixedCash(5)   // "3.45"
decimal.NewFromFloat(3.47).StringFixedCash(5)   // "3.45"
decimal.NewFromFloat(3.48).StringFixedCash(5)   // "3.50"

// Dime rounding (10 cents)
decimal.NewFromFloat(3.45).StringFixedCash(10)  // "3.50"
decimal.NewFromFloat(3.44).StringFixedCash(10)  // "3.40"

// Quarter rounding (25 cents)
decimal.NewFromFloat(3.41).StringFixedCash(25)  // "3.50"
decimal.NewFromFloat(3.37).StringFixedCash(25)  // "3.25"

// Half-dollar rounding (50 cents)
decimal.NewFromFloat(3.75).StringFixedCash(50)  // "4.00"
decimal.NewFromFloat(3.24).StringFixedCash(50)  // "3.00"

// Dollar rounding (100 cents)
decimal.NewFromFloat(3.50).StringFixedCash(100) // "4.00"
decimal.NewFromFloat(3.49).StringFixedCash(100) // "3.00"

Use cases:

// Point of sale (no pennies)
total := calculateTotal()
displayAmount := total.StringFixedCash(5)

// Cash payment (round to nickel in some countries)
cashAmount, _ := decimal.NewFromString(total.StringFixedCash(5))

Rounding Methods

All rounding methods return new Decimal values (immutable).

Round() - Half-Up (Standard)

func (d Decimal) Round(places int32) Decimal
// Rounds to places decimal places using half-up strategy
// When digit is 5, rounds away from zero

Examples:

decimal.NewFromFloat(5.45).Round(1)     // 5.5
decimal.NewFromFloat(5.44).Round(1)     // 5.4
decimal.NewFromFloat(5.445).Round(2)    // 5.45
decimal.NewFromFloat(-5.45).Round(1)    // -5.5 (away from zero)

// Integer rounding (negative places)
decimal.NewFromInt(545).Round(-1)       // 550 (round to tens)
decimal.NewFromInt(544).Round(-1)       // 540
decimal.NewFromInt(545).Round(-2)       // 500 (round to hundreds)

Use cases:

// Standard financial rounding
price := calculatePrice().Round(2)

// Round percentage
percentage := calculated.Round(1)

RoundBank() - Banker's Rounding

func (d Decimal) RoundBank(places int32) Decimal
// Rounds using banker's rounding (half-to-even)
// When digit is exactly 5, rounds to nearest even

Examples:

// Half-to-even
decimal.NewFromFloat(5.45).RoundBank(1)   // 5.4 (to even)
decimal.NewFromFloat(5.55).RoundBank(1)   // 5.6 (to even)
decimal.NewFromFloat(5.35).RoundBank(1)   // 5.4 (to even)

// Not exactly 5
decimal.NewFromFloat(5.46).RoundBank(1)   // 5.5 (normal rounding)

// Integer rounding
decimal.NewFromInt(545).RoundBank(-1)     // 540 (to even)
decimal.NewFromInt(555).RoundBank(-1)     // 560 (to even)

Use cases:

// Financial calculations where bias matters
result := calculation.RoundBank(2)

// Compliance with regulations requiring banker's rounding
taxAmount := amount.Mul(rate).RoundBank(2)

RoundUp() - Away from Zero

func (d Decimal) RoundUp(places int32) Decimal
// Always rounds away from zero
// Positive: ceiling, Negative: floor

Examples:

decimal.NewFromFloat(5.41).RoundUp(1)     // 5.5
decimal.NewFromFloat(5.40).RoundUp(1)     // 5.4 (no change)
decimal.NewFromFloat(-5.41).RoundUp(1)    // -5.5 (away from zero)

// Integer rounding
decimal.NewFromInt(541).RoundUp(-2)       // 600
decimal.NewFromInt(500).RoundUp(-2)       // 500 (no change)

Use cases:

// Conservative estimates (ensure enough)
bufferAmount := estimate.RoundUp(2)

// Allocate resources (round up to ensure coverage)
requiredUnits := calculated.RoundUp(0)

RoundDown() - Towards Zero

func (d Decimal) RoundDown(places int32) Decimal
// Always rounds towards zero (truncation)
// Positive: floor, Negative: ceiling

Examples:

decimal.NewFromFloat(5.49).RoundDown(1)   // 5.4
decimal.NewFromFloat(-5.49).RoundDown(1)  // -5.4 (towards zero)

// Integer rounding
decimal.NewFromInt(549).RoundDown(-2)     // 500

Use cases:

// Pessimistic estimates
minAmount := estimate.RoundDown(2)

// Truncate fractional parts
wholeUnits := amount.RoundDown(0)

RoundCeil() - Towards Positive Infinity

func (d Decimal) RoundCeil(places int32) Decimal
// Always rounds towards +∞ (ceiling)

Examples:

decimal.NewFromFloat(5.41).RoundCeil(1)   // 5.5 (up)
decimal.NewFromFloat(5.40).RoundCeil(1)   // 5.4 (no change)
decimal.NewFromFloat(-5.41).RoundCeil(1)  // -5.4 (up towards +∞)

// Integer rounding
decimal.NewFromInt(541).RoundCeil(-2)     // 600
decimal.NewFromInt(-541).RoundCeil(-2)    // -500

Use cases:

// Always charge at least minimum
chargeAmount := calculated.RoundCeil(2)

// Ensure positive minimum
positiveMin := value.RoundCeil(0)  // at least 1 if > 0

RoundFloor() - Towards Negative Infinity

func (d Decimal) RoundFloor(places int32) Decimal
// Always rounds towards -∞ (floor)

Examples:

decimal.NewFromFloat(5.49).RoundFloor(1)  // 5.4 (down)
decimal.NewFromFloat(-5.41).RoundFloor(1) // -5.5 (down towards -∞)

// Integer rounding
decimal.NewFromInt(549).RoundFloor(-2)    // 500
decimal.NewFromInt(-541).RoundFloor(-2)   // -600

Use cases:

// Always give customer benefit of doubt
discount := calculated.RoundFloor(2)

// Ensure doesn't exceed limit
cappedAmount := amount.RoundFloor(2)

Truncate() - Cut Off Digits

func (d Decimal) Truncate(precision int32) Decimal
// Removes digits beyond precision without rounding
// precision is last digit kept (must be >= 0)

Examples:

d := decimal.NewFromString("123.456")

d.Truncate(2)   // "123.45" (keep 2 decimal places)
d.Truncate(1)   // "123.4"  (keep 1 decimal place)
d.Truncate(0)   // "123"    (no decimal places)

// No negative precision (unlike Round methods)

Use cases:

// Display without rounding
display := value.Truncate(2).String()

// Remove fractional part
integer := value.Truncate(0)

RoundCash() - Cash Rounding

func (d Decimal) RoundCash(interval uint8) Decimal
// Rounds to nearest cash interval
// interval must be: 5, 10, 25, 50, or 100
// Returns Decimal (not string)

Examples:

decimal.NewFromFloat(3.43).RoundCash(5)   // 3.45
decimal.NewFromFloat(3.43).RoundCash(10)  // 3.40
decimal.NewFromFloat(3.43).RoundCash(25)  // 3.50

Use cases:

// Calculate cash payment amount
cashTotal := total.RoundCash(5)

// Store rounded value for later
rounded := amount.RoundCash(5)
db.Exec("INSERT INTO transactions (amount) VALUES (?)", rounded)

Floor() and Ceil() - Integer Rounding

func (d Decimal) Floor() Decimal
// Returns nearest integer <= d

func (d Decimal) Ceil() Decimal
// Returns nearest integer >= d

Examples:

// Floor
decimal.NewFromFloat(5.9).Floor()    // 5
decimal.NewFromFloat(-5.1).Floor()   // -6 (down to -∞)
decimal.NewFromInt(5).Floor()        // 5 (no change)

// Ceil
decimal.NewFromFloat(5.1).Ceil()     // 6
decimal.NewFromFloat(-5.9).Ceil()    // -5 (up to +∞)
decimal.NewFromInt(5).Ceil()         // 5 (no change)

Use cases:

// Count whole items (round up)
itemsNeeded := calculated.Ceil()

// Count completed items (round down)
itemsCompleted := calculated.Floor()

Common Patterns

Currency Display

// Always show 2 decimal places
func formatCurrency(amount decimal.Decimal) string {
    return "$" + amount.StringFixed(2)
}

// Usage
fmt.Println(formatCurrency(decimal.NewFromString("19.99")))  // "$19.99"
fmt.Println(formatCurrency(decimal.NewFromInt(20)))          // "$20.00"

Percentage Display

// Show percentage with 1 decimal place
func formatPercent(rate decimal.Decimal) string {
    percent := rate.Mul(decimal.NewFromInt(100))
    return percent.StringFixed(1) + "%"
}

// Usage
rate := decimal.NewFromFloat(0.085)
fmt.Println(formatPercent(rate))  // "8.5%"

Financial Calculations with Rounding

// Calculate tax (round to 2 places)
subtotal := decimal.NewFromString("99.99")
taxRate := decimal.NewFromFloat(0.08)
taxAmount := subtotal.Mul(taxRate).Round(2)  // 8.00

// Total
total := subtotal.Add(taxAmount)  // 107.99

Point of Sale Rounding

// Cash transaction (round to nickel)
total := calculateTotal()
cashAmount := total.RoundCash(5)

fmt.Printf("Total: %s\n", total.StringFixed(2))
fmt.Printf("Cash: %s\n", cashAmount.StringFixed(2))

Banker's Rounding for Fairness

// Process many transactions with fair rounding
var totalRounded decimal.Decimal
for _, amount := range amounts {
    rounded := amount.RoundBank(2)  // Fair rounding
    totalRounded = totalRounded.Add(rounded)
}

When to Round

Before Display

// Round just before showing to user
displayPrice := price.StringFixed(2)

After Calculation

// Calculate first, then round final result
result := a.Mul(b).Add(c).Div(d).Round(2)

Before Storage

// Round before storing in database
rounded := calculated.Round(2)
db.Exec("INSERT INTO amounts (value) VALUES (?)", rounded)

Intermediate Steps

// Round intermediate results for financial precision
taxAmount := subtotal.Mul(taxRate).Round(2)  // Round tax
discountAmount := subtotal.Mul(discountRate).Round(2)  // Round discount
total := subtotal.Add(taxAmount).Sub(discountAmount)  // No rounding needed

Rounding Comparison

Value: 5.455

Round(2)      → 5.46  (half-up)
RoundBank(2)  → 5.46  (to even)
RoundUp(2)    → 5.46  (away from zero)
RoundDown(2)  → 5.45  (towards zero)
RoundCeil(2)  → 5.46  (towards +∞)
RoundFloor(2) → 5.45  (towards -∞)
Truncate(2)   → 5.45  (cut off)

Value: 5.445

Round(2)      → 5.45  (half-up, but floating point!)
RoundBank(2)  → 5.44  (to even)
RoundUp(2)    → 5.45  (away from zero)
RoundDown(2)  → 5.44  (towards zero)
RoundCeil(2)  → 5.45  (towards +∞)
RoundFloor(2) → 5.44  (towards -∞)
Truncate(2)   → 5.44  (cut off)

Precision Configuration

DivisionPrecision

// Default: 16 decimal places
decimal.NewFromInt(1).Div(decimal.NewFromInt(3))
// "0.3333333333333333"

// Increase precision
decimal.DivisionPrecision = 4
decimal.NewFromInt(1).Div(decimal.NewFromInt(3))
// "0.3333"

// Restore default
decimal.DivisionPrecision = 16

Use cases:

// Financial application (2 decimal places)
decimal.DivisionPrecision = 2
unitPrice := totalPrice.Div(quantity)  // Always 2 decimals

// Scientific calculation (30 decimal places)
decimal.DivisionPrecision = 30
precise := a.Div(b)

Complete Conversion API → Complete Rounding API →

Install with Tessl CLI

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

docs

index.md

README.md

tile.json