brick / math

Arbitrary-precision arithmetic library for PHP
MIT License
1.78k stars 75 forks source link

BigDecimal's scale should be zero when all fractional digits are zero #81

Closed aprat84 closed 1 week ago

aprat84 commented 1 week ago

When working with databases (MySQL8) and decimal columns, it returns a string with every fractional digit, even when all are zero.

In that case, when all fractional digits are zero, BigDecimal still considers them, and creates an scaled instance:

$a = BigDecimal::of('2');
$b = BigDecimal::of('2.000');

var_dump((string)$a); // '2'
var_dump((string)$b); // '2.000'
var_dump((string)$a === (string)$b); // false

In reality, both are the same exact number, so IMHO they should be represented equally when stringified.

Should the parser consider only significant fractional digits? I mean, what is the real scale of 2.123000, 3 or 6?

What are your thoughts?


My use case is that I'm testing an XML generation, and printing on it BigDecimals using (string)$value, and it gives different results when using factories than when retrieving entities from DB.

BenMorel commented 1 week ago

Hi @aprat84,

In reality, both are the same exact number, so IMHO they should be represented equally when stringified.

I actually think that would make things more confusing. Depending on the application, one can expect BigDecimal to retain the number of decimals you explicitly passed. Java exhibits the same behaviour:

var x = new BigDecimal("123.000000");
System.out.println(x.scale()); // 6
System.out.println(x.toString()); // 123.000000

If you want to compare BigDecimal instances, you should use isEqualTo():

$a = BigDecimal::of('2');
$b = BigDecimal::of('2.000');
$a->isEqualTo($b); // true

If you want to strip trailing zeros, you can use stripTrailingZeros():

$a = BigDecimal::of('123.456000'); // 123.456000
$b = $a->stripTrailingZeros(); // 123.456

You can actually compare string representations of two BigDecimal numbers after applying stripTrailingZeros():

$a = BigDecimal::of('2')->stripTrailingZeros();
$b = BigDecimal::of('2.000')->stripTrailingZeros();

(string) $a === (string) $b; // true

But again, I would strongly recommend to use the built-in comparison methods.

I mean, what is the real scale of 2.123000, 3 or 6?

Maybe related:

aprat84 commented 1 week ago

Well, everything you said makes sense, but I don't see why it works like this, though I'm no math expert 😅

Comparing to JAVA BigDecimal, it's equals method also takes into account the scale, so it is not fully equivalent to your isEqualTo method...

As I said, I'm not comparing two instances of BigDecimal, or I would use isEqualTo, but testing generated XML. I'll use the stripTrailingZeros() method or toScale(...), I'll see.