The Additive Tier: Addition, Subtraction, and Safe Aliasing in Mercury

In the previous post, we introduced the concept of the Algebraic Operational Tiers and the Sigma Language notation, establishing how mathematical operations are structurally related. Now, it is time to look at how those relationships dictate actual C code in the Mercury arbitrary-precision math engine.

We are starting at the foundation: The Additive Tier.

The Foundational Tier

The Additive Tier represents direct linear scaling. Because addition is symmetrical, its inverse operations are fundamentally identical:

C=A+B ⟹ B=C−A and A=C−B

In Sigma notation, that flattens out cleanly:

  • C = A + B;
  • B = C - A;
  • A = C - B;

Because addition and subtraction are exact functional inverses of one another, they do not just belong in the same theoretical tier—in Mercury, they literally share the exact same underlying machinery.

The Traffic Cop Architecture

If you have ever tried to write a single big-number addition loop that handles positive numbers, negative numbers, and mixed signs simultaneously, you know it results in messy, heavily branched, bug-prone code.

Mercury avoids this entirely by separating the logical rules of algebra from the raw mathematics of bit manipulation. The public-facing functions, mercuryAdd and mercurySub, do not actually perform any arithmetic. They act as logical traffic cops.

When you pass two variables into mercuryAdd, the wrapper inspects two things:

  1. The Sign Bits: It checks (a[0] & 1) and (b[0] & 1).
  2. The Magnitudes: If the signs differ, it uses mercuryAbsCmp(Precision, a, b) to determine which number is physically larger.

Once the wrapper understands the landscape, it routes the variables to the underlying “Absolute” engines (mercuryAbsAdd or mercuryAbsSub) in the correct order. This guarantees that the core math engines never have to worry about negative numbers, and only ever have to subtract a smaller magnitude from a larger one. The wrapper simply applies the correct final sign bit to the output after the absolute math is finished.

The Engine Room: Base-2^32 Addition and Subtraction

Once mercuryAdd or mercurySub hands the operation off to the absolute engines, we get to see why Mercury uses a base-2^32 positional format instead of base-10 decimals.

By treating each 32-bit word as a single “digit,” we can leverage the native hardware of a 64-bit processor to handle our carries and borrows automatically. Here is the exact loop inside mercuryAbsAdd that handles the core math:

ulong reg = 0;

// The Core Addition Loop
for (; i <= bh && i <= h; i++) {
    // Add the 32-bit places into the 64-bit register
    reg += ((slong) a[2+i-al] + (slong) b[2+i-bl]);

    // Store the bottom 32 bits into the scratch stack
    scratch[i - l] = (uint) reg;

    // Shift the register right by 32 bits to extract the carry for the next loop
    reg >>= 32;
}

This is the exact equivalent of carrying the “1” in grade-school addition, executed natively on the silicon. We add the two 32-bit limbs into a 64-bit ulong register. The bottom 32 bits are our answer for that place, and whatever spills over into the top 32 bits is our carry. We just shift the register right (reg >>= 32) and move to the next loop.

Because addition and subtraction are inverses belonging to the same tier, the subtraction engine (mercuryAbsSub) mirrors this logic almost exactly, with one clever twist. To handle borrowing, the engine switches the register to a signed 64-bit integer (slong):

slong reg = 0;

// The Core Subtraction Loop
for (; i <= bh && i <= h; i++) {
    // Subtract the 32-bit places (including any previous borrow)
    reg += ((slong) a[2+i-al] - (slong) b[2+i-bl]);

    // Did we drop below zero? (Do we need to borrow?)
    if (reg < 0) {
        // Borrow exactly 2^32 from the next place
        reg += 0x100000000LL;
        scratch[i - l] = (uint) reg;
        reg = -1; // Carry the -1 borrow forward to the next loop
    } else {
        scratch[i - l] = (uint) reg;
        reg = 0;  // No borrow needed
    }
}

If subtracting the second place causes the signed reg to drop below zero, the processor instantly catches it. The engine handles the borrow by simply adding 0x100000000LL (which is exactly $2^{32}$) back to the register, logging the result, and explicitly setting the next register carry to -1.

Because our “traffic cop” wrappers guaranteed that we are always subtracting a smaller magnitude from a larger one, we know with absolute mathematical certainty that we will never run out of places to borrow from.

The Scratch Stack and Safe Aliasing

You might have noticed something important in the code snippet above: the result isn’t being written to the output variable (val). It is being written to scratch.

In Mercury, every mathematical operation uses a caller-supplied stack for temporary memory. mercuryAbsAdd calculates its entire answer into a temporary scratch array. The output variable val is completely untouched until the very end of the function, where mercuryLoadRaw finally copies the scratch data over.

This “write-last” architecture creates a powerful feature for developers: Safe Aliasing.

Because the output variable is not mutated during the calculation, you can safely use your output pointer as an input pointer. Calling mercuryAdd(stack, Precision, A, B, A) is completely safe. This allows you to easily execute A += B functionality without risking memory corruption or needing to manage your own temporary buffers.

Up Next

The Additive Tier gives us a stable, linear foundation. Next time, we will step up to the Multiplicative Tier, where we move into geometric scaling and explore how decimal long division perfectly explains Mercury’s nibble-sized pre-multiplication table.

JWCEssentials on GitHub

JWCEssentials/C/Mercury/Mercury.c

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>


The reCAPTCHA verification period has expired. Please reload the page.

This site uses Akismet to reduce spam. Learn how your comment data is processed.