Closed Bug 1867565 Opened 2 years ago Closed 1 year ago

[css-values] mod() and rem() don't handle signed zero well

Categories

(Core :: CSS Parsing and Computation, defect, P3)

defect

Tracking

()

RESOLVED FIXED
124 Branch
Tracking Status
firefox124 --- fixed

People

(Reporter: Oriol, Assigned: Oriol)

References

Details

Attachments

(1 file)

This is one of the failures that I detected in https://github.com/web-platform-tests/wpt/pull/43430

<!DOCTYPE html>
<script>
var el = document.documentElement;
el.style.zIndex = "calc(1 / mod(-1, -1))";
el.textContent = getComputedStyle(el).zIndex;
</script>

Expected: a very small (large in magnitude) negative value
Actual: 2147483647

These are all the related problematic cases:

  • mod(-1, -1) should be -0, but it's +0.
  • mod(1, -1) should be -0, but it's +0.
  • rem(-1, -1) should be -0, but it's +0.
  • rem(-1, 1) should be -0, but it's +0.

As per https://drafts.csswg.org/css-values/#round-func, mod(A, B) is a value "between zero and B", which is defined as

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

For rem(A, B), if one value is positive and the other is negative, then it chooses "between zero and -B". If both values are positive or both negative, it behaves like mod(A, B).

See Also: → 1867569

Presumably this is fixed by bug 1867569?

Severity: -- → S3
Depends on: 1867569
Priority: -- → P3

Doesn't seem to be the case...
https://searchfox.org/mozilla-central/rev/956e25260926097a4d54d5aeb0e06347841616bf/servo/components/style/values/generics/calc.rs#1047-1048

ModRemOp::Mod => dividend - divisor * (dividend / divisor).floor(),
ModRemOp::Rem => dividend - divisor * (dividend / divisor).trunc(),

E.g. for mod(-1, -1), this produces (-1) - (-1) * floor((-1) / (-1)) = -1 + 1 = +0 instead of -0

I'm not sure if there's a way to adjust the math to get the correct sign, but I suppose we could adjust the sign of the result as necessary. Maybe something like this?

ModRemOp::Mod => (dividend - divisor * (dividend / divisor).floor()).copysign(divisor),
ModRemOp::Rem => (dividend - divisor * (dividend / divisor).trunc()).copysign(
    if dividend.signum() == divisor.signum() { divisor } else { dividend }
),

For ModRemOp::Rem it can just be copysign(dividend) (if the signs are the same, then it doesn't matter which one we pick).
I think that would be correct, but in general changing the sign of a modulo can be wrong, so first I would check that the result is 0.
And in fact I don't think the current formulas can produce -0 at all, so I would do something like

ModRemOp::Mod => {
    let r = dividend - divisor * (dividend / divisor).floor();
    if r == 0.0 && divisor.is_sign_negative() { -0.0 } else { r }
},
ModRemOp::Rem => {
    let r = dividend - divisor * (dividend / divisor).trunc();
    if r == 0.0 && dividend.is_sign_negative() { -0.0 } else { r }
}
See Also: 1867569
No longer depends on: 1867569
See Also: → 1867569
Assignee: nobody → oriol-bugzilla
Status: NEW → ASSIGNED
Pushed by oriol-bugzilla@hotmail.com: https://hg.mozilla.org/integration/autoland/rev/f5380dcdf655 Fix mod() and rem() never returning -0. r=emilio
Status: ASSIGNED → RESOLVED
Closed: 1 year ago
Resolution: --- → FIXED
Target Milestone: --- → 124 Branch
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: