You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[css-values] mod(−0, +∞) and mod(+0, −∞) not handled correctly #4723
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.
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.
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(-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.
@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, +∞).
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.
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.
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.
Activity
jrus commentedon Feb 5, 2020
mod(−0, +∞) = +0is 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 commentedon Feb 6, 2020
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 commentedon Feb 6, 2020
Using limits, shouldn't we have
instead of NaN?
However, I'd expect
mod(A1, B) = mod(A2, B)to imply thatA1 - A2is an integer multiple ofB.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
even though
(-1) - (-2) = 1is not an integer multiple of 1e17. So not exclusive for ∞.jrus commentedon 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. Makingmod(-0, 5) == 0leads 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 commentedon Feb 8, 2020
@jrus You are saying
-0 ≡ 5 (mod 5), but5 ≡ +0 (mod 5), so-0 ≡ +0 (mod 5).Unlike
rem(), the nice property ofmod()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?
Not necessarily. What
mod(A, B)does is it projectsA ∈ ℝinto its equivalence class[A] ∈ ℝ / Bℤ, and then takes it back toℝby choosing the only representative of[A]between 0 andB(i.e. in the interval[0, B)or(B, 0], depending on the sign ofB). There is no division.If
B→+∞,Bℤbasically becomes{-∞, 0, +∞}. So[A] = {A}(ignoring infinities since it's not much well-defined). Then,A, it seems clear thatmod(A, +∞) = A, since[A]has no other representative.A, we could also saymod(A, +∞) = A. But then we would loose the property thatmod(A, B)must be between 0 andB. So NaN is reasonable, meaning that it's not possible to get a representative of[A]in the interval[0, +∞).jrus commentedon 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)return0as 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 commentedon Feb 10, 2020
Fair enough, I had only seen it with integers, but I thought I could generalize it.
Oh I didn't know this notation. I see your point, but floating point numbers don't have a
1⁺and a1⁻. Socotpi(1)could be either +∞ or −∞. See related #4101 abouttan().tan(x + 180deg) = tan(x)but in infinite cases we decided ontan(-90deg) = −∞andtan(90deg) = +∞.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.
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 commentedon Oct 21, 2021
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:mod(-0, 5)yield+0; while we can't tell what value amod(-1, infinity)should resolve to, "0 away from infinity" is just infinity and thus in the 0 equivalence class ^_^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 commentedon Oct 26, 2021
Both approaches can be reasonable, no strong opinion.
[css-values-4] Define that mod(-0, Infinity) yields NaN. #4723