mirror of
https://github.com/shopspring/decimal.git
synced 2024-11-22 20:40:48 +01:00
Exact representation in NewFromFloat (#78)
* Additional (and some breaking) tests for NewFromFloatWithExponent * Addressing tests for NewFromFloatWithExponent * Naming cosmetic correction * removing unused code * Improving FromFloatWithExponent * Tests for exact float representation added * Exact float representation in FromFloat * Adding breaking test for NewFromFloat * Fast path in FromFloat is unreliable, fixing it * Addressing special meaning of zero exponent in float64 * NewFromFloatWithExponent: subnormals support * NewFromFloatWithExponent: just a few additional test cases * NewFromFloat: documentation update * NewFromFloatWithExponent: optimization and some documentation * NewFromFloatWithExponent: optimizations * NewFromFloatWithExponent: optimizations
This commit is contained in:
parent
bf9a39e28b
commit
78e9b82f68
2 changed files with 75 additions and 50 deletions
20
decimal.go
20
decimal.go
|
@ -160,24 +160,12 @@ func NewFromString(value string) (Decimal, error) {
|
||||||
// NewFromFloat(123.45678901234567).String() // output: "123.4567890123456"
|
// NewFromFloat(123.45678901234567).String() // output: "123.4567890123456"
|
||||||
// NewFromFloat(.00000000000000001).String() // output: "0.00000000000000001"
|
// NewFromFloat(.00000000000000001).String() // output: "0.00000000000000001"
|
||||||
//
|
//
|
||||||
|
// NOTE: some float64 numbers can take up about 300 bytes of memory in decimal representation.
|
||||||
|
// Consider using NewFromFloatWithExponent if space is more important than precision.
|
||||||
|
//
|
||||||
// NOTE: this will panic on NaN, +/-inf
|
// NOTE: this will panic on NaN, +/-inf
|
||||||
func NewFromFloat(value float64) Decimal {
|
func NewFromFloat(value float64) Decimal {
|
||||||
floor := math.Floor(value)
|
return NewFromFloatWithExponent(value, math.MinInt32)
|
||||||
|
|
||||||
// fast path, where float is an int
|
|
||||||
if floor == value && value <= math.MaxInt64 && value >= math.MinInt64 {
|
|
||||||
return New(int64(value), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// slow path: float is a decimal
|
|
||||||
// HACK(vadim): do this the slow hacky way for now because the logic to
|
|
||||||
// convert a base-2 float to base-10 properly is not trivial
|
|
||||||
str := strconv.FormatFloat(value, 'f', -1, 64)
|
|
||||||
dec, err := NewFromString(str)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return dec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary
|
// NewFromFloatWithExponent converts a float64 to Decimal, with an arbitrary
|
||||||
|
|
103
decimal_test.go
103
decimal_test.go
|
@ -14,28 +14,35 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testTable = map[float64]string{
|
type testEnt struct {
|
||||||
3.141592653589793: "3.141592653589793",
|
float float64
|
||||||
3: "3",
|
short string
|
||||||
1234567890123456: "1234567890123456",
|
exact string
|
||||||
1234567890123456000: "1234567890123456000",
|
}
|
||||||
1234.567890123456: "1234.567890123456",
|
|
||||||
.1234567890123456: "0.1234567890123456",
|
var testTable = []*testEnt{
|
||||||
0: "0",
|
{3.141592653589793, "3.141592653589793", ""},
|
||||||
.1111111111111110: "0.111111111111111",
|
{3, "3", ""},
|
||||||
.1111111111111111: "0.1111111111111111",
|
{1234567890123456, "1234567890123456", ""},
|
||||||
.1111111111111119: "0.1111111111111119",
|
{1234567890123456000, "1234567890123456000", ""},
|
||||||
.000000000000000001: "0.000000000000000001",
|
{1234.567890123456, "1234.567890123456", ""},
|
||||||
.000000000000000002: "0.000000000000000002",
|
{.1234567890123456, "0.1234567890123456", ""},
|
||||||
.000000000000000003: "0.000000000000000003",
|
{0, "0", ""},
|
||||||
.000000000000000005: "0.000000000000000005",
|
{.1111111111111110, "0.111111111111111", ""},
|
||||||
.000000000000000008: "0.000000000000000008",
|
{.1111111111111111, "0.1111111111111111", ""},
|
||||||
.1000000000000001: "0.1000000000000001",
|
{.1111111111111119, "0.1111111111111119", ""},
|
||||||
.1000000000000002: "0.1000000000000002",
|
{.000000000000000001, "0.000000000000000001", ""},
|
||||||
.1000000000000003: "0.1000000000000003",
|
{.000000000000000002, "0.000000000000000002", ""},
|
||||||
.1000000000000005: "0.1000000000000005",
|
{.000000000000000003, "0.000000000000000003", ""},
|
||||||
.1000000000000008: "0.1000000000000008",
|
{.000000000000000005, "0.000000000000000005", ""},
|
||||||
1e25: "10000000000000000000000000",
|
{.000000000000000008, "0.000000000000000008", ""},
|
||||||
|
{.1000000000000001, "0.1000000000000001", ""},
|
||||||
|
{.1000000000000002, "0.1000000000000002", ""},
|
||||||
|
{.1000000000000003, "0.1000000000000003", ""},
|
||||||
|
{.1000000000000005, "0.1000000000000005", ""},
|
||||||
|
{.1000000000000008, "0.1000000000000008", ""},
|
||||||
|
{1e25, "10000000000000000000000000", ""},
|
||||||
|
{math.MaxInt64, strconv.FormatInt(math.MaxInt64, 10), ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
var testTableScientificNotation = map[string]string{
|
var testTableScientificNotation = map[string]string{
|
||||||
|
@ -54,12 +61,23 @@ var testTableScientificNotation = map[string]string{
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
for _, s := range testTable {
|
||||||
|
s.exact = strconv.FormatFloat(s.float, 'f', 300, 64)
|
||||||
|
if strings.ContainsRune(s.exact, '.') {
|
||||||
|
s.exact = strings.TrimRight(s.exact, "0")
|
||||||
|
s.exact = strings.TrimRight(s.exact, ".")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add negatives
|
// add negatives
|
||||||
for f, s := range testTable {
|
withNeg := testTable[:]
|
||||||
if f > 0 {
|
for _, s := range testTable {
|
||||||
testTable[-f] = "-" + s
|
if s.float > 0 {
|
||||||
|
withNeg = append(withNeg, &testEnt{-s.float, "-" + s.short, "-" + s.exact})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
testTable = withNeg
|
||||||
|
|
||||||
for e, s := range testTableScientificNotation {
|
for e, s := range testTableScientificNotation {
|
||||||
if string(e[0]) != "-" && s != "0" {
|
if string(e[0]) != "-" && s != "0" {
|
||||||
testTableScientificNotation["-"+e] = "-" + s
|
testTableScientificNotation["-"+e] = "-" + s
|
||||||
|
@ -68,8 +86,9 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromFloat(t *testing.T) {
|
func TestNewFromFloat(t *testing.T) {
|
||||||
for f, s := range testTable {
|
for _, x := range testTable {
|
||||||
d := NewFromFloat(f)
|
s := x.exact
|
||||||
|
d := NewFromFloat(x.float)
|
||||||
if d.String() != s {
|
if d.String() != s {
|
||||||
t.Errorf("expected %s, got %s (%s, %d)",
|
t.Errorf("expected %s, got %s (%s, %d)",
|
||||||
s, d.String(),
|
s, d.String(),
|
||||||
|
@ -92,7 +111,20 @@ func TestNewFromFloat(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewFromString(t *testing.T) {
|
func TestNewFromString(t *testing.T) {
|
||||||
for _, s := range testTable {
|
for _, x := range testTable {
|
||||||
|
s := x.short
|
||||||
|
d, err := NewFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error while parsing %s", s)
|
||||||
|
} else if d.String() != s {
|
||||||
|
t.Errorf("expected %s, got %s (%s, %d)",
|
||||||
|
s, d.String(),
|
||||||
|
d.value.String(), d.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, x := range testTable {
|
||||||
|
s := x.exact
|
||||||
d, err := NewFromString(s)
|
d, err := NewFromString(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("error while parsing %s", s)
|
t.Errorf("error while parsing %s", s)
|
||||||
|
@ -289,7 +321,8 @@ func TestNewFromBigIntWithExponent(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSON(t *testing.T) {
|
func TestJSON(t *testing.T) {
|
||||||
for _, s := range testTable {
|
for _, x := range testTable {
|
||||||
|
s := x.short
|
||||||
var doc struct {
|
var doc struct {
|
||||||
Amount Decimal `json:"amount"`
|
Amount Decimal `json:"amount"`
|
||||||
}
|
}
|
||||||
|
@ -358,7 +391,8 @@ func TestBadJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNullDecimalJSON(t *testing.T) {
|
func TestNullDecimalJSON(t *testing.T) {
|
||||||
for _, s := range testTable {
|
for _, x := range testTable {
|
||||||
|
s := x.short
|
||||||
var doc struct {
|
var doc struct {
|
||||||
Amount NullDecimal `json:"amount"`
|
Amount NullDecimal `json:"amount"`
|
||||||
}
|
}
|
||||||
|
@ -450,7 +484,8 @@ func TestNullDecimalBadJSON(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestXML(t *testing.T) {
|
func TestXML(t *testing.T) {
|
||||||
for _, s := range testTable {
|
for _, x := range testTable {
|
||||||
|
s := x.short
|
||||||
var doc struct {
|
var doc struct {
|
||||||
XMLName xml.Name `xml:"account"`
|
XMLName xml.Name `xml:"account"`
|
||||||
Amount Decimal `xml:"amount"`
|
Amount Decimal `xml:"amount"`
|
||||||
|
@ -2035,7 +2070,8 @@ func TestNullDecimal_Value(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBinary(t *testing.T) {
|
func TestBinary(t *testing.T) {
|
||||||
for x := range testTable {
|
for _, y := range testTable {
|
||||||
|
x := y.float
|
||||||
|
|
||||||
// Create the decimal
|
// Create the decimal
|
||||||
d1 := NewFromFloat(x)
|
d1 := NewFromFloat(x)
|
||||||
|
@ -2070,7 +2106,8 @@ func slicesEqual(a, b []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGobEncode(t *testing.T) {
|
func TestGobEncode(t *testing.T) {
|
||||||
for x := range testTable {
|
for _, y := range testTable {
|
||||||
|
x := y.float
|
||||||
d1 := NewFromFloat(x)
|
d1 := NewFromFloat(x)
|
||||||
|
|
||||||
b1, err := d1.GobEncode()
|
b1, err := d1.GobEncode()
|
||||||
|
|
Loading…
Reference in a new issue