github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/pingcap/tidb/util/codec/decimal.go (about)

     1  // Copyright 2015 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package codec
    15  
    16  import (
    17  	"bytes"
    18  	"math/big"
    19  
    20  	"github.com/insionng/yougam/libraries/juju/errors"
    21  	"github.com/insionng/yougam/libraries/pingcap/tidb/mysql"
    22  )
    23  
    24  const (
    25  	negativeSign int64 = 8
    26  	zeroSign     int64 = 16
    27  	positiveSign int64 = 24
    28  )
    29  
    30  func codecSign(value int64) int64 {
    31  	if value < 0 {
    32  		return negativeSign
    33  	}
    34  
    35  	return positiveSign
    36  }
    37  
    38  // EncodeDecimal encodes a decimal d into a byte slice which can be sorted lexicographically later.
    39  // EncodeDecimal guarantees that the encoded value is in ascending order for comparison.
    40  // Decimal encoding:
    41  // Byte -> value sign
    42  // EncodeInt -> exp value
    43  // EncodeBytes -> abs value bytes
    44  func EncodeDecimal(b []byte, d mysql.Decimal) []byte {
    45  	if d.Equals(mysql.ZeroDecimal) {
    46  		return append(b, byte(zeroSign))
    47  	}
    48  
    49  	v := d.BigIntValue()
    50  	valSign := codecSign(int64(v.Sign()))
    51  
    52  	absVal := new(big.Int)
    53  	absVal.Abs(v)
    54  
    55  	value := []byte(absVal.String())
    56  
    57  	// Trim right side "0", like "12.34000" -> "12.34" or "0.1234000" -> "0.1234".
    58  	if d.Exponent() != 0 {
    59  		value = bytes.TrimRight(value, "0")
    60  	}
    61  
    62  	// Get exp and value, format is "value":"exp".
    63  	// like "12.34" -> "0.1234":"2".
    64  	// like "-0.01234" -> "-0.1234":"-1".
    65  	exp := int64(0)
    66  	div := big.NewInt(10)
    67  	for ; ; exp++ {
    68  		if absVal.Sign() == 0 {
    69  			break
    70  		}
    71  		absVal = absVal.Div(absVal, div)
    72  	}
    73  
    74  	expVal := exp + int64(d.Exponent())
    75  	if valSign == negativeSign {
    76  		expVal = -expVal
    77  	}
    78  
    79  	b = append(b, byte(valSign))
    80  	b = EncodeInt(b, expVal)
    81  	if valSign == negativeSign {
    82  		b = EncodeBytesDesc(b, value)
    83  	} else {
    84  		b = EncodeBytes(b, value)
    85  	}
    86  	return b
    87  }
    88  
    89  // DecodeDecimal decodes bytes to decimal.
    90  // DecodeFloat decodes a float from a byte slice
    91  // Decimal decoding:
    92  // Byte -> value sign
    93  // Byte -> exp sign
    94  // DecodeInt -> exp value
    95  // DecodeBytes -> abs value bytes
    96  func DecodeDecimal(b []byte) ([]byte, mysql.Decimal, error) {
    97  	var (
    98  		r   = b
    99  		d   mysql.Decimal
   100  		err error
   101  	)
   102  
   103  	// Decode value sign.
   104  	valSign := int64(r[0])
   105  	r = r[1:]
   106  	if valSign == zeroSign {
   107  		d, err = mysql.ParseDecimal("0")
   108  		return r, d, errors.Trace(err)
   109  	}
   110  
   111  	// Decode exp value.
   112  	expVal := int64(0)
   113  	r, expVal, err = DecodeInt(r)
   114  	if err != nil {
   115  		return r, d, errors.Trace(err)
   116  	}
   117  
   118  	// Decode abs value bytes.
   119  	value := []byte{}
   120  	if valSign == negativeSign {
   121  		expVal = -expVal
   122  		r, value, err = DecodeBytesDesc(r)
   123  	} else {
   124  		r, value, err = DecodeBytes(r)
   125  	}
   126  	if err != nil {
   127  		return r, d, errors.Trace(err)
   128  	}
   129  
   130  	// Generate decimal string value.
   131  	var decimalStr []byte
   132  	if valSign == negativeSign {
   133  		decimalStr = append(decimalStr, '-')
   134  	}
   135  
   136  	if expVal <= 0 {
   137  		// Like decimal "0.1234" or "0.01234".
   138  		decimalStr = append(decimalStr, '0')
   139  		decimalStr = append(decimalStr, '.')
   140  		decimalStr = append(decimalStr, bytes.Repeat([]byte{'0'}, -int(expVal))...)
   141  		decimalStr = append(decimalStr, value...)
   142  	} else {
   143  		// Like decimal "12.34".
   144  		decimalStr = append(decimalStr, value[:expVal]...)
   145  		decimalStr = append(decimalStr, '.')
   146  		decimalStr = append(decimalStr, value[expVal:]...)
   147  	}
   148  
   149  	d, err = mysql.ParseDecimal(string(decimalStr))
   150  	return r, d, errors.Trace(err)
   151  }