Unverified Commit 8079585c authored by 望哥's avatar 望哥 Committed by GitHub

Merge pull request #3 from aliiohs/feature/AddDecimal

Feature/add decimal
parents 65c3ecbb 1e239ee7
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gxbig
import (
"math"
"strconv"
)
import (
"github.com/pkg/errors"
)
var (
ErrBadNumber = errors.Errorf("Bad Number")
ErrOverflow = errors.Errorf("Data Overflow")
ErrTruncated = errors.Errorf("Data Truncated")
ErrDivByZero = errors.Errorf("Division by 0")
)
// RoundMode is the type for round mode.
type RoundMode int32
// 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,
}
zeroBigDecimal = Decimal{}
)
// 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
}
// BigDecimalStructSize is the struct size of Decimal.
const BigDecimalStructSize = 40
// Decimal represents a decimal value.
type Decimal 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
// for hessian
Value string
}
func (Decimal) JavaClassName() string {
return "java.math.BigDecimal"
}
// IsNegative returns whether a decimal is negative.
func (d *Decimal) IsNegative() bool {
return d.negative
}
// GetDigitsFrac returns the digitsFrac.
func (d *Decimal) GetDigitsFrac() int8 {
return d.digitsFrac
}
// String returns the decimal string representation rounded to resultFrac.
func (d *Decimal) String() string {
tmp := *d
_ = tmp.Round(&tmp, int(tmp.resultFrac), ModeHalfEven)
//todo terror.Log(errors.Trace(err))
return string(tmp.ToString())
}
func (d *Decimal) stringSize() int {
// sign, zero integer and dot.
return int(d.digitsInt + d.digitsFrac + 3)
}
func (d *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) FromString(str []byte) error {
for i := 0; i < len(str); i++ {
if !isSpace(str[i]) {
str = str[i:]
break
}
}
if len(str) == 0 {
*d = zeroBigDecimal
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 = zeroBigDecimal
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 = errors.Cause(err1)
if err != ErrTruncated {
*d = zeroBigDecimal
}
}
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 = zeroBigDecimal
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 *Decimal) 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 = zeroBigDecimal
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 errors.Cause(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 = zeroBigDecimal
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 *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) Round(to *Decimal, 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 = zeroBigDecimal
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 = zeroBigDecimal
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 *Decimal) FromInt(val int64) *Decimal {
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 *Decimal) FromUint(val uint64) *Decimal {
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 *Decimal) 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 *Decimal) 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 *Decimal) FromFloat64(f float64) error {
s := strconv.FormatFloat(f, 'g', -1, 64)
return d.FromString([]byte(s))
}
// ToFloat64 converts decimal to float64 value.
func (d *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) 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 *Decimal) FromBin(bin []byte, precision, frac int) (binSize int, err error) {
if len(bin) == 0 {
*d = zeroBigDecimal
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 = zeroBigDecimal
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 = zeroBigDecimal
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 = zeroBigDecimal
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 = zeroBigDecimal
return binSize, ErrBadNumber
}
}
if d.digitsInt == 0 && d.digitsFrac == 0 {
*d = zeroBigDecimal
}
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 *Decimal) Compare(to *Decimal) int {
if d.negative == to.negative {
cmp, _ := doSub(d, to, nil)
//todo terror.Log(errors.Trace(err))
return cmp
}
if d.negative {
return -1
}
return 1
}
// DecimalNeg reverses decimal's sign.
func DecimalNeg(from *Decimal) *Decimal {
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 *Decimal) error {
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 *Decimal) error {
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 doSub(from1, from2, to *Decimal) (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 = zeroBigDecimal
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 *Decimal) 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, dec2 = from1, from2
} else {
idx1 = wordsInt2 - wordsInt1
dec1, dec2 = from2, from1
}
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 *Decimal) {
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 *Decimal) 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 = 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 = zeroBigDecimal.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 = zeroBigDecimal
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 *Decimal, fracIncr int) error {
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 *Decimal) error {
to.resultFrac = myMaxInt8(from1.resultFrac, from2.resultFrac)
return doDivMod(from1, from2, nil, to, 0)
}
func doDivMod(from1, from2, to, mod *Decimal, 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 = zeroBigDecimal
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 = zeroBigDecimal
return err
}
if wordsIntTo <= 0 {
if -wordsIntTo >= wordBufLen {
*to = zeroBigDecimal
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 Decimal from int.
func NewDecFromInt(i int64) *Decimal {
return new(Decimal).FromInt(i)
}
// NewDecFromUint creates a Decimal from uint.
func NewDecFromUint(i uint64) *Decimal {
return new(Decimal).FromUint(i)
}
// NewDecFromFloatForTest creates a Decimal from float, as it returns no error, it should only be used in test.
func NewDecFromFloatForTest(f float64) *Decimal {
dec := new(Decimal)
_ = dec.FromFloat64(f)
//todo terror.Log(errors.Trace(err))
return dec
}
// NewDecFromStringForTest creates a Decimal from string, as it returns no error, it should only be used in test.
func NewDecFromStringForTest(s string) *Decimal {
dec := new(Decimal)
_ = dec.FromString([]byte(s))
//todo terror.Log(errors.Trace(err))
return dec
}
// NewMaxOrMinDec returns the max or min value decimal for given precision and fraction.
func NewMaxOrMinDec(negative bool, prec, frac int) *Decimal {
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(Decimal)
_ = dec.FromString(str)
//todo terror.Log(errors.Trace(err))
return dec
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gxbig
import "testing"
func BenchmarkRound(b *testing.B) {
b.StopTimer()
var roundTo Decimal
tests := []struct {
input string
scale int
inputDec Decimal
}{
{input: "123456789.987654321", scale: 1},
{input: "15.1", scale: 0},
{input: "15.5", scale: 0},
{input: "15.9", scale: 0},
{input: "-15.1", scale: 0},
{input: "-15.5", scale: 0},
{input: "-15.9", scale: 0},
{input: "15.1", scale: 1},
{input: "-15.1", scale: 1},
{input: "15.17", scale: 1},
{input: "15.4", scale: -1},
{input: "-15.4", scale: -1},
{input: "5.4", scale: -1},
{input: ".999", scale: 0},
{input: "999999999", scale: -9},
}
for i := 0; i < len(tests); i++ {
tests[i].inputDec.FromString([]byte(tests[i].input))
}
b.StartTimer()
for n := 0; n < b.N; n++ {
for i := 0; i < len(tests); i++ {
tests[i].inputDec.Round(&roundTo, tests[i].scale, ModeHalfEven)
}
for i := 0; i < len(tests); i++ {
tests[i].inputDec.Round(&roundTo, tests[i].scale, ModeTruncate)
}
for i := 0; i < len(tests); i++ {
tests[i].inputDec.Round(&roundTo, tests[i].scale, modeCeiling)
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gxbig
import (
"strings"
"testing"
)
import (
"github.com/stretchr/testify/assert"
)
func TestFromInt(t *testing.T) {
tests := []struct {
input int64
output string
}{
{-12345, "-12345"},
{-1, "-1"},
{1, "1"},
{-9223372036854775807, "-9223372036854775807"},
{-9223372036854775808, "-9223372036854775808"},
}
for _, tt := range tests {
dec := NewDecFromInt(tt.input)
str := dec.ToString()
assert.Equal(t, string(str), tt.output)
}
}
func TestFromUint(t *testing.T) {
tests := []struct {
input uint64
output string
}{
{12345, "12345"},
{0, "0"},
{18446744073709551615, "18446744073709551615"},
}
for _, tt := range tests {
var dec Decimal
dec.FromUint(tt.input)
str := dec.ToString()
assert.Equal(t, string(str), tt.output)
}
}
func TestToInt(t *testing.T) {
tests := []struct {
input string
output int64
err error
}{
{"18446744073709551615", 9223372036854775807, ErrOverflow},
{"-1", -1, nil},
{"1", 1, nil},
{"-1.23", -1, ErrTruncated},
{"-9223372036854775807", -9223372036854775807, nil},
{"-9223372036854775808", -9223372036854775808, nil},
{"9223372036854775808", 9223372036854775807, ErrOverflow},
{"-9223372036854775809", -9223372036854775808, ErrOverflow},
}
for _, tt := range tests {
var dec Decimal
dec.FromString([]byte(tt.input))
result, ec := dec.ToInt()
assert.Equal(t, ec, tt.err)
assert.Equal(t, result, tt.output)
}
}
func TestToUint(t *testing.T) {
tests := []struct {
input string
output uint64
err error
}{
{"12345", 12345, nil},
{"0", 0, nil},
/* ULLONG_MAX = 18446744073709551615ULL */
{"18446744073709551615", 18446744073709551615, nil},
{"18446744073709551616", 18446744073709551615, ErrOverflow},
{"-1", 0, ErrOverflow},
{"1.23", 1, ErrTruncated},
{"9999999999999999999999999.000", 18446744073709551615, ErrOverflow},
}
for _, tt := range tests {
var dec Decimal
_ = dec.FromString([]byte(tt.input))
result, ec := dec.ToUint()
assert.Equal(t, ec, tt.err)
assert.Equal(t, result, tt.output)
}
}
func TestFromFloat(t *testing.T) {
tests := []struct {
s string
f float64
}{
{"12345", 12345},
{"123.45", 123.45},
{"-123.45", -123.45},
{"0.00012345000098765", 0.00012345000098765},
{"1234500009876.5", 1234500009876.5},
}
for _, tt := range tests {
dec := NewDecFromFloatForTest(tt.f)
str := dec.ToString()
assert.Equal(t, string(str), tt.s)
}
}
func TestToFloat(t *testing.T) {
tests := []struct {
s string
f float64
}{
{"12345", 12345},
{"123.45", 123.45},
{"-123.45", -123.45},
{"0.00012345000098765", 0.00012345000098765},
{"1234500009876.5", 1234500009876.5},
}
for _, ca := range tests {
var dec Decimal
_ = dec.FromString([]byte(ca.s))
f, _ := dec.ToFloat64()
assert.Equal(t, f, ca.f)
}
}
func TestToHashKey(t *testing.T) {
tests := []struct {
numbers []string
}{
{[]string{"1.1", "1.1000", "1.1000000", "1.10000000000", "01.1", "0001.1", "001.1000000"}},
{[]string{"-1.1", "-1.1000", "-1.1000000", "-1.10000000000", "-01.1", "-0001.1", "-001.1000000"}},
{[]string{".1", "0.1", "000000.1", ".10000", "0000.10000", "000000000000000000.1"}},
{[]string{"0", "0000", ".0", ".00000", "00000.00000", "-0", "-0000", "-.0", "-.00000", "-00000.00000"}},
{[]string{".123456789123456789", ".1234567891234567890", ".12345678912345678900", ".123456789123456789000", ".1234567891234567890000", "0.123456789123456789",
".1234567891234567890000000000", "0000000.123456789123456789000"}},
{[]string{"12345", "012345", "0012345", "0000012345", "0000000012345", "00000000000012345", "12345.", "12345.00", "12345.000000000", "000012345.0000"}},
{[]string{"123E5", "12300000", "00123E5", "000000123E5", "12300000.00000000"}},
{[]string{"123E-2", "1.23", "00000001.23", "1.2300000000000000", "000000001.23000000000000"}},
}
for _, ca := range tests {
keys := make([]string, 0, len(ca.numbers))
for _, num := range ca.numbers {
var dec Decimal
assert.Equal(t, dec.FromString([]byte(num)), nil)
key, err := dec.ToHashKey()
assert.Equal(t, err, nil)
keys = append(keys, string(key))
}
for i := 1; i < len(keys); i++ {
assert.Equal(t, keys[0], keys[i])
}
}
binTests := []struct {
hashNumbers []string
binNumbers []string
}{
{[]string{"1.1", "1.1000", "1.1000000", "1.10000000000", "01.1", "0001.1", "001.1000000"},
[]string{"1.1", "0001.1", "01.1"}},
{[]string{"-1.1", "-1.1000", "-1.1000000", "-1.10000000000", "-01.1", "-0001.1", "-001.1000000"},
[]string{"-1.1", "-0001.1", "-01.1"}},
{[]string{".1", "0.1", "000000.1", ".10000", "0000.10000", "000000000000000000.1"},
[]string{".1", "0.1", "000000.1", "00.1"}},
{[]string{"0", "0000", ".0", ".00000", "00000.00000", "-0", "-0000", "-.0", "-.00000", "-00000.00000"},
[]string{"0", "0000", "00", "-0", "-00", "-000000"}},
{[]string{".123456789123456789", ".1234567891234567890", ".12345678912345678900", ".123456789123456789000", ".1234567891234567890000", "0.123456789123456789",
".1234567891234567890000000000", "0000000.123456789123456789000"},
[]string{".123456789123456789", "0.123456789123456789", "0000.123456789123456789", "0000000.123456789123456789"}},
{[]string{"12345", "012345", "0012345", "0000012345", "0000000012345", "00000000000012345", "12345.", "12345.00", "12345.000000000", "000012345.0000"},
[]string{"12345", "012345", "000012345", "000000000000012345"}},
{[]string{"123E5", "12300000", "00123E5", "000000123E5", "12300000.00000000"},
[]string{"12300000", "123E5", "00123E5", "0000000000123E5"}},
{[]string{"123E-2", "1.23", "00000001.23", "1.2300000000000000", "000000001.23000000000000"},
[]string{"123E-2", "1.23", "000001.23", "0000000000001.23"}},
}
for _, ca := range binTests {
keys := make([]string, 0, len(ca.hashNumbers)+len(ca.binNumbers))
for _, num := range ca.hashNumbers {
var dec Decimal
assert.Equal(t, dec.FromString([]byte(num)), nil)
key, err := dec.ToHashKey()
assert.Equal(t, err, nil)
keys = append(keys, string(key))
}
for _, num := range ca.binNumbers {
var dec Decimal
assert.Equal(t, dec.FromString([]byte(num)), nil)
prec, frac := dec.PrecisionAndFrac() // remove leading zeros but trailing zeros remain
key, err := dec.ToBin(prec, frac)
assert.Equal(t, err, nil)
keys = append(keys, string(key))
}
for i := 1; i < len(keys); i++ {
assert.Equal(t, keys[0], keys[i])
}
}
}
func TestRemoveTrailingZeros(t *testing.T) {
tests := []string{
"0", "0.0", ".0", ".00000000", "0.0000", "0000", "0000.0", "0000.000",
"-0", "-0.0", "-.0", "-.00000000", "-0.0000", "-0000", "-0000.0", "-0000.000",
"123123123", "213123.", "21312.000", "21321.123", "213.1230000", "213123.000123000",
"-123123123", "-213123.", "-21312.000", "-21321.123", "-213.1230000", "-213123.000123000",
"123E5", "12300E-5", "0.00100E1", "0.001230E-3",
"123987654321.123456789000", "000000000123", "123456789.987654321", "999.999000",
}
for _, ca := range tests {
var dec Decimal
assert.Equal(t, dec.FromString([]byte(ca)), nil)
// calculate the number of digits after point but trailing zero
digitsFracExp := 0
str := string(dec.ToString())
point := strings.Index(str, ".")
if point != -1 {
pos := len(str) - 1
for pos > point {
if str[pos] != '0' {
break
}
pos--
}
digitsFracExp = pos - point
}
_, digitsFrac := dec.removeTrailingZeros()
assert.Equal(t, digitsFrac, digitsFracExp)
}
}
func TestShift(t *testing.T) {
type tcase struct {
input string
shift int
output string
err error
}
var dotest = func(t *testing.T, tests []tcase) {
for _, ca := range tests {
var dec Decimal
err := dec.FromString([]byte(ca.input))
//assert.Equal(t, err, IsNil)
//origin := dec
err = dec.Shift(ca.shift)
assert.Equal(t, err, ca.err)
result := dec.ToString()
//, Commentf("origin:%s\ndec:%s", origin.String(), string(result))
assert.Equal(t, string(result), ca.output)
}
}
wordBufLen = maxWordBufLen
tests := []tcase{
{"123.123", 1, "1231.23", nil},
{"123457189.123123456789000", 1, "1234571891.23123456789", nil},
{"123457189.123123456789000", 8, "12345718912312345.6789", nil},
{"123457189.123123456789000", 9, "123457189123123456.789", nil},
{"123457189.123123456789000", 10, "1234571891231234567.89", nil},
{"123457189.123123456789000", 17, "12345718912312345678900000", nil},
{"123457189.123123456789000", 18, "123457189123123456789000000", nil},
{"123457189.123123456789000", 19, "1234571891231234567890000000", nil},
{"123457189.123123456789000", 26, "12345718912312345678900000000000000", nil},
{"123457189.123123456789000", 27, "123457189123123456789000000000000000", nil},
{"123457189.123123456789000", 28, "1234571891231234567890000000000000000", nil},
{"000000000000000000000000123457189.123123456789000", 26, "12345718912312345678900000000000000", nil},
{"00000000123457189.123123456789000", 27, "123457189123123456789000000000000000", nil},
{"00000000000000000123457189.123123456789000", 28, "1234571891231234567890000000000000000", nil},
{"123", 1, "1230", nil},
{"123", 10, "1230000000000", nil},
{".123", 1, "1.23", nil},
{".123", 10, "1230000000", nil},
{".123", 14, "12300000000000", nil},
{"000.000", 1000, "0", nil},
{"000.", 1000, "0", nil},
{".000", 1000, "0", nil},
{"1", 1000, "1", ErrOverflow},
{"123.123", -1, "12.3123", nil},
{"123987654321.123456789000", -1, "12398765432.1123456789", nil},
{"123987654321.123456789000", -2, "1239876543.21123456789", nil},
{"123987654321.123456789000", -3, "123987654.321123456789", nil},
{"123987654321.123456789000", -8, "1239.87654321123456789", nil},
{"123987654321.123456789000", -9, "123.987654321123456789", nil},
{"123987654321.123456789000", -10, "12.3987654321123456789", nil},
{"123987654321.123456789000", -11, "1.23987654321123456789", nil},
{"123987654321.123456789000", -12, "0.123987654321123456789", nil},
{"123987654321.123456789000", -13, "0.0123987654321123456789", nil},
{"123987654321.123456789000", -14, "0.00123987654321123456789", nil},
{"00000087654321.123456789000", -14, "0.00000087654321123456789", nil},
}
dotest(t, tests)
wordBufLen = 2
tests = []tcase{
{"123.123", -2, "1.23123", nil},
{"123.123", -3, "0.123123", nil},
{"123.123", -6, "0.000123123", nil},
{"123.123", -7, "0.0000123123", nil},
{"123.123", -15, "0.000000000000123123", nil},
{"123.123", -16, "0.000000000000012312", ErrTruncated},
{"123.123", -17, "0.000000000000001231", ErrTruncated},
{"123.123", -18, "0.000000000000000123", ErrTruncated},
{"123.123", -19, "0.000000000000000012", ErrTruncated},
{"123.123", -20, "0.000000000000000001", ErrTruncated},
{"123.123", -21, "0", ErrTruncated},
{".000000000123", -1, "0.0000000000123", nil},
{".000000000123", -6, "0.000000000000000123", nil},
{".000000000123", -7, "0.000000000000000012", ErrTruncated},
{".000000000123", -8, "0.000000000000000001", ErrTruncated},
{".000000000123", -9, "0", ErrTruncated},
{".000000000123", 1, "0.00000000123", nil},
{".000000000123", 8, "0.0123", nil},
{".000000000123", 9, "0.123", nil},
{".000000000123", 10, "1.23", nil},
{".000000000123", 17, "12300000", nil},
{".000000000123", 18, "123000000", nil},
{".000000000123", 19, "1230000000", nil},
{".000000000123", 20, "12300000000", nil},
{".000000000123", 21, "123000000000", nil},
{".000000000123", 22, "1230000000000", nil},
{".000000000123", 23, "12300000000000", nil},
{".000000000123", 24, "123000000000000", nil},
{".000000000123", 25, "1230000000000000", nil},
{".000000000123", 26, "12300000000000000", nil},
{".000000000123", 27, "123000000000000000", nil},
{".000000000123", 28, "0.000000000123", ErrOverflow},
{"123456789.987654321", -1, "12345678.998765432", ErrTruncated},
{"123456789.987654321", -2, "1234567.899876543", ErrTruncated},
{"123456789.987654321", -8, "1.234567900", ErrTruncated},
{"123456789.987654321", -9, "0.123456789987654321", nil},
{"123456789.987654321", -10, "0.012345678998765432", ErrTruncated},
{"123456789.987654321", -17, "0.000000001234567900", ErrTruncated},
{"123456789.987654321", -18, "0.000000000123456790", ErrTruncated},
{"123456789.987654321", -19, "0.000000000012345679", ErrTruncated},
{"123456789.987654321", -26, "0.000000000000000001", ErrTruncated},
{"123456789.987654321", -27, "0", ErrTruncated},
{"123456789.987654321", 1, "1234567900", ErrTruncated},
{"123456789.987654321", 2, "12345678999", ErrTruncated},
{"123456789.987654321", 4, "1234567899877", ErrTruncated},
{"123456789.987654321", 8, "12345678998765432", ErrTruncated},
{"123456789.987654321", 9, "123456789987654321", nil},
{"123456789.987654321", 10, "123456789.987654321", ErrOverflow},
{"123456789.987654321", 0, "123456789.987654321", nil},
}
dotest(t, tests)
wordBufLen = maxWordBufLen
}
func TestRoundWithHalfEven(t *testing.T) {
tests := []struct {
input string
scale int
output string
err error
}{
{"123456789.987654321", 1, "123456790.0", nil},
{"15.1", 0, "15", nil},
{"15.5", 0, "16", nil},
{"15.9", 0, "16", nil},
{"-15.1", 0, "-15", nil},
{"-15.5", 0, "-16", nil},
{"-15.9", 0, "-16", nil},
{"15.1", 1, "15.1", nil},
{"-15.1", 1, "-15.1", nil},
{"15.17", 1, "15.2", nil},
{"15.4", -1, "20", nil},
{"-15.4", -1, "-20", nil},
{"5.4", -1, "10", nil},
{".999", 0, "1", nil},
{"999999999", -9, "1000000000", nil},
}
for _, ca := range tests {
var dec Decimal
dec.FromString([]byte(ca.input))
var rounded Decimal
err := dec.Round(&rounded, ca.scale, ModeHalfEven)
assert.Equal(t, err, ca.err)
result := rounded.ToString()
assert.Equal(t, string(result), ca.output)
}
}
func TestRoundWithTruncate(t *testing.T) {
tests := []struct {
input string
scale int
output string
err error
}{
{"123456789.987654321", 1, "123456789.9", nil},
{"15.1", 0, "15", nil},
{"15.5", 0, "15", nil},
{"15.9", 0, "15", nil},
{"-15.1", 0, "-15", nil},
{"-15.5", 0, "-15", nil},
{"-15.9", 0, "-15", nil},
{"15.1", 1, "15.1", nil},
{"-15.1", 1, "-15.1", nil},
{"15.17", 1, "15.1", nil},
{"15.4", -1, "10", nil},
{"-15.4", -1, "-10", nil},
{"5.4", -1, "0", nil},
{".999", 0, "0", nil},
{"999999999", -9, "0", nil},
}
for _, ca := range tests {
var dec Decimal
dec.FromString([]byte(ca.input))
var rounded Decimal
err := dec.Round(&rounded, ca.scale, ModeTruncate)
assert.Equal(t, err, ca.err)
result := rounded.ToString()
assert.Equal(t, string(result), ca.output)
}
}
func TestRoundWithCeil(t *testing.T) {
tests := []struct {
input string
scale int
output string
err error
}{
{"123456789.987654321", 1, "123456790.0", nil},
{"15.1", 0, "16", nil},
{"15.5", 0, "16", nil},
{"15.9", 0, "16", nil},
//TODO:fix me
{"-15.1", 0, "-16", nil},
{"-15.5", 0, "-16", nil},
{"-15.9", 0, "-16", nil},
{"15.1", 1, "15.1", nil},
{"-15.1", 1, "-15.1", nil},
{"15.17", 1, "15.2", nil},
{"15.4", -1, "20", nil},
{"-15.4", -1, "-20", nil},
{"5.4", -1, "10", nil},
{".999", 0, "1", nil},
{"999999999", -9, "1000000000", nil},
}
for _, ca := range tests {
var dec Decimal
dec.FromString([]byte(ca.input))
var rounded Decimal
err := dec.Round(&rounded, ca.scale, modeCeiling)
assert.Equal(t, err, ca.err)
result := rounded.ToString()
assert.Equal(t, string(result), ca.output)
}
}
func TestFromString(t *testing.T) {
type tcase struct {
input string
output string
err error
}
tests := []tcase{
{"1e18446744073709551620", "0", ErrBadNumber},
{"1e", "1", ErrTruncated},
{"1e001", "10", nil},
{"1e00", "1", nil},
{"1eabc", "1", ErrTruncated},
{"1e 1dddd ", "10", ErrTruncated},
{"1e - 1", "1", ErrTruncated},
{"1e -1", "0.1", nil},
}
for _, ca := range tests {
var dec Decimal
err := dec.FromString([]byte(ca.input))
if err != nil {
assert.Equal(t, err, ca.err)
}
result := string(dec.ToString())
assert.Equal(t, result, ca.output)
}
wordBufLen = 1
tests = []tcase{
{"123450000098765", "98765", ErrOverflow},
{"123450.000098765", "123450", ErrTruncated},
}
for _, ca := range tests {
var dec Decimal
err := dec.FromString([]byte(ca.input))
assert.Equal(t, err, ca.err)
result := string(dec.ToString())
assert.Equal(t, result, ca.output)
}
wordBufLen = maxWordBufLen
}
func TestToString(t *testing.T) {
type tcase struct {
input string
output string
}
tests := []tcase{
{"123.123", "123.123"},
{"123.1230", "123.1230"},
{"00123.123", "123.123"},
}
for _, ca := range tests {
var dec Decimal
_ = dec.FromString([]byte(ca.input))
result := dec.ToString()
assert.Equal(t, string(result), ca.output)
}
}
func TestToBinFromBin(t *testing.T) {
type tcase struct {
input string
precision int
frac int
output string
err error
}
tests := []tcase{
{"-10.55", 4, 2, "-10.55", nil},
{"0.0123456789012345678912345", 30, 25, "0.0123456789012345678912345", nil},
{"12345", 5, 0, "12345", nil},
{"12345", 10, 3, "12345.000", nil},
{"123.45", 10, 3, "123.450", nil},
{"-123.45", 20, 10, "-123.4500000000", nil},
{".00012345000098765", 15, 14, "0.00012345000098", ErrTruncated},
{".00012345000098765", 22, 20, "0.00012345000098765000", nil},
{".12345000098765", 30, 20, "0.12345000098765000000", nil},
{"-.000000012345000098765", 30, 20, "-0.00000001234500009876", ErrTruncated},
{"1234500009876.5", 30, 5, "1234500009876.50000", nil},
{"111111111.11", 10, 2, "11111111.11", ErrOverflow},
{"000000000.01", 7, 3, "0.010", nil},
{"123.4", 10, 2, "123.40", nil},
{"1000", 3, 0, "0", ErrOverflow},
}
for _, ca := range tests {
var dec Decimal
err := dec.FromString([]byte(ca.input))
assert.Equal(t, err, nil)
buf, err := dec.ToBin(ca.precision, ca.frac)
assert.Equal(t, err, ca.err)
var dec2 Decimal
_, err = dec2.FromBin(buf, ca.precision, ca.frac)
assert.Equal(t, err, nil)
str := dec2.ToString()
assert.Equal(t, string(str), ca.output)
}
var dec Decimal
dec.FromInt(1)
errTests := []struct {
prec int
frac int
}{
{82, 1},
{-1, 1},
{10, 31},
{10, -1},
}
for _, tt := range errTests {
_, _ = dec.ToBin(tt.prec, tt.frac)
//assert.Equal(t,ErrBadNumber.Equal(err), IsTrue)
}
}
func TestCompare(t *testing.T) {
type tcase struct {
a string
b string
cmp int
}
tests := []tcase{
{"12", "13", -1},
{"13", "12", 1},
{"-10", "10", -1},
{"10", "-10", 1},
{"-12", "-13", 1},
{"0", "12", -1},
{"-10", "0", -1},
{"4", "4", 0},
{"-1.1", "-1.2", 1},
{"1.2", "1.1", 1},
{"1.1", "1.2", -1},
}
for _, tt := range tests {
var a, b Decimal
_ = a.FromString([]byte(tt.a))
_ = b.FromString([]byte(tt.b))
assert.Equal(t, a.Compare(&b), tt.cmp)
}
}
func TestMaxDecimal(t *testing.T) {
type tcase struct {
prec int
frac int
result string
}
tests := []tcase{
{1, 1, "0.9"},
{1, 0, "9"},
{2, 1, "9.9"},
{4, 2, "99.99"},
{6, 3, "999.999"},
{8, 4, "9999.9999"},
{10, 5, "99999.99999"},
{12, 6, "999999.999999"},
{14, 7, "9999999.9999999"},
{16, 8, "99999999.99999999"},
{18, 9, "999999999.999999999"},
{20, 10, "9999999999.9999999999"},
{20, 20, "0.99999999999999999999"},
{20, 0, "99999999999999999999"},
{40, 20, "99999999999999999999.99999999999999999999"},
}
for _, tt := range tests {
var dec Decimal
maxDecimal(tt.prec, tt.frac, &dec)
str := dec.ToString()
assert.Equal(t, string(str), tt.result)
}
}
func TestNeg(t *testing.T) {
type testCase struct {
a string
result string
err error
}
tests := []testCase{
{"-0.0000000000000000000000000000000000000000000000000017382578996420603", "0.0000000000000000000000000000000000000000000000000017382578996420603", nil},
{"-13890436710184412000000000000000000000000000000000000000000000000000000000000", "13890436710184412000000000000000000000000000000000000000000000000000000000000", nil},
{"0", "0", nil},
}
for _, tt := range tests {
a := NewDecFromStringForTest(tt.a)
negResult := DecimalNeg(a)
result := negResult.ToString()
assert.Equal(t, string(result), tt.result)
}
}
func TestAdd(t *testing.T) {
type testCase struct {
a string
b string
result string
err error
}
tests := []testCase{
{".00012345000098765", "123.45", "123.45012345000098765", nil},
{".1", ".45", "0.55", nil},
{"1234500009876.5", ".00012345000098765", "1234500009876.50012345000098765", nil},
{"9999909999999.5", ".555", "9999910000000.055", nil},
{"99999999", "1", "100000000", nil},
{"989999999", "1", "990000000", nil},
{"999999999", "1", "1000000000", nil},
{"12345", "123.45", "12468.45", nil},
{"-12345", "-123.45", "-12468.45", nil},
{"-12345", "123.45", "-12221.55", nil},
{"12345", "-123.45", "12221.55", nil},
{"123.45", "-12345", "-12221.55", nil},
{"-123.45", "12345", "12221.55", nil},
{"5", "-6.0", "-1.0", nil},
{"2" + strings.Repeat("1", 71), strings.Repeat("8", 81), "8888888890" + strings.Repeat("9", 71), nil},
}
for _, tt := range tests {
a := NewDecFromStringForTest(tt.a)
b := NewDecFromStringForTest(tt.b)
var sum Decimal
err := DecimalAdd(a, b, &sum)
assert.Equal(t, err, tt.err)
result := sum.ToString()
assert.Equal(t, string(result), tt.result)
}
}
func TestSub(t *testing.T) {
type tcase struct {
a string
b string
result string
err error
}
tests := []tcase{
{".00012345000098765", "123.45", "-123.44987654999901235", nil},
{"1234500009876.5", ".00012345000098765", "1234500009876.49987654999901235", nil},
{"9999900000000.5", ".555", "9999899999999.945", nil},
{"1111.5551", "1111.555", "0.0001", nil},
{".555", ".555", "0", nil},
{"10000000", "1", "9999999", nil},
{"1000001000", ".1", "1000000999.9", nil},
{"1000000000", ".1", "999999999.9", nil},
{"12345", "123.45", "12221.55", nil},
{"-12345", "-123.45", "-12221.55", nil},
{"123.45", "12345", "-12221.55", nil},
{"-123.45", "-12345", "12221.55", nil},
{"-12345", "123.45", "-12468.45", nil},
{"12345", "-123.45", "12468.45", nil},
}
for _, tt := range tests {
var a, b, sum Decimal
a.FromString([]byte(tt.a))
b.FromString([]byte(tt.b))
err := DecimalSub(&a, &b, &sum)
assert.Equal(t, err, tt.err)
result := sum.ToString()
assert.Equal(t, string(result), tt.result)
}
}
func TestMul(t *testing.T) {
type tcase struct {
a string
b string
result string
err error
}
tests := []tcase{
{"12", "10", "120", nil},
{"-123.456", "98765.4321", "-12193185.1853376", nil},
{"-123456000000", "98765432100000", "-12193185185337600000000000", nil},
{"123456", "987654321", "121931851853376", nil},
{"123456", "9876543210", "1219318518533760", nil},
{"123", "0.01", "1.23", nil},
{"123", "0", "0", nil},
{"-0.0000000000000000000000000000000000000000000000000017382578996420603", "-13890436710184412000000000000000000000000000000000000000000000000000000000000", "0.000000000000000000000000000000", ErrTruncated},
{"1" + strings.Repeat("0", 60), "1" + strings.Repeat("0", 60), "0", ErrOverflow},
{"0.5999991229316", "0.918755041726043", "0.5512522192246113614062276588", nil},
{"0.5999991229317", "0.918755041726042", "0.5512522192247026369112773314", nil},
}
for _, tt := range tests {
var a, b, product Decimal
a.FromString([]byte(tt.a))
b.FromString([]byte(tt.b))
err := DecimalMul(&a, &b, &product)
assert.Equal(t, err, tt.err)
result := product.String()
assert.Equal(t, result, tt.result)
}
}
func TestDivMod(t *testing.T) {
type tcase struct {
a string
b string
result string
err error
}
tests := []tcase{
{"120", "10", "12.000000000", nil},
{"123", "0.01", "12300.000000000", nil},
{"120", "100000000000.00000", "0.000000001200000000", nil},
{"123", "0", "", ErrDivByZero},
{"0", "0", "", ErrDivByZero},
{"-12193185.1853376", "98765.4321", "-123.456000000000000000", nil},
{"121931851853376", "987654321", "123456.000000000", nil},
{"0", "987", "0", nil},
{"1", "3", "0.333333333", nil},
{"1.000000000000", "3", "0.333333333333333333", nil},
{"1", "1", "1.000000000", nil},
{"0.0123456789012345678912345", "9999999999", "0.000000000001234567890246913578148141", nil},
{"10.333000000", "12.34500", "0.837019036046982584042122316", nil},
{"10.000000000060", "2", "5.000000000030000000", nil},
{"51", "0.003430", "14868.804664723032069970", nil},
}
for _, tt := range tests {
var a, b, to Decimal
a.FromString([]byte(tt.a))
b.FromString([]byte(tt.b))
err := doDivMod(&a, &b, &to, nil, 5)
assert.Equal(t, err, tt.err)
if tt.err == ErrDivByZero {
continue
}
result := to.ToString()
assert.Equal(t, string(result), tt.result)
}
tests = []tcase{
{"234", "10", "4", nil},
{"234.567", "10.555", "2.357", nil},
{"-234.567", "10.555", "-2.357", nil},
{"234.567", "-10.555", "2.357", nil},
{"99999999999999999999999999999999999999", "3", "0", nil},
{"51", "0.003430", "0.002760", nil},
{"0.0000000001", "1.0", "0.0000000001", nil},
}
for _, tt := range tests {
var a, b, to Decimal
a.FromString([]byte(tt.a))
b.FromString([]byte(tt.b))
ec := doDivMod(&a, &b, nil, &to, 0)
assert.Equal(t, ec, tt.err)
if tt.err == ErrDivByZero {
continue
}
result := to.ToString()
assert.Equal(t, string(result), tt.result)
}
tests = []tcase{
{"1", "1", "1.0000", nil},
{"1.00", "1", "1.000000", nil},
{"1", "1.000", "1.0000", nil},
{"2", "3", "0.6667", nil},
{"51", "0.003430", "14868.8047", nil},
}
for _, tt := range tests {
var a, b, to Decimal
a.FromString([]byte(tt.a))
b.FromString([]byte(tt.b))
ec := DecimalDiv(&a, &b, &to, DivFracIncr)
assert.Equal(t, ec, tt.err)
if tt.err == ErrDivByZero {
continue
}
assert.Equal(t, to.String(), tt.result)
}
tests = []tcase{
{"1", "2.0", "1.0", nil},
{"1.0", "2", "1.0", nil},
{"2.23", "3", "2.23", nil},
{"51", "0.003430", "0.002760", nil},
}
for _, tt := range tests {
var a, b, to Decimal
_ = a.FromString([]byte(tt.a))
_ = b.FromString([]byte(tt.b))
ec := DecimalMod(&a, &b, &to)
assert.Equal(t, ec, tt.err)
if tt.err == ErrDivByZero {
continue
}
assert.Equal(t, to.String(), tt.result)
}
}
func TestMaxOrMin(t *testing.T) {
type tcase struct {
neg bool
prec int
frac int
result string
}
tests := []tcase{
{true, 2, 1, "-9.9"},
{false, 1, 1, "0.9"},
{true, 1, 0, "-9"},
{false, 0, 0, "0"},
{false, 4, 2, "99.99"},
}
for _, tt := range tests {
dec := NewMaxOrMinDec(tt.neg, tt.prec, tt.frac)
assert.Equal(t, dec.String(), tt.result)
}
}
func benchmarkBigDecimalToBinOrHashCases() []string {
return []string{
"1.000000000000", "3", "12.000000000", "120",
"120000", "100000000000.00000", "0.000000001200000000",
"98765.4321", "-123.456000000000000000",
"0", "0000000000", "0.00000000000",
}
}
func BenchmarkBigDecimalToBin(b *testing.B) {
cases := benchmarkBigDecimalToBinOrHashCases()
decs := make([]*Decimal, 0, len(cases))
for _, ca := range cases {
var dec Decimal
if err := dec.FromString([]byte(ca)); err != nil {
b.Fatal(err)
}
decs = append(decs, &dec)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, dec := range decs {
prec, frac := dec.PrecisionAndFrac()
_, err := dec.ToBin(prec, frac)
if err != nil {
b.Fatal(err)
}
}
}
}
func BenchmarkBigDecimalToHashKey(b *testing.B) {
cases := benchmarkBigDecimalToBinOrHashCases()
decs := make([]*Decimal, 0, len(cases))
for _, ca := range cases {
var dec Decimal
if err := dec.FromString([]byte(ca)); err != nil {
b.Fatal(err)
}
decs = append(decs, &dec)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, dec := range decs {
_, err := dec.ToHashKey()
if err != nil {
b.Fatal(err)
}
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package gxbig
import (
"math"
"strings"
"unicode"
)
// 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
}
// GetMaxFloat gets the max float for given flen and decimal.
func GetMaxFloat(flen int, decimal int) float64 {
intPartLen := flen - decimal
f := math.Pow10(intPartLen)
f -= math.Pow10(-decimal)
return f
}
// TruncateFloat tries to truncate f.
// If the result exceeds the max/min float that flen/decimal allowed, returns the max/min float allowed.
func TruncateFloat(f float64, flen int, decimal int) (float64, error) {
if math.IsNaN(f) {
// nan returns 0
//todo ErrOverflow.GenWithStackByArgs("DOUBLE", "")
return 0, nil
}
maxF := GetMaxFloat(flen, decimal)
if !math.IsInf(f, 0) {
f = Round(f, decimal)
}
var err error
if f > maxF {
f = maxF
//err = ErrOverflow.GenWithStackByArgs("DOUBLE", "")
} else if f < -maxF {
f = -maxF
// err = ErrOverflow.GenWithStackByArgs("DOUBLE", "")
}
//todo errors.Trace(err)
return f, err
}
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
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment