From 76d25592c30bbd0010e9b7217a7840a13986d187 Mon Sep 17 00:00:00 2001 From: Seth Hollyman Date: Tue, 10 Nov 2020 22:38:38 +0000 Subject: [PATCH] feat: support large decimals This PR updates the handling for the decimal logical type to support larger precisions and scales. Previously, the implementation supported decimals up to 64 bits. --- logical_type.go | 18 ++++++------------ logical_type_test.go | 7 +++++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/logical_type.go b/logical_type.go index cd723fa..94ce35a 100644 --- a/logical_type.go +++ b/logical_type.go @@ -12,7 +12,6 @@ package goavro import ( "errors" "fmt" - "math" "math/big" "time" ) @@ -285,13 +284,10 @@ func nativeFromDecimalBytes(fn toNativeFn, precision, scale int) toNativeFn { if !ok { return nil, bytes, fmt.Errorf("cannot transform to native decimal, expected []byte, received %T", d) } - i := big.NewInt(0) - fromSignedBytes(i, bs) - if i.BitLen() > 64 { - // Avro spec specifies we return underlying type if the logicalType is invalid - return d, b, err - } - r := big.NewRat(i.Int64(), int64(math.Pow10(scale))) + num := big.NewInt(0) + fromSignedBytes(num, bs) + denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil) + r := new(big.Rat).SetFrac(num, denom) return r, b, nil } } @@ -302,12 +298,10 @@ func decimalBytesFromNative(fromNativeFn fromNativeFn, toBytesFn toBytesFn, prec if !ok { return nil, fmt.Errorf("cannot transform to bytes, expected *big.Rat, received %T", d) } - // we reduce accuracy to precision by dividing and multiplying by digit length + // Reduce accuracy to precision by dividing and multiplying by digit length num := big.NewInt(0).Set(r.Num()) denom := big.NewInt(0).Set(r.Denom()) - - // we get the scaled decimal representation - i := new(big.Int).Mul(num, big.NewInt(int64(math.Pow10(scale)))) + i := new(big.Int).Mul(num, new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)) // divide that by the denominator precnum := new(big.Int).Div(i, denom) bout, err := toBytesFn(precnum) diff --git a/logical_type_test.go b/logical_type_test.go index 2d28093..d3e2bec 100644 --- a/logical_type_test.go +++ b/logical_type_test.go @@ -144,6 +144,13 @@ func TestDecimalBytesLogicalTypeEncode(t *testing.T) { testBinaryCodecPass(t, schema, big.NewRat(617, 50), []byte("\x04\x04\xd2")) testBinaryCodecPass(t, schema, big.NewRat(-617, 50), []byte("\x04\xfb\x2e")) testBinaryCodecPass(t, schema, big.NewRat(0, 1), []byte("\x02\x00")) + // Test with a large decimal of precision 77 and scale 38 + largeDecimalSchema := `{"type": "bytes", "logicalType": "decimal", "precision": 77, "scale": 38}` + n, _ := new(big.Int).SetString("12345678901234567890123456789012345678911111111111111111111111111111111111111", 10) + d, _ := new(big.Int).SetString("100000000000000000000000000000000000000", 10) + largeRat := new(big.Rat).SetFrac(n, d) + testBinaryCodecPass(t, largeDecimalSchema, largeRat, []byte("\x40\x1b\x4b\x68\x19\x26\x11\xfa\xea\x20\x8f\xca\x21\x62\x7b\xe9\xda\xee\x32\x19\x83\x83\x95\x5d\xe8\x13\x1f\x4b\xf1\xc7\x1c\x71\xc7")) + } func TestDecimalFixedLogicalTypeEncode(t *testing.T) {