background

Recently, while writing go, I encountered a problem that GO has no built-in Decimal type and only a math/ BIG for large number operations. In the case of shopping payments, it is necessary to use floating-point calculations, usually to two decimal places.

After consulting some information, I found a solution with a relatively high start number.

Shopspring/decimal: github.com/shopspring/… File address: pkg.go.dev/github.com/…

Note that the Decimal library “can only” represent numbers up to 2^31 Decimal places. But it’s enough to meet our current needs.

The idea is to use fixed-point decimal notation, where you move the decimal number back as many decimal places as you have, value holds the number of decimal places, exp holds the number of decimal places, number=value*10^exp, because the decimal number can be very large, So I’m borrowing the standard package math/big for this big integer. Exp uses int32, which is why the Decimal library “can only” represent numbers up to 2^31 Decimal places.

Here is the definition of Decimal in the source code

type Decimal struct {
	value *big.Int

	// NOTE(vadim): this must be an int32, because we cast it to float64 during
	// calculations. If exp is 64 bit, we might lose precision.
	// If we cared about being able to represent every possible decimal, we
	// could make exp a *big.Int but it would hurt performance and numbers
	// like that are unrealistic.
	exp int32
}
Copy the code

The sample

Prerequisites: Go version >=1.7

go get github.com/shopspring/decimal
Copy the code
package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main(a) {
	// Convert string to Decimal
	price, err := decimal.NewFromString("136.02")
	iferr ! =nil {
		panic(err)
	}
	
	// Negative numbers are supported
	n, err := decimal.NewFromString("123.4567")
	n.String() / / the output: "123.4567"

	// Convert int to Decimal
	quantity := decimal.NewFromInt(3)

	// If the integer department is empty, it can be converted normally
	fee, _ := decimal.NewFromString("035")
	taxRate, _ := decimal.NewFromString("08875")
	/ / by
	subtotal := price.Mul(quantity)
	// First add and then multiply
	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)                         / / the 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

	// Support scientific enumeration
	fmt.Println(NewFromFloat(-1e13).String()) // output: "-10000000000000"
	
	By default, there is no exact number of digits after the decimal point
	d1 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3))
	d1.String() / / the output: "0.6666666666666667"

	// Keep three decimal places
	decimal.DivisionPrecision = 3
	d4 := decimal.NewFromFloat(2).Div(decimal.NewFromFloat(3))
	d4.String() / / the output: "0.667"
}
Copy the code