瀏覽代碼

Implemented two's complement encoding and got inspired by the encoding specs from some other CQL drivers

Ben Hood 11 年之前
父節點
當前提交
4493f46cc3
共有 2 個文件被更改,包括 110 次插入18 次删除
  1. 64 10
      marshal.go
  2. 46 8
      marshal_test.go

+ 64 - 10
marshal.go

@@ -6,6 +6,7 @@ package gocql
 
 import (
 	"bytes"
+	"encoding/binary"
 	"fmt"
 	"math"
 	"math/big"
@@ -14,6 +15,10 @@ import (
 	"time"
 )
 
+var (
+	bigOne = big.NewInt(1)
+)
+
 // Marshaler is the interface implemented by objects that can marshal
 // themselves into values understood by Cassandra.
 type Marshaler interface {
@@ -691,15 +696,10 @@ func marshalDecimal(info *TypeInfo, value interface{}) ([]byte, error) {
 			return nil, nil
 		}
 
-		unscaled := v.UnscaledBig().Bytes()
-		if len(unscaled) == 0 {
-			unscaled = []byte{0}
-		}
-		scale := v.Scale()
-		buf := make([]byte, 4+len(unscaled))
-		copy(buf[0:4], encInt(int32(scale)))
-		copy(buf[4:], unscaled)
-		return buf, nil
+		b := new(bytes.Buffer)
+		binary.Write(b, binary.BigEndian, v.Scale())
+		encBigInt2C(b, v.UnscaledBig())
+		return b.Bytes(), nil
 	}
 	return nil, marshalErrorf("can not marshal %T into %s", value, info)
 }
@@ -711,7 +711,7 @@ func unmarshalDecimal(info *TypeInfo, data []byte, value interface{}) error {
 	case **inf.Dec:
 		if len(data) > 4 {
 			scale := decInt(data[0:4])
-			unscaled := new(big.Int).SetBytes(data[4:])
+			unscaled := decBigInt2C(data[4:])
 			*v = inf.NewDecBig(unscaled, inf.Scale(scale))
 		}
 		return nil
@@ -719,6 +719,60 @@ func unmarshalDecimal(info *TypeInfo, data []byte, value interface{}) error {
 	return unmarshalErrorf("can not unmarshal %s into %T", info, value)
 }
 
+func decBigInt2C(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
+}
+
+func encBigInt2C(out *bytes.Buffer, n *big.Int) (err error) {
+	if n.Sign() < 0 {
+		// A negative number has to be converted to two's-complement
+		// form. So we'll subtract 1 and invert. 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 {
+			err = out.WriteByte(0xff)
+			if err != nil {
+				return
+			}
+		}
+		_, err = out.Write(bytes)
+	} else if n.Sign() == 0 {
+		// Zero is written as a single 0 zero rather than no bytes.
+		err = out.WriteByte(0x00)
+	} 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.
+			err = out.WriteByte(0)
+			if err != nil {
+				return
+			}
+		}
+		_, err = out.Write(bytes)
+	}
+	return
+}
+
 func marshalTimestamp(info *TypeInfo, value interface{}) ([]byte, error) {
 	switch v := value.(type) {
 	case Marshaler:

+ 46 - 8
marshal_test.go

@@ -10,8 +10,6 @@ import (
 	"time"
 )
 
-var quarter, _ = inf.NewDec(0, 0).SetString("0.25")
-
 var marshalTests = []struct {
 	Info  *TypeInfo
 	Data  []byte
@@ -120,11 +118,6 @@ var marshalTests = []struct {
 		[]byte("\x40\x09\x21\xfb\x53\xc8\xd4\xf1"),
 		float64(3.14159265),
 	},
-	{
-		&TypeInfo{Type: TypeDecimal},
-		[]byte("\x00\x01\x86\xa0\xcb\xd7\x12\xbb\x6d"),
-		inf.NewDec(875486690157, 100000),
-	},
 	{
 		&TypeInfo{Type: TypeDecimal},
 		[]byte("\x00\x00\x00\x00\x00"),
@@ -138,7 +131,47 @@ var marshalTests = []struct {
 	{
 		&TypeInfo{Type: TypeDecimal},
 		[]byte("\x00\x00\x00\x02\x19"),
-		quarter,
+		decimalize("0.25"),
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x13\xD5\a;\x20\x14\xA2\x91"),
+		decimalize("-0.0012095473475870063"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x13*\xF8\xC4\xDF\xEB]o"),
+		decimalize("0.0012095473475870063"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x12\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V"),
+		decimalize("-1042342234234.123423435647768234"), // From the iconara/cql-rb test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\r\nJ\x04\"^\x91\x04\x8a\xb1\x18\xfe"),
+		decimalize("1243878957943.1234124191998"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x06\xe5\xde]\x98Y"),
+		decimalize("-112233.441191"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x14\x00\xfa\xce"),
+		decimalize("0.00000000000000064206"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\x00\x00\x00\x14\xff\x052"),
+		decimalize("-0.00000000000000064206"), // From the datastax/python-driver test suite
+	},
+	{
+		&TypeInfo{Type: TypeDecimal},
+		[]byte("\xff\xff\xff\x9c\x00\xfa\xce"),
+		inf.NewDec(64206, -100), // From the datastax/python-driver test suite
 	},
 	{
 		&TypeInfo{Type: TypeTimestamp},
@@ -209,6 +242,11 @@ var marshalTests = []struct {
 	},
 }
 
+func decimalize(s string) *inf.Dec {
+	i, _ := new(inf.Dec).SetString(s)
+	return i
+}
+
 func TestMarshal(t *testing.T) {
 	for i, test := range marshalTests {
 		data, err := Marshal(test.Info, test.Value)