Negative coordinates break chunk maths in two places, not one
Truncating division puts block −1 in chunk 0 instead of −1, and a plain % gives an in-chunk offset of −1 instead of 15. Both are off, and both look right until you go west of spawn.
Every chunk calculation is trivial in the positive quadrant and wrong in the other three. There are exactly two places it goes wrong, they are independent of each other, and both produce answers that look plausible.
The two operations
chunk = floor(block / 16) not trunc(block / 16)
offset = ((block % 16) + 16) % 16 not block % 16
Region coordinates are the same pair, one level up, with 32 instead of 16:
region = floor(chunk / 32) -> r.<x>.<z>.mca
regionOff = ((chunk % 32) + 32) % 32
So a region is 32 × 32 chunks, which is 512 × 512 blocks.
Where each one bites
Division. Most languages truncate toward zero, and floor rounds toward negative infinity. They agree for positives and disagree for every negative that is not an exact multiple:
- block
−1→floor(−1/16) = −1✓, buttrunc(−1/16) = 0✗
Block −1 is one block west of the origin and belongs to chunk −1. Truncation puts it in chunk 0, alongside block +1. Every negative chunk index comes out one too high.
Remainder. In JavaScript, Java, C and most others, % keeps the sign of the left operand:
- block
−1→ correct offset15, but−1 % 16 = −1✗
An offset of −1 is not a position inside a 16-wide chunk at all. Indexing a block array with it either throws or silently reads the wrong cell.
A worked example
Block −1290 on one axis:
| step | correct | naive |
|---|---|---|
| chunk | floor(−1290/16) = −81 | trunc = −80 ✗ |
| offset in chunk | 6 | −1290 % 16 = −10 ✗ |
| region | floor(−81/32) = −3 | trunc = −2 ✗ |
| offset in region | 15 | −81 % 32 = −17 ✗ |
Four values, four wrong answers, none of them out of range enough to look obviously broken. Region −3 spans chunks −96 to −65, and chunk −81 sits 15 in from its start — which is exactly what the corrected remainder returns.
The Nether conversion is lossy in one direction
Overworld to Nether divides by 8, and it floors as well:
netherX = floor(overworldX / 8)
Coming back multiplies by 8. That is not a round trip. floor throws away the remainder, so returning lands you on a multiple of 8:
- Overworld
1000→ Nether125→ back to1000✓ - Overworld
1007→ Nether125→ back to1000✗ — seven blocks short
The error is 0 to 7 blocks per axis, so up to 7 on X and 7 on Z at once. That is the whole reason precise portal linking needs the Nether coordinate written down rather than re-derived: once you have divided, the overworld position it came from is no longer recoverable.
For a Nether hub this rarely matters — 7 blocks is inside a portal's own capture radius. For linking two portals that must not steal each other's traffic, it matters a great deal.
Negative overworld coordinates make it worse in the familiar way: overworld −1 gives Nether floor(−1/8) = −1, while truncation gives 0 and sends you to the wrong side of the axis.