blob: 3a97ef529b97e644c21d479f15d76e19deaebb20 [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001package inf
2
3import (
4 "math/big"
5)
6
7// Rounder represents a method for rounding the (possibly infinite decimal)
8// result of a division to a finite Dec. It is used by Dec.Round() and
9// Dec.Quo().
10//
11// See the Example for results of using each Rounder with some sample values.
12//
13type Rounder rounder
14
15// See http://speleotrove.com/decimal/damodel.html#refround for more detailed
16// definitions of these rounding modes.
17var (
18 RoundDown Rounder // towards 0
19 RoundUp Rounder // away from 0
20 RoundFloor Rounder // towards -infinity
21 RoundCeil Rounder // towards +infinity
22 RoundHalfDown Rounder // to nearest; towards 0 if same distance
23 RoundHalfUp Rounder // to nearest; away from 0 if same distance
24 RoundHalfEven Rounder // to nearest; even last digit if same distance
25)
26
27// RoundExact is to be used in the case when rounding is not necessary.
28// When used with Quo or Round, it returns the result verbatim when it can be
29// expressed exactly with the given precision, and it returns nil otherwise.
30// QuoExact is a shorthand for using Quo with RoundExact.
31var RoundExact Rounder
32
33type rounder interface {
34
35 // When UseRemainder() returns true, the Round() method is passed the
36 // remainder of the division, expressed as the numerator and denominator of
37 // a rational.
38 UseRemainder() bool
39
40 // Round sets the rounded value of a quotient to z, and returns z.
41 // quo is rounded down (truncated towards zero) to the scale obtained from
42 // the Scaler in Quo().
43 //
44 // When the remainder is not used, remNum and remDen are nil.
45 // When used, the remainder is normalized between -1 and 1; that is:
46 //
47 // -|remDen| < remNum < |remDen|
48 //
49 // remDen has the same sign as y, and remNum is zero or has the same sign
50 // as x.
51 Round(z, quo *Dec, remNum, remDen *big.Int) *Dec
52}
53
54type rndr struct {
55 useRem bool
56 round func(z, quo *Dec, remNum, remDen *big.Int) *Dec
57}
58
59func (r rndr) UseRemainder() bool {
60 return r.useRem
61}
62
63func (r rndr) Round(z, quo *Dec, remNum, remDen *big.Int) *Dec {
64 return r.round(z, quo, remNum, remDen)
65}
66
67var intSign = []*big.Int{big.NewInt(-1), big.NewInt(0), big.NewInt(1)}
68
69func roundHalf(f func(c int, odd uint) (roundUp bool)) func(z, q *Dec, rA, rB *big.Int) *Dec {
70 return func(z, q *Dec, rA, rB *big.Int) *Dec {
71 z.Set(q)
72 brA, brB := rA.BitLen(), rB.BitLen()
73 if brA < brB-1 {
74 // brA < brB-1 => |rA| < |rB/2|
75 return z
76 }
77 roundUp := false
78 srA, srB := rA.Sign(), rB.Sign()
79 s := srA * srB
80 if brA == brB-1 {
81 rA2 := new(big.Int).Lsh(rA, 1)
82 if s < 0 {
83 rA2.Neg(rA2)
84 }
85 roundUp = f(rA2.Cmp(rB)*srB, z.UnscaledBig().Bit(0))
86 } else {
87 // brA > brB-1 => |rA| > |rB/2|
88 roundUp = true
89 }
90 if roundUp {
91 z.UnscaledBig().Add(z.UnscaledBig(), intSign[s+1])
92 }
93 return z
94 }
95}
96
97func init() {
98 RoundExact = rndr{true,
99 func(z, q *Dec, rA, rB *big.Int) *Dec {
100 if rA.Sign() != 0 {
101 return nil
102 }
103 return z.Set(q)
104 }}
105 RoundDown = rndr{false,
106 func(z, q *Dec, rA, rB *big.Int) *Dec {
107 return z.Set(q)
108 }}
109 RoundUp = rndr{true,
110 func(z, q *Dec, rA, rB *big.Int) *Dec {
111 z.Set(q)
112 if rA.Sign() != 0 {
113 z.UnscaledBig().Add(z.UnscaledBig(), intSign[rA.Sign()*rB.Sign()+1])
114 }
115 return z
116 }}
117 RoundFloor = rndr{true,
118 func(z, q *Dec, rA, rB *big.Int) *Dec {
119 z.Set(q)
120 if rA.Sign()*rB.Sign() < 0 {
121 z.UnscaledBig().Add(z.UnscaledBig(), intSign[0])
122 }
123 return z
124 }}
125 RoundCeil = rndr{true,
126 func(z, q *Dec, rA, rB *big.Int) *Dec {
127 z.Set(q)
128 if rA.Sign()*rB.Sign() > 0 {
129 z.UnscaledBig().Add(z.UnscaledBig(), intSign[2])
130 }
131 return z
132 }}
133 RoundHalfDown = rndr{true, roundHalf(
134 func(c int, odd uint) bool {
135 return c > 0
136 })}
137 RoundHalfUp = rndr{true, roundHalf(
138 func(c int, odd uint) bool {
139 return c >= 0
140 })}
141 RoundHalfEven = rndr{true, roundHalf(
142 func(c int, odd uint) bool {
143 return c > 0 || c == 0 && odd == 1
144 })}
145}