Make calculator % agree with //

YU
35m ago
+87-2

Problem and behavior

The task calculator floors // but used JavaScript's truncating %, so (a // b) * b + a % b did not give back a when the operands had opposite signs. For example, -7 % 3 showed -1 while -7 // 3 showed -3.

This takes approach 1, accepted in #602: % now returns the remainder of floored division, so its result takes the sign of the divisor. For integers this matches Python while the arithmetic stays exact. -7 % 3 is 2 and 7 % -3 is -2. // does not change.

Notes

The divisor is added only when the signs differ, because ((a % b) + b) % b would turn 0.1 % 1 into 0.10000000000000009. An exact multiple returns 0 rather than -0, because atan2 reads the sign. A non-finite divisor keeps the plain remainder, so -1 % 10^400 stays -1 where Python gives inf; this differs from Python on purpose, so a finite expression keeps working. Non-negative operands keep their results, and a zero divisor is still refused. docs/task-mode.md now states the behavior for negative operands and infinite divisors.

The identity is promised for integers while the arithmetic stays exact. // rounds the quotient before flooring it, so 1 // 0.1 is 10 where Python gives 9, and that case already failed before this change. This change adds cases of its own: (-9.3 // 3.1) * 3.1 + (-9.3 % 3.1) was -9.3 and is now -6.2, and (5.4 // -1.8) * -1.8 + (5.4 % -1.8) was 5.4 and is now 3.6. % now follows Python there, while -9.3 // 3.1 stays -3 where Python gives -4. Deriving // from the remainder, as Python does, would fix this, but it changes 1 // 0.1 from 10 to 9, which #602 ruled out, so // is unchanged here. A dividend of the other sign that is tiny beside the divisor rounds to the divisor, so -1e-20 % 3 is 3, as with Python floats.

Testing

tests/browser/calculator.test.js adds negative operands, the identity across sign combinations, non-negative results, zero divisors, the atan2 case, tiny remainders, and infinite divisors. The tests for negative operands, negative zero, and infinite divisors fail without the fix. ./scripts/test.sh passes; cargo-audit, cargo-deny, actionlint, desktop checks, and one venv-only Python test were skipped locally.

Close #602


Summary by cubic

Aligns the calculator's % with its floored //: % now returns the remainder of floored division, so the result takes the divisor's sign and (a // b) * b + a % b equals a for integers. -7 % 3 is now 2 instead of -1, matching Python, while // and non-negative operands are unchanged.

Notes

  • The identity is guaranteed for integers only; // rounds the quotient before flooring, so 1 // 0.1 stays 10 and fractional operands can drift.
  • An infinite divisor leaves the dividend unchanged, so -1 % 10^400 is -1 instead of Python's -inf; a tiny remainder of the other sign rounds to the divisor itself, so -1e-20 % 3 is 3, matching Python floats.
  • Docs and browser tests updated to cover sign combinations, zero divisors, infinite divisors, and unchanged positive results.