|
- package decimal
-
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "math"
- "math/big"
- "strings"
- )
-
- // Decimal represents decimal type in the Microsoft Open Specifications: http://msdn.microsoft.com/en-us/library/ee780893.aspx
- type Decimal struct {
- integer [4]uint32 // Little-endian
- positive bool
- prec uint8
- scale uint8
- }
-
- var (
- scaletblflt64 [39]float64
- int10 big.Int
- int1e5 big.Int
- )
-
- func init() {
- var acc float64 = 1
- for i := 0; i <= 38; i++ {
- scaletblflt64[i] = acc
- acc *= 10
- }
-
- int10.SetInt64(10)
- int1e5.SetInt64(1e5)
- }
-
- const autoScale = 100
-
- // SetInteger sets the ind'th element in the integer array
- func (d *Decimal) SetInteger(integer uint32, ind uint8) {
- d.integer[ind] = integer
- }
-
- // SetPositive sets the positive member
- func (d *Decimal) SetPositive(positive bool) {
- d.positive = positive
- }
-
- // SetPrec sets the prec member
- func (d *Decimal) SetPrec(prec uint8) {
- d.prec = prec
- }
-
- // SetScale sets the scale member
- func (d *Decimal) SetScale(scale uint8) {
- d.scale = scale
- }
-
- // IsPositive returns true if the Decimal is positive
- func (d *Decimal) IsPositive() bool {
- return d.positive
- }
-
- // ToFloat64 converts decimal to a float64
- func (d Decimal) ToFloat64() float64 {
- val := float64(0)
- for i := 3; i >= 0; i-- {
- val *= 0x100000000
- val += float64(d.integer[i])
- }
- if !d.positive {
- val = -val
- }
- if d.scale != 0 {
- val /= scaletblflt64[d.scale]
- }
- return val
- }
-
- // BigInt converts decimal to a bigint
- func (d Decimal) BigInt() big.Int {
- bytes := make([]byte, 16)
- binary.BigEndian.PutUint32(bytes[0:4], d.integer[3])
- binary.BigEndian.PutUint32(bytes[4:8], d.integer[2])
- binary.BigEndian.PutUint32(bytes[8:12], d.integer[1])
- binary.BigEndian.PutUint32(bytes[12:16], d.integer[0])
- var x big.Int
- x.SetBytes(bytes)
- if !d.positive {
- x.Neg(&x)
- }
- return x
- }
-
- // Bytes converts decimal to a scaled byte slice
- func (d Decimal) Bytes() []byte {
- x := d.BigInt()
- return ScaleBytes(x.String(), d.scale)
- }
-
- // UnscaledBytes converts decimal to a unscaled byte slice
- func (d Decimal) UnscaledBytes() []byte {
- x := d.BigInt()
- return x.Bytes()
- }
-
- // String converts decimal to a string
- func (d Decimal) String() string {
- return string(d.Bytes())
- }
-
- // Float64ToDecimal converts float64 to decimal
- func Float64ToDecimal(f float64) (Decimal, error) {
- return Float64ToDecimalScale(f, autoScale)
- }
-
- // Float64ToDecimalScale converts float64 to decimal; user can specify the scale
- func Float64ToDecimalScale(f float64, scale uint8) (Decimal, error) {
- var dec Decimal
- if math.IsNaN(f) {
- return dec, errors.New("NaN")
- }
- if math.IsInf(f, 0) {
- return dec, errors.New("Infinity can't be converted to decimal")
- }
- dec.positive = f >= 0
- if !dec.positive {
- f = math.Abs(f)
- }
- if f > 3.402823669209385e+38 {
- return dec, errors.New("Float value is out of range")
- }
- dec.prec = 20
- var integer float64
- for dec.scale = 0; dec.scale <= scale; dec.scale++ {
- integer = f * scaletblflt64[dec.scale]
- _, frac := math.Modf(integer)
- if frac == 0 && scale == autoScale {
- break
- }
- }
- for i := 0; i < 4; i++ {
- mod := math.Mod(integer, 0x100000000)
- integer -= mod
- integer /= 0x100000000
- dec.integer[i] = uint32(mod)
- if mod-math.Trunc(mod) >= 0.5 {
- dec.integer[i] = uint32(mod) + 1
- }
- }
- return dec, nil
- }
-
- // Int64ToDecimalScale converts float64 to decimal; user can specify the scale
- func Int64ToDecimalScale(v int64, scale uint8) Decimal {
- positive := v >= 0
- if !positive {
- if v == math.MinInt64 {
- // Special case - can't negate
- return Decimal{
- integer: [4]uint32{0, 0x80000000, 0, 0},
- positive: false,
- prec: 20,
- scale: 0,
- }
- }
- v = -v
- }
- return Decimal{
- integer: [4]uint32{uint32(v), uint32(v >> 32), 0, 0},
- positive: positive,
- prec: 20,
- scale: scale,
- }
- }
-
- // StringToDecimalScale converts string to decimal
- func StringToDecimalScale(v string, outScale uint8) (Decimal, error) {
- var r big.Int
- var unscaled string
- var inScale int
-
- point := strings.LastIndexByte(v, '.')
- if point == -1 {
- inScale = 0
- unscaled = v
- } else {
- inScale = len(v) - point - 1
- unscaled = v[:point] + v[point+1:]
- }
- if inScale > math.MaxUint8 {
- return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale too large", v)
- }
-
- _, ok := r.SetString(unscaled, 10)
- if !ok {
- return Decimal{}, fmt.Errorf("can't parse %q as a decimal number", v)
- }
-
- if inScale > int(outScale) {
- return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: scale %d is larger than the scale %d of the target column", v, inScale, outScale)
- }
- for inScale < int(outScale) {
- if int(outScale)-inScale >= 5 {
- r.Mul(&r, &int1e5)
- inScale += 5
- } else {
- r.Mul(&r, &int10)
- inScale++
- }
- }
-
- bytes := r.Bytes()
- if len(bytes) > 16 {
- return Decimal{}, fmt.Errorf("can't parse %q as a decimal number: precision too large", v)
- }
- var out [4]uint32
- for i, b := range bytes {
- pos := len(bytes) - i - 1
- out[pos/4] += uint32(b) << uint(pos%4*8)
- }
- return Decimal{
- integer: out,
- positive: r.Sign() >= 0,
- prec: 20,
- scale: uint8(inScale),
- }, nil
- }
-
- // ScaleBytes converts a stringified decimal to a scaled byte slice
- func ScaleBytes(s string, scale uint8) []byte {
- z := make([]byte, 0, len(s)+1)
- if s[0] == '-' || s[0] == '+' {
- z = append(z, byte(s[0]))
- s = s[1:]
- }
- pos := len(s) - int(scale)
- if pos <= 0 {
- z = append(z, byte('0'))
- } else if pos > 0 {
- z = append(z, []byte(s[:pos])...)
- }
- if scale > 0 {
- z = append(z, byte('.'))
- for pos < 0 {
- z = append(z, byte('0'))
- pos++
- }
- z = append(z, []byte(s[pos:])...)
- }
- return z
- }
|