Skip to content

Lua: a real modulo is Blizzard.j's ModuloReal formula, as on Jass - #1433

Merged
Frotty merged 3 commits into
masterfrom
lua-real-mod-fmod
Oct 11, 2026
Merged

Frotty merged 3 commits into
masterfrom
lua-real-mod-fmod

Conversation

@Frotty

@Frotty Frotty commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

A real modulo on Jass is Blizzard.j's ModuloReal: a - I2R(R2I(a / b)) * b, plus b when that is negative. On Lua it was math.fmod with that correction, and since #1426 Lua's float % for a divisor of at least 1. The three agree in the test Lua, which computes in double. Measured on the 3.0.0 client, they do not agree in the game. Only the Blizzard formula, computed in the game's Lua, gives Jass's results, so a real modulo on Lua is now that formula.

Measured

One probe, built as Jass and as Lua (dividends come from a function call, so nothing is folded), run in game:

Jass Lua % (master) Lua fmod + correction (before #1426) Lua Blizzard formula (this PR)
360. % 7. 3.000000000 3.000015259 3.000015259 3.000000000
7.5 % 360. 7.500000000 7.499999520 7.499999520 7.500000000
-7.5 % 2.5 (divisor at run time) 0.000000000 2.499999520 2.499999264 0.000000000
16777216. % 3. 1.000000000 1.500000000 1.500000000 1.000000000
-0.1 % 7. 6.899999616 6.900000064 6.899999616 6.899999616

With this PR, all 54 probe lines whose operands Jass and Lua read alike are identical to Jass. This covers 27 dividends, five literal divisors, run-time divisors of 2.5, 1 and 0.3, and the integer lines. The remaining 14 lines are dividends which the two read differently in the first place: Jass reads the literals 0.1, 3.14159 and 123456.789 one float off, and 1e10 wraps. No modulo rule changes that.

The integer mod by a positive literal (Lua's %, from #1426) agreed with Jass's ModuloInteger on every integer probed, including the 32-bit edges, and stays as it is.

What changes

  • __wurst_modReal is Blizzard.j's formula: q = a / b; r = a - R2I(q) * b; if r < 0 then r = r + b. R2I is the intrinsic which R2I calls already lower to; with the quotient in a local it is printed in place as Lua arithmetic, so an inlined real modulo has no call when the quotient is in range. The integer quotient converts to a real in the multiplication, as I2R does.
  • The R2I lowering and the real modulo share the one __wurst_rawR2I intrinsic.
  • The interpreter follows: WurstOperator.moduloReal, which the interpreter and its ModuloReal mock use, was the exact remainder (Java's %). It is now the same formula in float, with R2I as the interpreter's measured R2I (truncating, wrapping to 32 bits; now WurstOperator.realToInt, shared with that mock). Compile-time code and unit tests compute a real modulo the way the game does. For a quotient past 2^24 the two differ by more than rounding: 1e9 % 3 is -29 by the formula and 1 as the exact remainder. Constant folding never folds a real modulo, so it is unaffected.
  • A real division or modulo by zero stops the interpreters, as an integer one does (WurstOperator.divideReal, used by both). Measured on the 3.0.0 client, Jass stops the thread on 1. / 0., -1. / 0., 0. / 0., 5. % 0. and ModuloReal(5., 0.). Before, the interpreters answered an infinity, and the formula then answered the dividend. The game's Lua does not stop: 1. / 0. is 1.7014e38, 0. / 0. is 1. The Lua backend adds no check here, because one would cost every real division with a divisor that is not a literal.
  • Removed: the floored real % intrinsic and the real fmod intrinsic (rawFloorModReal, rawFmodReal), with their translator fields, translations and interpreter mocks.
  • docs/WC3_RUNTIME.md ("Numbers") records the measurement. AGENTS.md §7 says a real modulo on Lua is Blizzard.j's formula, never math.fmod or the float %.

Tests

  • modByADivisorThatBecomesALiteralIsTheLuaOperator, modKeepsTheSignOfANegativeZeroOnLua and the helper-lowering test now expect the formula, printed in place. They expect no %, math.fmod or __wurst_rawR2I( call for a real modulo, and still % for the integer cases. Each failed on master. -0.0 keeps its sign under the formula, as before.
  • realModIsModuloRealsFormulaInEveryRun (new): x % y equals the formula written out in Wurst, compared exactly (!=, since Jass's == on reals has a tolerance), in the interpreter, on Jass and on Lua, including 1e9 % 3. It fails on master in the interpreter run.
  • realDivisionByZeroStopsTheInterpreters (new): 1. / 0., 5. % 0. and 0. / 0. stop the interpreter and the Jass interpreter. It fails on master, which goes on with an infinity.
  • Passed: LuaBackendAuditTests, OptimizerTests, LuaTranslationTests, LuaNativesTests, BugTests, InterpreterTests, CompiletimeTests, CompiletimeNativesTest, NewFeatureTests, RealWorldExamples. The full suite is left to CI.

Measured on the 3.0.0 client, Lua's float % differs from fmod with the ModuloReal correction for a negative dividend, and the fmod form is the one which agrees with Jass (-0.1 % 7: 6.900000064 against 6.899999616). The integer % by a positive literal agrees with Jass and stays.
@Frotty

Frotty commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-11T11:33:10.643120Z c86ef4f Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: 9e54789b12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Measured on the 3.0.0 client, neither math.fmod nor Lua's float % gives Jass's real modulo in the game (360 % 7: 3.0000153), while a - R2I(a / b) * b, plus b when negative, computed in the game's Lua matches Jass on every probed operand both read alike.
@Frotty Frotty changed the title Lua: a real modulo is fmod again, never the float % Lua: a real modulo is Blizzard.j's ModuloReal formula, as on Jass Oct 11, 2026
@Frotty

Frotty commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a3ed1621d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

WurstOperator.moduloReal, which the interpreter and its ModuloReal mock use, was the exact remainder, while Jass and now Lua compute a - R2I(a / b) * b in float. It is that formula now, with the interpreter's measured R2I shared as WurstOperator.realToInt.
@Frotty

Frotty commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: c86ef4f206

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Frotty
Frotty merged commit 0f4f268 into master Oct 11, 2026
3 checks passed
@Frotty

Frotty commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0f4f268474

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

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

Successfully merging this pull request may close these issues.

1 participant