visit
Fast forward to Jan 2021 and a Google search for
decimal handling in golang
retrieves the following results:Here is some example code for decimal division. Note: make sure you are using the latest version of the library -
v2
at the time of this post.package main
import (
"fmt"
"log"
"github.com/cockroachdb/apd/v2"
)
func main() {
// Division needs a rounding specified.
// Precision here is the total number of digits
// to the left and right of the decimal place.
context := apd.BaseContext.WithPrecision(10)
// x = 1.234
xDecimal, _, err := apd.NewFromString("1.234")
if err != nil {
log.Fatalln(err)
}
// y = 3
yDecimal := apd.New(3, 0)
result := apd.New(0, 0)
_, err = context.Quo(result, xDecimal, yDecimal)
if err != nil {
log.Fatalln(err)
}
fmt.Println(result)
}
Produces the following result:
0.4113333333
Here is some example code based on discussion to control rounding.package main
import (
"errors"
"fmt"
"log"
"math"
"github.com/cockroachdb/apd/v2"
)
var (
// DecimalCtx is the default context for decimal operations. Any change
// in the exponent limits must still guarantee a safe conversion to the
// postgres binary decimal format in the wire protocol, which uses an
// int16. See pgwire/types.go.
DecimalCtx = &apd.Context{
Precision: 20,
Rounding: apd.RoundHalfUp,
MaxExponent: 2000,
MinExponent: -2000,
// Don't error on invalid operation, return NaN instead.
Traps: apd.DefaultTraps &^ apd.InvalidOperation,
}
)
func main() {
xDecimal, _, _ := apd.NewFromString("14.955")
// Max 4 digits to the left and right of decimal place. Max 1 digit to right of decimal place.
err := LimitDecimalWidth(xDecimal, 4, 1)
if err != nil {
log.Println(err)
}
fmt.Printf("x= %v\n", xDecimal)
// Max 4 digits to the left and right of decimal place. Max 2 digits to right of decimal place.
yDecimal, _, _ := apd.NewFromString("14.955")
err = LimitDecimalWidth(yDecimal, 4, 2)
if err != nil {
log.Println(err)
}
fmt.Printf("y= %v\n", yDecimal)
}
func LimitDecimalWidth(d *apd.Decimal, precision, scale int) error {
if d.Form != apd.Finite || precision <= 0 {
return nil
}
// Use +1 here because it is inverted later.
if scale < math.MinInt32+1 || scale > math.MaxInt32 {
return errors.New("out of range")
}
if scale > precision {
return fmt.Errorf("scale (%d) must be between 0 and precision (%d)", scale, precision)
}
// //www.postgresql.org/docs/9.5/static/datatype-numeric.html
// "If the scale of a value to be stored is greater than
// the declared scale of the column, the system will round the
// value to the specified number of fractional digits. Then,
// if the number of digits to the left of the decimal point
// exceeds the declared precision minus the declared scale, an
// error is raised."
c := DecimalCtx.WithPrecision(uint32(precision))
c.Traps = apd.InvalidOperation
if _, err := c.Quantize(d, d, -int32(scale)); err != nil {
var lt string
switch v := precision - scale; v {
case 0:
lt = "1"
default:
lt = fmt.Sprintf("10^%d", v)
}
return fmt.Errorf("value with precision %d, scale %d must round to an absolute value less than %s", precision, scale, lt)
}
return nil
}
Output
x= 15.0
y= 14.96
// PostgreSQL
SELECT ('1.234'::numeric/'3'::numeric)::numeric(20,10);
// CockroachDB
SELECT ('1.234'::decimal/'3'::decimal)::decimal(20,10);