forked from fl00r/go-tarantool-1.6
-
Notifications
You must be signed in to change notification settings - Fork 60
/
Copy pathdecimal.go
137 lines (121 loc) · 3.93 KB
/
decimal.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Package decimal provides support for Tarantool's decimal data type.
//
// Decimal data type supported in Tarantool since 2.2.
//
// Since: 1.7.0
//
// See also:
//
// - Tarantool MessagePack extensions:
// https://www.tarantool.io/en/doc/latest/dev_guide/internals/msgpack_extensions/#the-decimal-type
//
// - Tarantool data model:
// https://www.tarantool.io/en/doc/latest/book/box/data_model/
//
// - Tarantool issue for support decimal type:
// https://github.com/tarantool/tarantool/issues/692
//
// - Tarantool module decimal:
// https://www.tarantool.io/en/doc/latest/reference/reference_lua/decimal/
package decimal
import (
"fmt"
"reflect"
"github.com/shopspring/decimal"
"github.com/vmihailenco/msgpack/v5"
)
// Decimal numbers have 38 digits of precision, that is, the total
// number of digits before and after the decimal point can be 38.
// A decimal operation will fail if overflow happens (when a number is
// greater than 10^38 - 1 or less than -10^38 - 1).
//
// See also:
//
// - Tarantool module decimal:
// https://www.tarantool.io/en/doc/latest/reference/reference_lua/decimal/
// ExtID represents the Decimal MessagePack extension type identifier.
const ExtID = 1
const decimalPrecision = 38
var (
one decimal.Decimal = decimal.NewFromInt(1)
// -10^decimalPrecision - 1
minSupportedDecimal decimal.Decimal = maxSupportedDecimal.Neg().Sub(one)
// 10^decimalPrecision - 1
maxSupportedDecimal decimal.Decimal = decimal.New(1, decimalPrecision).Sub(one)
)
type Decimal struct {
decimal.Decimal
}
// MakeDecimal creates a new Decimal from a decimal.Decimal.
func MakeDecimal(decimal decimal.Decimal) Decimal {
return Decimal{Decimal: decimal}
}
// MakeDecimalFromString creates a new Decimal from a string.
func MakeDecimalFromString(src string) (Decimal, error) {
result := Decimal{}
dec, err := decimal.NewFromString(src)
if err != nil {
return result, err
}
result = MakeDecimal(dec)
return result, nil
}
// EncodeExt encodes a Decimal into a MessagePack extension.
func EncodeExt(_ *msgpack.Encoder, v reflect.Value) ([]byte, error) {
dec := v.Interface().(Decimal)
if dec.GreaterThan(maxSupportedDecimal) {
return nil,
fmt.Errorf(
"msgpack: decimal number is bigger than maximum supported number (10^%d - 1)",
decimalPrecision)
}
if dec.LessThan(minSupportedDecimal) {
return nil,
fmt.Errorf(
"msgpack: decimal number is lesser than minimum supported number (-10^%d - 1)",
decimalPrecision)
}
strBuf := dec.String()
bcdBuf, err := encodeStringToBCD(strBuf)
if err != nil {
return nil, fmt.Errorf("msgpack: can't encode string (%s) to a BCD buffer: %w", strBuf, err)
}
return bcdBuf, nil
}
// DecodeExt decodes a MessagePack extension into a Decimal.
func DecodeExt(d *msgpack.Decoder, v reflect.Value, extLen int) error {
b := make([]byte, extLen)
n, err := d.Buffered().Read(b)
if err != nil {
return err
}
if n < extLen {
return fmt.Errorf("msgpack: unexpected end of stream after %d decimal bytes", n)
}
// Decimal values can be encoded to fixext MessagePack, where buffer
// has a fixed length encoded by first byte, and ext MessagePack, where
// buffer length is not fixed and encoded by a number in a separate
// field:
//
// +--------+-------------------+------------+===============+
// | MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
// +--------+-------------------+------------+===============+
digits, exp, err := decodeStringFromBCD(b)
if err != nil {
return fmt.Errorf("msgpack: can't decode string from BCD buffer (%x): %w", b, err)
}
dec, err := decimal.NewFromString(digits)
if err != nil {
return fmt.Errorf("msgpack: can't encode string (%s) to a decimal number: %w", digits, err)
}
if exp != 0 {
dec = dec.Shift(int32(exp))
}
ptr := v.Addr().Interface().(*Decimal)
*ptr = MakeDecimal(dec)
return nil
}
func init() {
msgpack.RegisterExtEncoder(ExtID, Decimal{}, EncodeExt)
msgpack.RegisterExtDecoder(ExtID, Decimal{}, DecodeExt)
}