Skip to content

Commit

Permalink
add Format method
Browse files Browse the repository at this point in the history
Supports e, E, f, g, G, s, v.

ToSci has been removed and is now just String, which wraps .Text('G').
ToStandard has been removed, but can still be produced using the 'f'
format. The precision field is not supported because that could require
rounding and we don't have a context. Instead, the format methods here
always display the full precision. Although big.Float doesn't implement
's', I think we safely can because the precision is always fully
displayed.

Fixes #19
  • Loading branch information
maddyblue committed Jul 7, 2017
1 parent 564f678 commit 72773d4
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 90 deletions.
65 changes: 0 additions & 65 deletions decimal.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package apd

import (
"database/sql/driver"
"fmt"
"math"
"math/big"
"strconv"
Expand Down Expand Up @@ -198,11 +197,6 @@ func (c *Context) SetString(d *Decimal, s string) (*Decimal, Condition, error) {
return d, res, err
}

// String is a wrapper of ToSci.
func (d *Decimal) String() string {
return d.ToSci()
}

func (d *Decimal) strSpecials() (bool, string) {
switch d.Form {
case NaN:
Expand All @@ -218,65 +212,6 @@ func (d *Decimal) strSpecials() (bool, string) {
}
}

// ToSci returns d in scientific notation if an exponent is needed.
func (d *Decimal) ToSci() string {
// See: http://speleotrove.com/decimal/daconvs.html#reftostr
const adjExponentLimit = -6

set, s := d.strSpecials()
if !set {
s = d.Coeff.String()
adj := int(d.Exponent) + (len(s) - 1)
if d.Exponent <= 0 && adj >= adjExponentLimit {
if d.Exponent < 0 {
if left := -int(d.Exponent) - len(s); left > 0 {
s = "0." + strings.Repeat("0", left) + s
} else if left < 0 {
offset := -left
s = s[:offset] + "." + s[offset:]
} else {
s = "0." + s
}
}
} else {
dot := ""
if len(s) > 1 {
dot = "." + s[1:]
}
s = fmt.Sprintf("%s%sE%+d", s[:1], dot, adj)
}
}
if d.Negative {
s = "-" + s
}
return s
}

// ToStandard converts d to a standard notation string (i.e., no exponent
// part). This can result in long strings given large exponents.
func (d *Decimal) ToStandard() string {
set, s := d.strSpecials()
if !set {
s = d.Coeff.String()
if d.Exponent < 0 {
if left := -int(d.Exponent) - len(s); left > 0 {
s = "0." + strings.Repeat("0", left) + s
} else if left < 0 {
offset := -left
s = s[:offset] + "." + s[offset:]
} else {
s = "0." + s
}
} else if d.Exponent > 0 {
s += strings.Repeat("0", int(d.Exponent))
}
}
if d.Negative {
s = "-" + s
}
return s
}

// Set sets d's fields to the values of x and returns d.
func (d *Decimal) Set(x *Decimal) *Decimal {
if d == x {
Expand Down
169 changes: 150 additions & 19 deletions decimal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -388,28 +388,159 @@ func TestFloor(t *testing.T) {
}
}

func TestToStandard(t *testing.T) {
tests := map[string]string{
"0": "0",
"0.0": "0.0",
"0E2": "000",
"0E-2": "0.00",
"123.456E10": "1234560000000",
".9": "0.9",
"-.9": "-0.9",
"-123.456E10": "-1234560000000",
"-0E-2": "-0.00",
}

for c, r := range tests {
t.Run(c, func(t *testing.T) {
d, _, err := NewFromString(c)
func TestFormat(t *testing.T) {
tests := map[string]struct {
e, E, f, g, G string
}{
"NaN": {},
"Infinity": {},
"-Infinity": {},
"sNaN": {},
"0": {
e: "0e+0",
E: "0E+0",
},
"-0": {
e: "-0e+0",
E: "-0E+0",
},
"0.0": {
e: "0e-1",
E: "0E-1",
},
"-0.0": {
e: "-0e-1",
E: "-0E-1",
},
"0E+2": {
e: "0e+2",
f: "000",
g: "0e+2",
},
}
verbs := []string{"%e", "%E", "%f", "%g", "%G"}

for input, tc := range tests {
t.Run(input, func(t *testing.T) {
d, _, err := NewFromString(input)
if err != nil {
t.Fatal(err)
}
s := d.ToStandard()
if s != r {
t.Fatalf("got %v, expected %v", s, r)
for i, s := range []string{tc.e, tc.E, tc.f, tc.g, tc.G} {
if s == "" {
s = input
}
v := verbs[i]
t.Run(v, func(t *testing.T) {
out := fmt.Sprintf(v, d)
if out != s {
t.Fatalf("expected %s, got %s", s, out)
}
})
}
})
}
}

func TestFormatFlags(t *testing.T) {
const stdD = "1.23E+56"
tests := []struct {
d string
fmt string
out string
}{
{
d: stdD,
fmt: "%3G",
out: "1.23E+56",
},
{
d: stdD,
fmt: "%010G",
out: "001.23E+56",
},
{
d: stdD,
fmt: "%10G",
out: " 1.23E+56",
},
{
d: stdD,
fmt: "%+G",
out: "+1.23E+56",
},
{
d: stdD,
fmt: "% G",
out: " 1.23E+56",
},
{
d: stdD,
fmt: "%-10G",
out: "1.23E+56 ",
},
{
d: stdD,
fmt: "%-010G",
out: "1.23E+56 ",
},
{
d: "nan",
fmt: "%-10G",
out: "NaN ",
},
{
d: "nan",
fmt: "%10G",
out: " NaN",
},
{
d: "nan",
fmt: "%010G",
out: " NaN",
},
{
d: "inf",
fmt: "%-10G",
out: "Infinity ",
},
{
d: "inf",
fmt: "%10G",
out: " Infinity",
},
{
d: "inf",
fmt: "%010G",
out: " Infinity",
},
{
d: "-inf",
fmt: "%-10G",
out: "-Infinity ",
},
{
d: "-inf",
fmt: "%10G",
out: " -Infinity",
},
{
d: "-inf",
fmt: "%010G",
out: " -Infinity",
},
{
d: "0",
fmt: "%d",
out: "%!d(*apd.Decimal=0)",
},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("%s: %s", tc.d, tc.fmt), func(t *testing.T) {
d := newDecimal(t, &BaseContext, tc.d)
s := fmt.Sprintf(tc.fmt, d)
if s != tc.out {
t.Fatalf("expected %q, got %q", tc.out, s)
}
})
}
Expand Down
7 changes: 4 additions & 3 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
Package apd implements arbitrary-precision decimals.
apd implements much of the decimal specification from the General
Decimal Arithmetic (http://speleotrove.com/decimal/) description. This
is the same specification implemented by python’s decimal module
(https://docs.python.org/2/library/decimal.html) and GCC’s decimal extension.
Decimal Arithmetic (http://speleotrove.com/decimal/) description, which
is refered to here as GDA. This is the same specification implemented by
pythons decimal module (https://docs.python.org/2/library/decimal.html)
and GCCs decimal extension.
Features
Expand Down
2 changes: 1 addition & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func ExampleContext_RoundToIntegralExact() {
res, _ := apd.BaseContext.RoundToIntegralExact(d, d)
integer := !res.Inexact()
strict := !res.Rounded()
fmt.Printf("input: % 6s, output: % 3s, integer: %5t, strict: %5t, res:", input, d, integer, strict)
fmt.Printf("input: % 6s, output: %3s, integer: %5t, strict: %5t, res:", input, d, integer, strict)
if res != 0 {
fmt.Printf(" %s", res)
}
Expand Down
Loading

0 comments on commit 72773d4

Please sign in to comment.