Skip to content

Instantly share code, notes, and snippets.

@josephglanville
Created December 26, 2019 08:41
Show Gist options
  • Save josephglanville/d1453fcf8a249721950026c0e376810a to your computer and use it in GitHub Desktop.
Save josephglanville/d1453fcf8a249721950026c0e376810a to your computer and use it in GitHub Desktop.
Logical encoding/decoding of Avro Decimal
package logical
import (
"github.com/shopspring/decimal"
"math"
"math/big"
)
func EncodeDecimal(d decimal.Decimal, scale int32) []byte {
unscaled := d.Coefficient()
exp := d.Exponent()
delta := exp + scale
rescaled := big.NewInt(0).Mul(bigPow10(int(delta)), unscaled)
data := writeBigInt(rescaled)
return data
}
func DecodeDecimal(data []byte, scale int32, precision int32) decimal.Decimal {
coefficient := readBigInt(data)
exp := -scale
return decimal.NewFromBigInt(coefficient, exp).Truncate(precision)
}
func bigPow10(a int) *big.Int {
powRes := big.NewFloat(math.Pow10(a))
newInt, _ := powRes.Int(nil)
return newInt
}
var bigOne = big.NewInt(1)
func writeBigInt(n *big.Int) []byte {
data := make([]byte, (n.BitLen()+8)/8)
off := 0 // Current offset into data
if n.Sign() < 0 {
// A negative number has to be converted to two's-complement
// form. So we'll invert and subtract 1. If the
// most-significant-bit isn't set then we'll need to pad the
// beginning with 0xff in order to keep the number negative.
nMinus1 := new(big.Int).Neg(n)
nMinus1.Sub(nMinus1, bigOne)
bytes := nMinus1.Bytes()
for i := range bytes {
bytes[i] ^= 0xff
}
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
data[off] = 0xff
off++
}
for i := range bytes {
data[off] = bytes[i]
off++
}
return data
} else if n.Sign() == 0 {
// Zero is written as a single 0 zero rather than no bytes.
data[off] = 0x00
return data
} else {
bytes := n.Bytes()
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
// We'll have to pad this with 0x00 in order to stop it
// looking like a negative number.
data[off] = 0x00
off++
}
for i := range bytes {
data[off] = bytes[i]
off++
}
return data
}
}
func readBigInt(bytes []byte) *big.Int {
ret := new(big.Int)
if len(bytes) > 0 && bytes[0]&0x80 == 0x80 {
// This is a negative number.
notBytes := make([]byte, len(bytes))
for i := range notBytes {
notBytes[i] = ^bytes[i]
}
ret.SetBytes(notBytes)
ret.Add(ret, bigOne)
ret.Neg(ret)
return ret
}
ret.SetBytes(bytes)
return ret
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment