Arbitrary-precision fixed-point decimal numbers for Go, avoiding floating-point precision issues with support for arithmetic, rounding, serialization, and database integration.
Complete guide to choosing the right constructor and creating Decimal values.
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)✓ RECOMMENDED:
price, err := decimal.NewFromString("19.99")
if err != nil {
return err
}Why:
⚠ CAUTION:
d := decimal.NewFromFloat(0.1)
// May not be exactly 0.1 due to float64 representationWhen OK:
When NOT OK:
✓ SAFE:
quantity := decimal.NewFromInt(10)
multiplier := decimal.NewFromInt32(100)Use for:
New(value, exp):
// 19.99 = 1999 * 10^-2
price := decimal.New(1999, -2)
// 1000000 = 1 * 10^6
million := decimal.New(1, 6)Use when:
func NewFromString(value string) (Decimal, error)Supported formats:
"123", "-456""123.45", ".001", "123.""1.5e-3", "1.5E+2""1.500" → 1.500Examples:
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
}func RequireFromString(value string) DecimalUse when:
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 panicfunc NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)Use for:
$, €, £,, _, .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) // 5000func NewFromInt(value int64) DecimalExamples:
decimal.NewFromInt(123) // 123
decimal.NewFromInt(-10) // -10
decimal.NewFromInt(0) // 0func NewFromInt32(value int32) DecimalExamples:
decimal.NewFromInt32(42) // 42func NewFromUint64(value uint64) DecimalExamples:
decimal.NewFromUint64(123) // 123func NewFromFloat(value float64) Decimal
// Panics on NaN, +Inf, -InfPrecision:
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)) // PANICSafe Pattern:
if math.IsNaN(f) || math.IsInf(f, 0) {
return errors.New("invalid float")
}
d := decimal.NewFromFloat(f)func NewFromFloat32(value float32) Decimal
// Panics on NaN, +Inf, -InfPrecision:
Examples:
decimal.NewFromFloat32(float32(123.456)) // ~123.456 (less precise)func NewFromFloatWithExponent(value float64, exp int32) DecimalRounds 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)func New(value int64, exp int32) Decimal
// Returns: value * 10^expFormula: 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) // 0func NewFromBigInt(value *big.Int, exp int32) Decimal
// Returns: value * 10^expExamples:
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)func NewFromBigRat(value *big.Rat, precision int32) Decimal
// Rounds to precision decimal placesExamples:
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.3333333333func NewNullDecimal(d Decimal) NullDecimal
// Returns NullDecimal with Valid=trueUse for:
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)var d decimal.Decimal
// d equals 0, safe to use immediatelyZero 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 0var (
TaxRate = decimal.RequireFromString("0.08")
MinimumOrder = decimal.RequireFromString("10.00")
MaxDiscount = decimal.RequireFromString("100.00")
)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)
}var price decimal.Decimal
err := db.QueryRow("SELECT price FROM products WHERE id = ?", id).
Scan(&price)
// Decimal.Scan() automatically handles conversiontype ProductResponse struct {
Price decimal.Decimal `json:"price"`
}
var response ProductResponse
err := json.Unmarshal(data, &response)
// Decimal.UnmarshalJSON() handles both "19.99" and 19.99d, err := decimal.NewFromString(input)
if err != nil {
// Input was not a valid decimal string
return fmt.Errorf("invalid decimal: %w", err)
}Common invalid inputs:
"""abc", "$$$""1.2.3""1e"// 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)Fastest to slowest:
New(value, exp) - Direct constructionNewFromInt family - Integer conversionNewFromFloat family - Float conversionNewFromString - String parsing (slower but most accurate)NewFromBigInt / NewFromBigRat - Big number conversionRecommendation:
NewFromString unless performance is criticalInstall with Tessl CLI
npx tessl i tessl/golang-github-com-shopspring--decimal