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

[css-values] mod(−0, +∞) and mod(+0, −∞) not handled correctly #4723

Closed
Loirooriol opened this issue Jan 31, 2020 · 9 comments
Closed
Labels
Closed Accepted by Editor Discretion Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-values-4 Current Work

Comments

@Loirooriol
Copy link
Contributor

From https://drafts.csswg.org/css-values-4/#round-infinities

In mod(A, B) only, if B is infinite and A is non-zero and has opposite sign to B, the result is NaN.
Note: All other "infinite B" cases are valid, and just return A immediately.

This means that mod(−0, +∞) = −0 and mod(+0, −∞) = +0.

However, the returned value is supposed to be between zero and B, with

if B is positive the range starts at 0⁺, and if B is negative it starts at 0⁻.

So we should have mod(−0, +∞) = +0 and mod(+0, −∞) = −0.

@Loirooriol Loirooriol added the css-values-4 Current Work label Jan 31, 2020
@jrus
Copy link

jrus commented Feb 5, 2020

mod(−0, +∞) = +0 is arguably invalid. Should be ∞ or NaN instead.

But really, this shows why mod(x, ∞) is nonsensical in general. There’s no convention you can choose where the result won’t lead to confusing bugs.

If you just make mod(x, ∞) always NaN it would save trouble.

@tabatkins
Copy link
Member

It definitely shouldn't be infinity. Replace ∞ with a very large finite number, and the answer is definitely just +0. It could be NaN, but the general policy is that we define the behavior for ∞ arguments to be the limit as the argument approaches ∞, if the limit exists; it's only NaN if there's no limit.

@Loirooriol
Copy link
Contributor Author

Using limits, shouldn't we have

mod(finite negative, +∞) = +∞
mod(finite positive, −∞) = −∞

instead of NaN?

However, I'd expect mod(A1, B) = mod(A2, B) to imply that A1 - A2 is an integer multiple of B.
This doesn't hold with the above. Maybe that's what @jrus meant with "mod(x, ∞) is nonsensical in general"?

But I guess not having that property can be attributed to precision problems. Like

mod(-1, 1e17) = -1 + 1e17 = 1e17 = -2 + 1e17 = mod(-2, 1e17)

even though (-1) - (-2) = 1 is not an integer multiple of 1e17. So not exclusive for ∞.

@jrus
Copy link

jrus commented Feb 8, 2020

mod(-0, 5) should arguably be 5 rather than 0. Remember, -0 represents negative underflow, i.e. any real number which is less than zero but greater than the smallest otherwise representable negative number. When you map this into the range 0..5, what you should get is a number which is very slightly less than (but not possible to represent as a float less than) 5. Making mod(-0, 5) == 0 leads to category errors.

For example, cotpi(x) should equal cotpi(mod(x, 1)), but while cotpi(-0) = cotpi(1) = –∞, cotpi(0) = ∞


But mod(x, ∞) for any x is nonsensical either way; it implies dividing by ∞, taking the floor, multiplying the resulting quotient by ∞, and then subtracting. We get ∞ · 0 in the middle of that process.

@Loirooriol
Copy link
Contributor Author

Loirooriol commented Feb 8, 2020

@jrus You are saying -0 ≡ 5 (mod 5), but 5 ≡ +0 (mod 5), so -0 ≡ +0 (mod 5).

Unlike rem(), the nice property of mod() is that it always returns the same value for the same equivalence class. Breaking this for -0 doesn't seem worth it. We would also loose idempotency.

What's cotpi?

But mod(x, ∞) for any x is nonsensical either way; it implies dividing by 0.

Not necessarily. What mod(A, B) does is it projects A ∈ ℝ into its equivalence class [A] ∈ ℝ / Bℤ, and then takes it back to by choosing the only representative of [A] between 0 and B (i.e. in the interval [0, B) or (B, 0], depending on the sign of B). There is no division.

If B→+∞, Bℤ basically becomes {-∞, 0, +∞}. So [A] = {A} (ignoring infinities since it's not much well-defined). Then,

  • For positive finite A, it seems clear that mod(A, +∞) = A, since [A] has no other representative.
  • For negative finite A, we could also say mod(A, +∞) = A. But then we would loose the property that mod(A, B) must be between 0 and B. So NaN is reasonable, meaning that it's not possible to get a representative of [A] in the interval [0, +∞).

@jrus
Copy link

jrus commented Feb 9, 2020

That floating point arithmetic is not the same as real-number arithmetic is sort of my point. It tends to break down at edge cases.

I think it’s fine to make mod(-0, 5) return 0 as long as that is documented, and I am not arguing that should be changed, even though this is in some contexts going to cause categorical errors. I am just pointing out that the alternative interpretation about what to do at the edge case is also arguably valid for some situations; whatever choice you pick is going to cause a violation of expectations and some failures in calculations done without careful reading of the specification.

-0 ≡ 5 (mod 5) is a somewhat strange notation, since modular arithmetic with this notation as used in mathematics only applies to integers, not real numbers.

Sorry I said “divide by 0”, what I meant was “multiply 0 by ∞”, as I fixed slightly afterward. cotpi(x) = cot(πx), the cotangent of π times the argument. Trigonometric functions with a factor of π included can save some grief in many practical situations, since π cannot be exactly represented in floating point.

A clearer way to state my original point is: I can’t think of any practical computation where a person would ever end up with mod(x, ∞). I don’t think it really makes much sense. We can’t break the real number line into locally-metric-preserving equivalence classes with 0 ≡ ∞.

If you define it via a limiting argument, then clearly mod(x, ∞) has a limit of x for any x ≥ 0 and a limit of ∞ for any x < 0. But I think it would be better to just let mod(x, ∞) = NaN regardless.

@Loirooriol
Copy link
Contributor Author

modular arithmetic with this notation as used in mathematics only applies to integers

Fair enough, I had only seen it with integers, but I thought I could generalize it.

cotpi(x) = cot(πx), the cotangent of π times the argument.

Oh I didn't know this notation. I see your point, but floating point numbers don't have a 1⁺ and a 1⁻. So cotpi(1) could be either +∞ or −∞. See related #4101 about tan(). tan(x + 180deg) = tan(x) but in infinite cases we decided on tan(-90deg) = −∞ and tan(90deg) = +∞.

I can’t think of any practical computation where a person would ever end up with mod(x, ∞)

Sure, I don't think people will use an explicit ∞, but possibly they will use some calculation which may end up being ∞ in certain cases.

I think it would be better to just let mod(x, ∞) = NaN regardless.

NaN is reasonable for negative x, but not that sure about positive x. Intuitively I would expect x, though I could live with NaN.

@tabatkins
Copy link
Member

Re-reviewing this, I think I agree that the current behavior (mod(-0, infinity) yielding -0) is indeed wrong. I believe there are two reasonable approaches here:

  • Flip it to the appropriately-signed zero instead, just like mod(-0, 5) yield +0; while we can't tell what value a mod(-1, infinity) should resolve to, "0 away from infinity" is just infinity and thus in the 0 equivalence class ^_^
  • Pay attention to sign first, so a wrong-signed zero yields NaN like any other wrong-signed value would.

I'm relatively ambivalent between the two, but weakly prefer the second; a process which approaches -0 is doing so from the negative side, where all the similar non-zero negative values produce NaN, and so I think it's slightly better to be consistent there.

Thoughts?

@Loirooriol
Copy link
Contributor Author

Both approaches can be reasonable, no strong opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Closed Accepted by Editor Discretion Commenter Satisfied Commenter has indicated satisfaction with the resolution / edits. css-values-4 Current Work
Projects
None yet
Development

No branches or pull requests

3 participants