Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Differences between 0 and -0 #66

Open
julienfalque opened this issue Feb 9, 2022 · 12 comments
Open

Differences between 0 and -0 #66

julienfalque opened this issue Feb 9, 2022 · 12 comments

Comments

@julienfalque
Copy link

Decimal does not consider 0 and -0 to be exactly the same value:

  • 0 is considered positive (isPositive() returns true and isNegative() returns false) while
  • -0 is considered negative (isPositive() returns false and isNegative() returns true).

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?

@rtheunissen
Copy link
Contributor

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

@julienfalque
Copy link
Author

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 :)

@rtheunissen
Copy link
Contributor

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.

@julienfalque
Copy link
Author

I wasn't able to get all tests passing except on branch 1.x. Are branches master and 2.0 up-to-date?

@CreepPork
Copy link

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:
Screenshot_20230425_135641

@rtheunissen
Copy link
Contributor

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

@rtheunissen
Copy link
Contributor

Maybe we could do so for formatting, but not calculation?

@julienfalque
Copy link
Author

I fail to see a benefit in keeping -0 not equal to 0, even for calculation only.

@rtheunissen
Copy link
Contributor

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.

@rtheunissen
Copy link
Contributor

If someone would like to render the zero sign, they can do so with checks and a prefix.

@rtheunissen
Copy link
Contributor

@CreepPork
Copy link

CreepPork commented Jan 22, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants