GithubHelp home page GithubHelp logo

decimal's People

Contributors

acln0 avatar brobits avatar cael0 avatar ceshihao avatar edsrzf avatar faide avatar igrmk avatar jdaarevalo avatar klauspost avatar lantw44 avatar lovung avatar martinlindhe avatar mathieupost avatar mozgiii avatar mwoss avatar njason avatar nvanbenschoten avatar ox avatar randyp avatar raverkamp avatar rigelrozanski avatar rubensayshi avatar serprex avatar stevenroose avatar thiagoarruda avatar tomlinford avatar vadimg avatar vadimkulagin avatar victorquinn avatar zlasd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

decimal's Issues

Reading NULL value from database

Currently there is error when reading from database where source field is NULL. Please add NullDecimal type so that everyone doesn't need to roll their own.

type NullDecimal struct {
Decimal Decimal
Valid bool // Valid is true if Decimal is not NULL
}

// Scan implements the Scanner interface.
func (nd *NullDecimal) Scan(value interface{}) error {
if value == nil {
nd.Decimal, nd.Valid = Zero, false
return nil
}
nd.Valid = true
return nd.Scan(value)
}

// Value implements the driver Valuer interface.
func (nd NullDecimal) Value() (driver.Value, error) {
if !nd.Valid {
return nil, nil
}
return nd.Decimal.String(), nil
}

support >=, >, <, <=

This can easily be coded from the sign function and subtract function... I'll do this unless there is some resistance to including this

NewFromFloat loses accuracy / behavior change

This used to pass.

func TestNumericFromFloat(t *testing.T) {
	fromString, err := decimal.NewFromString("12345.678901")
	if err != nil {
		t.Fatal(err)
	}

	fromFloat := decimal.NewFromFloat(12345.678901)

	if !fromString.Equal(fromFloat) {
		t.Fatalf("expected fromString (%v) to equal fromFloat (%v) but it doesn't", fromString, fromFloat)
	}
}

But is doesn't pass as of 78e9b82.

--- FAIL: TestNumericFromFloat (0.00s)
	decimal_test.go:157: expected fromString (12345.678901) to equal fromFloat (12345.6789009999993140809237957000732421875) but it doesn't

RoundCash interval Is 15

fmt.Println(decimal.NewFromFloat(3.5).RoundCash(15))

the result Is 2.4

fmt.Println(decimal.NewFromFloat(3.45).RoundCash(15))

the result is 3.5 , not same with the doc.

Floor() and Ceil() mistreat positive exponent

New(1,1).Ceil() and New(1,1).Floor() return 1. Calling the same functions for New(10,0) gives 10 which is the expected value in both cases. Unless I missed something here, it looks like a bug.

#64 provides more details and suggests a possible solution.

Unable MarshalJson without quotes

I have strict json parser as a consumer of an api using the Decimal type. Is there a way to Marshal as a number rather than a string? The quotes seem to be hardcoded right now in the marshal.

New maintainer?

First off, awesome library.

I noticed that there are some new pull and feature requests recently. I currently have the time and motivation to work on this project to try to knock some of these out. Let me know if I can offer my services to be a maintainer of this project.

Convert Decimal to float64

Hi,
Would be great to have Float() function in addition to String(), to be able to get a float64 value from a decimal.
Thanks!

What is the right way to check for zero?

Let's say you have a system that accepts prices over API. Someone sends in a price in USD of $0.333, but you only accept orders to the nearest cent. So you truncate their order to $0.33. Fine.

But what happens if they submit an order of $0.003? Now you've truncated their order to $0.00, which should just be rejected. What's the best way to check for something like this? I tried just doing a raw equality check against decimal.Zero but that fails the equality check, and I couldn't see anything obvious in the source for such a check.

Got wrong result Round()

func Decimal() {
    f, exit := decimal.NewFromFloat(1.275).Round(2).Float64()
	if exit {
		fmt.Println("yes")
	}

	fmt.Println(f)
        // result should be 1.28, but got 1.27
}

