146 lines
3.8 KiB
Go
146 lines
3.8 KiB
Go
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
|
|
})}
|
|
}
|