Merge pull request #1185 from lavalamp/numeric
Numeric type for resources
This commit is contained in:
4
Godeps/Godeps.json
generated
4
Godeps/Godeps.json
generated
@@ -203,6 +203,10 @@
|
|||||||
{
|
{
|
||||||
"ImportPath": "gopkg.in/v2/yaml",
|
"ImportPath": "gopkg.in/v2/yaml",
|
||||||
"Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4"
|
"Rev": "d466437aa4adc35830964cffc5b5f262c63ddcb4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "speter.net/go/exp/math/dec/inf",
|
||||||
|
"Rev": "42ca6cd68aa922bc3f32f1e056e61b65945d9ad7"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
57
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/LICENSE
generated
vendored
Normal file
57
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
Copyright (c) 2012 Péter Surányi. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Portions of inf.Dec's source code have been derived from Go and are
|
||||||
|
covered by the following license:
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
210
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/benchmark_test.go
generated
vendored
Normal file
210
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/benchmark_test.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
package inf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const maxcap = 1024 * 1024
|
||||||
|
const bits = 256
|
||||||
|
const maxscale = 32
|
||||||
|
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
var decInput [][2]Dec
|
||||||
|
var intInput [][2]big.Int
|
||||||
|
|
||||||
|
var initBench = func() {
|
||||||
|
decInput = make([][2]Dec, maxcap)
|
||||||
|
intInput = make([][2]big.Int, maxcap)
|
||||||
|
max := new(big.Int).Lsh(big.NewInt(1), bits)
|
||||||
|
r := rand.New(rand.NewSource(0))
|
||||||
|
for i := 0; i < cap(decInput); i++ {
|
||||||
|
decInput[i][0].SetUnscaledBig(new(big.Int).Rand(r, max)).
|
||||||
|
SetScale(Scale(r.Int31n(int32(2*maxscale-1)) - int32(maxscale)))
|
||||||
|
decInput[i][1].SetUnscaledBig(new(big.Int).Rand(r, max)).
|
||||||
|
SetScale(Scale(r.Int31n(int32(2*maxscale-1)) - int32(maxscale)))
|
||||||
|
}
|
||||||
|
for i := 0; i < cap(intInput); i++ {
|
||||||
|
intInput[i][0].Rand(r, max)
|
||||||
|
intInput[i][1].Rand(r, max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmarkDec1(b *testing.B, f func(z *Dec)) {
|
||||||
|
once.Do(initBench)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f(&decInput[i%maxcap][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmarkDec2(b *testing.B, f func(x, y *Dec)) {
|
||||||
|
once.Do(initBench)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f(&decInput[i%maxcap][0], &decInput[i%maxcap][1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmarkInt1(b *testing.B, f func(z *big.Int)) {
|
||||||
|
once.Do(initBench)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f(&intInput[i%maxcap][0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doBenchmarkInt2(b *testing.B, f func(x, y *big.Int)) {
|
||||||
|
once.Do(initBench)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f(&intInput[i%maxcap][0], &intInput[i%maxcap][1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_String(b *testing.B) {
|
||||||
|
doBenchmarkDec1(b, func(x *Dec) {
|
||||||
|
x.String()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_StringScan(b *testing.B) {
|
||||||
|
doBenchmarkDec1(b, func(x *Dec) {
|
||||||
|
s := x.String()
|
||||||
|
d := new(Dec)
|
||||||
|
fmt.Sscan(s, d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_GobEncode(b *testing.B) {
|
||||||
|
doBenchmarkDec1(b, func(x *Dec) {
|
||||||
|
x.GobEncode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_GobEnDecode(b *testing.B) {
|
||||||
|
doBenchmarkDec1(b, func(x *Dec) {
|
||||||
|
g, _ := x.GobEncode()
|
||||||
|
new(Dec).GobDecode(g)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_Add(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
ys := y.Scale()
|
||||||
|
y.SetScale(x.Scale())
|
||||||
|
_ = new(Dec).Add(x, y)
|
||||||
|
y.SetScale(ys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_AddMixed(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
_ = new(Dec).Add(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_Sub(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
ys := y.Scale()
|
||||||
|
y.SetScale(x.Scale())
|
||||||
|
_ = new(Dec).Sub(x, y)
|
||||||
|
y.SetScale(ys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_SubMixed(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
_ = new(Dec).Sub(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_Mul(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
_ = new(Dec).Mul(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_Mul_QuoExact(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
v := new(Dec).Mul(x, y)
|
||||||
|
_ = new(Dec).QuoExact(v, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_QuoRound_Fixed_Down(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
_ = new(Dec).QuoRound(x, y, 0, RoundDown)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Dec_QuoRound_Fixed_HalfUp(b *testing.B) {
|
||||||
|
doBenchmarkDec2(b, func(x, y *Dec) {
|
||||||
|
_ = new(Dec).QuoRound(x, y, 0, RoundHalfUp)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_String(b *testing.B) {
|
||||||
|
doBenchmarkInt1(b, func(x *big.Int) {
|
||||||
|
x.String()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_StringScan(b *testing.B) {
|
||||||
|
doBenchmarkInt1(b, func(x *big.Int) {
|
||||||
|
s := x.String()
|
||||||
|
d := new(big.Int)
|
||||||
|
fmt.Sscan(s, d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_GobEncode(b *testing.B) {
|
||||||
|
doBenchmarkInt1(b, func(x *big.Int) {
|
||||||
|
x.GobEncode()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_GobEnDecode(b *testing.B) {
|
||||||
|
doBenchmarkInt1(b, func(x *big.Int) {
|
||||||
|
g, _ := x.GobEncode()
|
||||||
|
new(big.Int).GobDecode(g)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_Add(b *testing.B) {
|
||||||
|
doBenchmarkInt2(b, func(x, y *big.Int) {
|
||||||
|
_ = new(big.Int).Add(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_Sub(b *testing.B) {
|
||||||
|
doBenchmarkInt2(b, func(x, y *big.Int) {
|
||||||
|
_ = new(big.Int).Sub(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_Mul(b *testing.B) {
|
||||||
|
doBenchmarkInt2(b, func(x, y *big.Int) {
|
||||||
|
_ = new(big.Int).Mul(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_Quo(b *testing.B) {
|
||||||
|
doBenchmarkInt2(b, func(x, y *big.Int) {
|
||||||
|
_ = new(big.Int).Quo(x, y)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Benchmark_Int_QuoRem(b *testing.B) {
|
||||||
|
doBenchmarkInt2(b, func(x, y *big.Int) {
|
||||||
|
_, _ = new(big.Int).QuoRem(x, y, new(big.Int))
|
||||||
|
})
|
||||||
|
}
|
615
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec.go
generated
vendored
Normal file
615
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec.go
generated
vendored
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
// Package inf (type inf.Dec) implements "infinite-precision" decimal
|
||||||
|
// arithmetic.
|
||||||
|
// "Infinite precision" describes two characteristics: practically unlimited
|
||||||
|
// precision for decimal number representation and no support for calculating
|
||||||
|
// with any specific fixed precision.
|
||||||
|
// (Although there is no practical limit on precision, inf.Dec can only
|
||||||
|
// represent finite decimals.)
|
||||||
|
//
|
||||||
|
// This package is currently in experimental stage and the API may change.
|
||||||
|
//
|
||||||
|
// This package does NOT support:
|
||||||
|
// - rounding to specific precisions (as opposed to specific decimal positions)
|
||||||
|
// - the notion of context (each rounding must be explicit)
|
||||||
|
// - NaN and Inf values, and distinguishing between positive and negative zero
|
||||||
|
// - conversions to and from float32/64 types
|
||||||
|
//
|
||||||
|
// Features considered for possible addition:
|
||||||
|
// + formatting options
|
||||||
|
// + Exp method
|
||||||
|
// + combined operations such as AddRound/MulAdd etc
|
||||||
|
// + exchanging data in decimal32/64/128 formats
|
||||||
|
//
|
||||||
|
package inf
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - avoid excessive deep copying (quo and rounders)
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Dec represents a signed arbitrary-precision decimal.
|
||||||
|
// It is a combination of a sign, an arbitrary-precision integer coefficient
|
||||||
|
// value, and a signed fixed-precision exponent value.
|
||||||
|
// The sign and the coefficient value are handled together as a signed value
|
||||||
|
// and referred to as the unscaled value.
|
||||||
|
// (Positive and negative zero values are not distinguished.)
|
||||||
|
// Since the exponent is most commonly non-positive, it is handled in negated
|
||||||
|
// form and referred to as scale.
|
||||||
|
//
|
||||||
|
// The mathematical value of a Dec equals:
|
||||||
|
//
|
||||||
|
// unscaled * 10**(-scale)
|
||||||
|
//
|
||||||
|
// Note that different Dec representations may have equal mathematical values.
|
||||||
|
//
|
||||||
|
// unscaled scale String()
|
||||||
|
// -------------------------
|
||||||
|
// 0 0 "0"
|
||||||
|
// 0 2 "0.00"
|
||||||
|
// 0 -2 "0"
|
||||||
|
// 1 0 "1"
|
||||||
|
// 100 2 "1.00"
|
||||||
|
// 10 0 "10"
|
||||||
|
// 1 -1 "10"
|
||||||
|
//
|
||||||
|
// The zero value for a Dec represents the value 0 with scale 0.
|
||||||
|
//
|
||||||
|
// Operations are typically performed through the *Dec type.
|
||||||
|
// The semantics of the assignment operation "=" for "bare" Dec values is
|
||||||
|
// undefined and should not be relied on.
|
||||||
|
//
|
||||||
|
// Methods are typically of the form:
|
||||||
|
//
|
||||||
|
// func (z *Dec) Op(x, y *Dec) *Dec
|
||||||
|
//
|
||||||
|
// and implement operations z = x Op y with the result as receiver; if it
|
||||||
|
// is one of the operands it may be overwritten (and its memory reused).
|
||||||
|
// To enable chaining of operations, the result is also returned. Methods
|
||||||
|
// returning a result other than *Dec take one of the operands as the receiver.
|
||||||
|
//
|
||||||
|
// A "bare" Quo method (quotient / division operation) is not provided, as the
|
||||||
|
// result is not always a finite decimal and thus in general cannot be
|
||||||
|
// represented as a Dec.
|
||||||
|
// Instead, in the common case when rounding is (potentially) necessary,
|
||||||
|
// QuoRound should be used with a Scale and a Rounder.
|
||||||
|
// QuoExact or QuoRound with RoundExact can be used in the special cases when it
|
||||||
|
// is known that the result is always a finite decimal.
|
||||||
|
//
|
||||||
|
type Dec struct {
|
||||||
|
unscaled big.Int
|
||||||
|
scale Scale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale represents the type used for the scale of a Dec.
|
||||||
|
type Scale int32
|
||||||
|
|
||||||
|
const scaleSize = 4 // bytes in a Scale value
|
||||||
|
|
||||||
|
// Scaler represents a method for obtaining the scale to use for the result of
|
||||||
|
// an operation on x and y.
|
||||||
|
type scaler interface {
|
||||||
|
Scale(x *Dec, y *Dec) Scale
|
||||||
|
}
|
||||||
|
|
||||||
|
var bigInt = [...]*big.Int{
|
||||||
|
big.NewInt(0), big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4),
|
||||||
|
big.NewInt(5), big.NewInt(6), big.NewInt(7), big.NewInt(8), big.NewInt(9),
|
||||||
|
big.NewInt(10),
|
||||||
|
}
|
||||||
|
|
||||||
|
var exp10cache [64]big.Int = func() [64]big.Int {
|
||||||
|
e10, e10i := [64]big.Int{}, bigInt[1]
|
||||||
|
for i, _ := range e10 {
|
||||||
|
e10[i].Set(e10i)
|
||||||
|
e10i = new(big.Int).Mul(e10i, bigInt[10])
|
||||||
|
}
|
||||||
|
return e10
|
||||||
|
}()
|
||||||
|
|
||||||
|
// NewDec allocates and returns a new Dec set to the given int64 unscaled value
|
||||||
|
// and scale.
|
||||||
|
func NewDec(unscaled int64, scale Scale) *Dec {
|
||||||
|
return new(Dec).SetUnscaled(unscaled).SetScale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecBig allocates and returns a new Dec set to the given *big.Int unscaled
|
||||||
|
// value and scale.
|
||||||
|
func NewDecBig(unscaled *big.Int, scale Scale) *Dec {
|
||||||
|
return new(Dec).SetUnscaledBig(unscaled).SetScale(scale)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale returns the scale of x.
|
||||||
|
func (x *Dec) Scale() Scale {
|
||||||
|
return x.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unscaled returns the unscaled value of x for u and true for ok when the
|
||||||
|
// unscaled value can be represented as int64; otherwise it returns an undefined
|
||||||
|
// int64 value for u and false for ok. Use x.UnscaledBig().Int64() to avoid
|
||||||
|
// checking the validity of the value when the check is known to be redundant.
|
||||||
|
func (x *Dec) Unscaled() (u int64, ok bool) {
|
||||||
|
u = x.unscaled.Int64()
|
||||||
|
var i big.Int
|
||||||
|
ok = i.SetInt64(u).Cmp(&x.unscaled) == 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnscaledBig returns the unscaled value of x as *big.Int.
|
||||||
|
func (x *Dec) UnscaledBig() *big.Int {
|
||||||
|
return &x.unscaled
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetScale sets the scale of z, with the unscaled value unchanged, and returns
|
||||||
|
// z.
|
||||||
|
// The mathematical value of the Dec changes as if it was multiplied by
|
||||||
|
// 10**(oldscale-scale).
|
||||||
|
func (z *Dec) SetScale(scale Scale) *Dec {
|
||||||
|
z.scale = scale
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnscaled sets the unscaled value of z, with the scale unchanged, and
|
||||||
|
// returns z.
|
||||||
|
func (z *Dec) SetUnscaled(unscaled int64) *Dec {
|
||||||
|
z.unscaled.SetInt64(unscaled)
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUnscaledBig sets the unscaled value of z, with the scale unchanged, and
|
||||||
|
// returns z.
|
||||||
|
func (z *Dec) SetUnscaledBig(unscaled *big.Int) *Dec {
|
||||||
|
z.unscaled.Set(unscaled)
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets z to the value of x and returns z.
|
||||||
|
// It does nothing if z == x.
|
||||||
|
func (z *Dec) Set(x *Dec) *Dec {
|
||||||
|
if z != x {
|
||||||
|
z.SetUnscaledBig(x.UnscaledBig())
|
||||||
|
z.SetScale(x.Scale())
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign returns:
|
||||||
|
//
|
||||||
|
// -1 if x < 0
|
||||||
|
// 0 if x == 0
|
||||||
|
// +1 if x > 0
|
||||||
|
//
|
||||||
|
func (x *Dec) Sign() int {
|
||||||
|
return x.UnscaledBig().Sign()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg sets z to -x and returns z.
|
||||||
|
func (z *Dec) Neg(x *Dec) *Dec {
|
||||||
|
z.SetScale(x.Scale())
|
||||||
|
z.UnscaledBig().Neg(x.UnscaledBig())
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmp compares x and y and returns:
|
||||||
|
//
|
||||||
|
// -1 if x < y
|
||||||
|
// 0 if x == y
|
||||||
|
// +1 if x > y
|
||||||
|
//
|
||||||
|
func (x *Dec) Cmp(y *Dec) int {
|
||||||
|
xx, yy := upscale(x, y)
|
||||||
|
return xx.UnscaledBig().Cmp(yy.UnscaledBig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abs sets z to |x| (the absolute value of x) and returns z.
|
||||||
|
func (z *Dec) Abs(x *Dec) *Dec {
|
||||||
|
z.SetScale(x.Scale())
|
||||||
|
z.UnscaledBig().Abs(x.UnscaledBig())
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets z to the sum x+y and returns z.
|
||||||
|
// The scale of z is the greater of the scales of x and y.
|
||||||
|
func (z *Dec) Add(x, y *Dec) *Dec {
|
||||||
|
xx, yy := upscale(x, y)
|
||||||
|
z.SetScale(xx.Scale())
|
||||||
|
z.UnscaledBig().Add(xx.UnscaledBig(), yy.UnscaledBig())
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub sets z to the difference x-y and returns z.
|
||||||
|
// The scale of z is the greater of the scales of x and y.
|
||||||
|
func (z *Dec) Sub(x, y *Dec) *Dec {
|
||||||
|
xx, yy := upscale(x, y)
|
||||||
|
z.SetScale(xx.Scale())
|
||||||
|
z.UnscaledBig().Sub(xx.UnscaledBig(), yy.UnscaledBig())
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul sets z to the product x*y and returns z.
|
||||||
|
// The scale of z is the sum of the scales of x and y.
|
||||||
|
func (z *Dec) Mul(x, y *Dec) *Dec {
|
||||||
|
z.SetScale(x.Scale() + y.Scale())
|
||||||
|
z.UnscaledBig().Mul(x.UnscaledBig(), y.UnscaledBig())
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round sets z to the value of x rounded to Scale s using Rounder r, and
|
||||||
|
// returns z.
|
||||||
|
func (z *Dec) Round(x *Dec, s Scale, r Rounder) *Dec {
|
||||||
|
return z.QuoRound(x, NewDec(1, 0), s, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoRound sets z to the quotient x/y, rounded using the given Rounder to the
|
||||||
|
// specified scale.
|
||||||
|
//
|
||||||
|
// If the rounder is RoundExact but the result can not be expressed exactly at
|
||||||
|
// the specified scale, QuoRound returns nil, and the value of z is undefined.
|
||||||
|
//
|
||||||
|
// There is no corresponding Div method; the equivalent can be achieved through
|
||||||
|
// the choice of Rounder used.
|
||||||
|
//
|
||||||
|
func (z *Dec) QuoRound(x, y *Dec, s Scale, r Rounder) *Dec {
|
||||||
|
return z.quo(x, y, sclr{s}, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Dec) quo(x, y *Dec, s scaler, r Rounder) *Dec {
|
||||||
|
scl := s.Scale(x, y)
|
||||||
|
var zzz *Dec
|
||||||
|
if r.UseRemainder() {
|
||||||
|
zz, rA, rB := new(Dec).quoRem(x, y, scl, true, new(big.Int), new(big.Int))
|
||||||
|
zzz = r.Round(new(Dec), zz, rA, rB)
|
||||||
|
} else {
|
||||||
|
zz, _, _ := new(Dec).quoRem(x, y, scl, false, nil, nil)
|
||||||
|
zzz = r.Round(new(Dec), zz, nil, nil)
|
||||||
|
}
|
||||||
|
if zzz == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return z.Set(zzz)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoExact sets z to the quotient x/y and returns z when x/y is a finite
|
||||||
|
// decimal. Otherwise it returns nil and the value of z is undefined.
|
||||||
|
//
|
||||||
|
// The scale of a non-nil result is "x.Scale() - y.Scale()" or greater; it is
|
||||||
|
// calculated so that the remainder will be zero whenever x/y is a finite
|
||||||
|
// decimal.
|
||||||
|
func (z *Dec) QuoExact(x, y *Dec) *Dec {
|
||||||
|
return z.quo(x, y, scaleQuoExact{}, RoundExact)
|
||||||
|
}
|
||||||
|
|
||||||
|
// quoRem sets z to the quotient x/y with the scale s, and if useRem is true,
|
||||||
|
// it sets remNum and remDen to the numerator and denominator of the remainder.
|
||||||
|
// It returns z, remNum and remDen.
|
||||||
|
//
|
||||||
|
// The remainder is normalized to the range -1 < r < 1 to simplify rounding;
|
||||||
|
// that is, the results satisfy the following equation:
|
||||||
|
//
|
||||||
|
// x / y = z + (remNum/remDen) * 10**(-z.Scale())
|
||||||
|
//
|
||||||
|
// See Rounder for more details about rounding.
|
||||||
|
//
|
||||||
|
func (z *Dec) quoRem(x, y *Dec, s Scale, useRem bool,
|
||||||
|
remNum, remDen *big.Int) (*Dec, *big.Int, *big.Int) {
|
||||||
|
// difference (required adjustment) compared to "canonical" result scale
|
||||||
|
shift := s - (x.Scale() - y.Scale())
|
||||||
|
// pointers to adjusted unscaled dividend and divisor
|
||||||
|
var ix, iy *big.Int
|
||||||
|
switch {
|
||||||
|
case shift > 0:
|
||||||
|
// increased scale: decimal-shift dividend left
|
||||||
|
ix = new(big.Int).Mul(x.UnscaledBig(), exp10(shift))
|
||||||
|
iy = y.UnscaledBig()
|
||||||
|
case shift < 0:
|
||||||
|
// decreased scale: decimal-shift divisor left
|
||||||
|
ix = x.UnscaledBig()
|
||||||
|
iy = new(big.Int).Mul(y.UnscaledBig(), exp10(-shift))
|
||||||
|
default:
|
||||||
|
ix = x.UnscaledBig()
|
||||||
|
iy = y.UnscaledBig()
|
||||||
|
}
|
||||||
|
// save a copy of iy in case it to be overwritten with the result
|
||||||
|
iy2 := iy
|
||||||
|
if iy == z.UnscaledBig() {
|
||||||
|
iy2 = new(big.Int).Set(iy)
|
||||||
|
}
|
||||||
|
// set scale
|
||||||
|
z.SetScale(s)
|
||||||
|
// set unscaled
|
||||||
|
if useRem {
|
||||||
|
// Int division
|
||||||
|
_, intr := z.UnscaledBig().QuoRem(ix, iy, new(big.Int))
|
||||||
|
// set remainder
|
||||||
|
remNum.Set(intr)
|
||||||
|
remDen.Set(iy2)
|
||||||
|
} else {
|
||||||
|
z.UnscaledBig().Quo(ix, iy)
|
||||||
|
}
|
||||||
|
return z, remNum, remDen
|
||||||
|
}
|
||||||
|
|
||||||
|
type sclr struct{ s Scale }
|
||||||
|
|
||||||
|
func (s sclr) Scale(x, y *Dec) Scale {
|
||||||
|
return s.s
|
||||||
|
}
|
||||||
|
|
||||||
|
type scaleQuoExact struct{}
|
||||||
|
|
||||||
|
func (sqe scaleQuoExact) Scale(x, y *Dec) Scale {
|
||||||
|
rem := new(big.Rat).SetFrac(x.UnscaledBig(), y.UnscaledBig())
|
||||||
|
f2, f5 := factor2(rem.Denom()), factor(rem.Denom(), bigInt[5])
|
||||||
|
var f10 Scale
|
||||||
|
if f2 > f5 {
|
||||||
|
f10 = Scale(f2)
|
||||||
|
} else {
|
||||||
|
f10 = Scale(f5)
|
||||||
|
}
|
||||||
|
return x.Scale() - y.Scale() + f10
|
||||||
|
}
|
||||||
|
|
||||||
|
func factor(n *big.Int, p *big.Int) int {
|
||||||
|
// could be improved for large factors
|
||||||
|
d, f := n, 0
|
||||||
|
for {
|
||||||
|
dd, dm := new(big.Int).DivMod(d, p, new(big.Int))
|
||||||
|
if dm.Sign() == 0 {
|
||||||
|
f++
|
||||||
|
d = dd
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func factor2(n *big.Int) int {
|
||||||
|
// could be improved for large factors
|
||||||
|
f := 0
|
||||||
|
for ; n.Bit(f) == 0; f++ {
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func upscale(a, b *Dec) (*Dec, *Dec) {
|
||||||
|
if a.Scale() == b.Scale() {
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
if a.Scale() > b.Scale() {
|
||||||
|
bb := b.rescale(a.Scale())
|
||||||
|
return a, bb
|
||||||
|
}
|
||||||
|
aa := a.rescale(b.Scale())
|
||||||
|
return aa, b
|
||||||
|
}
|
||||||
|
|
||||||
|
func exp10(x Scale) *big.Int {
|
||||||
|
if int(x) < len(exp10cache) {
|
||||||
|
return &exp10cache[int(x)]
|
||||||
|
}
|
||||||
|
return new(big.Int).Exp(bigInt[10], big.NewInt(int64(x)), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Dec) rescale(newScale Scale) *Dec {
|
||||||
|
shift := newScale - x.Scale()
|
||||||
|
switch {
|
||||||
|
case shift < 0:
|
||||||
|
e := exp10(-shift)
|
||||||
|
return NewDecBig(new(big.Int).Quo(x.UnscaledBig(), e), newScale)
|
||||||
|
case shift > 0:
|
||||||
|
e := exp10(shift)
|
||||||
|
return NewDecBig(new(big.Int).Mul(x.UnscaledBig(), e), newScale)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeros = []byte("00000000000000000000000000000000" +
|
||||||
|
"00000000000000000000000000000000")
|
||||||
|
var lzeros = Scale(len(zeros))
|
||||||
|
|
||||||
|
func appendZeros(s []byte, n Scale) []byte {
|
||||||
|
for i := Scale(0); i < n; i += lzeros {
|
||||||
|
if n > i+lzeros {
|
||||||
|
s = append(s, zeros...)
|
||||||
|
} else {
|
||||||
|
s = append(s, zeros[0:n-i]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Dec) String() string {
|
||||||
|
if x == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
scale := x.Scale()
|
||||||
|
s := []byte(x.UnscaledBig().String())
|
||||||
|
if scale <= 0 {
|
||||||
|
if scale != 0 && x.unscaled.Sign() != 0 {
|
||||||
|
s = appendZeros(s, -scale)
|
||||||
|
}
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
negbit := Scale(-((x.Sign() - 1) / 2))
|
||||||
|
// scale > 0
|
||||||
|
lens := Scale(len(s))
|
||||||
|
if lens-negbit <= scale {
|
||||||
|
ss := make([]byte, 0, scale+2)
|
||||||
|
if negbit == 1 {
|
||||||
|
ss = append(ss, '-')
|
||||||
|
}
|
||||||
|
ss = append(ss, '0', '.')
|
||||||
|
ss = appendZeros(ss, scale-lens+negbit)
|
||||||
|
ss = append(ss, s[negbit:]...)
|
||||||
|
return string(ss)
|
||||||
|
}
|
||||||
|
// lens > scale
|
||||||
|
ss := make([]byte, 0, lens+1)
|
||||||
|
ss = append(ss, s[:lens-scale]...)
|
||||||
|
ss = append(ss, '.')
|
||||||
|
ss = append(ss, s[lens-scale:]...)
|
||||||
|
return string(ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format is a support routine for fmt.Formatter. It accepts the decimal
|
||||||
|
// formats 'd' and 'f', and handles both equivalently.
|
||||||
|
// Width, precision, flags and bases 2, 8, 16 are not supported.
|
||||||
|
func (x *Dec) Format(s fmt.State, ch rune) {
|
||||||
|
if ch != 'd' && ch != 'f' && ch != 'v' && ch != 's' {
|
||||||
|
fmt.Fprintf(s, "%%!%c(dec.Dec=%s)", ch, x.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(s, x.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Dec) scan(r io.RuneScanner) (*Dec, error) {
|
||||||
|
unscaled := make([]byte, 0, 256) // collects chars of unscaled as bytes
|
||||||
|
dp, dg := -1, -1 // indexes of decimal point, first digit
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
ch, _, err := r.ReadRune()
|
||||||
|
if err == io.EOF {
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case ch == '+' || ch == '-':
|
||||||
|
if len(unscaled) > 0 || dp >= 0 { // must be first character
|
||||||
|
r.UnreadRune()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
case ch == '.':
|
||||||
|
if dp >= 0 {
|
||||||
|
r.UnreadRune()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
dp = len(unscaled)
|
||||||
|
continue // don't add to unscaled
|
||||||
|
case ch >= '0' && ch <= '9':
|
||||||
|
if dg == -1 {
|
||||||
|
dg = len(unscaled)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
r.UnreadRune()
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
unscaled = append(unscaled, byte(ch))
|
||||||
|
}
|
||||||
|
if dg == -1 {
|
||||||
|
return nil, fmt.Errorf("no digits read")
|
||||||
|
}
|
||||||
|
if dp >= 0 {
|
||||||
|
z.SetScale(Scale(len(unscaled) - dp))
|
||||||
|
} else {
|
||||||
|
z.SetScale(0)
|
||||||
|
}
|
||||||
|
_, ok := z.UnscaledBig().SetString(string(unscaled), 10)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid decimal: %s", string(unscaled))
|
||||||
|
}
|
||||||
|
return z, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetString sets z to the value of s, interpreted as a decimal (base 10),
|
||||||
|
// and returns z and a boolean indicating success. The scale of z is the
|
||||||
|
// number of digits after the decimal point (including any trailing 0s),
|
||||||
|
// or 0 if there is no decimal point. If SetString fails, the value of z
|
||||||
|
// is undefined but the returned value is nil.
|
||||||
|
func (z *Dec) SetString(s string) (*Dec, bool) {
|
||||||
|
r := strings.NewReader(s)
|
||||||
|
_, err := z.scan(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
_, _, err = r.ReadRune()
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
// err == io.EOF => scan consumed all of s
|
||||||
|
return z, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan is a support routine for fmt.Scanner; it sets z to the value of
|
||||||
|
// the scanned number. It accepts the decimal formats 'd' and 'f', and
|
||||||
|
// handles both equivalently. Bases 2, 8, 16 are not supported.
|
||||||
|
// The scale of z is the number of digits after the decimal point
|
||||||
|
// (including any trailing 0s), or 0 if there is no decimal point.
|
||||||
|
func (z *Dec) Scan(s fmt.ScanState, ch rune) error {
|
||||||
|
if ch != 'd' && ch != 'f' && ch != 's' && ch != 'v' {
|
||||||
|
return fmt.Errorf("Dec.Scan: invalid verb '%c'", ch)
|
||||||
|
}
|
||||||
|
s.SkipSpace()
|
||||||
|
_, err := z.scan(s)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gob encoding version
|
||||||
|
const decGobVersion byte = 1
|
||||||
|
|
||||||
|
func scaleBytes(s Scale) []byte {
|
||||||
|
buf := make([]byte, scaleSize)
|
||||||
|
i := scaleSize
|
||||||
|
for j := 0; j < scaleSize; j++ {
|
||||||
|
i--
|
||||||
|
buf[i] = byte(s)
|
||||||
|
s >>= 8
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func scale(b []byte) (s Scale) {
|
||||||
|
for j := 0; j < scaleSize; j++ {
|
||||||
|
s <<= 8
|
||||||
|
s |= Scale(b[j])
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode implements the gob.GobEncoder interface.
|
||||||
|
func (x *Dec) GobEncode() ([]byte, error) {
|
||||||
|
buf, err := x.UnscaledBig().GobEncode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
buf = append(append(buf, scaleBytes(x.Scale())...), decGobVersion)
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode implements the gob.GobDecoder interface.
|
||||||
|
func (z *Dec) GobDecode(buf []byte) error {
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return fmt.Errorf("Dec.GobDecode: no data")
|
||||||
|
}
|
||||||
|
b := buf[len(buf)-1]
|
||||||
|
if b != decGobVersion {
|
||||||
|
return fmt.Errorf("Dec.GobDecode: encoding version %d not supported", b)
|
||||||
|
}
|
||||||
|
l := len(buf) - scaleSize - 1
|
||||||
|
err := z.UnscaledBig().GobDecode(buf[:l])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
z.SetScale(scale(buf[l : l+scaleSize]))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText implements the encoding.TextMarshaler interface.
|
||||||
|
func (x *Dec) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(x.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||||
|
func (z *Dec) UnmarshalText(data []byte) error {
|
||||||
|
_, ok := z.SetString(string(data))
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid inf.Dec")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
33
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_go1_2_test.go
generated
vendored
Normal file
33
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_go1_2_test.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// +build go1.2
|
||||||
|
|
||||||
|
package inf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ encoding.TextMarshaler = new(Dec)
|
||||||
|
var _ encoding.TextUnmarshaler = new(Dec)
|
||||||
|
|
||||||
|
type Obj struct {
|
||||||
|
Val *Dec
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecJsonMarshalUnmarshal(t *testing.T) {
|
||||||
|
o := Obj{Val: NewDec(123, 2)}
|
||||||
|
js, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Marshal(%v): got %v, want ok", o, err)
|
||||||
|
}
|
||||||
|
o2 := &Obj{}
|
||||||
|
err = json.Unmarshal(js, o2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json.Unmarshal(%#q): got %v, want ok", js, err)
|
||||||
|
}
|
||||||
|
if o.Val.Scale() != o2.Val.Scale() ||
|
||||||
|
o.Val.UnscaledBig().Cmp(o2.Val.UnscaledBig()) != 0 {
|
||||||
|
t.Fatalf("json.Unmarshal(json.Marshal(%v)): want %v, got %v", o, o, o2)
|
||||||
|
}
|
||||||
|
}
|
40
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_internal_test.go
generated
vendored
Normal file
40
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_internal_test.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package inf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var decQuoRemZZZ = []struct {
|
||||||
|
z, x, y *Dec
|
||||||
|
r *big.Rat
|
||||||
|
srA, srB int
|
||||||
|
}{
|
||||||
|
// basic examples
|
||||||
|
{NewDec(1, 0), NewDec(2, 0), NewDec(2, 0), big.NewRat(0, 1), 0, 1},
|
||||||
|
{NewDec(15, 1), NewDec(3, 0), NewDec(2, 0), big.NewRat(0, 1), 0, 1},
|
||||||
|
{NewDec(1, 1), NewDec(1, 0), NewDec(10, 0), big.NewRat(0, 1), 0, 1},
|
||||||
|
{NewDec(0, 0), NewDec(2, 0), NewDec(3, 0), big.NewRat(2, 3), 1, 1},
|
||||||
|
{NewDec(0, 0), NewDec(2, 0), NewDec(6, 0), big.NewRat(1, 3), 1, 1},
|
||||||
|
{NewDec(1, 1), NewDec(2, 0), NewDec(12, 0), big.NewRat(2, 3), 1, 1},
|
||||||
|
|
||||||
|
// examples from the Go Language Specification
|
||||||
|
{NewDec(1, 0), NewDec(5, 0), NewDec(3, 0), big.NewRat(2, 3), 1, 1},
|
||||||
|
{NewDec(-1, 0), NewDec(-5, 0), NewDec(3, 0), big.NewRat(-2, 3), -1, 1},
|
||||||
|
{NewDec(-1, 0), NewDec(5, 0), NewDec(-3, 0), big.NewRat(-2, 3), 1, -1},
|
||||||
|
{NewDec(1, 0), NewDec(-5, 0), NewDec(-3, 0), big.NewRat(2, 3), -1, -1},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecQuoRem(t *testing.T) {
|
||||||
|
for i, a := range decQuoRemZZZ {
|
||||||
|
z, rA, rB := new(Dec), new(big.Int), new(big.Int)
|
||||||
|
s := scaleQuoExact{}.Scale(a.x, a.y)
|
||||||
|
z.quoRem(a.x, a.y, s, true, rA, rB)
|
||||||
|
if a.z.Cmp(z) != 0 || a.r.Cmp(new(big.Rat).SetFrac(rA, rB)) != 0 {
|
||||||
|
t.Errorf("#%d QuoRemZZZ got %v, %v, %v; expected %v, %v", i, z, rA, rB, a.z, a.r)
|
||||||
|
}
|
||||||
|
if a.srA != rA.Sign() || a.srB != rB.Sign() {
|
||||||
|
t.Errorf("#%d QuoRemZZZ wrong signs, got %v, %v; expected %v, %v", i, rA.Sign(), rB.Sign(), a.srA, a.srB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
379
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_test.go
generated
vendored
Normal file
379
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/dec_test.go
generated
vendored
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
package inf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"speter.net/go/exp/math/dec/inf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decFunZZ func(z, x, y *inf.Dec) *inf.Dec
|
||||||
|
type decArgZZ struct {
|
||||||
|
z, x, y *inf.Dec
|
||||||
|
}
|
||||||
|
|
||||||
|
var decSumZZ = []decArgZZ{
|
||||||
|
{inf.NewDec(0, 0), inf.NewDec(0, 0), inf.NewDec(0, 0)},
|
||||||
|
{inf.NewDec(1, 0), inf.NewDec(1, 0), inf.NewDec(0, 0)},
|
||||||
|
{inf.NewDec(1111111110, 0), inf.NewDec(123456789, 0), inf.NewDec(987654321, 0)},
|
||||||
|
{inf.NewDec(-1, 0), inf.NewDec(-1, 0), inf.NewDec(0, 0)},
|
||||||
|
{inf.NewDec(864197532, 0), inf.NewDec(-123456789, 0), inf.NewDec(987654321, 0)},
|
||||||
|
{inf.NewDec(-1111111110, 0), inf.NewDec(-123456789, 0), inf.NewDec(-987654321, 0)},
|
||||||
|
{inf.NewDec(12, 2), inf.NewDec(1, 1), inf.NewDec(2, 2)},
|
||||||
|
}
|
||||||
|
|
||||||
|
var decProdZZ = []decArgZZ{
|
||||||
|
{inf.NewDec(0, 0), inf.NewDec(0, 0), inf.NewDec(0, 0)},
|
||||||
|
{inf.NewDec(0, 0), inf.NewDec(1, 0), inf.NewDec(0, 0)},
|
||||||
|
{inf.NewDec(1, 0), inf.NewDec(1, 0), inf.NewDec(1, 0)},
|
||||||
|
{inf.NewDec(-991*991, 0), inf.NewDec(991, 0), inf.NewDec(-991, 0)},
|
||||||
|
{inf.NewDec(2, 3), inf.NewDec(1, 1), inf.NewDec(2, 2)},
|
||||||
|
{inf.NewDec(2, -3), inf.NewDec(1, -1), inf.NewDec(2, -2)},
|
||||||
|
{inf.NewDec(2, 3), inf.NewDec(1, 1), inf.NewDec(2, 2)},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecSignZ(t *testing.T) {
|
||||||
|
var zero inf.Dec
|
||||||
|
for _, a := range decSumZZ {
|
||||||
|
s := a.z.Sign()
|
||||||
|
e := a.z.Cmp(&zero)
|
||||||
|
if s != e {
|
||||||
|
t.Errorf("got %d; want %d for z = %v", s, e, a.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecAbsZ(t *testing.T) {
|
||||||
|
var zero inf.Dec
|
||||||
|
for _, a := range decSumZZ {
|
||||||
|
var z inf.Dec
|
||||||
|
z.Abs(a.z)
|
||||||
|
var e inf.Dec
|
||||||
|
e.Set(a.z)
|
||||||
|
if e.Cmp(&zero) < 0 {
|
||||||
|
e.Sub(&zero, &e)
|
||||||
|
}
|
||||||
|
if z.Cmp(&e) != 0 {
|
||||||
|
t.Errorf("got z = %v; want %v", z, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecFunZZ(t *testing.T, msg string, f decFunZZ, a decArgZZ) {
|
||||||
|
var z inf.Dec
|
||||||
|
f(&z, a.x, a.y)
|
||||||
|
if (&z).Cmp(a.z) != 0 {
|
||||||
|
t.Errorf("%s%+v\n\tgot z = %v; want %v", msg, a, &z, a.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecSumZZ(t *testing.T) {
|
||||||
|
AddZZ := func(z, x, y *inf.Dec) *inf.Dec { return z.Add(x, y) }
|
||||||
|
SubZZ := func(z, x, y *inf.Dec) *inf.Dec { return z.Sub(x, y) }
|
||||||
|
for _, a := range decSumZZ {
|
||||||
|
arg := a
|
||||||
|
testDecFunZZ(t, "AddZZ", AddZZ, arg)
|
||||||
|
|
||||||
|
arg = decArgZZ{a.z, a.y, a.x}
|
||||||
|
testDecFunZZ(t, "AddZZ symmetric", AddZZ, arg)
|
||||||
|
|
||||||
|
arg = decArgZZ{a.x, a.z, a.y}
|
||||||
|
testDecFunZZ(t, "SubZZ", SubZZ, arg)
|
||||||
|
|
||||||
|
arg = decArgZZ{a.y, a.z, a.x}
|
||||||
|
testDecFunZZ(t, "SubZZ symmetric", SubZZ, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecProdZZ(t *testing.T) {
|
||||||
|
MulZZ := func(z, x, y *inf.Dec) *inf.Dec { return z.Mul(x, y) }
|
||||||
|
for _, a := range decProdZZ {
|
||||||
|
arg := a
|
||||||
|
testDecFunZZ(t, "MulZZ", MulZZ, arg)
|
||||||
|
|
||||||
|
arg = decArgZZ{a.z, a.y, a.x}
|
||||||
|
testDecFunZZ(t, "MulZZ symmetric", MulZZ, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decUnscaledTests = []struct {
|
||||||
|
d *inf.Dec
|
||||||
|
u int64 // ignored when ok == false
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{new(inf.Dec), 0, true},
|
||||||
|
{inf.NewDec(-1<<63, 0), -1 << 63, true},
|
||||||
|
{inf.NewDec(-(-1<<63 + 1), 0), -(-1<<63 + 1), true},
|
||||||
|
{new(inf.Dec).Neg(inf.NewDec(-1<<63, 0)), 0, false},
|
||||||
|
{new(inf.Dec).Sub(inf.NewDec(-1<<63, 0), inf.NewDec(1, 0)), 0, false},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), 0, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecUnscaled(t *testing.T) {
|
||||||
|
for i, tt := range decUnscaledTests {
|
||||||
|
u, ok := tt.d.Unscaled()
|
||||||
|
if ok != tt.ok {
|
||||||
|
t.Errorf("#%d Unscaled: got %v, expected %v", i, ok, tt.ok)
|
||||||
|
} else if ok && u != tt.u {
|
||||||
|
t.Errorf("#%d Unscaled: got %v, expected %v", i, u, tt.u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decRoundTests = [...]struct {
|
||||||
|
in *inf.Dec
|
||||||
|
s inf.Scale
|
||||||
|
r inf.Rounder
|
||||||
|
exp *inf.Dec
|
||||||
|
}{
|
||||||
|
{inf.NewDec(123424999999999993, 15), 2, inf.RoundHalfUp, inf.NewDec(12342, 2)},
|
||||||
|
{inf.NewDec(123425000000000001, 15), 2, inf.RoundHalfUp, inf.NewDec(12343, 2)},
|
||||||
|
{inf.NewDec(123424999999999993, 15), 15, inf.RoundHalfUp, inf.NewDec(123424999999999993, 15)},
|
||||||
|
{inf.NewDec(123424999999999993, 15), 16, inf.RoundHalfUp, inf.NewDec(1234249999999999930, 16)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -1, inf.RoundHalfUp, inf.NewDec(1844674407370955162, -1)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -2, inf.RoundHalfUp, inf.NewDec(184467440737095516, -2)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -3, inf.RoundHalfUp, inf.NewDec(18446744073709552, -3)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -4, inf.RoundHalfUp, inf.NewDec(1844674407370955, -4)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -5, inf.RoundHalfUp, inf.NewDec(184467440737096, -5)},
|
||||||
|
{inf.NewDecBig(new(big.Int).Lsh(big.NewInt(1), 64), 0), -6, inf.RoundHalfUp, inf.NewDec(18446744073710, -6)},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecRound(t *testing.T) {
|
||||||
|
for i, tt := range decRoundTests {
|
||||||
|
z := new(inf.Dec).Round(tt.in, tt.s, tt.r)
|
||||||
|
if tt.exp.Cmp(z) != 0 {
|
||||||
|
t.Errorf("#%d Round got %v; expected %v", i, z, tt.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decStringTests = []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
val int64
|
||||||
|
scale inf.Scale // skip SetString if negative
|
||||||
|
ok bool
|
||||||
|
scanOk bool
|
||||||
|
}{
|
||||||
|
{in: "", ok: false, scanOk: false},
|
||||||
|
{in: "a", ok: false, scanOk: false},
|
||||||
|
{in: "z", ok: false, scanOk: false},
|
||||||
|
{in: "+", ok: false, scanOk: false},
|
||||||
|
{in: "-", ok: false, scanOk: false},
|
||||||
|
{in: "g", ok: false, scanOk: false},
|
||||||
|
{in: ".", ok: false, scanOk: false},
|
||||||
|
{in: ".-0", ok: false, scanOk: false},
|
||||||
|
{in: ".+0", ok: false, scanOk: false},
|
||||||
|
// Scannable but not SetStringable
|
||||||
|
{"0b", "ignored", 0, 0, false, true},
|
||||||
|
{"0x", "ignored", 0, 0, false, true},
|
||||||
|
{"0xg", "ignored", 0, 0, false, true},
|
||||||
|
{"0.0g", "ignored", 0, 1, false, true},
|
||||||
|
// examples from godoc for Dec
|
||||||
|
{"0", "0", 0, 0, true, true},
|
||||||
|
{"0.00", "0.00", 0, 2, true, true},
|
||||||
|
{"ignored", "0", 0, -2, true, false},
|
||||||
|
{"1", "1", 1, 0, true, true},
|
||||||
|
{"1.00", "1.00", 100, 2, true, true},
|
||||||
|
{"10", "10", 10, 0, true, true},
|
||||||
|
{"ignored", "10", 1, -1, true, false},
|
||||||
|
// other tests
|
||||||
|
{"+0", "0", 0, 0, true, true},
|
||||||
|
{"-0", "0", 0, 0, true, true},
|
||||||
|
{"0.0", "0.0", 0, 1, true, true},
|
||||||
|
{"0.1", "0.1", 1, 1, true, true},
|
||||||
|
{"0.", "0", 0, 0, true, true},
|
||||||
|
{"-10", "-10", -1, -1, true, true},
|
||||||
|
{"-1", "-1", -1, 0, true, true},
|
||||||
|
{"-0.1", "-0.1", -1, 1, true, true},
|
||||||
|
{"-0.01", "-0.01", -1, 2, true, true},
|
||||||
|
{"+0.", "0", 0, 0, true, true},
|
||||||
|
{"-0.", "0", 0, 0, true, true},
|
||||||
|
{".0", "0.0", 0, 1, true, true},
|
||||||
|
{"+.0", "0.0", 0, 1, true, true},
|
||||||
|
{"-.0", "0.0", 0, 1, true, true},
|
||||||
|
{"0.0000000000", "0.0000000000", 0, 10, true, true},
|
||||||
|
{"0.0000000001", "0.0000000001", 1, 10, true, true},
|
||||||
|
{"-0.0000000000", "0.0000000000", 0, 10, true, true},
|
||||||
|
{"-0.0000000001", "-0.0000000001", -1, 10, true, true},
|
||||||
|
{"-10", "-10", -10, 0, true, true},
|
||||||
|
{"+10", "10", 10, 0, true, true},
|
||||||
|
{"00", "0", 0, 0, true, true},
|
||||||
|
{"023", "23", 23, 0, true, true}, // decimal, not octal
|
||||||
|
{"-02.3", "-2.3", -23, 1, true, true}, // decimal, not octal
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecGetString(t *testing.T) {
|
||||||
|
z := new(inf.Dec)
|
||||||
|
for i, test := range decStringTests {
|
||||||
|
if !test.ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
z.SetUnscaled(test.val)
|
||||||
|
z.SetScale(test.scale)
|
||||||
|
|
||||||
|
s := z.String()
|
||||||
|
if s != test.out {
|
||||||
|
t.Errorf("#%da got %s; want %s", i, s, test.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
s = fmt.Sprintf("%d", z)
|
||||||
|
if s != test.out {
|
||||||
|
t.Errorf("#%db got %s; want %s", i, s, test.out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecSetString(t *testing.T) {
|
||||||
|
tmp := new(inf.Dec)
|
||||||
|
for i, test := range decStringTests {
|
||||||
|
if test.scale < 0 {
|
||||||
|
// SetString only supports scale >= 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// initialize to a non-zero value so that issues with parsing
|
||||||
|
// 0 are detected
|
||||||
|
tmp.Set(inf.NewDec(1234567890, 123))
|
||||||
|
n1, ok1 := new(inf.Dec).SetString(test.in)
|
||||||
|
n2, ok2 := tmp.SetString(test.in)
|
||||||
|
expected := inf.NewDec(test.val, test.scale)
|
||||||
|
if ok1 != test.ok || ok2 != test.ok {
|
||||||
|
t.Errorf("#%d (input '%s') ok incorrect (should be %t)", i, test.in, test.ok)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ok1 {
|
||||||
|
if n1 != nil {
|
||||||
|
t.Errorf("#%d (input '%s') n1 != nil", i, test.in)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ok2 {
|
||||||
|
if n2 != nil {
|
||||||
|
t.Errorf("#%d (input '%s') n2 != nil", i, test.in)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if n1.Cmp(expected) != 0 {
|
||||||
|
t.Errorf("#%d (input '%s') got: %s want: %d", i, test.in, n1, test.val)
|
||||||
|
}
|
||||||
|
if n2.Cmp(expected) != 0 {
|
||||||
|
t.Errorf("#%d (input '%s') got: %s want: %d", i, test.in, n2, test.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecScan(t *testing.T) {
|
||||||
|
tmp := new(inf.Dec)
|
||||||
|
for i, test := range decStringTests {
|
||||||
|
if test.scale < 0 {
|
||||||
|
// SetString only supports scale >= 0
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// initialize to a non-zero value so that issues with parsing
|
||||||
|
// 0 are detected
|
||||||
|
tmp.Set(inf.NewDec(1234567890, 123))
|
||||||
|
n1, n2 := new(inf.Dec), tmp
|
||||||
|
nn1, err1 := fmt.Sscan(test.in, n1)
|
||||||
|
nn2, err2 := fmt.Sscan(test.in, n2)
|
||||||
|
if !test.scanOk {
|
||||||
|
if err1 == nil || err2 == nil {
|
||||||
|
t.Errorf("#%d (input '%s') ok incorrect, should be %t", i, test.in, test.scanOk)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected := inf.NewDec(test.val, test.scale)
|
||||||
|
if nn1 != 1 || err1 != nil || nn2 != 1 || err2 != nil {
|
||||||
|
t.Errorf("#%d (input '%s') error %d %v, %d %v", i, test.in, nn1, err1, nn2, err2)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n1.Cmp(expected) != 0 {
|
||||||
|
t.Errorf("#%d (input '%s') got: %s want: %d", i, test.in, n1, test.val)
|
||||||
|
}
|
||||||
|
if n2.Cmp(expected) != 0 {
|
||||||
|
t.Errorf("#%d (input '%s') got: %s want: %d", i, test.in, n2, test.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decScanNextTests = []struct {
|
||||||
|
in string
|
||||||
|
ok bool
|
||||||
|
next rune
|
||||||
|
}{
|
||||||
|
{"", false, 0},
|
||||||
|
{"a", false, 'a'},
|
||||||
|
{"z", false, 'z'},
|
||||||
|
{"+", false, 0},
|
||||||
|
{"-", false, 0},
|
||||||
|
{"g", false, 'g'},
|
||||||
|
{".", false, 0},
|
||||||
|
{".-0", false, '-'},
|
||||||
|
{".+0", false, '+'},
|
||||||
|
{"0b", true, 'b'},
|
||||||
|
{"0x", true, 'x'},
|
||||||
|
{"0xg", true, 'x'},
|
||||||
|
{"0.0g", true, 'g'},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecScanNext(t *testing.T) {
|
||||||
|
for i, test := range decScanNextTests {
|
||||||
|
rdr := strings.NewReader(test.in)
|
||||||
|
n1 := new(inf.Dec)
|
||||||
|
nn1, _ := fmt.Fscan(rdr, n1)
|
||||||
|
if (test.ok && nn1 == 0) || (!test.ok && nn1 > 0) {
|
||||||
|
t.Errorf("#%d (input '%s') ok incorrect should be %t", i, test.in, test.ok)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r := rune(0)
|
||||||
|
nn2, err := fmt.Fscanf(rdr, "%c", &r)
|
||||||
|
if test.next != r {
|
||||||
|
t.Errorf("#%d (input '%s') next incorrect, got %c should be %c, %d, %v", i, test.in, r, test.next, nn2, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var decGobEncodingTests = []string{
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"10",
|
||||||
|
"42",
|
||||||
|
"1234567890",
|
||||||
|
"298472983472983471903246121093472394872319615612417471234712061",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecGobEncoding(t *testing.T) {
|
||||||
|
var medium bytes.Buffer
|
||||||
|
enc := gob.NewEncoder(&medium)
|
||||||
|
dec := gob.NewDecoder(&medium)
|
||||||
|
for i, test := range decGobEncodingTests {
|
||||||
|
for j := 0; j < 2; j++ {
|
||||||
|
for k := inf.Scale(-5); k <= 5; k++ {
|
||||||
|
medium.Reset() // empty buffer for each test case (in case of failures)
|
||||||
|
stest := test
|
||||||
|
if j != 0 {
|
||||||
|
// negative numbers
|
||||||
|
stest = "-" + test
|
||||||
|
}
|
||||||
|
var tx inf.Dec
|
||||||
|
tx.SetString(stest)
|
||||||
|
tx.SetScale(k) // test with positive, negative, and zero scale
|
||||||
|
if err := enc.Encode(&tx); err != nil {
|
||||||
|
t.Errorf("#%d%c: encoding failed: %s", i, 'a'+j, err)
|
||||||
|
}
|
||||||
|
var rx inf.Dec
|
||||||
|
if err := dec.Decode(&rx); err != nil {
|
||||||
|
t.Errorf("#%d%c: decoding failed: %s", i, 'a'+j, err)
|
||||||
|
}
|
||||||
|
if rx.Cmp(&tx) != 0 {
|
||||||
|
t.Errorf("#%d%c: transmission failed: got %s want %s", i, 'a'+j, &rx, &tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/example_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package inf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
import "speter.net/go/exp/math/dec/inf"
|
||||||
|
|
||||||
|
func ExampleDec_SetString() {
|
||||||
|
d := new(inf.Dec)
|
||||||
|
d.SetString("012345.67890") // decimal; leading 0 ignored; trailing 0 kept
|
||||||
|
fmt.Println(d)
|
||||||
|
// Output: 12345.67890
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDec_Scan() {
|
||||||
|
// The Scan function is rarely used directly;
|
||||||
|
// the fmt package recognizes it as an implementation of fmt.Scanner.
|
||||||
|
d := new(inf.Dec)
|
||||||
|
_, err := fmt.Sscan("184467440.73709551617", d)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error scanning value:", err)
|
||||||
|
} else {
|
||||||
|
fmt.Println(d)
|
||||||
|
}
|
||||||
|
// Output: 184467440.73709551617
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDec_QuoRound_scale2RoundDown() {
|
||||||
|
// 10 / 3 is an infinite decimal; it has no exact Dec representation
|
||||||
|
x, y := inf.NewDec(10, 0), inf.NewDec(3, 0)
|
||||||
|
// use 2 digits beyond the decimal point, round towards 0
|
||||||
|
z := new(inf.Dec).QuoRound(x, y, 2, inf.RoundDown)
|
||||||
|
fmt.Println(z)
|
||||||
|
// Output: 3.33
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDec_QuoRound_scale2RoundCeil() {
|
||||||
|
// -42 / 400 is an finite decimal with 3 digits beyond the decimal point
|
||||||
|
x, y := inf.NewDec(-42, 0), inf.NewDec(400, 0)
|
||||||
|
// use 2 digits beyond decimal point, round towards positive infinity
|
||||||
|
z := new(inf.Dec).QuoRound(x, y, 2, inf.RoundCeil)
|
||||||
|
fmt.Println(z)
|
||||||
|
// Output: -0.10
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDec_QuoExact_ok() {
|
||||||
|
// 1 / 25 is a finite decimal; it has exact Dec representation
|
||||||
|
x, y := inf.NewDec(1, 0), inf.NewDec(25, 0)
|
||||||
|
z := new(inf.Dec).QuoExact(x, y)
|
||||||
|
fmt.Println(z)
|
||||||
|
// Output: 0.04
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleDec_QuoExact_fail() {
|
||||||
|
// 1 / 3 is an infinite decimal; it has no exact Dec representation
|
||||||
|
x, y := inf.NewDec(1, 0), inf.NewDec(3, 0)
|
||||||
|
z := new(inf.Dec).QuoExact(x, y)
|
||||||
|
fmt.Println(z)
|
||||||
|
// Output: <nil>
|
||||||
|
}
|
145
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder.go
generated
vendored
Normal file
145
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder.go
generated
vendored
Normal file
@@ -0,0 +1,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
|
||||||
|
})}
|
||||||
|
}
|
72
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder_example_test.go
generated
vendored
Normal file
72
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder_example_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package inf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"speter.net/go/exp/math/dec/inf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This example displays the results of Dec.Round with each of the Rounders.
|
||||||
|
//
|
||||||
|
func ExampleRounder() {
|
||||||
|
var vals = []struct {
|
||||||
|
x string
|
||||||
|
s inf.Scale
|
||||||
|
}{
|
||||||
|
{"-0.18", 1}, {"-0.15", 1}, {"-0.12", 1}, {"-0.10", 1},
|
||||||
|
{"-0.08", 1}, {"-0.05", 1}, {"-0.02", 1}, {"0.00", 1},
|
||||||
|
{"0.02", 1}, {"0.05", 1}, {"0.08", 1}, {"0.10", 1},
|
||||||
|
{"0.12", 1}, {"0.15", 1}, {"0.18", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
var rounders = []struct {
|
||||||
|
name string
|
||||||
|
rounder inf.Rounder
|
||||||
|
}{
|
||||||
|
{"RoundDown", inf.RoundDown}, {"RoundUp", inf.RoundUp},
|
||||||
|
{"RoundCeil", inf.RoundCeil}, {"RoundFloor", inf.RoundFloor},
|
||||||
|
{"RoundHalfDown", inf.RoundHalfDown}, {"RoundHalfUp", inf.RoundHalfUp},
|
||||||
|
{"RoundHalfEven", inf.RoundHalfEven}, {"RoundExact", inf.RoundExact},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("The results of new(inf.Dec).Round(x, s, inf.RoundXXX):\n")
|
||||||
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.AlignRight)
|
||||||
|
fmt.Fprint(w, "x\ts\t|\t")
|
||||||
|
for _, r := range rounders {
|
||||||
|
fmt.Fprintf(w, "%s\t", r.name[5:])
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
for _, v := range vals {
|
||||||
|
fmt.Fprintf(w, "%s\t%d\t|\t", v.x, v.s)
|
||||||
|
for _, r := range rounders {
|
||||||
|
x, _ := new(inf.Dec).SetString(v.x)
|
||||||
|
z := new(inf.Dec).Round(x, v.s, r.rounder)
|
||||||
|
fmt.Fprintf(w, "%d\t", z)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
}
|
||||||
|
w.Flush()
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// The results of new(inf.Dec).Round(x, s, inf.RoundXXX):
|
||||||
|
//
|
||||||
|
// x s | Down Up Ceil Floor HalfDown HalfUp HalfEven Exact
|
||||||
|
// -0.18 1 | -0.1 -0.2 -0.1 -0.2 -0.2 -0.2 -0.2 <nil>
|
||||||
|
// -0.15 1 | -0.1 -0.2 -0.1 -0.2 -0.1 -0.2 -0.2 <nil>
|
||||||
|
// -0.12 1 | -0.1 -0.2 -0.1 -0.2 -0.1 -0.1 -0.1 <nil>
|
||||||
|
// -0.10 1 | -0.1 -0.1 -0.1 -0.1 -0.1 -0.1 -0.1 -0.1
|
||||||
|
// -0.08 1 | 0.0 -0.1 0.0 -0.1 -0.1 -0.1 -0.1 <nil>
|
||||||
|
// -0.05 1 | 0.0 -0.1 0.0 -0.1 0.0 -0.1 0.0 <nil>
|
||||||
|
// -0.02 1 | 0.0 -0.1 0.0 -0.1 0.0 0.0 0.0 <nil>
|
||||||
|
// 0.00 1 | 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
|
||||||
|
// 0.02 1 | 0.0 0.1 0.1 0.0 0.0 0.0 0.0 <nil>
|
||||||
|
// 0.05 1 | 0.0 0.1 0.1 0.0 0.0 0.1 0.0 <nil>
|
||||||
|
// 0.08 1 | 0.0 0.1 0.1 0.0 0.1 0.1 0.1 <nil>
|
||||||
|
// 0.10 1 | 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1
|
||||||
|
// 0.12 1 | 0.1 0.2 0.2 0.1 0.1 0.1 0.1 <nil>
|
||||||
|
// 0.15 1 | 0.1 0.2 0.2 0.1 0.1 0.2 0.2 <nil>
|
||||||
|
// 0.18 1 | 0.1 0.2 0.2 0.1 0.2 0.2 0.2 <nil>
|
||||||
|
|
||||||
|
}
|
109
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder_test.go
generated
vendored
Normal file
109
Godeps/_workspace/src/speter.net/go/exp/math/dec/inf/rounder_test.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package inf_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"speter.net/go/exp/math/dec/inf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var decRounderInputs = [...]struct {
|
||||||
|
quo *inf.Dec
|
||||||
|
rA, rB *big.Int
|
||||||
|
}{
|
||||||
|
// examples from go language spec
|
||||||
|
{inf.NewDec(1, 0), big.NewInt(2), big.NewInt(3)}, // 5 / 3
|
||||||
|
{inf.NewDec(-1, 0), big.NewInt(-2), big.NewInt(3)}, // -5 / 3
|
||||||
|
{inf.NewDec(-1, 0), big.NewInt(2), big.NewInt(-3)}, // 5 / -3
|
||||||
|
{inf.NewDec(1, 0), big.NewInt(-2), big.NewInt(-3)}, // -5 / -3
|
||||||
|
// examples from godoc
|
||||||
|
{inf.NewDec(-1, 1), big.NewInt(-8), big.NewInt(10)},
|
||||||
|
{inf.NewDec(-1, 1), big.NewInt(-5), big.NewInt(10)},
|
||||||
|
{inf.NewDec(-1, 1), big.NewInt(-2), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(-8), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(-5), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(-2), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(0), big.NewInt(1)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(2), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(5), big.NewInt(10)},
|
||||||
|
{inf.NewDec(0, 1), big.NewInt(8), big.NewInt(10)},
|
||||||
|
{inf.NewDec(1, 1), big.NewInt(2), big.NewInt(10)},
|
||||||
|
{inf.NewDec(1, 1), big.NewInt(5), big.NewInt(10)},
|
||||||
|
{inf.NewDec(1, 1), big.NewInt(8), big.NewInt(10)},
|
||||||
|
}
|
||||||
|
|
||||||
|
var decRounderResults = [...]struct {
|
||||||
|
rounder inf.Rounder
|
||||||
|
results [len(decRounderInputs)]*inf.Dec
|
||||||
|
}{
|
||||||
|
{inf.RoundExact, [...]*inf.Dec{nil, nil, nil, nil,
|
||||||
|
nil, nil, nil, nil, nil, nil,
|
||||||
|
inf.NewDec(0, 1), nil, nil, nil, nil, nil, nil}},
|
||||||
|
{inf.RoundDown, [...]*inf.Dec{
|
||||||
|
inf.NewDec(1, 0), inf.NewDec(-1, 0), inf.NewDec(-1, 0), inf.NewDec(1, 0),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(-1, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(1, 1), inf.NewDec(1, 1)}},
|
||||||
|
{inf.RoundUp, [...]*inf.Dec{
|
||||||
|
inf.NewDec(2, 0), inf.NewDec(-2, 0), inf.NewDec(-2, 0), inf.NewDec(2, 0),
|
||||||
|
inf.NewDec(-2, 1), inf.NewDec(-2, 1), inf.NewDec(-2, 1),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(-1, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(1, 1), inf.NewDec(1, 1),
|
||||||
|
inf.NewDec(2, 1), inf.NewDec(2, 1), inf.NewDec(2, 1)}},
|
||||||
|
{inf.RoundHalfDown, [...]*inf.Dec{
|
||||||
|
inf.NewDec(2, 0), inf.NewDec(-2, 0), inf.NewDec(-2, 0), inf.NewDec(2, 0),
|
||||||
|
inf.NewDec(-2, 1), inf.NewDec(-1, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(1, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(1, 1), inf.NewDec(2, 1)}},
|
||||||
|
{inf.RoundHalfUp, [...]*inf.Dec{
|
||||||
|
inf.NewDec(2, 0), inf.NewDec(-2, 0), inf.NewDec(-2, 0), inf.NewDec(2, 0),
|
||||||
|
inf.NewDec(-2, 1), inf.NewDec(-2, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(-1, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(1, 1), inf.NewDec(1, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(2, 1), inf.NewDec(2, 1)}},
|
||||||
|
{inf.RoundHalfEven, [...]*inf.Dec{
|
||||||
|
inf.NewDec(2, 0), inf.NewDec(-2, 0), inf.NewDec(-2, 0), inf.NewDec(2, 0),
|
||||||
|
inf.NewDec(-2, 1), inf.NewDec(-2, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(1, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(2, 1), inf.NewDec(2, 1)}},
|
||||||
|
{inf.RoundFloor, [...]*inf.Dec{
|
||||||
|
inf.NewDec(1, 0), inf.NewDec(-2, 0), inf.NewDec(-2, 0), inf.NewDec(1, 0),
|
||||||
|
inf.NewDec(-2, 1), inf.NewDec(-2, 1), inf.NewDec(-2, 1),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(-1, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(1, 1), inf.NewDec(1, 1)}},
|
||||||
|
{inf.RoundCeil, [...]*inf.Dec{
|
||||||
|
inf.NewDec(2, 0), inf.NewDec(-1, 0), inf.NewDec(-1, 0), inf.NewDec(2, 0),
|
||||||
|
inf.NewDec(-1, 1), inf.NewDec(-1, 1), inf.NewDec(-1, 1),
|
||||||
|
inf.NewDec(0, 1), inf.NewDec(0, 1), inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(0, 1),
|
||||||
|
inf.NewDec(1, 1), inf.NewDec(1, 1), inf.NewDec(1, 1),
|
||||||
|
inf.NewDec(2, 1), inf.NewDec(2, 1), inf.NewDec(2, 1)}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecRounders(t *testing.T) {
|
||||||
|
for i, a := range decRounderResults {
|
||||||
|
for j, input := range decRounderInputs {
|
||||||
|
q := new(inf.Dec).Set(input.quo)
|
||||||
|
rA, rB := new(big.Int).Set(input.rA), new(big.Int).Set(input.rB)
|
||||||
|
res := a.rounder.Round(new(inf.Dec), q, rA, rB)
|
||||||
|
if a.results[j] == nil && res == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (a.results[j] == nil && res != nil) ||
|
||||||
|
(a.results[j] != nil && res == nil) ||
|
||||||
|
a.results[j].Cmp(res) != 0 {
|
||||||
|
t.Errorf("#%d,%d Rounder got %v; expected %v", i, j, res, a.results[j])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
412
pkg/api/resource/quantity.go
Normal file
412
pkg/api/resource/quantity.go
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"speter.net/go/exp/math/dec/inf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quantity is a fixed-point representation of a number.
|
||||||
|
// It provides convenient marshaling/unmarshaling in JSON and YAML,
|
||||||
|
// in addition to String() and Int64() accessors.
|
||||||
|
//
|
||||||
|
// The serialization format is:
|
||||||
|
//
|
||||||
|
// <quantity> ::= <signedNumber><suffix>
|
||||||
|
// (Note that <suffix> may be empty, from the "" case in <decimalSI>.)
|
||||||
|
// <digit> ::= 0 | 1 | ... | 9
|
||||||
|
// <digits> ::= <digit> | <digit><digits>
|
||||||
|
// <number> ::= <digits> | <digits>.<digits> | <digits>. | .<digits>
|
||||||
|
// <sign> ::= "+" | "-"
|
||||||
|
// <signedNumber> ::= <number> | <sign><number>
|
||||||
|
// <suffix> ::= <binarySI> | <decimalExponent> | <decimalSI>
|
||||||
|
// <binarySI> ::= Ki | Mi | Gi | Ti | Pi | Ei
|
||||||
|
// (International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)
|
||||||
|
// <decimalSI> ::= m | "" | k | M | G | T | P | E
|
||||||
|
// (Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)
|
||||||
|
// <decimalExponent> ::= "e" <signedNumber> | "E" <signedNumber>
|
||||||
|
//
|
||||||
|
// No matter which of the three exponent forms is used, no quantity may represent
|
||||||
|
// a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal
|
||||||
|
// places. Numbers larger or more precise will be capped or rounded up.
|
||||||
|
// (E.g.: 0.1m will rounded up to 1m.)
|
||||||
|
// This may be extended in the future if we require larger or smaller quantities.
|
||||||
|
//
|
||||||
|
// When a Quantity is parsed from a string, it will remember the type of suffix
|
||||||
|
// it had, and will use the same type again when it is serialized.
|
||||||
|
//
|
||||||
|
// Before serializing, Quantity will be put in "canonical form".
|
||||||
|
// This means that Exponent/suffix will be adjusted up or down (with a
|
||||||
|
// corresponding increase or decrease in Mantissa) such that:
|
||||||
|
// a. No precision is lost
|
||||||
|
// b. No fractional digits will be emitted
|
||||||
|
// c. The exponent (or suffix) is as large as possible.
|
||||||
|
// The sign will be omitted unless the number is negative.
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// 1.5 will be serialized as "1500m"
|
||||||
|
// 1.5Gi will be serialized as "1536Mi"
|
||||||
|
//
|
||||||
|
// NOTE: We reserve the right to amend this canonical format, perhaps to
|
||||||
|
// allow 1.5 to be canonical.
|
||||||
|
// TODO: Remove above disclaimer after all bikeshedding about format is over,
|
||||||
|
// or after March 2015.
|
||||||
|
//
|
||||||
|
// Note that the quantity will NEVER be internally represented by a
|
||||||
|
// floating point number. That is the whole point of this exercise.
|
||||||
|
//
|
||||||
|
// Non-canonical values will still parse as long as they are well formed,
|
||||||
|
// but will be re-emitted in their canonical form. (So always use canonical
|
||||||
|
// form, or don't diff.)
|
||||||
|
//
|
||||||
|
// This format is intended to make it difficult to use these numbers without
|
||||||
|
// writing some sort of special handling code in the hopes that that will
|
||||||
|
// cause implementors to also use a fixed point implementation.
|
||||||
|
type Quantity struct {
|
||||||
|
// Amount is public, so you can manipulate it if the accessor
|
||||||
|
// functions are not sufficient.
|
||||||
|
Amount *inf.Dec
|
||||||
|
|
||||||
|
// Change Format at will. See the comment for Canonicalize for
|
||||||
|
// more details.
|
||||||
|
Format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format lists the three possible formattings of a quantity.
|
||||||
|
type Format string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DecimalExponent = Format("DecimalExponent") // e.g., 12e6
|
||||||
|
BinarySI = Format("BinarySI") // e.g., 12Mi (12 * 2^20)
|
||||||
|
DecimalSI = Format("DecimalSI") // e.g., 12M (12 * 10^6)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseOrDie turns the given string into a quantity or panics; for tests
|
||||||
|
// or others cases where you know the string is valid.
|
||||||
|
func ParseOrDie(str string) *Quantity {
|
||||||
|
q, err := ParseQuantity(str)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot parse '%v': %v", str, err))
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// splitREString is used to separate a number from its suffix; as such,
|
||||||
|
// this is overly permissive, but that's OK-- it will be checked later.
|
||||||
|
splitREString = "^([+-]?[0-9.]+)([eEimkKMGTP]*[-+]?[0-9]*)$"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// splitRE is used to get the various parts of a number.
|
||||||
|
splitRE = regexp.MustCompile(splitREString)
|
||||||
|
|
||||||
|
// Errors that could happen while parsing a string.
|
||||||
|
ErrFormatWrong = errors.New("quantities must match the regular expression '" + splitREString + "'")
|
||||||
|
ErrNumeric = errors.New("unable to parse numeric part of quantity")
|
||||||
|
ErrSuffix = errors.New("unable to parse quantity's suffix")
|
||||||
|
|
||||||
|
// Commonly needed big.Int values-- treat as read only!
|
||||||
|
bigTen = big.NewInt(10)
|
||||||
|
bigZero = big.NewInt(0)
|
||||||
|
bigOne = big.NewInt(1)
|
||||||
|
bigThousand = big.NewInt(1000)
|
||||||
|
big1024 = big.NewInt(1024)
|
||||||
|
|
||||||
|
// Commonly needed inf.Dec values-- treat as read only!
|
||||||
|
decZero = inf.NewDec(0, 0)
|
||||||
|
decOne = inf.NewDec(1, 0)
|
||||||
|
decMinusOne = inf.NewDec(-1, 0)
|
||||||
|
decThousand = inf.NewDec(1000, 0)
|
||||||
|
dec1024 = inf.NewDec(1024, 0)
|
||||||
|
decMinus1024 = inf.NewDec(-1024, 0)
|
||||||
|
|
||||||
|
// Largest (in magnitude) number allowed.
|
||||||
|
maxAllowed = inf.NewDec((1<<63)-1, 0) // == max int64
|
||||||
|
|
||||||
|
// The maximum value we can represent milli-units for.
|
||||||
|
// Compare with the return value of Quantity.Value() to
|
||||||
|
// see if it's safe to use Quantity.MilliValue().
|
||||||
|
MaxMilliValue = ((1 << 63) - 1) / 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseQuantity turns str into a Quantity, or returns an error.
|
||||||
|
func ParseQuantity(str string) (*Quantity, error) {
|
||||||
|
parts := splitRE.FindStringSubmatch(strings.TrimSpace(str))
|
||||||
|
// regexp returns are entire match, followed by an entry for each () section.
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return nil, ErrFormatWrong
|
||||||
|
}
|
||||||
|
|
||||||
|
amount := new(inf.Dec)
|
||||||
|
if _, ok := amount.SetString(parts[1]); !ok {
|
||||||
|
return nil, ErrNumeric
|
||||||
|
}
|
||||||
|
|
||||||
|
base, exponent, format, ok := quantitySuffixer.interpret(suffix(parts[2]))
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// So that no bigOne but us has to think about suffixes, remove it.
|
||||||
|
if base == 10 {
|
||||||
|
amount.SetScale(amount.Scale() + inf.Scale(-exponent))
|
||||||
|
} else if base == 2 {
|
||||||
|
// numericSuffix = 2 ** exponent
|
||||||
|
numericSuffix := big.NewInt(1).Lsh(bigOne, uint(exponent))
|
||||||
|
ub := amount.UnscaledBig()
|
||||||
|
amount.SetUnscaledBig(ub.Mul(ub, numericSuffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap at min/max bounds.
|
||||||
|
sign := amount.Sign()
|
||||||
|
if sign == -1 {
|
||||||
|
amount.Neg(amount)
|
||||||
|
}
|
||||||
|
// This rounds non-bigZero values up to the minimum representable
|
||||||
|
// value, under the theory that if you want some resources, you
|
||||||
|
// should get some resources, even if you asked for way too small
|
||||||
|
// of an amount.
|
||||||
|
// Arguably, this should be inf.RoundHalfUp (normal rounding), but
|
||||||
|
// that would have the side effect of rounding values < .5m to bigZero.
|
||||||
|
amount.Round(amount, 3, inf.RoundUp)
|
||||||
|
|
||||||
|
// The max is just a simple cap.
|
||||||
|
if amount.Cmp(maxAllowed) > 0 {
|
||||||
|
amount.Set(maxAllowed)
|
||||||
|
}
|
||||||
|
if format == BinarySI && amount.Cmp(decOne) < 0 && amount.Cmp(decZero) > 0 {
|
||||||
|
// This avoids rounding and hopefully confusion, too.
|
||||||
|
format = DecimalSI
|
||||||
|
}
|
||||||
|
if sign == -1 {
|
||||||
|
amount.Neg(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Quantity{amount, format}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeFactors divides in a loop; the return values have the property that
|
||||||
|
// d == result * factor ^ times
|
||||||
|
// d may be modified in place.
|
||||||
|
// If d == 0, then the return values will be (0, 0)
|
||||||
|
func removeFactors(d, factor *big.Int) (result *big.Int, times int) {
|
||||||
|
q := big.NewInt(0)
|
||||||
|
m := big.NewInt(0)
|
||||||
|
for d.Cmp(bigZero) != 0 {
|
||||||
|
q.DivMod(d, factor, m)
|
||||||
|
if m.Cmp(bigZero) != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
times++
|
||||||
|
d, q = q, d
|
||||||
|
}
|
||||||
|
return d, times
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonicalize returns the canonical form of q and its suffix (see comment on Quantity).
|
||||||
|
//
|
||||||
|
// Note about BinarySI:
|
||||||
|
// * If q.Format is set to BinarySI and q.Amount represents a non-bigZero value between
|
||||||
|
// -1 and +1, it will be emitted as if q.Format were DecimalSI.
|
||||||
|
// * Otherwise, if q.Format is set to BinarySI, frational parts of q.Amount will be
|
||||||
|
// rounded up. (1.1i becomes 2i.)
|
||||||
|
func (q *Quantity) Canonicalize() (string, suffix) {
|
||||||
|
if q.Amount == nil {
|
||||||
|
return "0", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
format := q.Format
|
||||||
|
switch format {
|
||||||
|
case DecimalExponent, DecimalSI:
|
||||||
|
case BinarySI:
|
||||||
|
if q.Amount.Cmp(decMinus1024) > 0 && q.Amount.Cmp(dec1024) < 0 {
|
||||||
|
// This avoids rounding and hopefully confusion, too.
|
||||||
|
format = DecimalSI
|
||||||
|
} else {
|
||||||
|
tmp := &inf.Dec{}
|
||||||
|
tmp.Round(q.Amount, 0, inf.RoundUp)
|
||||||
|
if tmp.Cmp(q.Amount) != 0 {
|
||||||
|
// Don't lose precision-- show as DecimalSI
|
||||||
|
format = DecimalSI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
format = DecimalExponent
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If BinarySI formatting is requested but would cause rounding, upgrade to
|
||||||
|
// bigOne of the other formats.
|
||||||
|
switch format {
|
||||||
|
case DecimalExponent, DecimalSI:
|
||||||
|
mantissa := q.Amount.UnscaledBig()
|
||||||
|
exponent := int(-q.Amount.Scale())
|
||||||
|
amount := big.NewInt(0).Set(mantissa)
|
||||||
|
// move all factors of 10 into the exponent for easy reasoning
|
||||||
|
amount, times := removeFactors(amount, bigTen)
|
||||||
|
exponent += times
|
||||||
|
|
||||||
|
// make sure exponent is a multiple of 3
|
||||||
|
for exponent%3 != 0 {
|
||||||
|
amount.Mul(amount, bigTen)
|
||||||
|
exponent--
|
||||||
|
}
|
||||||
|
|
||||||
|
suffix, _ := quantitySuffixer.construct(10, exponent, format)
|
||||||
|
number := amount.String()
|
||||||
|
return number, suffix
|
||||||
|
case BinarySI:
|
||||||
|
tmp := &inf.Dec{}
|
||||||
|
tmp.Round(q.Amount, 0, inf.RoundUp)
|
||||||
|
|
||||||
|
amount, exponent := removeFactors(tmp.UnscaledBig(), big1024)
|
||||||
|
suffix, _ := quantitySuffixer.construct(2, exponent*10, format)
|
||||||
|
number := amount.String()
|
||||||
|
return number, suffix
|
||||||
|
}
|
||||||
|
return "0", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the Quantity as a string.
|
||||||
|
func (q *Quantity) String() string {
|
||||||
|
number, suffix := q.Canonicalize()
|
||||||
|
return number + string(suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
|
func (q Quantity) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"` + q.String() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaller interface.
|
||||||
|
func (q *Quantity) UnmarshalJSON(value []byte) error {
|
||||||
|
str := string(value)
|
||||||
|
parsed, err := ParseQuantity(strings.Trim(str, `"`))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// This copy is safe because parsed will not be referred to again.
|
||||||
|
*q = *parsed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewQuantity returns a new Quantity representing the given
|
||||||
|
// value in the given format.
|
||||||
|
func NewQuantity(value int64, format Format) *Quantity {
|
||||||
|
return &Quantity{
|
||||||
|
Amount: inf.NewDec(value, 0),
|
||||||
|
Format: format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMilliQuantity returns a new Quantity representing the given
|
||||||
|
// value * 1/1000 in the given format. Note that BinarySI formatting
|
||||||
|
// will round fractional values, and will be changed to DecimalSI for
|
||||||
|
// values x where (-1 < x < 1) && (x != 0).
|
||||||
|
func NewMilliQuantity(value int64, format Format) *Quantity {
|
||||||
|
return &Quantity{
|
||||||
|
Amount: inf.NewDec(value, 3),
|
||||||
|
Format: format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the value of q; any fractional part will be lost.
|
||||||
|
func (q *Quantity) Value() int64 {
|
||||||
|
if q.Amount == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
tmp := &inf.Dec{}
|
||||||
|
return tmp.Round(q.Amount, 0, inf.RoundUp).UnscaledBig().Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MilliValue returns the value of q * 1000; this could overflow an int64;
|
||||||
|
// if that's a concern, call Value() first to verify the number is small enough.
|
||||||
|
func (q *Quantity) MilliValue() int64 {
|
||||||
|
if q.Amount == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
tmp := &inf.Dec{}
|
||||||
|
return tmp.Round(tmp.Mul(q.Amount, decThousand), 0, inf.RoundUp).UnscaledBig().Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets q's value to be value.
|
||||||
|
func (q *Quantity) Set(value int64) {
|
||||||
|
if q.Amount == nil {
|
||||||
|
q.Amount = &inf.Dec{}
|
||||||
|
}
|
||||||
|
q.Amount.SetUnscaled(value)
|
||||||
|
q.Amount.SetScale(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMilli sets q's value to be value * 1/1000.
|
||||||
|
func (q *Quantity) SetMilli(value int64) {
|
||||||
|
if q.Amount == nil {
|
||||||
|
q.Amount = &inf.Dec{}
|
||||||
|
}
|
||||||
|
q.Amount.SetUnscaled(value)
|
||||||
|
q.Amount.SetScale(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy is a convenience function that makes a deep copy for you. Non-deep
|
||||||
|
// copies of quantities share pointers and you will regret that.
|
||||||
|
func (q *Quantity) Copy() *Quantity {
|
||||||
|
if q.Amount == nil {
|
||||||
|
return NewQuantity(0, q.Format)
|
||||||
|
}
|
||||||
|
tmp := &inf.Dec{}
|
||||||
|
return &Quantity{
|
||||||
|
Amount: tmp.Set(q.Amount),
|
||||||
|
Format: q.Format,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qFlag is a helper type for the Flag function
|
||||||
|
type qFlag struct {
|
||||||
|
dest *Quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qf qFlag) Set(val string) error {
|
||||||
|
q, err := ParseQuantity(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// This copy is OK because q will not be referenced again.
|
||||||
|
*qf.dest = *q
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qf qFlag) String() string {
|
||||||
|
return qf.dest.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuantityFlag is a helper that makes a quantity flag (using standard flag package).
|
||||||
|
// Will panic if defaultValue is not a valid quantity.
|
||||||
|
func QuantityFlag(flagName, defaultValue, description string) *Quantity {
|
||||||
|
q, err := ParseQuantity(defaultValue)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("can't use %v as a quantity: %v", defaultValue, err))
|
||||||
|
}
|
||||||
|
flag.Var(qFlag{q}, flagName, description)
|
||||||
|
return q
|
||||||
|
}
|
59
pkg/api/resource/quantity_example_test.go
Normal file
59
pkg/api/resource/quantity_example_test.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 resource_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFormat() {
|
||||||
|
memorySize := resource.NewQuantity(5*1024*1024*1024, resource.BinarySI)
|
||||||
|
fmt.Printf("memorySize = %v\n", memorySize)
|
||||||
|
|
||||||
|
diskSize := resource.NewQuantity(5*1000*1000*1000, resource.DecimalSI)
|
||||||
|
fmt.Printf("diskSize = %v\n", diskSize)
|
||||||
|
|
||||||
|
cores := resource.NewMilliQuantity(5300, resource.DecimalSI)
|
||||||
|
fmt.Printf("cores = %v\n", cores)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// memorySize = 5Gi
|
||||||
|
// diskSize = 5G
|
||||||
|
// cores = 5300m
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleParseOrDie() {
|
||||||
|
memorySize := resource.ParseOrDie("5Gi")
|
||||||
|
fmt.Printf("memorySize = %v (%v)\n", memorySize.Value(), memorySize.Format)
|
||||||
|
|
||||||
|
diskSize := resource.ParseOrDie("5G")
|
||||||
|
fmt.Printf("diskSize = %v (%v)\n", diskSize.Value(), diskSize.Format)
|
||||||
|
|
||||||
|
cores := resource.ParseOrDie("5300m")
|
||||||
|
fmt.Printf("milliCores = %v (%v)\n", cores.MilliValue(), cores.Format)
|
||||||
|
|
||||||
|
cores2 := resource.ParseOrDie("5.4")
|
||||||
|
fmt.Printf("milliCores = %v (%v)\n", cores2.MilliValue(), cores2.Format)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// memorySize = 5368709120 (BinarySI)
|
||||||
|
// diskSize = 5000000000 (DecimalSI)
|
||||||
|
// milliCores = 5300 (DecimalSI)
|
||||||
|
// milliCores = 5400 (DecimalSI)
|
||||||
|
}
|
489
pkg/api/resource/quantity_test.go
Normal file
489
pkg/api/resource/quantity_test.go
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"reflect"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
fuzz "github.com/google/gofuzz"
|
||||||
|
"speter.net/go/exp/math/dec/inf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testQuantityFlag = QuantityFlag("quantityFlag", "1M", "dummy flag for testing the quantity flag mechanism")
|
||||||
|
)
|
||||||
|
|
||||||
|
func dec(i int64, exponent int) *inf.Dec {
|
||||||
|
// See the below test-- scale is the negative of an exponent.
|
||||||
|
return inf.NewDec(i, inf.Scale(-exponent))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDec(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
got *inf.Dec
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{dec(1, 0), "1"},
|
||||||
|
{dec(1, 1), "10"},
|
||||||
|
{dec(5, 2), "500"},
|
||||||
|
{dec(8, 3), "8000"},
|
||||||
|
{dec(2, 0), "2"},
|
||||||
|
{dec(1, -1), "0.1"},
|
||||||
|
{dec(3, -2), "0.03"},
|
||||||
|
{dec(4, -3), "0.004"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
if e, a := item.expect, item.got.String(); e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuantityParse(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
input string
|
||||||
|
expect Quantity
|
||||||
|
}{
|
||||||
|
{"0", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
{"0m", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
{"0Ki", Quantity{dec(0, 0), BinarySI}},
|
||||||
|
{"0k", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
{"0Mi", Quantity{dec(0, 0), BinarySI}},
|
||||||
|
{"0M", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
{"0Gi", Quantity{dec(0, 0), BinarySI}},
|
||||||
|
{"0G", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
{"0Ti", Quantity{dec(0, 0), BinarySI}},
|
||||||
|
{"0T", Quantity{dec(0, 0), DecimalSI}},
|
||||||
|
|
||||||
|
// Binary suffixes
|
||||||
|
{"1Ki", Quantity{dec(1024, 0), BinarySI}},
|
||||||
|
{"8Ki", Quantity{dec(8*1024, 0), BinarySI}},
|
||||||
|
{"7Mi", Quantity{dec(7*1024*1024, 0), BinarySI}},
|
||||||
|
{"6Gi", Quantity{dec(6*1024*1024*1024, 0), BinarySI}},
|
||||||
|
{"5Ti", Quantity{dec(5*1024*1024*1024*1024, 0), BinarySI}},
|
||||||
|
{"4Pi", Quantity{dec(4*1024*1024*1024*1024*1024, 0), BinarySI}},
|
||||||
|
{"3Ei", Quantity{dec(3*1024*1024*1024*1024*1024*1024, 0), BinarySI}},
|
||||||
|
|
||||||
|
{"10Ti", Quantity{dec(10*1024*1024*1024*1024, 0), BinarySI}},
|
||||||
|
{"100Ti", Quantity{dec(100*1024*1024*1024*1024, 0), BinarySI}},
|
||||||
|
|
||||||
|
// Decimal suffixes
|
||||||
|
{"3m", Quantity{dec(3, -3), DecimalSI}},
|
||||||
|
{"9", Quantity{dec(9, 0), DecimalSI}},
|
||||||
|
{"8k", Quantity{dec(8, 3), DecimalSI}},
|
||||||
|
{"7M", Quantity{dec(7, 6), DecimalSI}},
|
||||||
|
{"6G", Quantity{dec(6, 9), DecimalSI}},
|
||||||
|
{"5T", Quantity{dec(5, 12), DecimalSI}},
|
||||||
|
{"40T", Quantity{dec(4, 13), DecimalSI}},
|
||||||
|
{"300T", Quantity{dec(3, 14), DecimalSI}},
|
||||||
|
{"2P", Quantity{dec(2, 15), DecimalSI}},
|
||||||
|
{"1E", Quantity{dec(1, 18), DecimalSI}},
|
||||||
|
|
||||||
|
// Decimal exponents
|
||||||
|
{"1E-3", Quantity{dec(1, -3), DecimalExponent}},
|
||||||
|
{"1e3", Quantity{dec(1, 3), DecimalExponent}},
|
||||||
|
{"1E6", Quantity{dec(1, 6), DecimalExponent}},
|
||||||
|
{"1e9", Quantity{dec(1, 9), DecimalExponent}},
|
||||||
|
{"1E12", Quantity{dec(1, 12), DecimalExponent}},
|
||||||
|
{"1e15", Quantity{dec(1, 15), DecimalExponent}},
|
||||||
|
{"1E18", Quantity{dec(1, 18), DecimalExponent}},
|
||||||
|
|
||||||
|
// Nonstandard but still parsable
|
||||||
|
{"1e14", Quantity{dec(1, 14), DecimalExponent}},
|
||||||
|
{"1e13", Quantity{dec(1, 13), DecimalExponent}},
|
||||||
|
{"1e3", Quantity{dec(1, 3), DecimalExponent}},
|
||||||
|
{"100.035k", Quantity{dec(100035, 0), DecimalSI}},
|
||||||
|
|
||||||
|
// Things that look like floating point
|
||||||
|
{"0.001", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{"0.0005k", Quantity{dec(5, -1), DecimalSI}},
|
||||||
|
{"0.005", Quantity{dec(5, -3), DecimalSI}},
|
||||||
|
{"0.05", Quantity{dec(5, -2), DecimalSI}},
|
||||||
|
{"0.5", Quantity{dec(5, -1), DecimalSI}},
|
||||||
|
{"0.00050k", Quantity{dec(5, -1), DecimalSI}},
|
||||||
|
{"0.00500", Quantity{dec(5, -3), DecimalSI}},
|
||||||
|
{"0.05000", Quantity{dec(5, -2), DecimalSI}},
|
||||||
|
{"0.50000", Quantity{dec(5, -1), DecimalSI}},
|
||||||
|
{"0.5e0", Quantity{dec(5, -1), DecimalExponent}},
|
||||||
|
{"0.5e-1", Quantity{dec(5, -2), DecimalExponent}},
|
||||||
|
{"0.5e-2", Quantity{dec(5, -3), DecimalExponent}},
|
||||||
|
{"0.5e0", Quantity{dec(5, -1), DecimalExponent}},
|
||||||
|
{"10.035M", Quantity{dec(10035, 3), DecimalSI}},
|
||||||
|
|
||||||
|
{"1.2e3", Quantity{dec(12, 2), DecimalExponent}},
|
||||||
|
{"1.3E+6", Quantity{dec(13, 5), DecimalExponent}},
|
||||||
|
{"1.40e9", Quantity{dec(14, 8), DecimalExponent}},
|
||||||
|
{"1.53E12", Quantity{dec(153, 10), DecimalExponent}},
|
||||||
|
{"1.6e15", Quantity{dec(16, 14), DecimalExponent}},
|
||||||
|
{"1.7E18", Quantity{dec(17, 17), DecimalExponent}},
|
||||||
|
|
||||||
|
{"9.01", Quantity{dec(901, -2), DecimalSI}},
|
||||||
|
{"8.1k", Quantity{dec(81, 2), DecimalSI}},
|
||||||
|
{"7.123456M", Quantity{dec(7123456, 0), DecimalSI}},
|
||||||
|
{"6.987654321G", Quantity{dec(6987654321, 0), DecimalSI}},
|
||||||
|
{"5.444T", Quantity{dec(5444, 9), DecimalSI}},
|
||||||
|
{"40.1T", Quantity{dec(401, 11), DecimalSI}},
|
||||||
|
{"300.2T", Quantity{dec(3002, 11), DecimalSI}},
|
||||||
|
{"2.5P", Quantity{dec(25, 14), DecimalSI}},
|
||||||
|
{"1.01E", Quantity{dec(101, 16), DecimalSI}},
|
||||||
|
|
||||||
|
// Things that saturate/round
|
||||||
|
{"3.001m", Quantity{dec(4, -3), DecimalSI}},
|
||||||
|
{"1.1E-3", Quantity{dec(2, -3), DecimalExponent}},
|
||||||
|
{"0.0001", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{"0.0005", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{"0.00050", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{"0.5e-3", Quantity{dec(1, -3), DecimalExponent}},
|
||||||
|
{"0.9m", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{"0.12345", Quantity{dec(124, -3), DecimalSI}},
|
||||||
|
{"0.12354", Quantity{dec(124, -3), DecimalSI}},
|
||||||
|
{"9Ei", Quantity{maxAllowed, BinarySI}},
|
||||||
|
{"9223372036854775807Ki", Quantity{maxAllowed, BinarySI}},
|
||||||
|
{"12E", Quantity{maxAllowed, DecimalSI}},
|
||||||
|
|
||||||
|
// We'll accept fractional binary stuff, too.
|
||||||
|
{"100.035Ki", Quantity{dec(10243584, -2), BinarySI}},
|
||||||
|
{"0.5Mi", Quantity{dec(.5*1024*1024, 0), BinarySI}},
|
||||||
|
{"0.05Gi", Quantity{dec(536870912, -1), BinarySI}},
|
||||||
|
{"0.025Ti", Quantity{dec(274877906944, -1), BinarySI}},
|
||||||
|
|
||||||
|
// Things written by trolls
|
||||||
|
{"0.000001Ki", Quantity{dec(2, -3), DecimalSI}}, // rounds up, changes format
|
||||||
|
{".001", Quantity{dec(1, -3), DecimalSI}},
|
||||||
|
{".0001k", Quantity{dec(100, -3), DecimalSI}},
|
||||||
|
{"1.", Quantity{dec(1, 0), DecimalSI}},
|
||||||
|
{"1.G", Quantity{dec(1, 9), DecimalSI}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
got, err := ParseQuantity(item.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: unexpected error: %v", item.input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, a := item.expect.Amount, got.Amount; e.Cmp(a) != 0 {
|
||||||
|
t.Errorf("%v: expected %v, got %v", item.input, e, a)
|
||||||
|
}
|
||||||
|
if e, a := item.expect.Format, got.Format; e != a {
|
||||||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the negative version of everything
|
||||||
|
desired := &inf.Dec{}
|
||||||
|
for _, item := range table {
|
||||||
|
got, err := ParseQuantity("-" + item.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("-%v: unexpected error: %v", item.input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
desired.Neg(item.expect.Amount)
|
||||||
|
if e, a := desired, got.Amount; e.Cmp(a) != 0 {
|
||||||
|
t.Errorf("%v: expected %v, got %v", item.input, e, a)
|
||||||
|
}
|
||||||
|
if e, a := item.expect.Format, got.Format; e != a {
|
||||||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try everything with an explicit +
|
||||||
|
for _, item := range table {
|
||||||
|
got, err := ParseQuantity("+" + item.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("-%v: unexpected error: %v", item.input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, a := item.expect.Amount, got.Amount; e.Cmp(a) != 0 {
|
||||||
|
t.Errorf("%v: expected %v, got %v", item.input, e, a)
|
||||||
|
}
|
||||||
|
if e, a := item.expect.Format, got.Format; e != a {
|
||||||
|
t.Errorf("%v: expected %#v, got %#v", item.input, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalid := []string{
|
||||||
|
"1.1.M",
|
||||||
|
"1+1.0M",
|
||||||
|
"0.1mi",
|
||||||
|
"0.1am",
|
||||||
|
"aoeu",
|
||||||
|
".5i",
|
||||||
|
"1i",
|
||||||
|
"-3.01i",
|
||||||
|
}
|
||||||
|
for _, item := range invalid {
|
||||||
|
_, err := ParseQuantity(item)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("%v parsed unexpectedly", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuantityString(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
in Quantity
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{Quantity{dec(1024*1024*1024, 0), BinarySI}, "1Gi"},
|
||||||
|
{Quantity{dec(300*1024*1024, 0), BinarySI}, "300Mi"},
|
||||||
|
{Quantity{dec(6*1024, 0), BinarySI}, "6Ki"},
|
||||||
|
{Quantity{dec(1001*1024*1024*1024, 0), BinarySI}, "1001Gi"},
|
||||||
|
{Quantity{dec(1024*1024*1024*1024, 0), BinarySI}, "1Ti"},
|
||||||
|
{Quantity{dec(5, 0), BinarySI}, "5"},
|
||||||
|
{Quantity{dec(500, -3), BinarySI}, "500m"},
|
||||||
|
{Quantity{dec(1, 9), DecimalSI}, "1G"},
|
||||||
|
{Quantity{dec(1000, 6), DecimalSI}, "1G"},
|
||||||
|
{Quantity{dec(1000000, 3), DecimalSI}, "1G"},
|
||||||
|
{Quantity{dec(1000000000, 0), DecimalSI}, "1G"},
|
||||||
|
{Quantity{dec(1, -3), DecimalSI}, "1m"},
|
||||||
|
{Quantity{dec(80, -3), DecimalSI}, "80m"},
|
||||||
|
{Quantity{dec(1080, -3), DecimalSI}, "1080m"},
|
||||||
|
{Quantity{dec(108, -2), DecimalSI}, "1080m"},
|
||||||
|
{Quantity{dec(10800, -4), DecimalSI}, "1080m"},
|
||||||
|
{Quantity{dec(300, 6), DecimalSI}, "300M"},
|
||||||
|
{Quantity{dec(1, 12), DecimalSI}, "1T"},
|
||||||
|
{Quantity{dec(1234567, 6), DecimalSI}, "1234567M"},
|
||||||
|
{Quantity{dec(1234567, -3), BinarySI}, "1234567m"},
|
||||||
|
{Quantity{dec(3, 3), DecimalSI}, "3k"},
|
||||||
|
{Quantity{dec(1025, 0), BinarySI}, "1025"},
|
||||||
|
{Quantity{dec(0, 0), DecimalSI}, "0"},
|
||||||
|
{Quantity{dec(0, 0), BinarySI}, "0"},
|
||||||
|
{Quantity{dec(1, 9), DecimalExponent}, "1e9"},
|
||||||
|
{Quantity{dec(1, -3), DecimalExponent}, "1e-3"},
|
||||||
|
{Quantity{dec(80, -3), DecimalExponent}, "80e-3"},
|
||||||
|
{Quantity{dec(300, 6), DecimalExponent}, "300e6"},
|
||||||
|
{Quantity{dec(1, 12), DecimalExponent}, "1e12"},
|
||||||
|
{Quantity{dec(1, 3), DecimalExponent}, "1e3"},
|
||||||
|
{Quantity{dec(3, 3), DecimalExponent}, "3e3"},
|
||||||
|
{Quantity{dec(3, 3), DecimalSI}, "3k"},
|
||||||
|
{Quantity{dec(0, 0), DecimalExponent}, "0"},
|
||||||
|
}
|
||||||
|
for _, item := range table {
|
||||||
|
got := item.in.String()
|
||||||
|
if e, a := item.expect, got; e != a {
|
||||||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
desired := &inf.Dec{} // Avoid modifying the values in the table.
|
||||||
|
for _, item := range table {
|
||||||
|
if item.in.Amount.Cmp(decZero) == 0 {
|
||||||
|
// Don't expect it to print "-0" ever
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q := item.in
|
||||||
|
q.Amount = desired.Neg(q.Amount)
|
||||||
|
if e, a := "-"+item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuantityParseEmit(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
in string
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{"1Ki", "1Ki"},
|
||||||
|
{"1Mi", "1Mi"},
|
||||||
|
{"1Gi", "1Gi"},
|
||||||
|
{"1024Mi", "1Gi"},
|
||||||
|
{"1000M", "1G"},
|
||||||
|
{".000001Ki", "2m"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
q, err := ParseQuantity(item.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't parse %v", item.in)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, a := item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, item := range table {
|
||||||
|
q, err := ParseQuantity("-" + item.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't parse %v", item.in)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if q.Amount.Cmp(decZero) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if e, a := "-"+item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("%#v: expected %v, got %v", item.in, e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fuzzer = fuzz.New().Funcs(
|
||||||
|
func(q *Quantity, c fuzz.Continue) {
|
||||||
|
q.Amount = &inf.Dec{}
|
||||||
|
if c.RandBool() {
|
||||||
|
q.Format = BinarySI
|
||||||
|
if c.RandBool() {
|
||||||
|
q.Amount.SetScale(0)
|
||||||
|
q.Amount.SetUnscaled(c.Int63())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Be sure to test cases like 1Mi
|
||||||
|
q.Amount.SetScale(0)
|
||||||
|
q.Amount.SetUnscaled(c.Int63n(1024) << uint(10*c.Intn(5)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.RandBool() {
|
||||||
|
q.Format = DecimalSI
|
||||||
|
} else {
|
||||||
|
q.Format = DecimalExponent
|
||||||
|
}
|
||||||
|
if c.RandBool() {
|
||||||
|
q.Amount.SetScale(inf.Scale(c.Intn(4)))
|
||||||
|
q.Amount.SetUnscaled(c.Int63())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Be sure to test cases like 1M
|
||||||
|
q.Amount.SetScale(inf.Scale(3 - c.Intn(15)))
|
||||||
|
q.Amount.SetUnscaled(c.Int63n(1000))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSON(t *testing.T) {
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
q := &Quantity{}
|
||||||
|
fuzzer.Fuzz(q)
|
||||||
|
b, err := json.Marshal(q)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error encoding %v", q)
|
||||||
|
}
|
||||||
|
q2 := &Quantity{}
|
||||||
|
err = json.Unmarshal(b, q2)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: error decoding %v", q, string(b))
|
||||||
|
}
|
||||||
|
if q2.Amount.Cmp(q.Amount) != 0 {
|
||||||
|
t.Errorf("Expected equal: %v, %v (json was '%v')", q, q2, string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMilliNewSet(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
value int64
|
||||||
|
format Format
|
||||||
|
expect string
|
||||||
|
exact bool
|
||||||
|
}{
|
||||||
|
{1, DecimalSI, "1m", true},
|
||||||
|
{1000, DecimalSI, "1", true},
|
||||||
|
{1234000, DecimalSI, "1234", true},
|
||||||
|
{1024, BinarySI, "1024m", false}, // Format changes
|
||||||
|
{1000000, "invalidFormatDefaultsToExponent", "1e3", true},
|
||||||
|
{1024 * 1024, BinarySI, "1048576m", false}, // Format changes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
q := NewMilliQuantity(item.value, item.format)
|
||||||
|
if e, a := item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||||||
|
}
|
||||||
|
if !item.exact {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
q2, err := ParseQuantity(q.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Round trip failed on %v", q)
|
||||||
|
}
|
||||||
|
if e, a := item.value, q2.MilliValue(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
q := NewQuantity(0, item.format)
|
||||||
|
q.SetMilli(item.value)
|
||||||
|
if e, a := item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSet(t *testing.T) {
|
||||||
|
table := []struct {
|
||||||
|
value int64
|
||||||
|
format Format
|
||||||
|
expect string
|
||||||
|
}{
|
||||||
|
{1, DecimalSI, "1"},
|
||||||
|
{1000, DecimalSI, "1k"},
|
||||||
|
{1234000, DecimalSI, "1234k"},
|
||||||
|
{1024, BinarySI, "1Ki"},
|
||||||
|
{1000000, "invalidFormatDefaultsToExponent", "1e6"},
|
||||||
|
{1024 * 1024, BinarySI, "1Mi"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
q := NewQuantity(item.value, item.format)
|
||||||
|
if e, a := item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v; %#v", e, a, q)
|
||||||
|
}
|
||||||
|
q2, err := ParseQuantity(q.String())
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Round trip failed on %v", q)
|
||||||
|
}
|
||||||
|
if e, a := item.value, q2.Value(); e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range table {
|
||||||
|
q := NewQuantity(0, item.format)
|
||||||
|
q.Set(item.value)
|
||||||
|
if e, a := item.expect, q.String(); e != a {
|
||||||
|
t.Errorf("Set: Expected %v, got %v; %#v", e, a, q)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUninitializedNoCrash(t *testing.T) {
|
||||||
|
var q Quantity
|
||||||
|
|
||||||
|
q.Value()
|
||||||
|
q.MilliValue()
|
||||||
|
q.Copy()
|
||||||
|
q.String()
|
||||||
|
q.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopy(t *testing.T) {
|
||||||
|
q := NewQuantity(5, DecimalSI)
|
||||||
|
c := q.Copy()
|
||||||
|
c.Set(6)
|
||||||
|
if q.Value() == 6 {
|
||||||
|
t.Errorf("Copy didn't")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQFlagSet(t *testing.T) {
|
||||||
|
qf := qFlag{&Quantity{}}
|
||||||
|
qf.Set("1Ki")
|
||||||
|
if e, a := "1Ki", qf.String(); e != a {
|
||||||
|
t.Errorf("Unexpected result %v != %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
134
pkg/api/resource/suffix.go
Normal file
134
pkg/api/resource/suffix.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 Google Inc. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 resource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type suffix string
|
||||||
|
|
||||||
|
// suffixer can interpret and construct suffixes.
|
||||||
|
type suffixer interface {
|
||||||
|
interpret(suffix) (base, exponent int, fmt Format, ok bool)
|
||||||
|
construct(base, exponent int, fmt Format) (s suffix, ok bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// quantitySuffixer handles suffixes for all three formats that quantity
|
||||||
|
// can handle.
|
||||||
|
var quantitySuffixer = newSuffixer()
|
||||||
|
|
||||||
|
type bePair struct {
|
||||||
|
base, exponent int
|
||||||
|
}
|
||||||
|
|
||||||
|
type listSuffixer struct {
|
||||||
|
suffixToBE map[suffix]bePair
|
||||||
|
beToSuffix map[bePair]suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *listSuffixer) addSuffix(s suffix, pair bePair) {
|
||||||
|
if ls.suffixToBE == nil {
|
||||||
|
ls.suffixToBE = map[suffix]bePair{}
|
||||||
|
}
|
||||||
|
if ls.beToSuffix == nil {
|
||||||
|
ls.beToSuffix = map[bePair]suffix{}
|
||||||
|
}
|
||||||
|
ls.suffixToBE[s] = pair
|
||||||
|
ls.beToSuffix[pair] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *listSuffixer) lookup(s suffix) (base, exponent int, ok bool) {
|
||||||
|
pair, ok := ls.suffixToBE[s]
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return pair.base, pair.exponent, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls *listSuffixer) construct(base, exponent int) (s suffix, ok bool) {
|
||||||
|
s, ok = ls.beToSuffix[bePair{base, exponent}]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type suffixHandler struct {
|
||||||
|
decSuffixes listSuffixer
|
||||||
|
binSuffixes listSuffixer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSuffixer() suffixer {
|
||||||
|
sh := &suffixHandler{}
|
||||||
|
|
||||||
|
sh.binSuffixes.addSuffix("Ki", bePair{2, 10})
|
||||||
|
sh.binSuffixes.addSuffix("Mi", bePair{2, 20})
|
||||||
|
sh.binSuffixes.addSuffix("Gi", bePair{2, 30})
|
||||||
|
sh.binSuffixes.addSuffix("Ti", bePair{2, 40})
|
||||||
|
sh.binSuffixes.addSuffix("Pi", bePair{2, 50})
|
||||||
|
sh.binSuffixes.addSuffix("Ei", bePair{2, 60})
|
||||||
|
// Don't emit an error when trying to produce
|
||||||
|
// a suffix for 2^0.
|
||||||
|
sh.decSuffixes.addSuffix("", bePair{2, 0})
|
||||||
|
|
||||||
|
sh.decSuffixes.addSuffix("m", bePair{10, -3})
|
||||||
|
sh.decSuffixes.addSuffix("", bePair{10, 0})
|
||||||
|
sh.decSuffixes.addSuffix("k", bePair{10, 3})
|
||||||
|
sh.decSuffixes.addSuffix("M", bePair{10, 6})
|
||||||
|
sh.decSuffixes.addSuffix("G", bePair{10, 9})
|
||||||
|
sh.decSuffixes.addSuffix("T", bePair{10, 12})
|
||||||
|
sh.decSuffixes.addSuffix("P", bePair{10, 15})
|
||||||
|
sh.decSuffixes.addSuffix("E", bePair{10, 18})
|
||||||
|
|
||||||
|
return sh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *suffixHandler) construct(base, exponent int, fmt Format) (s suffix, ok bool) {
|
||||||
|
switch fmt {
|
||||||
|
case DecimalSI:
|
||||||
|
return sh.decSuffixes.construct(base, exponent)
|
||||||
|
case BinarySI:
|
||||||
|
return sh.binSuffixes.construct(base, exponent)
|
||||||
|
case DecimalExponent:
|
||||||
|
if base != 10 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
if exponent == 0 {
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
return suffix("e" + strconv.FormatInt(int64(exponent), 10)), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh *suffixHandler) interpret(suffix suffix) (base, exponent int, fmt Format, ok bool) {
|
||||||
|
// Try lookup tables first
|
||||||
|
if b, e, ok := sh.decSuffixes.lookup(suffix); ok {
|
||||||
|
return b, e, DecimalSI, true
|
||||||
|
}
|
||||||
|
if b, e, ok := sh.binSuffixes.lookup(suffix); ok {
|
||||||
|
return b, e, BinarySI, true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(suffix) > 1 && (suffix[0] == 'E' || suffix[0] == 'e') {
|
||||||
|
parsed, err := strconv.ParseInt(string(suffix[1:]), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, DecimalExponent, false
|
||||||
|
}
|
||||||
|
return 10, int(parsed), DecimalExponent, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, DecimalExponent, false
|
||||||
|
}
|
Reference in New Issue
Block a user