I'm still new to Go, so why I got what I don't want, can anybody explain ,thx.

Is there any way to fix this?

Thousands separator

I don't see a format option for thousands separators, such as commas. Am I missing it?

Renamings: NewFromFloatWithPrecision, NewFromFloatExact

This is arguably of course. In my opinion NewFromFloatWithPrecision(value float64, prec int32) is much more friendly name than NewFromFloatWithExponent(value float64, exp int32). Probably not all people working with decimal numbers know what is exponent. Argument prec should be treated as -exp in this case and current method should be deprecated.

Frankly speaking I would go further and deprecate NewFromFloat in favor of NewFromFloatExact. This should make people think about memory needed to store float without specifying precision.

The correct way of using decimal

What would be the correct way to handle this operations to get exactly the initial 0.19 back?

package main

import (
    "fmt"

    "github.com/shopspring/decimal"
)

func main() {
    p := decimal.NewFromFloat(0.19)
    sixty := decimal.NewFromFloat(60)
    r := p.Div(sixty)
    fmt.Println("Results: ", r, r.Mul(sixty))
}

Thanks,
-rif

NaN and +/-Inf Support?

Hi @vadimg, thanks for your work on this library. Have you considered adding support for the notions of either NaN or +/-Inf? If not, would you be opposed to me taking a shot at introducing them? I think both would be nice to have, but the notion of infinity especially would make this library an even more comparable replacement to big.Float.

Future request, add more functions

I think the java's BigDecimal is a very good guid to follow.
Functions like movePoint or neg is very useful, current I use decimal like this to do that

func toDecimal(precision int, scale int, value []byte) decimal.Decimal {
    positive := value[0] & 0x80 == 0x80
    value[0] ^= 0x80
    if (!positive) {
        for i := 0; i < len(value); i++ {
            value[i] ^= 0xFF;
        }
    }
    x := precision - scale
    ipDigits := x / _DIG_PER_DEC
    ipDigitsX := x - ipDigits * _DIG_PER_DEC
    ipSize := (ipDigits << 2) + int(_DIG_TO_BYTES[ipDigitsX])
    offset := int(_DIG_TO_BYTES[ipDigitsX])
    ip := decimal.Zero
    if offset > 0 {
        ip = decimal.NewFromFloat(float64(bigEndianInteger(value, 0, offset)))
    }
    for ; offset < ipSize; offset += 4 {
        i := bigEndianInteger(value, offset, 4);
        ip = ip.Mul(decimal.NewFromFloat(math.Pow10(_DIG_PER_DEC))).Add(decimal.NewFromFloat(float64(i)))
    }
    shift := 0
    fp := decimal.Zero
    for ; shift + _DIG_PER_DEC <= scale; shift, offset =shift+ _DIG_PER_DEC, offset + 4 {
        i := bigEndianInteger(value, offset, 4);
        fp = fp.Add(decimal.NewFromFloat(float64(i)).Div(decimal.NewFromFloat(math.Pow10(shift+_DIG_PER_DEC))))
    }

    if shift < scale {
        i := bigEndianInteger(value, offset, int(_DIG_TO_BYTES[scale - shift]));
        fp = fp.Add(decimal.NewFromFloat(float64(i)).Div(decimal.NewFromFloat(math.Pow10(scale))))
    }
    result := ip.Add(fp)
    if (positive) {
        return result
    }
    return result.Mul(decimal.NewFromFloat(-1))
}

Code like .Div(decimal.NewFromFloat(math.Pow10(shift+_DIG_PER_DEC))) is very unreadable, it's just movePointLeft(shift+_DIG_PER_DEC)

RoundBank produces incorrect results depending on Decimal's internal representation

The RoundBank() function, given the decimal value 2.5, should return 2. This works as expected when your decimal is represented by New(25, -1).

