summaryrefslogtreecommitdiff
path: root/vendor/gopkg.in/inf.v0/rounder.go
blob: 3a97ef529b97e644c21d479f15d76e19deaebb20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package inf

import (
	"math/big"
)

// Rounder represents a method for rounding the (possibly infinite decimal)
// result of a division to a finite Dec. It is used by Dec.Round() and
// Dec.Quo().
//
// See the Example for results of using each Rounder with some sample values.
//
type Rounder rounder

// See http://speleotrove.com/decimal/damodel.html#refround for more detailed
// definitions of these rounding modes.
var (
	RoundDown     Rounder // towards 0
	RoundUp       Rounder // away from 0
	RoundFloor    Rounder // towards -infinity
	RoundCeil     Rounder // towards +infinity
	RoundHalfDown Rounder // to nearest; towards 0 if same distance
	RoundHalfUp   Rounder // to nearest; away from 0 if same distance
	RoundHalfEven Rounder // to nearest; even last digit if same distance
)

// RoundExact is to be used in the case when rounding is not necessary.
// When used with Quo or Round, it returns the result verbatim when it can be
// expressed exactly with the given precision, and it returns nil otherwise.
// QuoExact is a shorthand for using Quo with RoundExact.
var RoundExact Rounder

type rounder interface {

	// When UseRemainder() returns true, the Round() method is passed the
	// remainder of the division, expressed as the numerator and denominator of
	// a rational.
	UseRemainder() bool

	// Round sets the rounded value of a quotient to z, and returns z.
	// quo is rounded down (truncated towards zero) to the scale obtained from
	// the Scaler in Quo().
	//
	// When the remainder is not used, remNum and remDen are nil.
	// When used, the remainder is normalized between -1 and 1; that is:
	//
	//  -|remDen| < remNum < |remDen|
	//
	// remDen has the same sign as y, and remNum is zero or has the same sign
	// as x.
	Round(z, quo *Dec, remNum, remDen *big.Int) *Dec
}

type rndr struct {
	useRem bool
	round  func(z, quo *Dec, remNum, remDen *big.Int) *Dec
}

func (r rndr) UseRemainder() bool {
	return r.useRem
}

func (r rndr) Round(z, quo *Dec, remNum, remDen *big.Int) *Dec {
	return r.round(z, quo, remNum, remDen)
}

var intSign = []*big.Int{big.NewInt(-1), big.NewInt(0), big.NewInt(1)}

func roundHalf(f func(c int, odd uint) (roundUp bool)) func(z, q *Dec, rA, rB *big.Int) *Dec {
	return func(z, q *Dec, rA, rB *big.Int) *Dec {
		z.Set(q)
		brA, brB := rA.BitLen(), rB.BitLen()
		if brA < brB-1 {
			// brA < brB-1 => |rA| < |rB/2|
			return z
		}
		roundUp := false
		srA, srB := rA.Sign(), rB.Sign()
		s := srA * srB
		if brA == brB-1 {
			rA2 := new(big.Int).Lsh(rA, 1)
			if s < 0 {
				rA2.Neg(rA2)
			}
			roundUp = f(rA2.Cmp(rB)*srB, z.UnscaledBig().Bit(0))
		} else {
			// brA > brB-1 => |rA| > |rB/2|
			roundUp = true
		}
		if roundUp {
			z.UnscaledBig().Add(z.UnscaledBig(), intSign[s+1])
		}
		return z
	}
}

func init() {
	RoundExact = rndr{true,
		func(z, q *Dec, rA, rB *big.Int) *Dec {
			if rA.Sign() != 0 {
				return nil
			}
			return z.Set(q)
		}}
	RoundDown = rndr{false,
		func(z, q *Dec, rA, rB *big.Int) *Dec {
			return z.Set(q)
		}}
	RoundUp = rndr{true,
		func(z, q *Dec, rA, rB *big.Int) *Dec {
			z.Set(q)
			if rA.Sign() != 0 {
				z.UnscaledBig().Add(z.UnscaledBig(), intSign[rA.Sign()*rB.Sign()+1])
			}
			return z
		}}
	RoundFloor = rndr{true,
		func(z, q *Dec, rA, rB *big.Int) *Dec {
			z.Set(q)
			if rA.Sign()*rB.Sign() < 0 {
				z.UnscaledBig().Add(z.UnscaledBig(), intSign[0])
			}
			return z
		}}
	RoundCeil = rndr{true,
		func(z, q *Dec, rA, rB *big.Int) *Dec {
			z.Set(q)
			if rA.Sign()*rB.Sign() > 0 {
				z.UnscaledBig().Add(z.UnscaledBig(), intSign[2])
			}
			return z
		}}
	RoundHalfDown = rndr{true, roundHalf(
		func(c int, odd uint) bool {
			return c > 0
		})}
	RoundHalfUp = rndr{true, roundHalf(
		func(c int, odd uint) bool {
			return c >= 0
		})}
	RoundHalfEven = rndr{true, roundHalf(
		func(c int, odd uint) bool {
			return c > 0 || c == 0 && odd == 1
		})}
}