1. Introduction
The Go Decimal library is a powerful tool for handling arbitrary-precision fixed-point decimals in the Go language. It allows for addition, subtraction, multiplication, and division operations without losing precision. Additionally, it provides functionalities such as database/SQL serialization/deserialization, as well as JSON/XML serialization/deserialization.
2. Installation
To install the Go Decimal library, you can use the following command:
go get github.com/shopspring/decimal
Please note that the Decimal library requires Go version >=1.7.
3. Basic Usage
To use the Decimal library in a Go program, import the "github.com/shopspring/decimal" package. Here's a simple example code demonstrating basic usage:
package main
import (
"fmt"
"github.com/shopspring/decimal"
)
func main() {
price, err := decimal.NewFromString("136.02")
if err != nil {
panic(err)
}
quantity := decimal.NewFromInt(3)
fee, _ := decimal.NewFromString(".035")
taxRate, _ := decimal.NewFromString(".08875")
subtotal := price.Mul(quantity)
preTax := subtotal.Mul(fee).Add(decimal.NewFromFloat(1))
total := preTax.Mul(taxRate).Add(decimal.NewFromFloat(1))
fmt.Println("Subtotal:", subtotal) // Subtotal: 408.06
fmt.Println("Pre-tax:", preTax) // Pre-tax: 422.3421
fmt.Println("Taxes:", total.Sub(preTax)) // Taxes: 37.482861375
fmt.Println("Total:", total) // Total: 459.824961375
fmt.Println("Tax rate:", total.Sub(preTax).Div(preTax)) // Tax rate: 0.08875
}
4. Creating Decimal Variables
The Decimal library provides various methods to create Decimal variables. The following are the supported APIs:
-
decimal.NewFromBigInt(value *big.Int, exp int32) Decimal
-
decimal.NewFromFloat(value float64) Decimal
-
decimal.NewFromFloat32(value float32) Decimal
-
decimal.NewFromFloatWithExponent(value float64, exp int32) Decimal
-
decimal.NewFromFormattedString(value string, replRegexp *regexp.Regexp) (Decimal, error)
-
decimal.NewFromInt(value int64) Decimal
-
decimal.NewFromInt32(value int32) Decimal
-
decimal.NewFromString(value string) (Decimal, error)
-
decimal.RequireFromString(value string) Decimal
5. Arithmetic Operations
The Go Decimal library provides several arithmetic operations that can be performed on Decimal variables. Here are some supported operations:
-
Add(d2 Decimal) Decimal
: Adds two Decimal values and returns the result. -
Sub(d2 Decimal) Decimal
: Subtracts one Decimal value from another and returns the result. -
Div(d2 Decimal) Decimal
: Divides one Decimal value by another and returns the result. -
DivRound(d2 Decimal, precision int32) Decimal
: Divides one Decimal value by another and returns the result with specified precision. -
Mod(d2 Decimal) Decimal
: Computes the modulus (remainder) of one Decimal value divided by another and returns the result. -
Mul(d2 Decimal) Decimal
: Multiplies two Decimal values and returns the result.
You can use these operations to perform common arithmetic calculations on Decimal values. Here's an example demonstrating the use of these operations:
price, _ := decimal.NewFromString("136.02")
quantity := decimal.NewFromInt(3)
subtotal := price.Mul(quantity)
tax := subtotal.Mul(decimal.NewFromFloat(0.08875))
total := subtotal.Add(tax)
fmt.Println("Subtotal:", subtotal) // Subtotal: 408.06
fmt.Println("Tax:", tax) // Tax: 36.244985
fmt.Println("Total:", total) // Total: 444.304985
In the above example, we use the Mul()
method to calculate subtotal
by multiplying price
and quantity
. Then we calculate tax
by multiplying subtotal
with the tax rate. Finally, we calculate total
by adding subtotal
and tax
using the Add()
method.
6. Rounding Operations
The Go Decimal library provides several rounding operations that can be used to round Decimal values to a specified precision. Here are some available rounding operations:
-
Round(places int32) Decimal
: Rounds the decimal to the specified number of decimal places. -
RoundBank(places int32) Decimal
: Rounds the decimal using the banker's rounding to the specified number of decimal places. -
RoundCash(interval uint8) Decimal
: Rounds the decimal to a specific interval, such as 5 cents, 10 cents, 25 cents, 50 cents, or 1 dollar. -
RoundCeil(places int32) Decimal
: Rounds the decimal towards positive infinity. -
RoundDown(places int32) Decimal
: Rounds the decimal towards zero. -
RoundFloor(places int32) Decimal
: Rounds the decimal towards negative infinity. -
RoundUp(places int32) Decimal
: Rounds the decimal away from zero.
6.1. Round
Round rounds the decimal to the specified number of decimal places. If places < 0, it rounds the integer part to the nearest 10^(-places).
NewFromFloat(5.45).Round(1).String() // Output: "5.5"
NewFromFloat(545).Round(-1).String() // Output: "550"
6.2. RoundBank
RoundBank rounds the decimal to places decimal places. If the distance between the last digit to be rounded and the closest two integers is equal, the rounded value takes the even number.
If places < 0, the integer part will be rounded to the nearest 10^(-places).
NewFromFloat(5.45).RoundBank(1).String() // Output: "5.4"
NewFromFloat(545).RoundBank(-1).String() // Output: "540"
NewFromFloat(5.46).RoundBank(1).String() // Output: "5.5"
NewFromFloat(546).RoundBank(-1).String() // Output: "550"
NewFromFloat(5.55).RoundBank(1).String() // Output: "5.6"
NewFromFloat(555).RoundBank(-1).String() // Output: "560"
6.3. RoundCash
RoundCash (also known as cash/penny/Irish rounding) rounds the decimal to specific intervals. The payable amount of a cash transaction will be rounded to the nearest multiple of the smallest currency unit. Available intervals are: 5, 10, 25, 50, and 100; any other number will result in an exception.
5: 5 cents rounding 3.43 => 3.45
10: 10 cents rounding 3.45 => 3.50 (5 is rounded up)
25: 25 cents rounding 3.41 => 3.50
50: 50 cents rounding 3.75 => 4.00
100: 100 cents rounding 3.50 => 4.00
6.4. RoundCeil
RoundCeil rounds the decimal towards positive infinity.
NewFromFloat(545).RoundCeil(-2).String() // Output: "600"
NewFromFloat(500).RoundCeil(-2).String() // Output: "500"
NewFromFloat(1.1001).RoundCeil(2).String() // Output: "1.11"
NewFromFloat(-1.454).RoundCeil(1).String() // Output: "-1.5"
6.5. RoundDown
RoundDown rounds the decimal towards zero.
NewFromFloat(545).RoundDown(-2).String() // Output: "500"
NewFromFloat(-500).RoundDown(-2).String() // Output: "-500"
NewFromFloat(1.1001).RoundDown(2).String() // Output: "1.1"
NewFromFloat(-1.454).RoundDown(1).String() // Output: "-1.5"
6.6. RoundFloor
RoundFloor rounds the decimal towards negative infinity.
NewFromFloat(545).RoundFloor(-2).String() // Output: "500"
NewFromFloat(-500).RoundFloor(-2).String() // Output: "-500"
NewFromFloat(1.1001).RoundFloor(2).String() // Output: "1.1"
NewFromFloat(-1.454).RoundFloor(1).String() // Output: "-1.4"
6.7. RoundUp
RoundUp rounds the decimal away from zero.
NewFromFloat(545).RoundUp(-2).String() // Output: "600"
NewFromFloat(500).RoundUp(-2).String() // Output: "500"
NewFromFloat(1.1001).RoundUp(2).String() // Output: "1.11"
NewFromFloat(-1.454).RoundUp(1).String() // Output: "-1.4"
7. Convert Decimal Type to String
The Go Decimal library provides methods to convert Decimal values to string representations. Here are some available methods:
-
String(): string
: Returns the string representation of the decimal number with a fixed decimal point. -
StringFixed(places int32) string
: Returns the rounded string representation with a specified number of decimal places. -
StringFixedBank(places int32) string
: Returns the rounded (banker's rounding) string representation with a specified number of decimal places.
You can choose the appropriate method according to your needs. Below is an example of converting a decimal number to a string:
d := decimal.NewFromFloat(5.45)
str := d.String()
fmt.Println("String representation of the decimal number:", str) // String representation of the decimal number: 5.45
// StringFixed Example
NewFromFloat(0).StringFixed(2) // Output: "0.00"
NewFromFloat(0).StringFixed(0) // Output: "0"
NewFromFloat(5.45).StringFixed(0) // Output: "5"
NewFromFloat(5.45).StringFixed(1) // Output: "5.5"
NewFromFloat(5.45).StringFixed(2) // Output: "5.45"
NewFromFloat(5.45).StringFixed(3) // Output: "5.450"
NewFromFloat(545).StringFixed(-1) // Output: "550"
// StringFixedBank Example
NewFromFloat(0).StringFixedBank(2) // Output: "0.00"
NewFromFloat(0).StringFixedBank(0) // Output: "0"
NewFromFloat(5.45).StringFixedBank(0) // Output: "5"
NewFromFloat(5.45).StringFixedBank(1) // Output: "5.4"
NewFromFloat(5.45).StringFixedBank(2) // Output: "5.45"
NewFromFloat(5.45).StringFixedBank(3) // Output: "5.450"
NewFromFloat(545).StringFixedBank(-1) // Output: "540"
8. Common Questions
Q: Why not use float64 directly? A: float64 cannot accurately represent numbers like 0.1, which can lead to small errors. In situations involving financial calculations, these errors may accumulate over time and cause significant issues.
Q: Why not use big.Rat directly? A: Although big.Rat can represent rational numbers, it is not suitable for representing currency. Decimal numbers are better for financial calculations as they can accurately represent decimal fractions without losing precision.
Q: Why is the API not similar to big.Int? A: The API of the Decimal library prioritizes usability and correctness over performance. While the API of big.Int reduces memory allocations for performance reasons, it may lead to complex and error-prone code. The API of the Decimal library is designed to be simple and easy to understand.