@@ -65,6 +65,19 @@ var PowPrecisionNegativeExponent = 16
6565// silently lose precision.
6666var MarshalJSONWithoutQuotes = false
6767
68+ // TrimTrailingZeros specifies whether trailing zeroes should be trimmed from a string representation of decimal.
69+ // If set to true, trailing zeroes will be truncated (2.00 -> 2, 3.11 -> 3.11, 13.000 -> 13),
70+ // otherwise trailing zeroes will be preserved (2.00 -> 2.00, 3.11 -> 3.11, 13.000 -> 13.000).
71+ // Setting this value to false can be useful for APIs where exact decimal string representation matters.
72+ var TrimTrailingZeros = true
73+
74+ // AvoidScientificNotation specifies whether scientific notation should be used when decimal is turned
75+ // into a string that has a "negative" precision.
76+ //
77+ // For example, 1200 rounded to the nearest 100 cannot accurately be shown as "1200" because the last two
78+ // digits are unknown. With this set to false, that number would be expressed as "1.2E3" instead.
79+ var AvoidScientificNotation = true
80+
6881// ExpMaxIterations specifies the maximum number of iterations needed to calculate
6982// precise natural exponent value using ExpHullAbrham method.
7083var ExpMaxIterations = 1000
@@ -1476,7 +1489,7 @@ func (d Decimal) InexactFloat64() float64 {
14761489//
14771490// -12.345
14781491func (d Decimal ) String () string {
1479- return d .string (true )
1492+ return d .string (TrimTrailingZeros , AvoidScientificNotation )
14801493}
14811494
14821495// StringFixed returns a rounded fixed-point string with places digits after
@@ -1490,10 +1503,12 @@ func (d Decimal) String() string {
14901503// NewFromFloat(5.45).StringFixed(1) // output: "5.5"
14911504// NewFromFloat(5.45).StringFixed(2) // output: "5.45"
14921505// NewFromFloat(5.45).StringFixed(3) // output: "5.450"
1493- // NewFromFloat(545).StringFixed(-1) // output: "550"
1506+ // NewFromFloat(545).StringFixed(-1) // output: "540"
1507+ //
1508+ // Regardless of the `AvoidScientificNotation` option, the returned string will never be in scientific notation.
14941509func (d Decimal ) StringFixed (places int32 ) string {
14951510 rounded := d .Round (places )
1496- return rounded .string (false )
1511+ return rounded .string (false , true )
14971512}
14981513
14991514// StringFixedBank returns a banker rounded fixed-point string with places digits
@@ -1508,16 +1523,20 @@ func (d Decimal) StringFixed(places int32) string {
15081523// NewFromFloat(5.45).StringFixedBank(2) // output: "5.45"
15091524// NewFromFloat(5.45).StringFixedBank(3) // output: "5.450"
15101525// NewFromFloat(545).StringFixedBank(-1) // output: "540"
1526+ //
1527+ // Regardless of the `AvoidScientificNotation` option, the returned string will never be in scientific notation.
15111528func (d Decimal ) StringFixedBank (places int32 ) string {
15121529 rounded := d .RoundBank (places )
1513- return rounded .string (false )
1530+ return rounded .string (false , true )
15141531}
15151532
15161533// StringFixedCash returns a Swedish/Cash rounded fixed-point string. For
15171534// more details see the documentation at function RoundCash.
1535+ //
1536+ // Regardless of the `AvoidScientificNotation` option, the returned string will never be in scientific notation.
15181537func (d Decimal ) StringFixedCash (interval uint8 ) string {
15191538 rounded := d .RoundCash (interval )
1520- return rounded .string (false )
1539+ return rounded .string (false , true )
15211540}
15221541
15231542// Round rounds the decimal to places decimal places.
@@ -1526,7 +1545,7 @@ func (d Decimal) StringFixedCash(interval uint8) string {
15261545// Example:
15271546//
15281547// NewFromFloat(5.45).Round(1).String() // output: "5.5"
1529- // NewFromFloat(545).Round(-1).String() // output: "550"
1548+ // NewFromFloat(545).Round(-1).String() // output: "550" (with AvoidScientificNotation, "5.5E2" otherwise)
15301549func (d Decimal ) Round (places int32 ) Decimal {
15311550 if d .exp == - places {
15321551 return d
@@ -1911,10 +1930,17 @@ func (d Decimal) StringScaled(exp int32) string {
19111930 return d .rescale (exp ).String ()
19121931}
19131932
1914- func (d Decimal ) string (trimTrailingZeros bool ) string {
1915- if d .exp > = 0 {
1933+ func (d Decimal ) string (trimTrailingZeros , avoidScientificNotation bool ) string {
1934+ if d .exp = = 0 {
19161935 return d .rescale (0 ).value .String ()
19171936 }
1937+ if d .exp >= 0 {
1938+ if avoidScientificNotation {
1939+ return d .rescale (0 ).value .String ()
1940+ } else {
1941+ return d .ScientificNotationString ()
1942+ }
1943+ }
19181944
19191945 abs := new (big.Int ).Abs (d .value )
19201946 str := abs .String ()
@@ -1956,6 +1982,31 @@ func (d Decimal) string(trimTrailingZeros bool) string {
19561982 return number
19571983}
19581984
1985+ // ScientificNotationString serializes the decimal into standard scientific notation.
1986+ //
1987+ // The notation is normalized to have one non-zero digit followed by a decimal point and
1988+ // the remaining significant digits followed by "E" and the base-10 exponent.
1989+ //
1990+ // A zero, which has no significant digits, is simply serialized to "0".
1991+ func (d Decimal ) ScientificNotationString () string {
1992+ exp := int (d .exp )
1993+ intStr := new (big.Int ).Abs (d .value ).String ()
1994+ if intStr == "0" {
1995+ return intStr
1996+ }
1997+ first := intStr [0 ]
1998+ var remaining string
1999+ if len (intStr ) > 1 {
2000+ remaining = "." + intStr [1 :]
2001+ exp = exp + len (intStr ) - 1
2002+ }
2003+ number := string (first ) + remaining + "E" + strconv .Itoa (exp )
2004+ if d .value .Sign () < 0 {
2005+ return "-" + number
2006+ }
2007+ return number
2008+ }
2009+
19592010func (d * Decimal ) ensureInitialized () {
19602011 if d .value == nil {
19612012 d .value = new (big.Int )
0 commit comments