decimal icon indicating copy to clipboard operation
decimal copied to clipboard

NewFromString And NewFromFloat May Produce Different Decimals Given The Same Number

Open marcocali opened this issue 5 years ago • 8 comments

Due to how the exp is calculated on NewFromString and NewFromFloat, two different Decimals may be created given the same number.

package main

import (
	"testing"
	
	"github.com/stretchr/testify/assert"
	"github.com/shopspring/decimal"
)

func Test(t *testing.T) {
	n1, _ := decimal.NewFromString("10")
	n2 := decimal.NewFromFloat(10)
	
	assert.Equal(t, n1, n2, "equal numbers")
}

=== RUN Test Test: prog.go:14: Error Trace: prog.go:14 Error: Not equal: expected: decimal.Decimal{value:(*big.Int)(0xc00000e860), exp:0} actual : decimal.Decimal{value:(*big.Int)(0xc00000e8a0), exp:1}

    	            	Diff:
    	Test:       	Test
    	Messages:   	equal numbers

--- FAIL: Test (0.00s) FAIL

https://play.golang.org/p/5mOcyDheifa

marcocali avatar May 05 '20 07:05 marcocali

Thanks @marcocali for pointing this out. We will take a closer look on this :)

mwoss avatar May 05 '20 15:05 mwoss

I have nearly the same issue with such code:

assert.Equal(t, decimal.Zero, decimal.NewFromFloat(0))

which fails because of the exp value:

Error:      	Not equal: 
        	expected: decimal.Decimal{value:(*big.Int)(0xc00000e760), exp:1}
            	actual  : decimal.Decimal{value:(*big.Int)(0xc00019bd80), exp:0}

faide avatar Jul 01 '20 12:07 faide

I apologize for such late response @marcocali, @faide.

Yes, you are exactly right. Both NewFromFloat and NewFromString do not provide always 1:1 results. As semantically both functions return the same values, the underneath representation differs. It is a known issue, and that's why you should always use Cmp or Equals method to compare two decimals. By using those methods you will be able to compare decimal values, not underneath struct values. Anyhow, we will try to correct this behaviour, but it may be difficult and may take some time.

mwoss avatar Jul 13 '20 22:07 mwoss

Is there a way to adjust the exp value that can be done before trying to compare?

zbup avatar Jan 10 '22 15:01 zbup

this inconsistency problem brings some kind of trouble, for most assertions in unit tests, I can simply use assert.Equal(t, struct1, struct2) to judge if they are equal if the both don't include a decimal field. Because of this problem, I have to assert each field one by one like:

assert.Equal(t, struct1.Field1, struct2.Field1)
assert.Equal(t, struct1.Field2, struct2.Field2)
...
assert.True(t, struct1.DecimalField.Equal(struct2.DecimalField))

Hope it can be solved.

Martin91 avatar Feb 17 '22 11:02 Martin91

May I know any update on this issue? this is caused us problem when we are writing test and test is crucial in software development. we can't simply use refelect.DeepEqual() or assert.ElementMatch() to check on our test.

pangminfu avatar Oct 19 '22 05:10 pangminfu

Well,

this is not perfect but at least you can use:

assert.True(t, struct1.DecimalField.Equal(struct2.DecimalField))

as @Martin91 pointed out above.

The point here is to use decimal1.Equal(decimal2) or decimal1.Cmp(decimal2) before asserting with your test library

faide avatar Oct 19 '22 08:10 faide