However, when represented by New(250, -2), the function RoundBank() gives the incorrect result of 3.

Here is a currently failing test which illustrates the problem:

func TestRoundBankAnomaly(t *testing.T) {
    a := New(25, -1)
    b := New(250, -2)

    if !a.Equal(b) {
        t.Errorf("Expected %s to equal %s", a, b)
    }

    expected := New(2, 0)

    aRounded := a.RoundBank(0)
    if !aRounded.Equal(expected) {
        t.Errorf("Expected bank rounding %s to equal %s, but it was %s", a, expected, aRounded)
    }

    bRounded := b.RoundBank(0)
    if !bRounded.Equal(expected) {
        t.Errorf("Expected bank rounding %s to equal %s, but it was %s", b, expected, bRounded)
    }
}

math.MaxInt64 + math.MaxInt64 got minus result

As title says, when adding two MaxInt64s, I got the wrong number:

     d1 := decimal.NewFromFloat(math.MaxInt64).Add(decimal.NewFromFloat(math.MaxInt64))
     fmt.Printf("d1=%s\n", d1.String())  //result: -18446744073709551616

Here is the golang's big package example:

	var x, y, z big.Float
	x.SetInt64(math.MaxInt64)
	y.SetFloat64(math.MaxInt64)
	z.SetPrec(32)
	z.Add(&x, &y)
	fmt.Printf("z=%.40g\n", &z)  // result: 18446744073709551616

Bug in NewFromFloat

Hi, after commit 78e9b82 NewFromFloat works not as it should

fmt.Println(decimal.NewFromFloat(0.1).String()) fmt.Println(decimal.NewFromFloat(0.01).String()) fmt.Println(decimal.NewFromFloat(0.02).String()) fmt.Println(decimal.NewFromFloat(0.0023).String()) fmt.Println(decimal.NewFromFloat(0.69).String()) fmt.Println(decimal.NewFromFloat(0.98).String())

return

0.1000000000000000055511151231257827021181583404541015625
0.01000000000000000020816681711721685132943093776702880859375
0.0200000000000000004163336342344337026588618755340576171875
0.0022999999999999999611421941381195210851728916168212890625
0.689999999999999946709294817992486059665679931640625
0.979999999999999982236431605997495353221893310546875

Reverting back to commit bf9a39e works as it should:
0.1
0.01
0.02
0.0023
0.69
0.98

Tested on go version go1.9.2 linux/amd64
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Model name: Intel(R) Core(TM) i3-4170 CPU @ 3.70GHz

Error decoding string '""': can't convert "" to decimal

as title indicates issue is when used in struct and a retrieved value is an empty string, omitempty is no help. This makes using the library hard as when pulling in data from outside sources you don't know when they could could suddenly do this, and this error is than breaking because no data will be unmarshaled

type example struct { Value decimal.Decimal json:"value, omitempty" }

relevant code in library :

 // UnmarshalJSON implements the json.Unmarshaler interface.
func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
	if string(decimalBytes) == "null" {
		return nil
	}

	str, err := unquoteIfQuoted(decimalBytes)
	if err != nil {
		return fmt.Errorf("Error decoding string '%s': %s", decimalBytes, err)
	}

	decimal, err := NewFromString(str)
	*d = decimal
	if err != nil {
		return fmt.Errorf("Error decoding string '%s': %s", str, err)
	}
	return nil 

and :

