Why arithmetic needs attention
Addition and multiplication look trivial, and in a first draft they usually are. The trouble comes from the host language you write the interpreter in. Its integers have a particular size, its division rounds in a particular way, and its remainder operator has a particular sign rule. If you pass those behaviors through without thinking, your interpreter inherits them silently, and a program may behave differently when you port the interpreter to another language.
The goal is not to find the single correct answer. It is to choose a behavior on purpose, check it against the language definition you are following, and write it down.
Division by zero
Dividing by zero has no mathematical answer, so the language must say what happens. Common policies include:
- Stop the program with an error message.
- Push a fixed value, often 0, and keep going.
- Ask the user what the result should be.
Different Funge-family definitions have taken different approaches, and the choice can vary between versions, so check the specification you are targeting rather than relying on memory. Whatever you choose, make sure the interpreter does not crash with an unhandled exception from the host language. A raw crash from a divide instruction is one of the most common bugs in a first interpreter.
The same question applies to the remainder instruction. A program that divides by zero will usually also take a remainder by zero, so treat the two consistently unless the language says otherwise.
Test it directly: push 5, push 0, run the divide instruction, and confirm that the interpreter does what your notes say.
Order of operands
Stack languages pop operands in reverse. To compute “7 minus 3”, a program pushes 7 first and 3 second, so the instruction pops 3 and then 7. A common slip is subtracting in the wrong order, which gives -4 instead of 4. The same issue affects division and remainder. Write the code as “pop b, pop a, push a minus b” and the order will stay clear.
Negative remainders
For positive numbers every language agrees. With negative numbers they split into two families.
Truncating division rounds the quotient toward zero. C-style languages work this way, and the remainder takes the sign of the left operand. Flooring division rounds toward negative infinity. Python works this way, and the remainder takes the sign of the right operand.
Here are the results for dividing -7 by 2:
| Convention | Quotient | Remainder | Check |
|---|---|---|---|
| Truncating | -3 | -1 | (-3 x 2) + (-1) = -7 |
| Flooring | -4 | 1 | (-4 x 2) + 1 = -7 |
And for 7 divided by -2:
| Convention | Quotient | Remainder | Check |
|---|---|---|---|
| Truncating | -3 | 1 | (-3 x -2) + 1 = 7 |
| Flooring | -4 | -1 | (-4 x -2) + (-1) = 7 |
Both conventions keep the identity that quotient times divisor plus remainder equals the original number. They differ only in which pair they choose. If you write the interpreter in Python and the language you are implementing follows truncation, you must adjust the result yourself. If a language definition leaves this open, pick one and document it so that programs written for your interpreter behave predictably.
A real-world consequence: a program that uses remainder to wrap an index around a list may work with positive values and then fail when a value goes negative.
Integer size
Most Befunge-style definitions assume signed integers of a fixed width, often 32 bits, though exact details depend on the version. A signed 32-bit integer holds values from -2147483648 to 2147483647. Python integers, by contrast, grow without limit, so a Python interpreter will never overflow unless you make it.
That difference is visible in a factorial program. In 32-bit arithmetic, 12 factorial is 479001600, which fits. 13 factorial is 6227020800, which does not. With wrap-around, a 32-bit signed result would be 6227020800 minus 4294967296, which is 1932053504. An interpreter with unbounded integers would print 6227020800 instead. Neither answer is “wrong” in the abstract, but programs written for one may not run properly on the other.
You have three reasonable options:
- Use unbounded integers and document that clearly.
- Wrap results to a fixed width after every operation.
- Raise an error on overflow.
Wrapping is the closest match to how many reference implementations behave. In a language like Python you do it by masking and then adjusting the sign. In languages such as C or Java, fixed-width types may already do it for you, although in C, signed overflow is technically undefined behavior, so unsigned arithmetic with a conversion is safer.
Characters and numbers
Output instructions turn a number into a character. What should happen for a negative number, or one beyond the range your output can encode? Decide whether to wrap, to replace with a substitute character, or to stop. This ties into the broader topic of interpreter input output, since the same question appears when reading characters in.
A small test list
Keep these as tiny programs with expected output:
- 5 divided by 0, and 5 remainder 0
- -7 divided by 2, and -7 remainder 2
- 7 divided by -2, and 7 remainder -2
- the largest and smallest integers plus or minus one
- subtraction and division with operands in both orders
Because each case is only a few cells, a failure is easy to read in a trace, and the ladder of small checks described under interpreter testing grows naturally from this list.