Skip to content

Commit d0d8341

Browse files
authored
DGS-20014 Add utilities to convert decimals from/to Protobuf (#1404)
* DGS-20014 Add utilities to convert decimals from/to Protobuf * Fix date * Add comment
1 parent 738a8b5 commit d0d8341

File tree

3 files changed

+174
-1
lines changed

3 files changed

+174
-1
lines changed

schemaregistry/confluent/types/decimal.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ message Decimal {
99
// The two's-complement representation of the unscaled integer value in big-endian byte order
1010
bytes value = 1;
1111

12-
// The precision
12+
// The precision (zero indicates unlimited precision)
1313
uint32 precision = 2;
1414

1515
// The scale
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright 2025 Confluent Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package protobuf
18+
19+
import (
20+
"github.com/confluentinc/confluent-kafka-go/v2/schemaregistry/confluent/types"
21+
"math/big"
22+
)
23+
24+
var one = big.NewInt(1)
25+
26+
// BigRatToDecimal converts a big.Rat to a Decimal protobuf message.
27+
func BigRatToDecimal(value *big.Rat, scale int32) (*types.Decimal, error) {
28+
if value == nil {
29+
return nil, nil
30+
}
31+
32+
exp := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
33+
i := (&big.Int{}).Mul(value.Num(), exp)
34+
i = i.Div(i, value.Denom())
35+
36+
var b []byte
37+
switch i.Sign() {
38+
case 0:
39+
b = []byte{0}
40+
41+
case 1:
42+
b = i.Bytes()
43+
if b[0]&0x80 > 0 {
44+
b = append([]byte{0}, b...)
45+
}
46+
47+
case -1:
48+
length := uint(i.BitLen()/8+1) * 8
49+
b = i.Add(i, (&big.Int{}).Lsh(one, length)).Bytes()
50+
}
51+
52+
return &types.Decimal{
53+
Value: b,
54+
Precision: 0,
55+
Scale: scale,
56+
}, nil
57+
}
58+
59+
// DecimalToBigRat converts a Decimal protobuf message to a big.Rat.
60+
func DecimalToBigRat(value *types.Decimal) (*big.Rat, error) {
61+
if value == nil {
62+
return nil, nil
63+
}
64+
65+
return ratFromBytes(value.Value, int(value.Scale)), nil
66+
}
67+
68+
func ratFromBytes(b []byte, scale int) *big.Rat {
69+
num := (&big.Int{}).SetBytes(b)
70+
if len(b) > 0 && b[0]&0x80 > 0 {
71+
num.Sub(num, new(big.Int).Lsh(one, uint(len(b))*8))
72+
}
73+
denom := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(scale)), nil)
74+
return new(big.Rat).SetFrac(num, denom)
75+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Copyright 2025 Confluent Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package protobuf
18+
19+
import (
20+
"math/big"
21+
"reflect"
22+
"testing"
23+
)
24+
25+
func TestDecimalConversion(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
input *big.Rat
29+
scale int32
30+
}{
31+
{
32+
name: "1st value",
33+
input: big.NewRat(0, 1),
34+
scale: 0,
35+
},
36+
{
37+
name: "2nd value",
38+
input: big.NewRat(101, 100),
39+
scale: 2,
40+
},
41+
{
42+
name: "3rd value",
43+
input: big.NewRat(123456789123456789, 100),
44+
scale: 2,
45+
},
46+
{
47+
name: "4th value",
48+
input: big.NewRat(1234, 1),
49+
scale: 0,
50+
},
51+
{
52+
name: "5h value",
53+
input: big.NewRat(12345, 10),
54+
scale: 1,
55+
},
56+
{
57+
name: "6th value",
58+
input: big.NewRat(-0, 1),
59+
scale: 0,
60+
},
61+
{
62+
name: "7th value",
63+
input: big.NewRat(-101, 100),
64+
scale: 2,
65+
},
66+
{
67+
name: "8th value",
68+
input: big.NewRat(-123456789123456789, 100),
69+
scale: 2,
70+
},
71+
{
72+
name: "9th value",
73+
input: big.NewRat(-1234, 1),
74+
scale: 0,
75+
},
76+
{
77+
name: "10th value",
78+
input: big.NewRat(-12345, 10),
79+
scale: 1,
80+
},
81+
}
82+
83+
for _, test := range tests {
84+
t.Run(test.name, func(t *testing.T) {
85+
converted, err := BigRatToDecimal(test.input, test.scale)
86+
if err != nil {
87+
t.Fatalf("unexpected error: %v", err)
88+
}
89+
result, err := DecimalToBigRat(converted)
90+
if err != nil {
91+
t.Fatalf("unexpected error: %v", err)
92+
}
93+
if !reflect.DeepEqual(test.input, result) {
94+
t.Fatalf("not equal: input %v, output %v", test.input, result)
95+
}
96+
})
97+
}
98+
}

0 commit comments

Comments
 (0)