Go’s Decimal Dilemma: decimal vs Standard Library Showdown

Sami Salih İbrahimbaş
4 min readAug 31, 2024

Go, with its simplicity and performance, has become a popular choice for building a wide range of applications, including financial systems. However, when it comes to handling decimal numbers with precision, Go’s standard library falls short, leaving developers to rely on external packages like shopspring/decimal. While decimal offers a convenient solution, it comes with its own set of drawbacks. In this article, we'll explore how to achieve robust decimal calculations using only Go's standard library, and we'll delve into the performance implications and trade-offs of each approach.

Article Cover

The decimal Package: A Quick Overview

The shopspring/decimal package provides a Decimal type for representing and manipulating decimal numbers with arbitrary precision. It offers a wide range of arithmetic operations and formatting capabilities, making it a go-to choice for many Go developers working with financial data.

However, decimal has a few notable drawbacks:

  • Panic on Errors: The package’s error handling relies on panic, which can abruptly terminate your application. In mission-critical financial systems, this behavior is undesirable.
  • External Dependency: Introducing an external dependency increases the complexity of your project and can pose challenges in environments with strict security or compliance requirements.

Embracing the Standard Library: math/big to the Rescue

Go’s standard library includes the math/big package, which provides types for working with large integers and rational numbers. The big.Rat type, in particular, allows us to represent decimal numbers as fractions, ensuring precise calculations without the need for external dependencies.

Performance Showdown: Benchmarking decimal vs. math/big

To assess the performance implications of each approach, we conducted a series of benchmarks comparing decimal and math/big for common financial calculations.

Tax Rate Calculation

We measured the time and memory usage for calculating tax rates using both packages. Here are the results:

BenchmarkTaxRateWithDecimal-12        1187887           989.9 ns/op      1448 B/op         48 allocs/op
BenchmarkTaxRateWithMath-12 871834 1359 ns/op 1528 B/op 62 allocs/op

Surprisingly, the standard library’s math/big outperformed decimal in both execution time and memory allocations.

Additional Benchmarks

We encourage you to conduct further benchmarks for other common financial operations, such as currency conversions, interest calculations, and rounding. The results may vary depending on the specific use case, but our initial findings suggest that math/big can offer a competitive or even superior performance in many scenarios.

Illustrative Examples: decimal vs. math/big in Action

Let’s explore a few examples to illustrate how to perform common decimal calculations using both decimal and math/big.

Basic Arithmetic

// Using shopspring/decimal
price, _ := decimal.NewFromString("12.34")
quantity := decimal.NewFromInt(5)
total := price.Mul(quantity)
// Using math/big
price, _ := new(big.Rat).SetString("12.34")
quantity := big.NewRat(5, 1)
total := new(big.Rat).Mul(price, quantity)

Benchmark Basic Arithmetic:

BenchmarkBasicArithmeticWithDecimal-12     11303857        103.8 ns/op      128 B/op        5 allocs/op
BenchmarkBasicArithmeticWithMath-12 4044417 281.3 ns/op 256 B/op 14 allocs/op

Percentage Calculations

// Using shopspring/decimal
price, _ := decimal.NewFromString("12.34")
discountRate, _ := decimal.NewFromString("0.15") // 15%
discountedPrice := price.Mul(decimal.NewFromFloat(1).Sub(discountRate))
// Using math/big
price, _ := new(big.Rat).SetString("12.34")
discountRate := big.NewRat(15, 100)
one := big.NewRat(1, 1)
discountedPrice := new(big.Rat).Mul(price, new(big.Rat).Sub(one, discountRate))

Benchmark Percentage Calculations:

BenchmarkPercentageCalcWithDecimal-12      3427479        355.9 ns/op      400 B/op       17 allocs/op
BenchmarkPercentageCalcWithMath-12 2488896 443.6 ns/op 456 B/op 24 allocs/op

Currency Conversion

// Using shopspring/decimal
dollars, _ := decimal.NewFromString("54.95")
exchangeRate, _ := decimal.NewFromString("1.1234") // USD to EUR
euros := dollars.Mul(exchangeRate) // 61.73083
// Using math/big
dollars, _ := new(big.Rat).SetString("54.95")
exchangeRate, _ := new(big.Rat).SetString("1.1234")
euros := new(big.Rat).Mul(dollars, exchangeRate) // 61.73083

Currency Conversion Benchmarks:

BenchmarkCurrencyConversionWithDecimal-12       5802156        183.3 ns/op      176 B/op        8 allocs/op
BenchmarkCurrencyConversionWithMath-12 2641281 440.4 ns/op 384 B/op 17 allocs/op

In each example, we demonstrate how to perform the same calculation using both decimal and math/big. The math/big approach may require a bit more code, but it eliminates the external dependency and potential panic issues.

Pros and Cons: Weighing Your Options

Decimal Package

Pros:

  • User-friendly API
  • Wide range of built-in functions
  • Extensive documentation and community support

Cons:

  • External dependency
  • Panic-based error handling
  • Potential performance overhead in some cases

math/big Standart Library

Pros:

  • No external dependencies
  • Panic-free error handling
  • Potentially better performance in certain scenarios

Cons:

  • Slightly more verbose API
  • Less extensive documentation and community support compared to decimal

Conclusion

While the decimal package offers a convenient way to handle decimal numbers in Go, the standard library's math/big package provides a compelling alternative, especially for financial applications where precision, performance, and control over error handling are paramount. By leveraging big.Rat, you can achieve robust decimal calculations without introducing external dependencies or risking unexpected panics.

We encourage you to experiment with both approaches and consider the specific requirements of your project when making a decision. If you prioritize performance, control, and minimizing external dependencies, math/big might be the right choice for you.

Remember: The best tool for the job depends on your specific needs and priorities. Don’t hesitate to explore and evaluate different options to find the optimal solution for your Go projects.

--

--

Sami Salih İbrahimbaş
Sami Salih İbrahimbaş

Written by Sami Salih İbrahimbaş

lifetime junior • software developer at monopayments

Responses (1)