Differences between 0 and -0
Decimal does not consider 0 and -0 to be exactly the same value:
- 0 is considered positive (
isPositive()returnstrueandisNegative()returnsfalse) while - -0 is considered negative (
isPositive()returnsfalseandisNegative()returnstrue).
Also, toString() and toFixed() will include the - sign for -0.
I found two ways to create a Decimal instance with -0:
-
new Decimal('-0') -
(new Decimal(0))->negate()
While the first is unlikely, the second may occur in case one negates a value without checking whether it's zero first. Given the differences listed above, this might induce unexpected behavior in the code doing this.
According to Wikipedia, 0 can either be considered:
- neither positive nor negative;
- both positive and negative.
I would suggest choosing one of these two possibilities and implement methods accordingly, i.e.:
- if neither positive nor negative:
-
isPositive():false -
isNegative():false -
isNonNegative():true -
isNonPositive():true
-
- if both positive and negative:
-
isStrictlyNegative():false -
isStrictlyPositive():false -
isPositive():true -
isNegative():true
-
Also, I think -0 should be converted to 0 internally.
WDYT?
I think this came up because something like -0.1 / 1000 would truncate to 0 if there is not enough precision available to represent that result correctly. Maybe storing the precision alongside the value was a bad design choice. From https://docs.python.org/3/library/decimal.html#decimal.Decimal:
The context precision does not affect how many digits are stored. That is determined exclusively by the number of digits in value. For example, Decimal('3.00000') records all five zeros even if the context precision is only three.
Maybe the implicit context approach in php-decimal (one global shared context) gives way to these small inconsistencies. The idea was to have ((new Decimal("-0.1", 2) / new Decimal("1000000"))->isNegative() return true and display as -0, but I can see how that is confusing. Maybe negative 0 should not exist here and zero be neither positive nor negative.
Should we go with what is practical and intuitive, or what is more correct from a mathematical point of view?
My current intuition is:
- I think -0 should be converted to 0 internally.
- isPositive(): false
- isNegative(): false
So you would choose the zero is neither positive nor negative interpretation? Do you think adding isNonNegative()/isNonPositive() methods makes sense? Also, would you considerer this a BC break?
I have no experience in C but, if you don't mind, I'd like to try contributing those changes and make this a learning opportunity :)
Yes I think neither positive nor negative, and would those methods be equivalent to eg. NonNegative = isPositive() or isZero()? I think the compound is nice because there is no ambiguity to the reader. Prefer local reasoning 😊
Feel free to look at the 2.0 branch, should be able to navigate that quite easily. The mpdecimal docs are handy too. We can use this issue for questions and comments.
I wasn't able to get all tests passing except on branch 1.x. Are branches master and 2.0 up-to-date?
There is also another way of causing this issue. If a negative decimal value is rounded to 0.00, it will still be negative. See the below attached screenshot:

If a negative decimal value is rounded to 0.00, it will still be negative
The work done here is good but I'm not sure that we should treat -0 and +0 as equal.
https://stackoverflow.com/questions/4083401/negative-zero-in-python
Maybe we could do so for formatting, but not calculation?
I fail to see a benefit in keeping -0 not equal to 0, even for calculation only.
If you have a negative number and round it incidentally to zero, it would be incorrect to change the sign. I'm sure the python folks thought it through.
I can see the value in always producing a sign-less "0" when formatting, but to have isNegative and arithmetic honor the sign unchanged.
If someone would like to render the zero sign, they can do so with checks and a prefix.
https://peps.python.org/pep-0682/
As the referenced document mentioned, I would also agree that the best solution for this would be an opt-in approach.
As far as I can understand, in 2.x you can already select the default rounding mode, why not this as an opt-in method too?
Because, in version 1.x, I already have too many ->toFixed(2, true, PHP_ROUND_HALF_UP) calls, that it already pollutes the code enough, but if I have to add another operation before that to correctly get the - sign right, then I don't think that's cool.