From 4c90195eed7ddee19d646520f06fd6079a322895 Mon Sep 17 00:00:00 2001 From: shenao78 Date: Wed, 22 Jan 2020 14:31:09 +0800 Subject: [PATCH] fix decimal --- application/mov/match/match.go | 19 +- application/mov/match/match_test.go | 160 ++ math/decimal/decimal.go | 143 ++ vendor/github.com/pingcap/types/helper.go | 168 ++ vendor/github.com/pingcap/types/mydecimal.go | 2332 ++++++++++++++++++++++++++ 5 files changed, 2812 insertions(+), 10 deletions(-) create mode 100644 math/decimal/decimal.go create mode 100644 vendor/github.com/pingcap/types/helper.go create mode 100644 vendor/github.com/pingcap/types/mydecimal.go diff --git a/application/mov/match/match.go b/application/mov/match/match.go index 4c0234f9..8361a725 100644 --- a/application/mov/match/match.go +++ b/application/mov/match/match.go @@ -10,6 +10,7 @@ import ( "github.com/bytom/vapor/consensus/segwit" "github.com/bytom/vapor/errors" vprMath "github.com/bytom/vapor/math" + "github.com/bytom/vapor/math/decimal" "github.com/bytom/vapor/protocol/bc" "github.com/bytom/vapor/protocol/bc/types" "github.com/bytom/vapor/protocol/vm" @@ -240,20 +241,18 @@ func IsMatched(orders []*common.Order) bool { return false } - rate := orderRatio(sortedOrders[0]) - oppositeRate := big.NewFloat(0).SetInt64(1) + rate := decimal.New(1, 0).Div(orderRatio(sortedOrders[0])) + oppositeRate := decimal.New(1, 0) for i := 1; i < len(sortedOrders); i++ { - oppositeRate.Mul(oppositeRate, orderRatio(sortedOrders[i])) + oppositeRate = oppositeRate.Mul(orderRatio(sortedOrders[i])) } - - one := big.NewFloat(0).SetInt64(1) - return one.Quo(one, rate).Cmp(oppositeRate) >= 0 + return rate.Cmp(oppositeRate) >= 0 } -func orderRatio(order *common.Order) *big.Float { - ratio := big.NewFloat(0).SetInt64(order.RatioNumerator) - ratio.Quo(ratio, big.NewFloat(0).SetInt64(order.RatioDenominator)) - return ratio +func orderRatio(order *common.Order) *decimal.Decimal { + numerator := decimal.New(order.RatioNumerator, 0) + denominator := decimal.New(order.RatioDenominator, 0) + return numerator.Div(denominator) } func setMatchTxArguments(txInput *types.TxInput, isPartialTrade bool, position int, receiveAmounts uint64) { diff --git a/application/mov/match/match_test.go b/application/mov/match/match_test.go index 640f4cfb..7d013ec8 100644 --- a/application/mov/match/match_test.go +++ b/application/mov/match/match_test.go @@ -256,3 +256,163 @@ func TestValidateTradePairs(t *testing.T) { } } } + +func TestIsMatched(t *testing.T) { + cases := []struct { + desc string + orders []*common.Order + wantMatched bool + }{ + { + desc: "ratio is equals", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 6250, + RatioDenominator: 3, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.BTC, + RatioNumerator: 3, + RatioDenominator: 6250, + }, + }, + wantMatched: true, + }, + { + desc: "ratio is equals, and numerator and denominator are multiples of the opposite", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 6250, + RatioDenominator: 3, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.BTC, + RatioNumerator: 9, + RatioDenominator: 18750, + }, + }, + wantMatched: true, + }, + { + desc: "matched with a slight diff", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 62500000000000000, + RatioDenominator: 30000000000001, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.BTC, + RatioNumerator: 3, + RatioDenominator: 6250, + }, + }, + wantMatched: true, + }, + { + desc: "not matched with a slight diff", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 62500000000000001, + RatioDenominator: 30000000000000, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.BTC, + RatioNumerator: 3, + RatioDenominator: 6250, + }, + }, + wantMatched: false, + }, + { + desc: "ring matched", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 2000000003, + RatioDenominator: 100000001, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.EOS, + RatioNumerator: 400000000001, + RatioDenominator: 2000000003, + }, + { + FromAssetID: &mock.EOS, + ToAssetID: &mock.BTC, + RatioNumerator: 100000001, + RatioDenominator: 400000000001, + }, + }, + wantMatched: true, + }, + { + desc: "ring matched with a slight diff", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 2000000003, + RatioDenominator: 100000001, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.EOS, + RatioNumerator: 400000000001, + RatioDenominator: 2000000003, + }, + { + FromAssetID: &mock.EOS, + ToAssetID: &mock.BTC, + RatioNumerator: 100000000, + RatioDenominator: 400000000001, + }, + }, + wantMatched: true, + }, + { + desc: "ring fail matched with a slight diff", + orders: []*common.Order{ + { + FromAssetID: &mock.BTC, + ToAssetID: &mock.ETH, + RatioNumerator: 2000000003, + RatioDenominator: 100000001, + }, + { + FromAssetID: &mock.ETH, + ToAssetID: &mock.EOS, + RatioNumerator: 400000000001, + RatioDenominator: 2000000003, + }, + { + FromAssetID: &mock.EOS, + ToAssetID: &mock.BTC, + RatioNumerator: 100000002, + RatioDenominator: 400000000001, + }, + }, + wantMatched: false, + }, + } + + for i, c := range cases { + gotMatched := IsMatched(c.orders) + if gotMatched != c.wantMatched { + t.Errorf("#%d(%s) got matched:%v, want matched:%v", i, c.desc, gotMatched, c.wantMatched) + } + } +} diff --git a/math/decimal/decimal.go b/math/decimal/decimal.go new file mode 100644 index 00000000..a04afca3 --- /dev/null +++ b/math/decimal/decimal.go @@ -0,0 +1,143 @@ +package decimal + +import ( + "github.com/pingcap/types" + "github.com/sirupsen/logrus" +) + +type Decimal struct { + value *types.MyDecimal +} + +func NewFromString(value string) (*Decimal, error) { + dec := new(types.MyDecimal) + if err := dec.FromString([]byte(value)); err != nil && err != types.ErrTruncated { + return nil, err + } + + return &Decimal{value: dec}, nil +} + +func New(value int64, exp int) *Decimal { + dec := types.NewDecFromInt(value) + if exp < 0 { + pow := types.NewDecFromInt(1) + if err := pow.Shift(-exp); err != nil { + logFatal(err,"decimal shift error") + } + + if err := types.DecimalDiv(dec, pow, dec, 10); err != nil { + logFatal(err,"decimal divide error") + } + } else { + if err := dec.Shift(exp); err != nil { + logFatal(err,"decimal shift error") + } + } + return &Decimal{value: dec} +} + +func (d *Decimal) Abs() *Decimal { + coefficient := New(1, 0) + if d.value.IsNegative() { + coefficient = New(-1, 0) + } + return d.Mul(coefficient) +} + +func (d *Decimal) Add(d2 *Decimal) *Decimal { + result := new(types.MyDecimal) + if err := types.DecimalAdd(d.value, d2.value, result); err != nil { + logFatal(err,"decimal add error") + } + + return &Decimal{value: result} +} + +func (d *Decimal) Sub(d2 *Decimal) *Decimal { + result := new(types.MyDecimal) + if err := types.DecimalSub(d.value, d2.value, result); err != nil { + logFatal(err,"decimal subtract error") + } + + return &Decimal{value: result} +} + +func (d *Decimal) Mul(d2 *Decimal) *Decimal { + result := new(types.MyDecimal) + if err := types.DecimalMul(d.value, d2.value, result); err != nil { + logFatal(err,"decimal multiply error") + } + + return &Decimal{value: result} +} + +func (d *Decimal) Div(d2 *Decimal) *Decimal { + result := new(types.MyDecimal) + if err := types.DecimalDiv(d.value, d2.value, result, 10); err != nil { + logFatal(err,"decimal divide error") + } + + return &Decimal{value: result} +} + +func (d *Decimal) Float64() float64 { + result, err := d.value.ToFloat64() + if err != nil { + logFatal(err,"decimal to float64 error") + } + + return result +} + +func (d *Decimal) Int64() int64 { + result, err := d.value.ToInt() + if err != nil { + logFatal(err,"decimal to int64 error") + } + + return result +} + +func (d *Decimal) Cmp(d2 *Decimal) int { + return d.value.Compare(d2.value) +} + +func (d *Decimal) LessThanOrEqual(d2 *Decimal) bool { + return d.Cmp(d2) <= 0 +} + +func (d *Decimal) GreaterThan(d2 *Decimal) bool { + return d.Cmp(d2) > 0 +} + +func (d *Decimal) String() string { + return d.value.String() +} + +func (d *Decimal) StringRoundFixed(places int) string { + result := new(types.MyDecimal) + if err := d.value.Round(result, places, types.ModeHalfEven); err != nil { + logFatal(err, "decimal string round fixed error") + } + + return result.String() +} + +func (d *Decimal) StringCeilFixed(places int) string { + fixedRate, err := NewFromString(d.StringRoundFixed(places)) + if err != nil { + logFatal(err, "decimal string ceil fixed error") + } + + if fixedRate.Cmp(d) != 0 { + fixedRate = fixedRate.Add(New(1, -places)) + } + return fixedRate.StringRoundFixed(places) +} + +func logFatal(err error, args ...interface{}) { + if err != types.ErrTruncated { + logrus.WithField("err", err).Fatal(args...) + } +} diff --git a/vendor/github.com/pingcap/types/helper.go b/vendor/github.com/pingcap/types/helper.go new file mode 100644 index 00000000..93bc1897 --- /dev/null +++ b/vendor/github.com/pingcap/types/helper.go @@ -0,0 +1,168 @@ +package types + +// Copyright 2015 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "math" + "strings" + "unicode" + + "github.com/sirupsen/logrus" +) + +// RoundFloat rounds float val to the nearest integer value with float64 format, like MySQL Round function. +// RoundFloat uses default rounding mode, see https://dev.mysql.com/doc/refman/5.7/en/precision-math-rounding.html +// so rounding use "round half away from zero". +// e.g, 1.5 -> 2, -1.5 -> -2. +func RoundFloat(f float64) float64 { + if math.Abs(f) < 0.5 { + return 0 + } + + return math.Trunc(f + math.Copysign(0.5, f)) +} + +// Round rounds the argument f to dec decimal places. +// dec defaults to 0 if not specified. dec can be negative +// to cause dec digits left of the decimal point of the +// value f to become zero. +func Round(f float64, dec int) float64 { + shift := math.Pow10(dec) + tmp := f * shift + if math.IsInf(tmp, 0) { + return f + } + return RoundFloat(tmp) / shift +} + +// Truncate truncates the argument f to dec decimal places. +// dec defaults to 0 if not specified. dec can be negative +// to cause dec digits left of the decimal point of the +// value f to become zero. +func Truncate(f float64, dec int) float64 { + shift := math.Pow10(dec) + tmp := f * shift + if math.IsInf(tmp, 0) { + return f + } + return math.Trunc(tmp) / shift +} + +func isSpace(c byte) bool { + return c == ' ' || c == '\t' +} + +func isDigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func myMax(a, b int) int { + if a > b { + return a + } + return b +} + +func myMaxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} + +func myMin(a, b int) int { + if a < b { + return a + } + return b +} + +func myMinInt8(a, b int8) int8 { + if a < b { + return a + } + return b +} + +const ( + maxUint = uint64(math.MaxUint64) + uintCutOff = maxUint/uint64(10) + 1 + intCutOff = uint64(math.MaxInt64) + 1 +) + +// strToInt converts a string to an integer in best effort. +func strToInt(str string) (int64, error) { + str = strings.TrimSpace(str) + if len(str) == 0 { + return 0, ErrTruncated + } + negative := false + i := 0 + if str[i] == '-' { + negative = true + i++ + } else if str[i] == '+' { + i++ + } + + var ( + err error + hasNum = false + ) + r := uint64(0) + for ; i < len(str); i++ { + if !unicode.IsDigit(rune(str[i])) { + err = ErrTruncated + break + } + hasNum = true + if r >= uintCutOff { + r = 0 + err = ErrBadNumber + break + } + r = r * uint64(10) + + r1 := r + uint64(str[i]-'0') + if r1 < r || r1 > maxUint { + r = 0 + err = ErrBadNumber + break + } + r = r1 + } + if !hasNum { + err = ErrTruncated + } + + if !negative && r >= intCutOff { + return math.MaxInt64, ErrBadNumber + } + + if negative && r > intCutOff { + return math.MinInt64, ErrBadNumber + } + + if negative { + r = -r + } + return int64(r), err +} + +// Log logs the error if it is not nil. +func Log(err error) { + if err != nil { + logrus.Error("encountered error", err) + } +} diff --git a/vendor/github.com/pingcap/types/mydecimal.go b/vendor/github.com/pingcap/types/mydecimal.go new file mode 100644 index 00000000..cc2966bb --- /dev/null +++ b/vendor/github.com/pingcap/types/mydecimal.go @@ -0,0 +1,2332 @@ +// Copyright 2016 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "errors" + "math" + "strconv" +) + +// RoundMode is the type for round mode. +type RoundMode int32 + +var ( + // ErrTruncated is returned when data has been truncated during conversion. + ErrTruncated = errors.New("err truncated") + // ErrOverflow is returned when data is out of range for a field type. + ErrOverflow = errors.New("err overflow") + // ErrDivByZero is return when do division by 0. + ErrDivByZero = errors.New("err div by zero") + // ErrBadNumber is return when parsing an invalid binary decimal number. + ErrBadNumber = errors.New("err bad number") +) + +// constant values. +const ( + ten0 = 1 + ten1 = 10 + ten2 = 100 + ten3 = 1000 + ten4 = 10000 + ten5 = 100000 + ten6 = 1000000 + ten7 = 10000000 + ten8 = 100000000 + ten9 = 1000000000 + + maxWordBufLen = 9 // A MyDecimal holds 9 words. + digitsPerWord = 9 // A word holds 9 digits. + wordSize = 4 // A word is 4 bytes int32. + digMask = ten8 + wordBase = ten9 + wordMax = wordBase - 1 + notFixedDec = 31 + + DivFracIncr = 4 + + // ModeHalfEven rounds normally. + ModeHalfEven RoundMode = 5 + // Truncate just truncates the decimal. + ModeTruncate RoundMode = 10 + // Ceiling is not supported now. + modeCeiling RoundMode = 0 + + MaxDecimalScale = 30 +) + +var ( + wordBufLen = 9 + mod9 = [128]int8{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, 2, 3, 4, 5, 6, 7, 8, + 0, 1, + } + div9 = [128]int{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, + 8, 8, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, 12, + 13, 13, 13, 13, 13, 13, 13, 13, 13, + 14, 14, + } + powers10 = [10]int32{ten0, ten1, ten2, ten3, ten4, ten5, ten6, ten7, ten8, ten9} + dig2bytes = [10]int{0, 1, 1, 2, 2, 3, 3, 4, 4, 4} + fracMax = [8]int32{ + 900000000, + 990000000, + 999000000, + 999900000, + 999990000, + 999999000, + 999999900, + 999999990, + } + zeroMyDecimal = MyDecimal{} +) + +// get the zero of MyDecimal with the specified result fraction digits +func zeroMyDecimalWithFrac(frac int8) MyDecimal { + zero := MyDecimal{} + zero.digitsFrac = frac + zero.resultFrac = frac + return zero +} + +// add adds a and b and carry, returns the sum and new carry. +func add(a, b, carry int32) (int32, int32) { + sum := a + b + carry + if sum >= wordBase { + carry = 1 + sum -= wordBase + } else { + carry = 0 + } + return sum, carry +} + +// add2 adds a and b and carry, returns the sum and new carry. +// It is only used in DecimalMul. +func add2(a, b, carry int32) (int32, int32) { + sum := int64(a) + int64(b) + int64(carry) + if sum >= wordBase { + carry = 1 + sum -= wordBase + } else { + carry = 0 + } + + if sum >= wordBase { + sum -= wordBase + carry++ + } + return int32(sum), carry +} + +// sub subtracts b and carry from a, returns the diff and new carry. +func sub(a, b, carry int32) (int32, int32) { + diff := a - b - carry + if diff < 0 { + carry = 1 + diff += wordBase + } else { + carry = 0 + } + return diff, carry +} + +// sub2 subtracts b and carry from a, returns the diff and new carry. +// the new carry may be 2. +func sub2(a, b, carry int32) (int32, int32) { + diff := a - b - carry + if diff < 0 { + carry = 1 + diff += wordBase + } else { + carry = 0 + } + if diff < 0 { + diff += wordBase + carry++ + } + return diff, carry +} + +// fixWordCntError limits word count in wordBufLen, and returns overflow or truncate error. +func fixWordCntError(wordsInt, wordsFrac int) (newWordsInt int, newWordsFrac int, err error) { + if wordsInt+wordsFrac > wordBufLen { + if wordsInt > wordBufLen { + return wordBufLen, 0, ErrOverflow + } + return wordsInt, wordBufLen - wordsInt, ErrTruncated + } + return wordsInt, wordsFrac, nil +} + +/* + countLeadingZeroes returns the number of leading zeroes that can be removed from fraction. + + @param i start index + @param word value to compare against list of powers of 10 +*/ +func countLeadingZeroes(i int, word int32) int { + leading := 0 + for word < powers10[i] { + i-- + leading++ + } + return leading +} + +/* + countTrailingZeros returns the number of trailing zeroes that can be removed from fraction. + + @param i start index + @param word value to compare against list of powers of 10 +*/ +func countTrailingZeroes(i int, word int32) int { + trailing := 0 + for word%powers10[i] == 0 { + i++ + trailing++ + } + return trailing +} + +func digitsToWords(digits int) int { + if digits+digitsPerWord-1 >= 0 && digits+digitsPerWord-1 < 128 { + return div9[digits+digitsPerWord-1] + } + return (digits + digitsPerWord - 1) / digitsPerWord +} + +// MyDecimalStructSize is the struct size of MyDecimal. +const MyDecimalStructSize = 40 + +// MyDecimal represents a decimal value. +type MyDecimal struct { + digitsInt int8 // the number of *decimal* digits before the point. + + digitsFrac int8 // the number of decimal digits after the point. + + resultFrac int8 // result fraction digits. + + negative bool + + // wordBuf is an array of int32 words. + // A word is an int32 value can hold 9 digits.(0 <= word < wordBase) + wordBuf [maxWordBufLen]int32 +} + +// IsNegative returns whether a decimal is negative. +func (d *MyDecimal) IsNegative() bool { + return d.negative +} + +// GetDigitsFrac returns the digitsFrac. +func (d *MyDecimal) GetDigitsFrac() int8 { + return d.digitsFrac +} + +// GetDigitsInt returns the digitsInt. +func (d *MyDecimal) GetDigitsInt() int8 { + return d.digitsInt +} + +// String returns the decimal string representation rounded to resultFrac. +func (d *MyDecimal) String() string { + tmp := *d + err := tmp.Round(&tmp, int(tmp.resultFrac), ModeHalfEven) + Log(err) + return string(tmp.ToString()) +} + +func (d *MyDecimal) stringSize() int { + // sign, zero integer and dot. + return int(d.digitsInt + d.digitsFrac + 3) +} + +func (d *MyDecimal) removeLeadingZeros() (wordIdx int, digitsInt int) { + digitsInt = int(d.digitsInt) + i := ((digitsInt - 1) % digitsPerWord) + 1 + for digitsInt > 0 && d.wordBuf[wordIdx] == 0 { + digitsInt -= i + i = digitsPerWord + wordIdx++ + } + if digitsInt > 0 { + digitsInt -= countLeadingZeroes((digitsInt-1)%digitsPerWord, d.wordBuf[wordIdx]) + } else { + digitsInt = 0 + } + return +} + +func (d *MyDecimal) removeTrailingZeros() (lastWordIdx int, digitsFrac int) { + digitsFrac = int(d.digitsFrac) + i := ((digitsFrac - 1) % digitsPerWord) + 1 + lastWordIdx = digitsToWords(int(d.digitsInt)) + digitsToWords(int(d.digitsFrac)) + for digitsFrac > 0 && d.wordBuf[lastWordIdx-1] == 0 { + digitsFrac -= i + i = digitsPerWord + lastWordIdx-- + } + if digitsFrac > 0 { + digitsFrac -= countTrailingZeroes(9-((digitsFrac-1)%digitsPerWord), d.wordBuf[lastWordIdx-1]) + } else { + digitsFrac = 0 + } + return +} + +// ToString converts decimal to its printable string representation without rounding. +// +// RETURN VALUE +// +// str - result string +// errCode - eDecOK/eDecTruncate/eDecOverflow +// +func (d *MyDecimal) ToString() (str []byte) { + str = make([]byte, d.stringSize()) + digitsFrac := int(d.digitsFrac) + wordStartIdx, digitsInt := d.removeLeadingZeros() + if digitsInt+digitsFrac == 0 { + digitsInt = 1 + wordStartIdx = 0 + } + + digitsIntLen := digitsInt + if digitsIntLen == 0 { + digitsIntLen = 1 + } + digitsFracLen := digitsFrac + length := digitsIntLen + digitsFracLen + if d.negative { + length++ + } + if digitsFrac > 0 { + length++ + } + str = str[:length] + strIdx := 0 + if d.negative { + str[strIdx] = '-' + strIdx++ + } + var fill int + if digitsFrac > 0 { + fracIdx := strIdx + digitsIntLen + fill = digitsFracLen - digitsFrac + wordIdx := wordStartIdx + digitsToWords(digitsInt) + str[fracIdx] = '.' + fracIdx++ + for ; digitsFrac > 0; digitsFrac -= digitsPerWord { + x := d.wordBuf[wordIdx] + wordIdx++ + for i := myMin(digitsFrac, digitsPerWord); i > 0; i-- { + y := x / digMask + str[fracIdx] = byte(y) + '0' + fracIdx++ + x -= y * digMask + x *= 10 + } + } + for ; fill > 0; fill-- { + str[fracIdx] = '0' + fracIdx++ + } + } + fill = digitsIntLen - digitsInt + if digitsInt == 0 { + fill-- /* symbol 0 before digital point */ + } + for ; fill > 0; fill-- { + str[strIdx] = '0' + strIdx++ + } + if digitsInt > 0 { + strIdx += digitsInt + wordIdx := wordStartIdx + digitsToWords(digitsInt) + for ; digitsInt > 0; digitsInt -= digitsPerWord { + wordIdx-- + x := d.wordBuf[wordIdx] + for i := myMin(digitsInt, digitsPerWord); i > 0; i-- { + y := x / 10 + strIdx-- + str[strIdx] = '0' + byte(x-y*10) + x = y + } + } + } else { + str[strIdx] = '0' + } + return +} + +// FromString parses decimal from string. +func (d *MyDecimal) FromString(str []byte) error { + for i := 0; i < len(str); i++ { + if !isSpace(str[i]) { + str = str[i:] + break + } + } + if len(str) == 0 { + *d = zeroMyDecimal + return ErrBadNumber + } + switch str[0] { + case '-': + d.negative = true + fallthrough + case '+': + str = str[1:] + } + var strIdx int + for strIdx < len(str) && isDigit(str[strIdx]) { + strIdx++ + } + digitsInt := strIdx + var digitsFrac int + var endIdx int + if strIdx < len(str) && str[strIdx] == '.' { + endIdx = strIdx + 1 + for endIdx < len(str) && isDigit(str[endIdx]) { + endIdx++ + } + digitsFrac = endIdx - strIdx - 1 + } else { + digitsFrac = 0 + endIdx = strIdx + } + if digitsInt+digitsFrac == 0 { + *d = zeroMyDecimal + return ErrBadNumber + } + wordsInt := digitsToWords(digitsInt) + wordsFrac := digitsToWords(digitsFrac) + wordsInt, wordsFrac, err := fixWordCntError(wordsInt, wordsFrac) + if err != nil { + digitsFrac = wordsFrac * digitsPerWord + if err == ErrOverflow { + digitsInt = wordsInt * digitsPerWord + } + } + d.digitsInt = int8(digitsInt) + d.digitsFrac = int8(digitsFrac) + wordIdx := wordsInt + strIdxTmp := strIdx + var word int32 + var innerIdx int + for digitsInt > 0 { + digitsInt-- + strIdx-- + word += int32(str[strIdx]-'0') * powers10[innerIdx] + innerIdx++ + if innerIdx == digitsPerWord { + wordIdx-- + d.wordBuf[wordIdx] = word + word = 0 + innerIdx = 0 + } + } + if innerIdx != 0 { + wordIdx-- + d.wordBuf[wordIdx] = word + } + + wordIdx = wordsInt + strIdx = strIdxTmp + word = 0 + innerIdx = 0 + for digitsFrac > 0 { + digitsFrac-- + strIdx++ + word = int32(str[strIdx]-'0') + word*10 + innerIdx++ + if innerIdx == digitsPerWord { + d.wordBuf[wordIdx] = word + wordIdx++ + word = 0 + innerIdx = 0 + } + } + if innerIdx != 0 { + d.wordBuf[wordIdx] = word * powers10[digitsPerWord-innerIdx] + } + if endIdx+1 <= len(str) && (str[endIdx] == 'e' || str[endIdx] == 'E') { + exponent, err1 := strToInt(string(str[endIdx+1:])) + if err1 != nil { + err = err1 + if err != ErrTruncated { + *d = zeroMyDecimal + } + } + if exponent > math.MaxInt32/2 { + negative := d.negative + maxDecimal(wordBufLen*digitsPerWord, 0, d) + d.negative = negative + err = ErrOverflow + } + if exponent < math.MinInt32/2 && err != ErrOverflow { + *d = zeroMyDecimal + err = ErrTruncated + } + if err != ErrOverflow { + shiftErr := d.Shift(int(exponent)) + if shiftErr != nil { + if shiftErr == ErrOverflow { + negative := d.negative + maxDecimal(wordBufLen*digitsPerWord, 0, d) + d.negative = negative + } + err = shiftErr + } + } + } + allZero := true + for i := 0; i < wordBufLen; i++ { + if d.wordBuf[i] != 0 { + allZero = false + break + } + } + if allZero { + d.negative = false + } + d.resultFrac = d.digitsFrac + return err +} + +// Shift shifts decimal digits in given number (with rounding if it need), shift > 0 means shift to left shift, +// shift < 0 means right shift. In fact it is multiplying on 10^shift. +// +// RETURN +// eDecOK OK +// eDecOverflow operation lead to overflow, number is untoched +// eDecTruncated number was rounded to fit into buffer +// +func (d *MyDecimal) Shift(shift int) error { + var err error + if shift == 0 { + return nil + } + var ( + // digitBegin is index of first non zero digit (all indexes from 0). + digitBegin int + // digitEnd is index of position after last decimal digit. + digitEnd int + // point is index of digit position just after point. + point = digitsToWords(int(d.digitsInt)) * digitsPerWord + // new point position. + newPoint = point + shift + // number of digits in result. + digitsInt, digitsFrac int + newFront int + ) + digitBegin, digitEnd = d.digitBounds() + if digitBegin == digitEnd { + *d = zeroMyDecimal + return nil + } + + digitsInt = newPoint - digitBegin + if digitsInt < 0 { + digitsInt = 0 + } + digitsFrac = digitEnd - newPoint + if digitsFrac < 0 { + digitsFrac = 0 + } + wordsInt := digitsToWords(digitsInt) + wordsFrac := digitsToWords(digitsFrac) + newLen := wordsInt + wordsFrac + if newLen > wordBufLen { + lack := newLen - wordBufLen + if wordsFrac < lack { + return ErrOverflow + } + /* cut off fraction part to allow new number to fit in our buffer */ + err = ErrTruncated + wordsFrac -= lack + diff := digitsFrac - wordsFrac*digitsPerWord + err1 := d.Round(d, digitEnd-point-diff, ModeHalfEven) + if err1 != nil { + return err1 + } + digitEnd -= diff + digitsFrac = wordsFrac * digitsPerWord + if digitEnd <= digitBegin { + /* + We lost all digits (they will be shifted out of buffer), so we can + just return 0. + */ + *d = zeroMyDecimal + return ErrTruncated + } + } + + if shift%digitsPerWord != 0 { + var lMiniShift, rMiniShift, miniShift int + var doLeft bool + /* + Calculate left/right shift to align decimal digits inside our bug + digits correctly. + */ + if shift > 0 { + lMiniShift = shift % digitsPerWord + rMiniShift = digitsPerWord - lMiniShift + doLeft = lMiniShift <= digitBegin + } else { + rMiniShift = (-shift) % digitsPerWord + lMiniShift = digitsPerWord - rMiniShift + doLeft = (digitsPerWord*wordBufLen - digitEnd) < rMiniShift + } + if doLeft { + d.doMiniLeftShift(lMiniShift, digitBegin, digitEnd) + miniShift = -lMiniShift + } else { + d.doMiniRightShift(rMiniShift, digitBegin, digitEnd) + miniShift = rMiniShift + } + newPoint += miniShift + /* + If number is shifted and correctly aligned in buffer we can finish. + */ + if shift+miniShift == 0 && (newPoint-digitsInt) < digitsPerWord { + d.digitsInt = int8(digitsInt) + d.digitsFrac = int8(digitsFrac) + return err /* already shifted as it should be */ + } + digitBegin += miniShift + digitEnd += miniShift + } + + /* if new 'decimal front' is in first digit, we do not need move digits */ + newFront = newPoint - digitsInt + if newFront >= digitsPerWord || newFront < 0 { + /* need to move digits */ + var wordShift int + if newFront > 0 { + /* move left */ + wordShift = newFront / digitsPerWord + to := digitBegin/digitsPerWord - wordShift + barier := (digitEnd-1)/digitsPerWord - wordShift + for ; to <= barier; to++ { + d.wordBuf[to] = d.wordBuf[to+wordShift] + } + for barier += wordShift; to <= barier; to++ { + d.wordBuf[to] = 0 + } + wordShift = -wordShift + } else { + /* move right */ + wordShift = (1 - newFront) / digitsPerWord + to := (digitEnd-1)/digitsPerWord + wordShift + barier := digitBegin/digitsPerWord + wordShift + for ; to >= barier; to-- { + d.wordBuf[to] = d.wordBuf[to-wordShift] + } + for barier -= wordShift; to >= barier; to-- { + d.wordBuf[to] = 0 + } + } + digitShift := wordShift * digitsPerWord + digitBegin += digitShift + digitEnd += digitShift + newPoint += digitShift + } + /* + If there are gaps then fill them with 0. + + Only one of following 'for' loops will work because wordIdxBegin <= wordIdxEnd. + */ + wordIdxBegin := digitBegin / digitsPerWord + wordIdxEnd := (digitEnd - 1) / digitsPerWord + wordIdxNewPoint := 0 + + /* We don't want negative new_point below */ + if newPoint != 0 { + wordIdxNewPoint = (newPoint - 1) / digitsPerWord + } + if wordIdxNewPoint > wordIdxEnd { + for wordIdxNewPoint > wordIdxEnd { + d.wordBuf[wordIdxNewPoint] = 0 + wordIdxNewPoint-- + } + } else { + for ; wordIdxNewPoint < wordIdxBegin; wordIdxNewPoint++ { + d.wordBuf[wordIdxNewPoint] = 0 + } + } + d.digitsInt = int8(digitsInt) + d.digitsFrac = int8(digitsFrac) + return err +} + +/* + digitBounds returns bounds of decimal digits in the number. + + start - index (from 0 ) of first decimal digits. + end - index of position just after last decimal digit. +*/ +func (d *MyDecimal) digitBounds() (start, end int) { + var i int + bufBeg := 0 + bufLen := digitsToWords(int(d.digitsInt)) + digitsToWords(int(d.digitsFrac)) + bufEnd := bufLen - 1 + + /* find non-zero digit from number beginning */ + for bufBeg < bufLen && d.wordBuf[bufBeg] == 0 { + bufBeg++ + } + if bufBeg >= bufLen { + return 0, 0 + } + + /* find non-zero decimal digit from number beginning */ + if bufBeg == 0 && d.digitsInt > 0 { + i = (int(d.digitsInt) - 1) % digitsPerWord + start = digitsPerWord - i - 1 + } else { + i = digitsPerWord - 1 + start = bufBeg * digitsPerWord + } + if bufBeg < bufLen { + start += countLeadingZeroes(i, d.wordBuf[bufBeg]) + } + + /* find non-zero digit at the end */ + for bufEnd > bufBeg && d.wordBuf[bufEnd] == 0 { + bufEnd-- + } + /* find non-zero decimal digit from the end */ + if bufEnd == bufLen-1 && d.digitsFrac > 0 { + i = (int(d.digitsFrac)-1)%digitsPerWord + 1 + end = bufEnd*digitsPerWord + i + i = digitsPerWord - i + 1 + } else { + end = (bufEnd + 1) * digitsPerWord + i = 1 + } + end -= countTrailingZeroes(i, d.wordBuf[bufEnd]) + return start, end +} + +/* + doMiniLeftShift does left shift for alignment of data in buffer. + + shift number of decimal digits on which it should be shifted + beg/end bounds of decimal digits (see digitsBounds()) + + NOTE + Result fitting in the buffer should be garanted. + 'shift' have to be from 1 to digitsPerWord-1 (inclusive) +*/ +func (d *MyDecimal) doMiniLeftShift(shift, beg, end int) { + bufFrom := beg / digitsPerWord + bufEnd := (end - 1) / digitsPerWord + cShift := digitsPerWord - shift + if beg%digitsPerWord < shift { + d.wordBuf[bufFrom-1] = d.wordBuf[bufFrom] / powers10[cShift] + } + for bufFrom < bufEnd { + d.wordBuf[bufFrom] = (d.wordBuf[bufFrom]%powers10[cShift])*powers10[shift] + d.wordBuf[bufFrom+1]/powers10[cShift] + bufFrom++ + } + d.wordBuf[bufFrom] = (d.wordBuf[bufFrom] % powers10[cShift]) * powers10[shift] +} + +/* + doMiniRightShift does right shift for alignment of data in buffer. + + shift number of decimal digits on which it should be shifted + beg/end bounds of decimal digits (see digitsBounds()) + + NOTE + Result fitting in the buffer should be garanted. + 'shift' have to be from 1 to digitsPerWord-1 (inclusive) +*/ +func (d *MyDecimal) doMiniRightShift(shift, beg, end int) { + bufFrom := (end - 1) / digitsPerWord + bufEnd := beg / digitsPerWord + cShift := digitsPerWord - shift + if digitsPerWord-((end-1)%digitsPerWord+1) < shift { + d.wordBuf[bufFrom+1] = (d.wordBuf[bufFrom] % powers10[shift]) * powers10[cShift] + } + for bufFrom > bufEnd { + d.wordBuf[bufFrom] = d.wordBuf[bufFrom]/powers10[shift] + (d.wordBuf[bufFrom-1]%powers10[shift])*powers10[cShift] + bufFrom-- + } + d.wordBuf[bufFrom] = d.wordBuf[bufFrom] / powers10[shift] +} + +// Round rounds the decimal to "frac" digits. +// +// to - result buffer. d == to is allowed +// frac - to what position after fraction point to round. can be negative! +// roundMode - round to nearest even or truncate +// ModeHalfEven rounds normally. +// Truncate just truncates the decimal. +// +// NOTES +// scale can be negative ! +// one TRUNCATED error (line XXX below) isn't treated very logical :( +// +// RETURN VALUE +// eDecOK/eDecTruncated +func (d *MyDecimal) Round(to *MyDecimal, frac int, roundMode RoundMode) (err error) { + // wordsFracTo is the number of fraction words in buffer. + wordsFracTo := (frac + 1) / digitsPerWord + if frac > 0 { + wordsFracTo = digitsToWords(frac) + } + wordsFrac := digitsToWords(int(d.digitsFrac)) + wordsInt := digitsToWords(int(d.digitsInt)) + + roundDigit := int32(roundMode) + /* TODO - fix this code as it won't work for CEILING mode */ + + if wordsInt+wordsFracTo > wordBufLen { + wordsFracTo = wordBufLen - wordsInt + frac = wordsFracTo * digitsPerWord + err = ErrTruncated + } + if int(d.digitsInt)+frac < 0 { + *to = zeroMyDecimal + return nil + } + if to != d { + copy(to.wordBuf[:], d.wordBuf[:]) + to.negative = d.negative + to.digitsInt = int8(myMin(wordsInt, wordBufLen) * digitsPerWord) + } + if wordsFracTo > wordsFrac { + idx := wordsInt + wordsFrac + for wordsFracTo > wordsFrac { + wordsFracTo-- + to.wordBuf[idx] = 0 + idx++ + } + to.digitsFrac = int8(frac) + to.resultFrac = to.digitsFrac + return + } + if frac >= int(d.digitsFrac) { + to.digitsFrac = int8(frac) + to.resultFrac = to.digitsFrac + return + } + + // Do increment. + toIdx := wordsInt + wordsFracTo - 1 + if frac == wordsFracTo*digitsPerWord { + doInc := false + switch roundMode { + // Notice: No support for ceiling mode now. + case modeCeiling: + // If any word after scale is not zero, do increment. + // e.g ceiling 3.0001 to scale 1, gets 3.1 + idx := toIdx + (wordsFrac - wordsFracTo) + for idx > toIdx { + if d.wordBuf[idx] != 0 { + doInc = true + break + } + idx-- + } + case ModeHalfEven: + digAfterScale := d.wordBuf[toIdx+1] / digMask // the first digit after scale. + // If first digit after scale is 5 and round even, do increment if digit at scale is odd. + doInc = (digAfterScale > 5) || (digAfterScale == 5) + case ModeTruncate: + // Never round, just truncate. + doInc = false + } + if doInc { + if toIdx >= 0 { + to.wordBuf[toIdx]++ + } else { + toIdx++ + to.wordBuf[toIdx] = wordBase + } + } else if wordsInt+wordsFracTo == 0 { + *to = zeroMyDecimal + return nil + } + } else { + /* TODO - fix this code as it won't work for CEILING mode */ + pos := wordsFracTo*digitsPerWord - frac - 1 + shiftedNumber := to.wordBuf[toIdx] / powers10[pos] + digAfterScale := shiftedNumber % 10 + if digAfterScale > roundDigit || (roundDigit == 5 && digAfterScale == 5) { + shiftedNumber += 10 + } + to.wordBuf[toIdx] = powers10[pos] * (shiftedNumber - digAfterScale) + } + /* + In case we're rounding e.g. 1.5e9 to 2.0e9, the decimal words inside + the buffer are as follows. + + Before <1, 5e8> + After <2, 5e8> + + Hence we need to set the 2nd field to 0. + The same holds if we round 1.5e-9 to 2e-9. + */ + if wordsFracTo < wordsFrac { + idx := wordsInt + wordsFracTo + if frac == 0 && wordsInt == 0 { + idx = 1 + } + for idx < wordBufLen { + to.wordBuf[idx] = 0 + idx++ + } + } + + // Handle carry. + var carry int32 + if to.wordBuf[toIdx] >= wordBase { + carry = 1 + to.wordBuf[toIdx] -= wordBase + for carry == 1 && toIdx > 0 { + toIdx-- + to.wordBuf[toIdx], carry = add(to.wordBuf[toIdx], 0, carry) + } + if carry > 0 { + if wordsInt+wordsFracTo >= wordBufLen { + wordsFracTo-- + frac = wordsFracTo * digitsPerWord + err = ErrTruncated + } + for toIdx = wordsInt + myMax(wordsFracTo, 0); toIdx > 0; toIdx-- { + if toIdx < wordBufLen { + to.wordBuf[toIdx] = to.wordBuf[toIdx-1] + } else { + err = ErrOverflow + } + } + to.wordBuf[toIdx] = 1 + /* We cannot have more than 9 * 9 = 81 digits. */ + if int(to.digitsInt) < digitsPerWord*wordBufLen { + to.digitsInt++ + } else { + err = ErrOverflow + } + } + } else { + for { + if to.wordBuf[toIdx] != 0 { + break + } + if toIdx == 0 { + /* making 'zero' with the proper scale */ + idx := wordsFracTo + 1 + to.digitsInt = 1 + to.digitsFrac = int8(myMax(frac, 0)) + to.negative = false + for toIdx < idx { + to.wordBuf[toIdx] = 0 + toIdx++ + } + to.resultFrac = to.digitsFrac + return nil + } + toIdx-- + } + } + /* Here we check 999.9 -> 1000 case when we need to increase intDigCnt */ + firstDig := mod9[to.digitsInt] + if firstDig > 0 && to.wordBuf[toIdx] >= powers10[firstDig] { + to.digitsInt++ + } + if frac < 0 { + frac = 0 + } + to.digitsFrac = int8(frac) + to.resultFrac = to.digitsFrac + return +} + +// FromInt sets the decimal value from int64. +func (d *MyDecimal) FromInt(val int64) *MyDecimal { + var uVal uint64 + if val < 0 { + d.negative = true + uVal = uint64(-val) + } else { + uVal = uint64(val) + } + return d.FromUint(uVal) +} + +// FromUint sets the decimal value from uint64. +func (d *MyDecimal) FromUint(val uint64) *MyDecimal { + x := val + wordIdx := 1 + for x >= wordBase { + wordIdx++ + x /= wordBase + } + d.digitsFrac = 0 + d.digitsInt = int8(wordIdx * digitsPerWord) + x = val + for wordIdx > 0 { + wordIdx-- + y := x / wordBase + d.wordBuf[wordIdx] = int32(x - y*wordBase) + x = y + } + return d +} + +// ToInt returns int part of the decimal, returns the result and errcode. +func (d *MyDecimal) ToInt() (int64, error) { + var x int64 + wordIdx := 0 + for i := d.digitsInt; i > 0; i -= digitsPerWord { + y := x + /* + Attention: trick! + we're calculating -|from| instead of |from| here + because |LONGLONG_MIN| > LONGLONG_MAX + so we can convert -9223372036854775808 correctly + */ + x = x*wordBase - int64(d.wordBuf[wordIdx]) + wordIdx++ + if y < math.MinInt64/wordBase || x > y { + /* + the decimal is bigger than any possible integer + return border integer depending on the sign + */ + if d.negative { + return math.MinInt64, ErrOverflow + } + return math.MaxInt64, ErrOverflow + } + } + /* boundary case: 9223372036854775808 */ + if !d.negative && x == math.MinInt64 { + return math.MaxInt64, ErrOverflow + } + if !d.negative { + x = -x + } + for i := d.digitsFrac; i > 0; i -= digitsPerWord { + if d.wordBuf[wordIdx] != 0 { + return x, ErrTruncated + } + wordIdx++ + } + return x, nil +} + +// ToUint returns int part of the decimal, returns the result and errcode. +func (d *MyDecimal) ToUint() (uint64, error) { + if d.negative { + return 0, ErrOverflow + } + var x uint64 + wordIdx := 0 + for i := d.digitsInt; i > 0; i -= digitsPerWord { + y := x + x = x*wordBase + uint64(d.wordBuf[wordIdx]) + wordIdx++ + if y > math.MaxUint64/wordBase || x < y { + return math.MaxUint64, ErrOverflow + } + } + for i := d.digitsFrac; i > 0; i -= digitsPerWord { + if d.wordBuf[wordIdx] != 0 { + return x, ErrTruncated + } + wordIdx++ + } + return x, nil +} + +// FromFloat64 creates a decimal from float64 value. +func (d *MyDecimal) FromFloat64(f float64) error { + s := strconv.FormatFloat(f, 'g', -1, 64) + return d.FromString([]byte(s)) +} + +// ToFloat64 converts decimal to float64 value. +func (d *MyDecimal) ToFloat64() (float64, error) { + f, err := strconv.ParseFloat(d.String(), 64) + if err != nil { + err = ErrOverflow + } + return f, err +} + +/* +ToBin converts decimal to its binary fixed-length representation +two representations of the same length can be compared with memcmp +with the correct -1/0/+1 result + + PARAMS + precision/frac - if precision is 0, internal value of the decimal will be used, + then the encoded value is not memory comparable. + + NOTE + the buffer is assumed to be of the size DecimalBinSize(precision, frac) + + RETURN VALUE + bin - binary value + errCode - eDecOK/eDecTruncate/eDecOverflow + + DESCRIPTION + for storage decimal numbers are converted to the "binary" format. + + This format has the following properties: + 1. length of the binary representation depends on the {precision, frac} + as provided by the caller and NOT on the digitsInt/digitsFrac of the decimal to + convert. + 2. binary representations of the same {precision, frac} can be compared + with memcmp - with the same result as DecimalCompare() of the original + decimals (not taking into account possible precision loss during + conversion). + + This binary format is as follows: + 1. First the number is converted to have a requested precision and frac. + 2. Every full digitsPerWord digits of digitsInt part are stored in 4 bytes + as is + 3. The first digitsInt % digitesPerWord digits are stored in the reduced + number of bytes (enough bytes to store this number of digits - + see dig2bytes) + 4. same for frac - full word are stored as is, + the last frac % digitsPerWord digits - in the reduced number of bytes. + 5. If the number is negative - every byte is inversed. + 5. The very first bit of the resulting byte array is inverted (because + memcmp compares unsigned bytes, see property 2 above) + + Example: + + 1234567890.1234 + + internally is represented as 3 words + + 1 234567890 123400000 + + (assuming we want a binary representation with precision=14, frac=4) + in hex it's + + 00-00-00-01 0D-FB-38-D2 07-5A-EF-40 + + now, middle word is full - it stores 9 decimal digits. It goes + into binary representation as is: + + + ........... 0D-FB-38-D2 ............ + + First word has only one decimal digit. We can store one digit in + one byte, no need to waste four: + + 01 0D-FB-38-D2 ............ + + now, last word. It's 123400000. We can store 1234 in two bytes: + + 01 0D-FB-38-D2 04-D2 + + So, we've packed 12 bytes number in 7 bytes. + And now we invert the highest bit to get the final result: + + 81 0D FB 38 D2 04 D2 + + And for -1234567890.1234 it would be + + 7E F2 04 C7 2D FB 2D +*/ +func (d *MyDecimal) ToBin(precision, frac int) ([]byte, error) { + if precision > digitsPerWord*maxWordBufLen || precision < 0 || frac > MaxDecimalScale || frac < 0 { + return nil, ErrBadNumber + } + var err error + var mask int32 + if d.negative { + mask = -1 + } + digitsInt := precision - frac + wordsInt := digitsInt / digitsPerWord + leadingDigits := digitsInt - wordsInt*digitsPerWord + wordsFrac := frac / digitsPerWord + trailingDigits := frac - wordsFrac*digitsPerWord + + wordsFracFrom := int(d.digitsFrac) / digitsPerWord + trailingDigitsFrom := int(d.digitsFrac) - wordsFracFrom*digitsPerWord + intSize := wordsInt*wordSize + dig2bytes[leadingDigits] + fracSize := wordsFrac*wordSize + dig2bytes[trailingDigits] + fracSizeFrom := wordsFracFrom*wordSize + dig2bytes[trailingDigitsFrom] + originIntSize := intSize + originFracSize := fracSize + bin := make([]byte, intSize+fracSize) + binIdx := 0 + wordIdxFrom, digitsIntFrom := d.removeLeadingZeros() + if digitsIntFrom+fracSizeFrom == 0 { + mask = 0 + digitsInt = 1 + } + + wordsIntFrom := digitsIntFrom / digitsPerWord + leadingDigitsFrom := digitsIntFrom - wordsIntFrom*digitsPerWord + iSizeFrom := wordsIntFrom*wordSize + dig2bytes[leadingDigitsFrom] + + if digitsInt < digitsIntFrom { + wordIdxFrom += wordsIntFrom - wordsInt + if leadingDigitsFrom > 0 { + wordIdxFrom++ + } + if leadingDigits > 0 { + wordIdxFrom-- + } + wordsIntFrom = wordsInt + leadingDigitsFrom = leadingDigits + err = ErrOverflow + } else if intSize > iSizeFrom { + for intSize > iSizeFrom { + intSize-- + bin[binIdx] = byte(mask) + binIdx++ + } + } + + if fracSize < fracSizeFrom { + wordsFracFrom = wordsFrac + trailingDigitsFrom = trailingDigits + err = ErrTruncated + } else if fracSize > fracSizeFrom && trailingDigitsFrom > 0 { + if wordsFrac == wordsFracFrom { + trailingDigitsFrom = trailingDigits + fracSize = fracSizeFrom + } else { + wordsFracFrom++ + trailingDigitsFrom = 0 + } + } + // xIntFrom part + if leadingDigitsFrom > 0 { + i := dig2bytes[leadingDigitsFrom] + x := (d.wordBuf[wordIdxFrom] % powers10[leadingDigitsFrom]) ^ mask + wordIdxFrom++ + writeWord(bin[binIdx:], x, i) + binIdx += i + } + + // wordsInt + wordsFrac part. + for stop := wordIdxFrom + wordsIntFrom + wordsFracFrom; wordIdxFrom < stop; binIdx += wordSize { + x := d.wordBuf[wordIdxFrom] ^ mask + wordIdxFrom++ + writeWord(bin[binIdx:], x, 4) + } + + // xFracFrom part + if trailingDigitsFrom > 0 { + var x int32 + i := dig2bytes[trailingDigitsFrom] + lim := trailingDigits + if wordsFracFrom < wordsFrac { + lim = digitsPerWord + } + + for trailingDigitsFrom < lim && dig2bytes[trailingDigitsFrom] == i { + trailingDigitsFrom++ + } + x = (d.wordBuf[wordIdxFrom] / powers10[digitsPerWord-trailingDigitsFrom]) ^ mask + writeWord(bin[binIdx:], x, i) + binIdx += i + } + if fracSize > fracSizeFrom { + binIdxEnd := originIntSize + originFracSize + for fracSize > fracSizeFrom && binIdx < binIdxEnd { + fracSize-- + bin[binIdx] = byte(mask) + binIdx++ + } + } + bin[0] ^= 0x80 + return bin, err +} + +// ToHashKey removes the leading and trailing zeros and generates a hash key. +// Two Decimals dec0 and dec1 with different fraction will generate the same hash keys if dec0.Compare(dec1) == 0. +func (d *MyDecimal) ToHashKey() ([]byte, error) { + _, digitsInt := d.removeLeadingZeros() + _, digitsFrac := d.removeTrailingZeros() + prec := digitsInt + digitsFrac + if prec == 0 { // zeroDecimal + prec = 1 + } + buf, err := d.ToBin(prec, digitsFrac) + if err == ErrTruncated { + // This err is caused by shorter digitsFrac; + // After removing the trailing zeros from a Decimal, + // so digitsFrac may be less than the real digitsFrac of the Decimal, + // thus ErrTruncated may be raised, we can ignore it here. + err = nil + } + return buf, err +} + +// PrecisionAndFrac returns the internal precision and frac number. +func (d *MyDecimal) PrecisionAndFrac() (precision, frac int) { + frac = int(d.digitsFrac) + _, digitsInt := d.removeLeadingZeros() + precision = digitsInt + frac + if precision == 0 { + precision = 1 + } + return +} + +// IsZero checks whether it's a zero decimal. +func (d *MyDecimal) IsZero() bool { + isZero := true + for _, val := range d.wordBuf { + if val != 0 { + isZero = false + break + } + } + return isZero +} + +// FromBin Restores decimal from its binary fixed-length representation. +func (d *MyDecimal) FromBin(bin []byte, precision, frac int) (binSize int, err error) { + if len(bin) == 0 { + *d = zeroMyDecimal + return 0, ErrBadNumber + } + digitsInt := precision - frac + wordsInt := digitsInt / digitsPerWord + leadingDigits := digitsInt - wordsInt*digitsPerWord + wordsFrac := frac / digitsPerWord + trailingDigits := frac - wordsFrac*digitsPerWord + wordsIntTo := wordsInt + if leadingDigits > 0 { + wordsIntTo++ + } + wordsFracTo := wordsFrac + if trailingDigits > 0 { + wordsFracTo++ + } + + binIdx := 0 + mask := int32(-1) + if bin[binIdx]&0x80 > 0 { + mask = 0 + } + binSize = DecimalBinSize(precision, frac) + dCopy := make([]byte, 40) + dCopy = dCopy[:binSize] + copy(dCopy, bin) + dCopy[0] ^= 0x80 + bin = dCopy + oldWordsIntTo := wordsIntTo + wordsIntTo, wordsFracTo, err = fixWordCntError(wordsIntTo, wordsFracTo) + if err != nil { + if wordsIntTo < oldWordsIntTo { + binIdx += dig2bytes[leadingDigits] + (wordsInt-wordsIntTo)*wordSize + } else { + trailingDigits = 0 + wordsFrac = wordsFracTo + } + } + d.negative = mask != 0 + d.digitsInt = int8(wordsInt*digitsPerWord + leadingDigits) + d.digitsFrac = int8(wordsFrac*digitsPerWord + trailingDigits) + + wordIdx := 0 + if leadingDigits > 0 { + i := dig2bytes[leadingDigits] + x := readWord(bin[binIdx:], i) + binIdx += i + d.wordBuf[wordIdx] = x ^ mask + if uint64(d.wordBuf[wordIdx]) >= uint64(powers10[leadingDigits+1]) { + *d = zeroMyDecimal + return binSize, ErrBadNumber + } + if wordIdx > 0 || d.wordBuf[wordIdx] != 0 { + wordIdx++ + } else { + d.digitsInt -= int8(leadingDigits) + } + } + for stop := binIdx + wordsInt*wordSize; binIdx < stop; binIdx += wordSize { + d.wordBuf[wordIdx] = readWord(bin[binIdx:], 4) ^ mask + if uint32(d.wordBuf[wordIdx]) > wordMax { + *d = zeroMyDecimal + return binSize, ErrBadNumber + } + if wordIdx > 0 || d.wordBuf[wordIdx] != 0 { + wordIdx++ + } else { + d.digitsInt -= digitsPerWord + } + } + + for stop := binIdx + wordsFrac*wordSize; binIdx < stop; binIdx += wordSize { + d.wordBuf[wordIdx] = readWord(bin[binIdx:], 4) ^ mask + if uint32(d.wordBuf[wordIdx]) > wordMax { + *d = zeroMyDecimal + return binSize, ErrBadNumber + } + wordIdx++ + } + + if trailingDigits > 0 { + i := dig2bytes[trailingDigits] + x := readWord(bin[binIdx:], i) + d.wordBuf[wordIdx] = (x ^ mask) * powers10[digitsPerWord-trailingDigits] + if uint32(d.wordBuf[wordIdx]) > wordMax { + *d = zeroMyDecimal + return binSize, ErrBadNumber + } + } + + if d.digitsInt == 0 && d.digitsFrac == 0 { + *d = zeroMyDecimal + } + d.resultFrac = int8(frac) + return binSize, err +} + +// DecimalBinSize returns the size of array to hold a binary representation of a decimal. +func DecimalBinSize(precision, frac int) int { + digitsInt := precision - frac + wordsInt := digitsInt / digitsPerWord + wordsFrac := frac / digitsPerWord + xInt := digitsInt - wordsInt*digitsPerWord + xFrac := frac - wordsFrac*digitsPerWord + return wordsInt*wordSize + dig2bytes[xInt] + wordsFrac*wordSize + dig2bytes[xFrac] +} + +func readWord(b []byte, size int) int32 { + var x int32 + switch size { + case 1: + x = int32(int8(b[0])) + case 2: + x = int32(int8(b[0]))<<8 + int32(b[1]) + case 3: + if b[0]&128 > 0 { + x = int32(uint32(255)<<24 | uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])) + } else { + x = int32(uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])) + } + case 4: + x = int32(b[3]) + int32(b[2])<<8 + int32(b[1])<<16 + int32(int8(b[0]))<<24 + } + return x +} + +func writeWord(b []byte, word int32, size int) { + v := uint32(word) + switch size { + case 1: + b[0] = byte(word) + case 2: + b[0] = byte(v >> 8) + b[1] = byte(v) + case 3: + b[0] = byte(v >> 16) + b[1] = byte(v >> 8) + b[2] = byte(v) + case 4: + b[0] = byte(v >> 24) + b[1] = byte(v >> 16) + b[2] = byte(v >> 8) + b[3] = byte(v) + } +} + +// Compare compares one decimal to another, returns -1/0/1. +func (d *MyDecimal) Compare(to *MyDecimal) int { + if d.negative == to.negative { + cmp, err := doSub(d, to, nil) + Log(err) + return cmp + } + if d.negative { + return -1 + } + return 1 +} + +// DecimalNeg reverses decimal's sign. +func DecimalNeg(from *MyDecimal) *MyDecimal { + to := *from + if from.IsZero() { + return &to + } + to.negative = !from.negative + return &to +} + +// DecimalAdd adds two decimals, sets the result to 'to'. +// Note: DO NOT use `from1` or `from2` as `to` since the metadata +// of `to` may be changed during evaluating. +func DecimalAdd(from1, from2, to *MyDecimal) error { + from1, from2, to = validateArgs(from1, from2, to) + to.resultFrac = myMaxInt8(from1.resultFrac, from2.resultFrac) + if from1.negative == from2.negative { + return doAdd(from1, from2, to) + } + _, err := doSub(from1, from2, to) + return err +} + +// DecimalSub subs one decimal from another, sets the result to 'to'. +func DecimalSub(from1, from2, to *MyDecimal) error { + from1, from2, to = validateArgs(from1, from2, to) + to.resultFrac = myMaxInt8(from1.resultFrac, from2.resultFrac) + if from1.negative == from2.negative { + _, err := doSub(from1, from2, to) + return err + } + return doAdd(from1, from2, to) +} + +func validateArgs(f1, f2, to *MyDecimal) (*MyDecimal, *MyDecimal, *MyDecimal) { + if to == nil { + return f1, f2, to + } + if f1 == to { + tmp := *f1 + f1 = &tmp + } + if f2 == to { + tmp := *f2 + f2 = &tmp + } + to.digitsFrac = 0 + to.digitsInt = 0 + to.resultFrac = 0 + to.negative = false + for i := range to.wordBuf { + to.wordBuf[i] = 0 + } + return f1, f2, to +} + +func doSub(from1, from2, to *MyDecimal) (cmp int, err error) { + var ( + wordsInt1 = digitsToWords(int(from1.digitsInt)) + wordsFrac1 = digitsToWords(int(from1.digitsFrac)) + wordsInt2 = digitsToWords(int(from2.digitsInt)) + wordsFrac2 = digitsToWords(int(from2.digitsFrac)) + wordsFracTo = myMax(wordsFrac1, wordsFrac2) + + start1 = 0 + stop1 = wordsInt1 + idx1 = 0 + start2 = 0 + stop2 = wordsInt2 + idx2 = 0 + ) + if from1.wordBuf[idx1] == 0 { + for idx1 < stop1 && from1.wordBuf[idx1] == 0 { + idx1++ + } + start1 = idx1 + wordsInt1 = stop1 - idx1 + } + if from2.wordBuf[idx2] == 0 { + for idx2 < stop2 && from2.wordBuf[idx2] == 0 { + idx2++ + } + start2 = idx2 + wordsInt2 = stop2 - idx2 + } + + var carry int32 + if wordsInt2 > wordsInt1 { + carry = 1 + } else if wordsInt2 == wordsInt1 { + end1 := stop1 + wordsFrac1 - 1 + end2 := stop2 + wordsFrac2 - 1 + for idx1 <= end1 && from1.wordBuf[end1] == 0 { + end1-- + } + for idx2 <= end2 && from2.wordBuf[end2] == 0 { + end2-- + } + wordsFrac1 = end1 - stop1 + 1 + wordsFrac2 = end2 - stop2 + 1 + for idx1 <= end1 && idx2 <= end2 && from1.wordBuf[idx1] == from2.wordBuf[idx2] { + idx1++ + idx2++ + } + if idx1 <= end1 { + if idx2 <= end2 && from2.wordBuf[idx2] > from1.wordBuf[idx1] { + carry = 1 + } else { + carry = 0 + } + } else { + if idx2 <= end2 { + carry = 1 + } else { + if to == nil { + return 0, nil + } + *to = zeroMyDecimalWithFrac(to.resultFrac) + return 0, nil + } + } + } + + if to == nil { + if carry > 0 == from1.negative { // from2 is negative too. + return 1, nil + } + return -1, nil + } + + to.negative = from1.negative + + /* ensure that always idx1 > idx2 (and wordsInt1 >= wordsInt2) */ + if carry > 0 { + from1, from2 = from2, from1 + start1, start2 = start2, start1 + wordsInt1, wordsInt2 = wordsInt2, wordsInt1 + wordsFrac1, wordsFrac2 = wordsFrac2, wordsFrac1 + to.negative = !to.negative + } + + wordsInt1, wordsFracTo, err = fixWordCntError(wordsInt1, wordsFracTo) + idxTo := wordsInt1 + wordsFracTo + to.digitsFrac = from1.digitsFrac + if to.digitsFrac < from2.digitsFrac { + to.digitsFrac = from2.digitsFrac + } + to.digitsInt = int8(wordsInt1 * digitsPerWord) + if err != nil { + if to.digitsFrac > int8(wordsFracTo*digitsPerWord) { + to.digitsFrac = int8(wordsFracTo * digitsPerWord) + } + if wordsFrac1 > wordsFracTo { + wordsFrac1 = wordsFracTo + } + if wordsFrac2 > wordsFracTo { + wordsFrac2 = wordsFracTo + } + if wordsInt2 > wordsInt1 { + wordsInt2 = wordsInt1 + } + } + carry = 0 + + /* part 1 - max(frac) ... min (frac) */ + if wordsFrac1 > wordsFrac2 { + idx1 = start1 + wordsInt1 + wordsFrac1 + stop1 = start1 + wordsInt1 + wordsFrac2 + idx2 = start2 + wordsInt2 + wordsFrac2 + for wordsFracTo > wordsFrac1 { + wordsFracTo-- + idxTo-- + to.wordBuf[idxTo] = 0 + } + for idx1 > stop1 { + idxTo-- + idx1-- + to.wordBuf[idxTo] = from1.wordBuf[idx1] + } + } else { + idx1 = start1 + wordsInt1 + wordsFrac1 + idx2 = start2 + wordsInt2 + wordsFrac2 + stop2 = start2 + wordsInt2 + wordsFrac1 + for wordsFracTo > wordsFrac2 { + wordsFracTo-- + idxTo-- + to.wordBuf[idxTo] = 0 + } + for idx2 > stop2 { + idxTo-- + idx2-- + to.wordBuf[idxTo], carry = sub(0, from2.wordBuf[idx2], carry) + } + } + + /* part 2 - min(frac) ... wordsInt2 */ + for idx2 > start2 { + idxTo-- + idx1-- + idx2-- + to.wordBuf[idxTo], carry = sub(from1.wordBuf[idx1], from2.wordBuf[idx2], carry) + } + + /* part 3 - wordsInt2 ... wordsInt1 */ + for carry > 0 && idx1 > start1 { + idxTo-- + idx1-- + to.wordBuf[idxTo], carry = sub(from1.wordBuf[idx1], 0, carry) + } + for idx1 > start1 { + idxTo-- + idx1-- + to.wordBuf[idxTo] = from1.wordBuf[idx1] + } + for idxTo > 0 { + idxTo-- + to.wordBuf[idxTo] = 0 + } + return 0, err +} + +func doAdd(from1, from2, to *MyDecimal) error { + var ( + err error + wordsInt1 = digitsToWords(int(from1.digitsInt)) + wordsFrac1 = digitsToWords(int(from1.digitsFrac)) + wordsInt2 = digitsToWords(int(from2.digitsInt)) + wordsFrac2 = digitsToWords(int(from2.digitsFrac)) + wordsIntTo = myMax(wordsInt1, wordsInt2) + wordsFracTo = myMax(wordsFrac1, wordsFrac2) + ) + + var x int32 + if wordsInt1 > wordsInt2 { + x = from1.wordBuf[0] + } else if wordsInt2 > wordsInt1 { + x = from2.wordBuf[0] + } else { + x = from1.wordBuf[0] + from2.wordBuf[0] + } + if x > wordMax-1 { /* yes, there is */ + wordsIntTo++ + to.wordBuf[0] = 0 /* safety */ + } + + wordsIntTo, wordsFracTo, err = fixWordCntError(wordsIntTo, wordsFracTo) + if err == ErrOverflow { + maxDecimal(wordBufLen*digitsPerWord, 0, to) + return err + } + idxTo := wordsIntTo + wordsFracTo + to.negative = from1.negative + to.digitsInt = int8(wordsIntTo * digitsPerWord) + to.digitsFrac = myMaxInt8(from1.digitsFrac, from2.digitsFrac) + + if err != nil { + if to.digitsFrac > int8(wordsFracTo*digitsPerWord) { + to.digitsFrac = int8(wordsFracTo * digitsPerWord) + } + if wordsFrac1 > wordsFracTo { + wordsFrac1 = wordsFracTo + } + if wordsFrac2 > wordsFracTo { + wordsFrac2 = wordsFracTo + } + if wordsInt1 > wordsIntTo { + wordsInt1 = wordsIntTo + } + if wordsInt2 > wordsIntTo { + wordsInt2 = wordsIntTo + } + } + var dec1, dec2 = from1, from2 + var idx1, idx2, stop, stop2 int + /* part 1 - max(frac) ... min (frac) */ + if wordsFrac1 > wordsFrac2 { + idx1 = wordsInt1 + wordsFrac1 + stop = wordsInt1 + wordsFrac2 + idx2 = wordsInt2 + wordsFrac2 + if wordsInt1 > wordsInt2 { + stop2 = wordsInt1 - wordsInt2 + } + } else { + idx1 = wordsInt2 + wordsFrac2 + stop = wordsInt2 + wordsFrac1 + idx2 = wordsInt1 + wordsFrac1 + if wordsInt2 > wordsInt1 { + stop2 = wordsInt2 - wordsInt1 + } + dec1, dec2 = from2, from1 + } + for idx1 > stop { + idxTo-- + idx1-- + to.wordBuf[idxTo] = dec1.wordBuf[idx1] + } + + /* part 2 - min(frac) ... min(digitsInt) */ + carry := int32(0) + for idx1 > stop2 { + idx1-- + idx2-- + idxTo-- + to.wordBuf[idxTo], carry = add(dec1.wordBuf[idx1], dec2.wordBuf[idx2], carry) + } + + /* part 3 - min(digitsInt) ... max(digitsInt) */ + stop = 0 + if wordsInt1 > wordsInt2 { + idx1 = wordsInt1 - wordsInt2 + dec1 = from1 + } else { + idx1 = wordsInt2 - wordsInt1 + dec1 = from2 + } + for idx1 > stop { + idxTo-- + idx1-- + to.wordBuf[idxTo], carry = add(dec1.wordBuf[idx1], 0, carry) + } + if carry > 0 { + idxTo-- + to.wordBuf[idxTo] = 1 + } + return err +} + +func maxDecimal(precision, frac int, to *MyDecimal) { + digitsInt := precision - frac + to.negative = false + to.digitsInt = int8(digitsInt) + idx := 0 + if digitsInt > 0 { + firstWordDigits := digitsInt % digitsPerWord + if firstWordDigits > 0 { + to.wordBuf[idx] = powers10[firstWordDigits] - 1 /* get 9 99 999 ... */ + idx++ + } + for digitsInt /= digitsPerWord; digitsInt > 0; digitsInt-- { + to.wordBuf[idx] = wordMax + idx++ + } + } + to.digitsFrac = int8(frac) + if frac > 0 { + lastDigits := frac % digitsPerWord + for frac /= digitsPerWord; frac > 0; frac-- { + to.wordBuf[idx] = wordMax + idx++ + } + if lastDigits > 0 { + to.wordBuf[idx] = fracMax[lastDigits-1] + } + } +} + +/* +DecimalMul multiplies two decimals. + + from1, from2 - factors + to - product + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW; + + NOTES + in this implementation, with wordSize=4 we have digitsPerWord=9, + and 63-digit number will take only 7 words (basically a 7-digit + "base 999999999" number). Thus there's no need in fast multiplication + algorithms, 7-digit numbers can be multiplied with a naive O(n*n) + method. + + XXX if this library is to be used with huge numbers of thousands of + digits, fast multiplication must be implemented. +*/ +func DecimalMul(from1, from2, to *MyDecimal) error { + from1, from2, to = validateArgs(from1, from2, to) + var ( + err error + wordsInt1 = digitsToWords(int(from1.digitsInt)) + wordsFrac1 = digitsToWords(int(from1.digitsFrac)) + wordsInt2 = digitsToWords(int(from2.digitsInt)) + wordsFrac2 = digitsToWords(int(from2.digitsFrac)) + wordsIntTo = digitsToWords(int(from1.digitsInt) + int(from2.digitsInt)) + wordsFracTo = wordsFrac1 + wordsFrac2 + idx1 = wordsInt1 + idx2 = wordsInt2 + idxTo int + tmp1 = wordsIntTo + tmp2 = wordsFracTo + ) + to.resultFrac = myMinInt8(from1.resultFrac+from2.resultFrac, MaxDecimalScale) + wordsIntTo, wordsFracTo, err = fixWordCntError(wordsIntTo, wordsFracTo) + to.negative = from1.negative != from2.negative + to.digitsFrac = from1.digitsFrac + from2.digitsFrac + if to.digitsFrac > notFixedDec { + to.digitsFrac = notFixedDec + } + to.digitsInt = int8(wordsIntTo * digitsPerWord) + if err == ErrOverflow { + return err + } + if err != nil { + if to.digitsFrac > int8(wordsFracTo*digitsPerWord) { + to.digitsFrac = int8(wordsFracTo * digitsPerWord) + } + if to.digitsInt > int8(wordsIntTo*digitsPerWord) { + to.digitsInt = int8(wordsIntTo * digitsPerWord) + } + if tmp1 > wordsIntTo { + tmp1 -= wordsIntTo + tmp2 = tmp1 >> 1 + wordsInt2 -= tmp1 - tmp2 + wordsFrac1 = 0 + wordsFrac2 = 0 + } else { + tmp2 -= wordsFracTo + tmp1 = tmp2 >> 1 + if wordsFrac1 <= wordsFrac2 { + wordsFrac1 -= tmp1 + wordsFrac2 -= tmp2 - tmp1 + } else { + wordsFrac2 -= tmp1 + wordsFrac1 -= tmp2 - tmp1 + } + } + } + startTo := wordsIntTo + wordsFracTo - 1 + start2 := idx2 + wordsFrac2 - 1 + stop1 := idx1 - wordsInt1 + stop2 := idx2 - wordsInt2 + to.wordBuf = zeroMyDecimal.wordBuf + + for idx1 += wordsFrac1 - 1; idx1 >= stop1; idx1-- { + carry := int32(0) + idxTo = startTo + idx2 = start2 + for idx2 >= stop2 { + var hi, lo int32 + p := int64(from1.wordBuf[idx1]) * int64(from2.wordBuf[idx2]) + hi = int32(p / wordBase) + lo = int32(p - int64(hi)*wordBase) + to.wordBuf[idxTo], carry = add2(to.wordBuf[idxTo], lo, carry) + carry += hi + idx2-- + idxTo-- + } + if carry > 0 { + if idxTo < 0 { + return ErrOverflow + } + to.wordBuf[idxTo], carry = add2(to.wordBuf[idxTo], 0, carry) + } + for idxTo--; carry > 0; idxTo-- { + if idxTo < 0 { + return ErrOverflow + } + to.wordBuf[idxTo], carry = add(to.wordBuf[idxTo], 0, carry) + } + startTo-- + } + + /* Now we have to check for -0.000 case */ + if to.negative { + idx := 0 + end := wordsIntTo + wordsFracTo + for { + if to.wordBuf[idx] != 0 { + break + } + idx++ + /* We got decimal zero */ + if idx == end { + *to = zeroMyDecimalWithFrac(to.resultFrac) + break + } + } + } + + idxTo = 0 + dToMove := wordsIntTo + digitsToWords(int(to.digitsFrac)) + for to.wordBuf[idxTo] == 0 && to.digitsInt > digitsPerWord { + idxTo++ + to.digitsInt -= digitsPerWord + dToMove-- + } + if idxTo > 0 { + curIdx := 0 + for dToMove > 0 { + to.wordBuf[curIdx] = to.wordBuf[idxTo] + curIdx++ + idxTo++ + dToMove-- + } + } + return err +} + +// DecimalDiv does division of two decimals. +// +// from1 - dividend +// from2 - divisor +// to - quotient +// fracIncr - increment of fraction +func DecimalDiv(from1, from2, to *MyDecimal, fracIncr int) error { + from1, from2, to = validateArgs(from1, from2, to) + to.resultFrac = myMinInt8(from1.resultFrac+int8(fracIncr), MaxDecimalScale) + return doDivMod(from1, from2, to, nil, fracIncr) +} + +/* +DecimalMod does modulus of two decimals. + + from1 - dividend + from2 - divisor + to - modulus + + RETURN VALUE + E_DEC_OK/E_DEC_TRUNCATED/E_DEC_OVERFLOW/E_DEC_DIV_ZERO; + + NOTES + see do_div_mod() + + DESCRIPTION + the modulus R in R = M mod N + + is defined as + + 0 <= |R| < |M| + sign R == sign M + R = M - k*N, where k is integer + + thus, there's no requirement for M or N to be integers +*/ +func DecimalMod(from1, from2, to *MyDecimal) error { + from1, from2, to = validateArgs(from1, from2, to) + to.resultFrac = myMaxInt8(from1.resultFrac, from2.resultFrac) + return doDivMod(from1, from2, nil, to, 0) +} + +func doDivMod(from1, from2, to, mod *MyDecimal, fracIncr int) error { + var ( + frac1 = digitsToWords(int(from1.digitsFrac)) * digitsPerWord + prec1 = int(from1.digitsInt) + frac1 + frac2 = digitsToWords(int(from2.digitsFrac)) * digitsPerWord + prec2 = int(from2.digitsInt) + frac2 + ) + if mod != nil { + to = mod + } + + /* removing all the leading zeros */ + i := ((prec2 - 1) % digitsPerWord) + 1 + idx2 := 0 + for prec2 > 0 && from2.wordBuf[idx2] == 0 { + prec2 -= i + i = digitsPerWord + idx2++ + } + if prec2 <= 0 { + /* short-circuit everything: from2 == 0 */ + return ErrDivByZero + } + + prec2 -= countLeadingZeroes((prec2-1)%digitsPerWord, from2.wordBuf[idx2]) + i = ((prec1 - 1) % digitsPerWord) + 1 + idx1 := 0 + for prec1 > 0 && from1.wordBuf[idx1] == 0 { + prec1 -= i + i = digitsPerWord + idx1++ + } + if prec1 <= 0 { + /* short-circuit everything: from1 == 0 */ + *to = zeroMyDecimalWithFrac(to.resultFrac) + return nil + } + prec1 -= countLeadingZeroes((prec1-1)%digitsPerWord, from1.wordBuf[idx1]) + + /* let's fix fracIncr, taking into account frac1,frac2 increase */ + fracIncr -= frac1 - int(from1.digitsFrac) + frac2 - int(from2.digitsFrac) + if fracIncr < 0 { + fracIncr = 0 + } + + digitsIntTo := (prec1 - frac1) - (prec2 - frac2) + if from1.wordBuf[idx1] >= from2.wordBuf[idx2] { + digitsIntTo++ + } + var wordsIntTo int + if digitsIntTo < 0 { + digitsIntTo /= digitsPerWord + wordsIntTo = 0 + } else { + wordsIntTo = digitsToWords(digitsIntTo) + } + var wordsFracTo int + var err error + if mod != nil { + // we're calculating N1 % N2. + // The result will have + // digitsFrac=max(frac1, frac2), as for subtraction + // digitsInt=from2.digitsInt + to.negative = from1.negative + to.digitsFrac = myMaxInt8(from1.digitsFrac, from2.digitsFrac) + } else { + wordsFracTo = digitsToWords(frac1 + frac2 + fracIncr) + wordsIntTo, wordsFracTo, err = fixWordCntError(wordsIntTo, wordsFracTo) + to.negative = from1.negative != from2.negative + to.digitsInt = int8(wordsIntTo * digitsPerWord) + to.digitsFrac = int8(wordsFracTo * digitsPerWord) + } + idxTo := 0 + stopTo := wordsIntTo + wordsFracTo + if mod == nil { + for digitsIntTo < 0 && idxTo < wordBufLen { + to.wordBuf[idxTo] = 0 + idxTo++ + digitsIntTo++ + } + } + i = digitsToWords(prec1) + len1 := i + digitsToWords(2*frac2+fracIncr+1) + 1 + if len1 < 3 { + len1 = 3 + } + + tmp1 := make([]int32, len1) + copy(tmp1, from1.wordBuf[idx1:idx1+i]) + + start1 := 0 + var stop1 int + start2 := idx2 + stop2 := idx2 + digitsToWords(prec2) - 1 + + /* removing end zeroes */ + for from2.wordBuf[stop2] == 0 && stop2 >= start2 { + stop2-- + } + len2 := stop2 - start2 + stop2++ + + /* + calculating norm2 (normalized from2.wordBuf[start2]) - we need from2.wordBuf[start2] to be large + (at least > DIG_BASE/2), but unlike Knuth's Alg. D we don't want to + normalize input numbers (as we don't make a copy of the divisor). + Thus we normalize first dec1 of buf2 only, and we'll normalize tmp1[start1] + on the fly for the purpose of guesstimation only. + It's also faster, as we're saving on normalization of from2. + */ + normFactor := wordBase / int64(from2.wordBuf[start2]+1) + norm2 := int32(normFactor * int64(from2.wordBuf[start2])) + if len2 > 0 { + norm2 += int32(normFactor * int64(from2.wordBuf[start2+1]) / wordBase) + } + dcarry := int32(0) + if tmp1[start1] < from2.wordBuf[start2] { + dcarry = tmp1[start1] + start1++ + } + + // main loop + var guess int64 + for ; idxTo < stopTo; idxTo++ { + /* short-circuit, if possible */ + if dcarry == 0 && tmp1[start1] < from2.wordBuf[start2] { + guess = 0 + } else { + /* D3: make a guess */ + x := int64(tmp1[start1]) + int64(dcarry)*wordBase + y := int64(tmp1[start1+1]) + guess = (normFactor*x + normFactor*y/wordBase) / int64(norm2) + if guess >= wordBase { + guess = wordBase - 1 + } + + if len2 > 0 { + /* remove normalization */ + if int64(from2.wordBuf[start2+1])*guess > (x-guess*int64(from2.wordBuf[start2]))*wordBase+y { + guess-- + } + if int64(from2.wordBuf[start2+1])*guess > (x-guess*int64(from2.wordBuf[start2]))*wordBase+y { + guess-- + } + } + + /* D4: multiply and subtract */ + idx2 = stop2 + idx1 = start1 + len2 + var carry int32 + for carry = 0; idx2 > start2; idx1-- { + var hi, lo int32 + idx2-- + x = guess * int64(from2.wordBuf[idx2]) + hi = int32(x / wordBase) + lo = int32(x - int64(hi)*wordBase) + tmp1[idx1], carry = sub2(tmp1[idx1], lo, carry) + carry += hi + } + if dcarry < carry { + carry = 1 + } else { + carry = 0 + } + + /* D5: check the remainder */ + if carry > 0 { + /* D6: correct the guess */ + guess-- + idx2 = stop2 + idx1 = start1 + len2 + for carry = 0; idx2 > start2; idx1-- { + idx2-- + tmp1[idx1], carry = add(tmp1[idx1], from2.wordBuf[idx2], carry) + } + } + } + if mod == nil { + to.wordBuf[idxTo] = int32(guess) + } + dcarry = tmp1[start1] + start1++ + } + if mod != nil { + /* + now the result is in tmp1, it has + digitsInt=prec1-frac1 + digitsFrac=max(frac1, frac2) + */ + if dcarry != 0 { + start1-- + tmp1[start1] = dcarry + } + idxTo = 0 + + digitsIntTo = prec1 - frac1 - start1*digitsPerWord + if digitsIntTo < 0 { + /* If leading zeroes in the fractional part were earlier stripped */ + wordsIntTo = digitsIntTo / digitsPerWord + } else { + wordsIntTo = digitsToWords(digitsIntTo) + } + + wordsFracTo = digitsToWords(int(to.digitsFrac)) + err = nil + if wordsIntTo == 0 && wordsFracTo == 0 { + *to = zeroMyDecimal + return err + } + if wordsIntTo <= 0 { + if -wordsIntTo >= wordBufLen { + *to = zeroMyDecimal + return ErrTruncated + } + stop1 = start1 + wordsIntTo + wordsFracTo + wordsFracTo += wordsIntTo + to.digitsInt = 0 + for wordsIntTo < 0 { + to.wordBuf[idxTo] = 0 + idxTo++ + wordsIntTo++ + } + } else { + if wordsIntTo > wordBufLen { + to.digitsInt = int8(digitsPerWord * wordBufLen) + to.digitsFrac = 0 + return ErrOverflow + } + stop1 = start1 + wordsIntTo + wordsFracTo + to.digitsInt = int8(myMin(wordsIntTo*digitsPerWord, int(from2.digitsInt))) + } + if wordsIntTo+wordsFracTo > wordBufLen { + stop1 -= wordsIntTo + wordsFracTo - wordBufLen + wordsFracTo = wordBufLen - wordsIntTo + to.digitsFrac = int8(wordsFracTo * digitsPerWord) + err = ErrTruncated + } + for start1 < stop1 { + to.wordBuf[idxTo] = tmp1[start1] + idxTo++ + start1++ + } + } + idxTo, digitsIntTo = to.removeLeadingZeros() + to.digitsInt = int8(digitsIntTo) + if idxTo != 0 { + copy(to.wordBuf[:], to.wordBuf[idxTo:]) + } + return err +} + +// DecimalPeak returns the length of the encoded decimal. +func DecimalPeak(b []byte) (int, error) { + if len(b) < 3 { + return 0, ErrBadNumber + } + precision := int(b[0]) + frac := int(b[1]) + return DecimalBinSize(precision, frac) + 2, nil +} + +// NewDecFromInt creates a MyDecimal from int. +func NewDecFromInt(i int64) *MyDecimal { + return new(MyDecimal).FromInt(i) +} + +// NewDecFromUint creates a MyDecimal from uint. +func NewDecFromUint(i uint64) *MyDecimal { + return new(MyDecimal).FromUint(i) +} + +// NewDecFromFloatForTest creates a MyDecimal from float, as it returns no error, it should only be used in test. +func NewDecFromFloatForTest(f float64) *MyDecimal { + dec := new(MyDecimal) + err := dec.FromFloat64(f) + Log(err) + return dec +} + +// NewDecFromStringForTest creates a MyDecimal from string, as it returns no error, it should only be used in test. +func NewDecFromStringForTest(s string) *MyDecimal { + dec := new(MyDecimal) + err := dec.FromString([]byte(s)) + Log(err) + return dec +} + +// NewMaxOrMinDec returns the max or min value decimal for given precision and fraction. +func NewMaxOrMinDec(negative bool, prec, frac int) *MyDecimal { + str := make([]byte, prec+2) + for i := 0; i < len(str); i++ { + str[i] = '9' + } + if negative { + str[0] = '-' + } else { + str[0] = '+' + } + str[1+prec-frac] = '.' + dec := new(MyDecimal) + err := dec.FromString(str) + Log(err) + return dec +} -- 2.11.0