func NewFromString(value string) (Decimal, error) {
	originalInput := value
	var intString string
	var exp int64

	// Check if number is using scientific notation
	eIndex := strings.IndexAny(value, "Ee")
	if eIndex != -1 {
		expInt, err := strconv.ParseInt(value[eIndex+1:], 10, 32)
		if err != nil {
			if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
				return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", value)
			}
			return Decimal{}, fmt.Errorf("can't convert %s to decimal: exponent is not numeric", value)
		}
		value = value[:eIndex]
		exp = expInt
	}

	parts := strings.Split(value, ".")
	if len(parts) == 1 {
		// There is no decimal point, we can just parse the original string as
		// an int
		intString = value
	} else if len(parts) == 2 {
		// strip the insignificant digits for more accurate comparisons.
		decimalPart := strings.TrimRight(parts[1], "0")
		intString = parts[0] + decimalPart
		expInt := -len(decimalPart)
		exp += int64(expInt)
	} else {
		return Decimal{}, fmt.Errorf("can't convert %s to decimal: too many .s", value)
	}

	dValue := new(big.Int)
	_, ok := dValue.SetString(intString, 10)
	if !ok {
		return Decimal{}, fmt.Errorf("can't convert %s to decimal", value)
	}

	if exp < math.MinInt32 || exp > math.MaxInt32 {
		// NOTE(vadim): I doubt a string could realistically be this long
		return Decimal{}, fmt.Errorf("can't convert %s to decimal: fractional part too long", originalInput)
	}

	return Decimal{
		value: dValue,
		exp:   int32(exp),
	}, nil
}

there is a check for scientific notation, and a check if there is a decimal, but none for an empty string,
there seems to be 2 ways to fix this, either conform if possible to the omitempty with golang json, if this is not possible than add a check for empty values and return 0 value int, similar to the "is no decimal point we can just parse the original string as an int"

looking at closed issues and the code further I see fix for null, possibly just add "" as well eg :

