Ever tried to do a subtraction in MIPS and the sub instruction just isn’t there?
On top of that, you’re not alone. A lot of beginners hit that wall when they open a textbook that only shows add, addi, and a handful of other ops. The short answer: you can still subtract—just get a little creative with two’s‑complement and the add instruction you already have Easy to understand, harder to ignore..
What Is Subtract‑Without‑sub in MIPS
In plain English, “subtract without sub” means using the instructions that are available—primarily add and addi—to achieve the same result as a subtraction. MIPS doesn’t force you to use a dedicated subtract opcode; it gives you the building blocks to do the math yourself That's the part that actually makes a difference..
Two’s‑Complement Basics
MIPS, like virtually every modern CPU, stores integers in two’s‑complement form. That little trick lets you turn a subtraction into an addition:
A - B == A + (‑B)
So if you can get the negative of a number, you can just add it. In real terms, the negative of a 32‑bit value is obtained by inverting every bit (not) and then adding one (addi $t, $t, 1). That’s the core idea behind all the tricks you’ll see.
Why MIPS Doesn’t Need a Separate sub
The original MIPS ISA designers kept the instruction set lean. Adding a separate subtract opcode would have been redundant—add already does the job once you have the two’s‑complement representation of the subtrahend. The hardware can perform both operations in a single cycle; the assembler just gives you a convenient mnemonic.
Why It Matters / Why People Care
You might wonder, “Why bother?” Here are three real‑world reasons:
- Embedded environments – Some tiny MIPS cores (think microcontrollers) ship with a reduced instruction set that omits
sub. If you’re writing firmware for such a chip, you need the workaround anyway. - Learning the fundamentals – Understanding two’s‑complement arithmetic sharpens your grasp of how computers actually do math. It’s the kind of low‑level insight that makes debugging easier later on.
- Assembler tricks – Even when
subexists, you might want to generate the same machine code manually (e.g., for a custom assembler or a teaching tool). Knowing the pattern helps you read raw binaries.
In practice, the ability to subtract without a dedicated opcode shows you’re thinking like a computer, not just a high‑level programmer Most people skip this — try not to..
How It Works (or How to Do It)
Below is the step‑by‑step recipe most textbooks gloss over. I’ll break it into bite‑size chunks, each with a tiny example And that's really what it comes down to..
1. Load the operands
Assume you have two registers, $s0 (minuend) and $s1 (subtrahend). If the values are in memory, use lw first:
lw $s0, 0($a0) # load A
lw $s1, 0($a1) # load B
2. Compute the two’s‑complement of the subtrahend
Two ways to do this: the classic “invert‑then‑add‑one” or the handy nor‑plus‑addi combo.
Method A – NOT + ADDI
nor $t0, $zero, $s1 # $t0 = ~B (NOR with $zero is NOT)
addi $t0, $t0, 1 # $t0 = -B
Method B – XOR with -1 + ADDI (some like it because xor is often faster on certain pipelines)
xori $t0, $s1, -1 # $t0 = ~B
addi $t0, $t0, 1 # $t0 = -B
Either way, $t0 now holds the negative of $s1 That's the part that actually makes a difference..
3. Add the negated subtrahend to the minuend
Now the real subtraction happens:
add $t1, $s0, $t0 # $t1 = A + (-B) = A - B
$t1 holds the result. If you need to store it back:
sw $t1, 0($a2) # store result
4. One‑instruction shortcut using subu pseudo‑op
If your assembler supports pseudo‑ops, you can write:
sub $t1, $s0, $s1 # expands to the sequence above
But the point of this article is the manual expansion, which works even when sub is disabled.
5. Handling overflow
MIPS offers two subtraction instructions: sub (traps on overflow) and subu (ignores overflow). When you emulate subtraction with add, you’ll get the unsigned behavior automatically—no overflow exception. If you need signed overflow detection, you must add extra checks:
# Detect signed overflow after A - B
xor $t2, $s0, $s1 # sign bits differ?
xor $t3, $s0, $t1 # sign of result vs. sign of A
and $t4, $t2, $t3
bgez $t4, no_overflow # if $t4 >= 0, no overflow
# overflow handling code here
no_overflow:
That’s a bit more advanced, but worth knowing if you’re writing a compiler backend.
Common Mistakes / What Most People Get Wrong
Mistake #1 – Forgetting to add the “+1”
People often invert the bits and think they’re done. Without the extra addi $t0, $t0, 1, you’ve just computed the one’s‑complement, which is off by one for every negative number. The result ends up being A + (~B), not A - B.
It sounds simple, but the gap is usually here Easy to understand, harder to ignore..
Mistake #2 – Using sub as a macro and assuming it works on reduced cores
Some reduced‑instruction MIPS cores still accept the mnemonic sub because the assembler expands it, but the hardware will raise an illegal‑instruction exception. If you’re targeting such a core, you must write the explicit sequence yourself.
Mistake #3 – Mixing signed and unsigned registers
add treats its operands as unsigned when it comes to overflow detection (i.Here's the thing — e. , it never traps). If you later compare the result with a signed value using slt, you could get a surprising sign‑bit flip. Keep the data type consistent throughout the routine.
Mistake #4 – Overlooking pipeline hazards
On a classic 5‑stage MIPS pipeline, the nor/xori result isn’t ready until the MEM stage. If you immediately use it in the next add, you’ll need a stall or a NOP. Modern superscalar cores hide this, but on older hardware you’ll see a one‑cycle bubble.
The official docs gloss over this. That's a mistake.
Practical Tips / What Actually Works
-
Use a temporary register – Reserve
$t0(or any$tregister) for the negated value. It keeps the original$s1intact for later reuse That alone is useful.. -
Combine NOT and ADDI in one line – Some assemblers let you write
addi $t0, $s1, -1followed bysub $t0, $zero, $t0. It’s a little less readable but saves a register. -
put to work
addufor speed – If you don’t care about overflow,adduis the same asaddbut avoids the extra check the hardware might perform. -
Write a macro – In your
.asmfile, define:.macro SUB_NO_SUB dest, src1, src2 xori $at, \src2, -1 addiu $at, $at, 1 addu \dest, \src1, $at .end_macroThen call
SUB_NO_SUB $t1, $s0, $s1. - Test with edge cases – Try subtracting 0, subtracting a larger number (to get a negative result), and subtracting0x80000000(the most negative 32‑bit int). It’s clean, reusable, and works on any MIPS core.
Those will reveal any sign‑extension bugs in your code.
FAQ
Q: Can I use addiu instead of addi for the “+1” step?
A: Yes. addiu treats the immediate as unsigned, but adding 1 never overflows, so both work identically here That alone is useful..
Q: What if my MIPS core lacks the nor instruction?
A: Use xor with -1 (xori $t0, $s1, -1). It produces the same bitwise NOT result Small thing, real impact..
Q: Is there a performance difference between the NOT+ADD method and using subu directly?
A: On a fully pipelined core, subu is a single‑cycle operation, while NOT+ADD takes two cycles (plus any stalls). If you’re writing tight loops, the difference can matter. Otherwise, readability wins.
Q: How do I subtract an immediate value without subi?
A: Negate the immediate at assembly time (addi $t0, $zero, -IMM) and then add it to the register. Example: addi $t0, $zero, -5 then add $t1, $s0, $t0.
Q: Does this technique work for 64‑bit MIPS (MIPS64)?
A: Absolutely. The same two’s‑complement logic applies; just use the 64‑bit registers ($t0‑$t9 still refer to the low 64 bits). If you need the high half, you’ll have to handle it separately Worth keeping that in mind..
And that’s it. Which means subtracting without a dedicated sub instruction is just a few extra lines of assembly, but the concept behind it—two’s‑complement arithmetic—lies at the heart of every integer operation a CPU does. Once you’ve got the pattern down, you’ll find it pops up in other places: implementing absolute value, doing conditional negation, or even building a tiny ALU in a teaching lab.
So next time you open a MIPS file and see no sub around, remember: you’ve already got everything you need. Just flip the bits, add one, and let add do the heavy lifting. Happy coding!
A Quick Reference Cheat‑Sheet
| Goal | Instruction(s) | Notes |
|---|---|---|
| Subtract two registers | subu $d, $s, $t (or sub $d, $s, $t) |
Fastest, one‑cycle on modern cores |
| Subtract register from immediate | addi $d, $s, -IMM |
Immediate must fit in 16‑bit signed field |
| Subtract two’s‑complement manually | xori $t, $s, -1<br>addiu $t, $t, 1<br>addu $d, $t, $t2 |
Works on any MIPS core, useful for teaching |
| Negate a register | xori $t, $s, -1<br>addiu $t, $t, 1 |
Equivalent to subu $t, $zero, $s |
| Subtract with overflow check | sub $d, $s, $t |
Overflow flag set in HI/LO? |
| Subtract without overflow check | subu $d, $s, $t |
Faster, no overflow trap |
When to Stick With the Built‑In sub
- Performance‑critical loops:
subis a single‑cycle operation; the “flip‑bits‑then‑add” trick takes two cycles. - Code clarity: Future readers (or yourself in six months) will immediately understand
sub $t0, $t1, $t2. - Debugging: Most debuggers and simulators highlight
subas a single arithmetic step; custom macros can be harder to trace.
In contrast, the manual two’s‑complement method shines when:
- Instruction set is limited (e.g., very small cores or teaching simulators).
- You want to expose the underlying math to students or for documentation.
- You’re writing a code generator that must output only a subset of instructions.
TL;DR
- Subtracting on MIPS is fundamentally a two’s‑complement addition.
- If the core offers
sub/subu, use it—clean, fast, and unmistakable. - If you’re forced to work with only
add/addi/xori, flip the subtrahend, add one, then add to the minuend. - Wrap the pattern in a macro (or inline function in C) to keep your source tidy.
- Remember that
subu(unsigned) is just the same assubbut without the overflow exception—pick the one that fits your safety requirements.
Final Thoughts
Subtracting without a dedicated sub instruction might feel like a hack, but it’s a direct window into how CPUs actually perform arithmetic. The two’s‑complement trick is universal: it works on 8‑bit microcontrollers, 32‑bit MIPS, 64‑bit RISC‑V, and even on the most exotic DSPs. Mastering it gives you a deeper appreciation for the elegance of binary math and the power of assembly language.
So, the next time you’re staring at a stripped‑down MIPS core or a minimalist assembler, remember: you don’t need a “sub” instruction to subtract. Worth adding: just invert the bits, add one, and let the adder do the rest. Happy hacking!
The “Add‑One‑and‑Negate” Pattern in Real‑World Code
Even though most production‑grade MIPS cores ship with sub/subu, you’ll still encounter the add‑one‑and‑negate idiom in a few practical places:
| Context | Why the pattern appears | Example |
|---|---|---|
| Boot‑loaders & firmware | Early‑stage code runs before the full instruction decoder is enabled; only a minimal subset of the ISA is guaranteed to work. | asm\n # Zero‑initialize a register without using `move`\n li $t0, 0 # may not be available yet\n xori $t0, $zero, -1 # invert zero → all‑ones\n addiu $t0, $t0, 1 # +1 → 0 again, but now $t0 is known to be zero\n |
| Self‑modifying code generators | A JIT that emits instructions on the fly may be constrained to a “safe” instruction set that the host guarantees to be present. | |
| Cross‑ISA macros | When writing portable assembly that targets both MIPS and a sibling ISA that lacks a sub (some older MIPS‑derived micro‑controllers), a macro that expands to the generic two‑step sequence keeps the source single‑sourced. , MARS, SPIM) let students experiment with a “restricted” instruction set to force them to think about the underlying arithmetic. g. |
```asm\n . |
| Educational tools | Many teaching simulators (e. | A lab assignment may explicitly forbid sub and ask students to implement subtraction with only add/xori. macro SUB dst, src1, src2\n xori \dst, \src2, -1\n addiu \dst, \dst, 1\n addu \dst, \src1, \dst\n . |
In each of these scenarios the “extra” instruction isn’t a performance penalty—it’s a trade‑off for portability, boot‑time simplicity, or pedagogical clarity.
Edge Cases Worth Knowing
-
Subtracting a value that doesn’t fit in a 16‑bit signed immediate
Theaddi $d,$s,-IMMform only works whenIMMfits in the signed 16‑bit field (-32768 … 32767). If you need to subtract a 32‑bit constant, you must load the constant into a register first (usinglui/orior a literal pool) and then apply the two‑step method or asubu. -
Subtracting from the zero register
sub $t0, $zero, $t1is perfectly legal and yields-$t1. That said, using the manual method can be a tiny bit slower because you need a temporary register to hold the negated value. If you have a spare register, the macro above works fine; otherwise, you can cheat withaddu $t0, $zero, $zero(a no‑op) followed by the two‑step sequence Turns out it matters.. -
Subtraction with a carry‑in
Some DSP‑style extensions expose a carry flag that can be added to the result (addu $d,$s,$t; addc $d,$d,$carry). The two‑step method can be adapted by adding the carry after theaddiu $t,$t,1step, effectively computinga - b + carryThat's the part that actually makes a difference.. -
Signed vs. unsigned overflow handling
The only semantic difference betweensubandsubuis the exception that may be raised on overflow. The hardware does not set any status bits that you can read later; the exception is delivered directly to the exception handler. If you need to detect overflow without trapping, you must test the sign bits before and after the operation:# Detect signed overflow of a - b sub $t0, $a0, $a1 # result in $t0, may trap xor $t1, $a0, $a1 # sign bits differ? xor $t2, $a0, $t0 # sign of a vs. result differ? This pattern works regardless of whether you used the built‑in `sub` or the manual two‑step version.
A Minimal Macro Library for “Subtraction‑Only” Environments
Below is a tiny, self‑contained macro set you can paste at the top of any MIPS source file. Even so, it assumes the presence of addu, addiu, xori, and a single temporary register ($at – the assembler temporary). If you cannot use $at, replace it with any caller‑saved register ($t0‑$t7) Easy to understand, harder to ignore..
# -------------------------------------------------
# Macro: NEG dst, src
# dst = -src (two's complement)
# -------------------------------------------------
.macro NEG dst, src
xori \dst, \src, -1 # invert bits
addiu \dst, \dst, 1 # add one
.endm
# -------------------------------------------------
# Macro: SUB dst, src1, src2
# dst = src1 - src2
# Uses $at as a scratch register.
# -------------------------------------------------
.macro SUB dst, src1, src2
NEG $at, \src2 # $at = -src2
addu \dst, \src1, $at # dst = src1 + (-src2)
.endm
# -------------------------------------------------
# Macro: SUBU dst, src1, src2
# Unsigned subtraction (no overflow trap)
# -------------------------------------------------
.macro SUBU dst, src1, src2
NEG $at, \src2
addu \dst, \src1, $at
.endm
With these three macros you can write high‑level‑looking arithmetic in an environment that only guarantees the addition family of instructions. The macros expand to exactly the same number of instructions as a hand‑written sequence, but they keep the source readable and maintainable.
Honestly, this part trips people up more than it should.
Conclusion
Subtraction on MIPS is not a mysterious operation hidden behind a special hardware path; it is simply addition of the two’s‑complement negation of the subtrahend. Modern cores expose sub/subu for convenience and performance, but the underlying principle remains the same and is fully accessible with the core instruction set (add, addu, addiu, xori) That's the whole idea..
Understanding this equivalence gives you several practical advantages:
- Portability – You can target any MIPS‑compatible core, even one stripped down to a handful of arithmetic instructions.
- Pedagogy – Demonstrating subtraction as “invert‑bits‑then‑add‑one” makes the abstract concept of two’s‑complement concrete for students.
- Flexibility – When writing code generators, boot‑loaders, or highly constrained firmware, you can fall back on the generic pattern without worrying about missing opcodes.
- Safety – By choosing
subvs.subuyou control whether overflow triggers an exception, and you can manually detect overflow when needed.
So, whether you’re optimizing a tight inner loop on a high‑performance MIPS processor or teaching the fundamentals of binary arithmetic to a classroom of eager novices, remember that the simplest, most universal way to subtract is to add the negated operand. Keep the one‑cycle sub in your toolbox for the cases where it’s available, but never be surprised when a couple of clever bit‑twiddles and an addu get the job done just as reliably. Happy coding!
In short, subtraction on MIPS is nothing more exotic than the classic two’s‑complement trick: invert the bits of the number you want to subtract, add one, and then add that to the minuend. Whether you hand‑write the bit‑twiddling sequence or let the assembler expand a SUB macro, the hardware is doing the same thing it would do for a dedicated sub instruction. This insight not only simplifies the mental model you give to students, but also gives you a clear, portable fallback for any MIPS core that might lack the full arithmetic repertoire And it works..
Real talk — this step gets skipped all the time.
So next time you’re faced with a stripped‑down MIPS core, a bootloader, or a teaching exercise, remember that the “magic” of subtraction is just a bitwise complement plus an addition. Keep that in mind, and you’ll always be able to write correct, efficient code—no matter what instruction set you’re handed Surprisingly effective..