func (d *Decimal) UnmarshalJSON(decimalBytes []byte) error {
-	if string(decimalBytes) == "null" {
+	if string(decimalBytes) == "null" || string(decimalBytes) == "" {
 		return nil
 	}

Binary Marshalling

Decimal implements JSON and text marshalling. This allows for encoding to json, xml and other text-based encoders. Encoding to binary (like with encoding/gob) does not work because the type does not implement BinaryMarshaler and BinaryUnmarshaler interfaces.

Pow doesn't work for decimal exponents

The current implementation of Pow only looks at the IntPart of the exponent, so something like 4^2.5 returns 16 :(

I'm not sure why the exponent argument is supposed to be a decimal if it actually only works for ints.

Rescaled decimal string representation does not match godoc

The godoc comment on the behavior of Decimal.rescale:

// Rescale returns a rescaled version of the decimal. Returned
// decimal may be less precise if the given exponent is bigger
// than the initial exponent of the Decimal.                  
// NOTE: this will truncate, NOT round                        
//                                                            
// Example:                                                   
//                                                            
//  d := New(12345, -4)                                       
//  d2 := d.rescale(-1)                                       
//  d3 := d2.rescale(-4)                                      
//  println(d1)                                               
//  println(d2)                                               
//  println(d3)                                               
//                                                            
// Output:                                                    
//                                                            
//  1.2345                                                    
//  1.2                                                       
//  1.2000                                                    
//                                                            

indicates that the following test should pass:

func TestNegativeScale(t *testing.T) {
       ref := New(12345, -4)
       for _, testCase := range []struct {
               d Decimal
               s string
       }{
               {ref, "1.2345"},
               {ref.rescale(-1), "1.2"},
               {ref.rescale(-1).rescale(-4), "1.2000"},
       } {
               if testCase.d.String() != testCase.s {
                       t.Errorf("expected %q got %q", testCase.s, testCase.d.String())
               }
       }
}

However, Decimal.String does not match this behavior. The third test case fails, as the zeroes to the right are dropped; you get "1.2".

In the case of representing currency amounts as a string, I'd want New(5, -2).String() to give me "5.00" instead of "5"... or if String isn't appropriate for this, then I need a StringXyz method variant that retains zero placeholders for the full scale of the decimal.

Problem in Truncate function

I tried to do following and it outputs 11.199999, I think the correct behaviour should be 11.200000
decimal.NewFromFloat(10.00000000).Add(decimal.NewFromFloat(1.20000000)).Truncate(6).Float64()

Bug: Creating a NewFromFloat[WithExponent](0) returns an empty Decimal{}

The culprit is here, around line 207:

	if exp2 == 0 {
		// specials
		if mant == 0 {
			return Decimal{}     // <---- No. Just... no. Bad!
		} else {
			// subnormal
			exp2++
		}
                ....

This works ok up until the point you try to query the contents of the Decimal struct, say using .Coefficient() . It throws a nil pointer dereference panic (as .value is nil). This is problematic.

To fix, change the return to:

return Decimal{ value: big.NewInt(0), exp: 0 }

Also, probably worth adding the check to ensureInitialised() on all functions that might try and reference .value in some way

Test failure on aarch64 for version 1.1.0

Testing: github.com/shopspring/decimal
Testing: "/builddir/build/BUILD/decimal-1.1.0/_build/src/github.com/shopspring/decimal"
+ GOPATH=/builddir/build/BUILD/decimal-1.1.0/_build:/usr/share/gocode
+ go test -buildmode pie -compiler gc -ldflags '-extldflags '\''-Wl,-z,relro   -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld '\'''
--- FAIL: TestFloat64 (0.00s)
    decimal_test.go:297: 0.00000000000000000100000000000000000000000000000000012341234 should be represented inexactly
    decimal_test.go:297: 0.00000000000000000299999999999999999999999900000000000123412341234 should be represented inexactly
    decimal_test.go:297: -0.00000000000000000100000000000000000000000000000000012341234 should be represented inexactly
    decimal_test.go:297: -0.00000000000000000299999999999999999999999900000000000123412341234 should be represented inexactly
FAIL
exit status 1
FAIL	github.com/shopspring/decimal	32.706s

big.Rat equivalence?

Hey there!

I saw this library featured in http://golangweekly.com/issues/49 and the first thing that occurred to me was why wouldn't you use the stdlib math/big library. Would you please elucidate me on this one?
Here's the equivalent README example using math/big.

package main

import (
    "fmt"
    "math/big"
)

func main() {
    price, ok := new(big.Rat).SetString("136.02")
    if !ok {
        panic("parsing decimal failed")
    }

    quantity := new(big.Rat).SetFloat64(3)

    fee, _ := new(big.Rat).SetString(".035")
    taxRate, _ := new(big.Rat).SetString(".08875")

    subtotal := new(big.Rat).Mul(price, quantity)

    preTax := new(big.Rat).Mul(subtotal, new(big.Rat).Add(fee, new(big.Rat).SetFloat64(1)))

    total := new(big.Rat).Mul(preTax, new(big.Rat).Add(taxRate, new(big.Rat).SetFloat64(1)))

    fmt.Println("Subtotal:", subtotal.FloatString(9))
    fmt.Println("Pre-tax:", preTax.FloatString(9))
    fmt.Println("Taxes:", new(big.Rat).Sub(total, preTax).FloatString(9))
    fmt.Println("Total:", total.FloatString(9))
    fmt.Println("Tax rate:", new(big.Rat).Quo(new(big.Rat).Sub(total, preTax), preTax).FloatString(9))
}

Which outputs:

Subtotal: 408.060000000
Pre-tax: 422.342100000
Taxes: 37.482861375
Total: 459.824961375
Tax rate: 0.088750000

Configurable decimal point JSON serialization via struct tags

Long time listener, first time caller.

I couldn't see any documentation on this, so I thought I'd ask: is there a reason that support for JSON serialization to a specified number of decimal places isn't supported via a struct tag? I'm thinking something like:

type Foo struct {
    Bar decimal.Decimal `json:"bar",decimalPlaces:"2"`
}

The advantage here being that if my API returns, e.g., a number corresponding to a dollar amount, it can be serialized as 5.20 rather than 5.2

Values of the Decimal type get marshaled to JSON as strings, not numbers

This line wraps the string representation of a value into double quotes which get transferred "as is" into the resulting JSON representation.

While I understand that a package is free to pick any "on-the-wire" representation for the values of the types it works with, I would make a claim that the selected approach is incorrect: the JSON spec defines the representation for "numbers" -- which includes both "plain" integers and floating-point values in the scientific notation, and the Decimal type provided by this package represents numbers, so I would expect it to serialize into JSON as numbers as well.

The rationale is that a receiving end for JSON representations serialized from Decimal values handled by this package is not necessarily written using the same package (or even in Go), so it naturally expects to parse these representations to whatever numeric type it supports -- be it floats, Java's BigDecimal, .NET's decimal and so on. Currently we either rely on some sloppiness of the receiver's decoder (for instance, the Json.Net library is happy to parse whatever this package produces into decimals) or require custom unmarshaling.

Actually the inconsistency I'm talking about was caught by the testing code in one of my packages -- which tested JSON serialization of whatever stuff my package generates -- after I converted its parts which work with amounts of money to use Decimal rather than stock float64: floats were marshaled as numbers, and Decimals suddenly got double-quoted (while containing the same string representations as floats).

TL;DR
I think that Decimals should be marshaled to JSON as numbers, not as strings.

Performance issue preventing production use

I'm currently working on an application that would benefit greatly from this lib, due to requirements with floating point precision. However benchmark tests of that application are worrying.

The scenario involves creating Decimal values, and then calling the various comparison methods on them. This sequence of events happens millions of times. Immediately after converting my code to use Decimal (instead of big.float, which was showing unacceptable precision loss after 20 significant digits), the performance time of our benchmark test went from 3-4 minutes for the test, to 22, on the same processor. pprof helped narrow the problem to the fact that your implementation of CMP involves the creation of an additional 2 decimal objects whenever the 'exp' values were different (which was pretty much every time), which is in line with the fact that after the change, 60% of the cpu time was spent allocating memory.

Is there a particular reason this approach was taken? Would a PR attempting to optimize this be accepted?

Start tagging releases?

Hey all! Thanks for the really useful package.

I was wondering if you are open to starting to tag releases with semantic versions (e.g. v0.0.1). I've been refreshing my go knowledge and discovered the go dep tool. It uses github tags for explicit version pins, and I'd love to pin a particular version of decimal instead of the master branch. Thanks! :)

Round up

Hello,
I want to know how I can easily round up with decimal?
For example with roundup method that doesn't exist :

pi, _ := NewFromString("3.14159265359")
fmt.Println(pi.RoundUp(1)) // 3.2

Thank

trimTrailingZeros should be an option

Right now it's assumed that trailing zeros should be removed upon a call to Value, as a decimal is being serialized into the database. This doesn't take into account cases where you want to keep the number of decimal places consistent across the board. For example, if I created a Decimal via:

dec, _ := decimal.NewFromString("100.00")

I would expect it to enter the database as "100.00". When in actuality, the trailing zeros are removed and "100" is saved. This creates some interesting scenarios when storing and retrieving decimals from the database. I would expect the precedent to be that decimals are represented as a string in exactly the same way they were created, unless the user does an operation that would change the precision(such as using StringFixed).

Usage as map key

As it stands, Decimal does not work as map key. This is because it uses value *big.Int. Why not just value big.Int ? Is there a real reason it is coded this way? Is this for performance? Have you measured this?

JSON Marshaling issue

Shouldn't this struct below be empty when using the json marshaller? Since the go json docs state that 0 is considered an empty value. But because of the cast to string, all the struct fields show up.

package main

import (
    "encoding/json"
    "github.com/shopspring/decimal"
    "fmt"
)

type Specs struct {
    Watts              decimal.Decimal `json:"watts,omitempty"`
    Amps               decimal.Decimal `json:"amps,omitempty"`
    Voltage            decimal.Decimal `json:"voltage,omitempty"`
}

func main() {
    s := new(Specs)

    res,err := json.Marshal(s)
    if err != nil {
      fmt.Printf("ERR: %s", err.Error())
      return  
    }
    fmt.Printf("SPECS: %s\n", res)
}

// outputs -> SPECS: {"watts":"0","amps":"0","voltage":"0"}

after Mul or Div, the method of Exponent returns wrong result

package main

import (
	"fmt"
	"github.com/shopspring/decimal"
)

func main() {
	s := "13181.463"
	f1, _ := decimal.NewFromString(s)
	d := decimal.NewFromFloat(1000)
	f2 := f1.Mul(d)
	f3 := f1.Div(d)
	f4 := f1.Mul(d).Div(d)
	fmt.Println(f1, f1.Exponent(), f2, f2.Exponent(), f3, f3.Exponent(), f4, f4.Exponent())
}

output:

13181.463 -3 13181463 -3 13.181463 -16 13181.463 -16

f2.Exponent, f3.Exponent(), f4.Exponent() are all return a wrong result.

NewFromFloat does not use correct exponent

We just updated decimal and it seems that the behaviour of decimal.NewFromFloat was recently changed.

decimal.NewFromFloat(127.38) will now return 127.3799999999999954525264911353588104248046875, while it was expected to return 127.38 (ie. 12738 with exponent -2).

Do not panic in libraries

I noticed in several places, you guys are panicking - with good intentions.

The problem is, this makes it a lot harder to "trust" the library will not cause problems when handed improper inputs.

The better solution to this is to return an error, which must be handled by the user expressly. While it's more code involved to work with the library, it's also a significantly safer approach to providing functionality for others to consume.

If this is a PR you'd accept, I would happily make this change.

Pointer Receivers

Is there a reason that this package mostly deals in value types? It makes json-ifying things somewhat tricky in some cases. Consider the following:

type WireType struct {
    Id  int             `"json:id"`
    Num decimal.Decimal `json:"num,string,omitempty"`
}

When this struct is marshaled, it will never omit Num, because json doesn't know when Num is zero.

The obvious fix for this is to make Num a pointer type, but since this lib relies heavliy on non-mutable/value types, this turns otherwise nice-looking code:

params := WireType{1, decimal.NewFromFloat(1.0),}
factor := decimal.NewFromFloat(2.0).Mul(params.Num)

Into this (where num is of type *decimal.Decimal):

one := decimal.NewFromFloat(1.0)
params := WireType{1, &one,}
factor := one.Mul(*params.Num)

Small potatoes? Maybe. But it would clean up the code a bit and make the API nicer in situations like this, especially in libraries which depend on decimal, which then results in the end developer needing to always dereference Decimal types in structs which depend on them.

Happy to work on a PR for this unless there are any fundamental objections.

Pow implementation

When calculating taxes and converting between day/month/year bases, it is necessary to use Pow with decimal exponent. Is there any way to do this with this library?

UnmarshalJSON throws error for null

When JSON data contains a null value for a Decimal field, the UnmarshalJSON function returns an error: Error decoding string 'null': can't convert null to decimal.

I am unsure if this is the intended behaviour. For Go native type float64, a null is set to 0.0 when unmarshaling. In this issue related to time.Time, the solution is to unmarshal null into the zero value.

To me, it makes sense to unmarshal null to the zero value of Decimal when the target variable is a struct. If the user wants to unmarshal to nil, she can still make the target variable a pointer:

type Example struct {
    AsStruct   decimal.Decimal
    AsPointer *decimal.Decimal
}

var data = `{"AsStruct": null, "AsPointer"; null}`
var e Example

_ = json.Unmarshal(&e) // Ignoring error for this example. Actually, there would be no error returned.

fmt.Println("AsStruct", e.AsStruct, "AsPointer", e.AsPointer)

would produce

AsStruct 0 AsPointer nil

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.