Perl Part I · Module 3 · Operators and Expressions
Arithmetic Operators
Addition and multiplication hold no surprises, so this chapter spends its time on the four that do. Perl's division never truncates, which catches everyone arriving from C. The remainder operator takes the sign of its right-hand operand, which is the opposite of what C, Java and SystemVerilog do and therefore an actively wrong intuition rather than merely a missing one. Exponentiation groups right to left and binds tighter than a minus sign, so two plausible readings of a short expression give 512 and 64, or -4 and 4. And dividing by zero does not produce infinity or a warning — it stops the program. Every number in this chapter was produced by running the code.
Foundation14 min readPerlPart IFundamentalsArithmeticOperators
Part I · Module 3 · Chapter 3.2 · Arithmetic Operators
1. The Family
Perl has the arithmetic you expect, plus exponentiation:
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | addition | 10 + 4 | 14 |
- | subtraction | 10 - 4 | 6 |
* | multiplication | 10 * 4 | 40 |
/ | division | 10 / 4 | 2.5 |
% | remainder | 10 % 4 | 2 |
** | exponentiation | 2 ** 8 | 256 |
- also works as a unary operator, meaning negation: -$x. That matters more than it sounds, as §5 shows.
The first four rows are unremarkable except for the one in /. The rest of this chapter is about the rows that are not unremarkable.
2. Division Never Truncates
use strict;
use warnings;
print 10 / 4, "\n";
print int(10 / 4), "\n";2.5
2Perl has one numeric scalar type, as Chapter 2.4 established, and / gives you the real quotient. There is no separate integer division that triggers when both operands happen to be whole numbers — an important difference from C and from SystemVerilog, where 10 / 4 is 2.
If you want a whole number, say so with int():
use strict;
use warnings;
print int(2.9), "\n";
print int(-2.9), "\n";2
-2int() truncates toward zero. It does not round. int(2.9) is 2, not 3, and int(-2.9) is -2, not -3. Rounding to the nearest whole number is a different operation, and printf is the usual way to do it — Chapter 2.5 introduced printf's formats, and Chapter 4.3 returns to them.
3. Dividing by Zero Stops the Program
use strict;
use warnings;
print "before\n";
print 1 / 0, "\n";
print "after\n";beforeIllegal division by zero at divzero.pl line 5.255Three things to read out of that, and the third is the one people get wrong.
- It is fatal, not a warning. The program dies. You do not get infinity,
NaN, orundef. - It is a run-time error. The file compiled cleanly — by the split from Chapter 1.5,
perl -cwould report this file as syntactically OK.perl -ccannot find this bug. beforewas already printed, andafternever was. The program did real work, emitted real output, and then stopped halfway. A script that writes a report file and dies at line 400 leaves a partial report on disk which looks like a complete one.
The practical consequence: a divisor that comes from your data — a test count, a cycle count, a file size — has to be checked before you divide by it. You do not have the tools to write that check yet; Chapter 3.9 and Module 8 give them to you. What you need now is to know that the check is necessary.
4. % Takes the Sign of Its Right Operand
This is the single most surprising behaviour in the chapter, and it is surprising in the worst way: readers arriving from another language have a confident, wrong expectation.
use strict;
use warnings;
print 7 % 3, "\n";
print -7 % 3, "\n";
print 7 % -3, "\n";
print -7 % -3, "\n";1
2
-2
-1Read the middle two rows again. -7 % 3 is 2, not -1. 7 % -3 is -2, not 1.
| Expression | Perl | What C, Java and SystemVerilog give |
|---|---|---|
7 % 3 | 1 | 1 |
-7 % 3 | 2 | -1 |
7 % -3 | -2 | 1 |
-7 % -3 | -1 | -1 |
The rule, stated once: with integer operands, Perl's % produces a result whose sign follows the right-hand operand. The two languages agree whenever both operands are positive, which is why this can hide for years in code that only ever takes a remainder of positive numbers — and then surface the first time a value goes negative.
The common uses of % are safe because they involve positive numbers: "is this cycle count even" is $n % 2, and "every tenth test" is $i % 10 == 0. Both are fine as long as the left operand cannot go negative — and that is now a thing you know to confirm rather than assume.
5. ** Groups Right to Left, and Binds Tighter Than Minus
Exponentiation has two properties that no other arithmetic operator here has.
It is right associative. Where / and * group left to right — Chapter 3.1 §3 — ** groups the other way:
use strict;
use warnings;
print 2 ** 3 ** 2, "\n";
print 2 ** (3 ** 2), "\n";
print (2 ** 3) ** 2;
print "\n";512
512
8print (...) interpreted as function at power-grouping.pl line 6.
Useless use of exponentiation (**) in void context at power-grouping.pl line 6.The first two agree, which demonstrates the rule: 2 ** 3 ** 2 means 2 ** (3 ** 2) — that is 2 ** 9, or 512. It does not mean (2 ** 3) ** 2, which is 64.
The third line is a deliberate trap worth seeing: it printed 8, not 64. print (2 ** 3) ** 2; is parsed as (print(2 ** 3)) ** 2 — Perl took the parenthesis immediately after print as the argument list, printed 8, and then squared print's return value and discarded it. This is a classic Perl bite, and notice what saved you: use warnings from Chapter 1.11 printed print (...) interpreted as function and named the line. That is the diagnostic doing exactly the job Module 1 argued for — the wrong answer came with a free explanation. Writing print +(2 ** 3) ** 2; or print ((2 ** 3) ** 2); gives 64.
It binds tighter than unary minus:
use strict;
use warnings;
print -2 ** 2, "\n";
print (-2) ** 2;
print "\n";
print +(-2) ** 2, "\n";-4
-2
4print (...) interpreted as function at unary-minus.pl line 5.
Useless use of exponentiation (**) in void context at unary-minus.pl line 5.-2 ** 2 is -4. Perl reads it as -(2 ** 2) — exponentiate first, then negate. If you want the square of negative two you must write (-2) ** 2, which is 4, and the middle line shows the print-parenthesis trap biting again for good measure.
Mathematical convention agrees with Perl here, so this is not an oddity so much as a rule people forget. The fix is the one Chapter 3.1 recommended: parenthesise it and remove the question.
6. A Worked Example
Putting the chapter together on something you would actually write — summarising a regression run:
use strict;
use warnings;
my $total = 184;
my $passed = 177;
my $rate = ($passed / $total) * 100;
my $failed = $total - $passed;
my $full_tens = int($total / 10);
my $remainder = $total % 10;
printf "passed : %d of %d (%.1f%%)\n", $passed, $total, $rate;
printf "failed : %d\n", $failed;
printf "batches : %d full batches of ten, %d left over\n", $full_tens, $remainder;passed : 177 of 184 (96.2%)
failed : 7
batches : 18 full batches of ten, 4 left overFour points in nine lines of code:
($passed / $total) * 100. The parentheses are not required — Chapter 3.1 showed that/and*group left to right anyway — and they are there so a reviewer does not have to recall that rule to check a percentage.int($total / 10)is18, and it isint()that truncated.184 / 10is18.4;/produced the fraction andint()removed it. Writing$total / 10and relying on%dto tidy it up would have worked here and would have hidden the decision.$total % 10is4, and18 * 10 + 4is184— the quotient and remainder reconstruct the original, which is the check worth doing whenever you split a count like this. This is also a case where%is safe by construction: a test total cannot be negative.%%prints a literal percent sign.printftreats%as the start of a conversion, so a percent sign in the output text has to be doubled.
7. Why This Matters in Engineering Work
Scripts in this trade compute throughputs, utilisation figures, pass rates and cycle budgets. The arithmetic is never hard; the failures come from three places, and this chapter has covered all three.
A divisor that can be zero. A regression with zero tests selected, a log with zero transactions, an empty file. $passed / $total is fine until $total is 0, and then the script dies at that line having already written part of its output. The failure is loud but late, and the partial artefact it leaves behind is the dangerous part.
An intuition carried in from another language. % is the clear case. An engineer who writes Perl on Monday and SystemVerilog on Tuesday has two different remainder operators with the same spelling, and the one place they differ is the one place nobody tests.
Assuming / truncates. A script computing "how many full batches" with $total / 10 gets 18.4, and whether that is a problem depends entirely on what happens to the value next. printf "%d" will show 18 and hide the issue; a comparison against an integer will not.
The habit that addresses all three: when a computed number will be acted on, check the divisor, parenthesise the grouping, and say explicitly whether you want truncation or rounding.
8. Common Mistakes
Expecting 10 / 4 to be 2. It is 2.5. Use int() if you want truncation, and know that int() truncates rather than rounds.
Expecting int() to round. int(2.9) is 2. For rounding, format with printf or add half before truncating — and be deliberate about negative values, where the two approaches differ.
Carrying C's % sign rule into Perl. -7 % 3 is 2 in Perl, -1 in C. The sign follows the right operand.
Reading 2 ** 3 ** 2 as 64. It is 512. ** is the only right-associative operator in this chapter.
Reading -2 ** 2 as 4. It is -4, because ** binds tighter than the minus sign.
Writing print (...) and expecting the parentheses to group arithmetic. They become print's argument list. §5 shows two cases of this; print +(...) is the usual fix.
Treating division by zero as something perl -c will catch. It is a run-time death in a file that compiles cleanly.
9. Exercise
Exercise 1 — the four signs. Run the §4 script and record all four results. Then write, in one sentence, the rule that explains them, and state what C would give for the second line.
Exercise 2 — truncate or round. Compute int(7 / 2) and printf "%.0f", 7 / 2. Record both, and explain why they differ. Then try int(-7 / 2) and printf "%.0f", -7 / 2 and say which of the two operations is sensitive to the sign.
Exercise 3 — make the grouping explicit. Predict 2 ** 2 ** 3 before running it. Then write the same expression twice with parentheses, once meaning what Perl means and once meaning the other thing, and confirm the two values.
Exercise 4 — the fatal divisor. Write a script that prints a line, then divides by a variable holding 0, then prints another line. Record STDOUT, STDERR and the exit status separately. Then run perl -c on the same file and state what that tells you about which errors a syntax check can find.
Exercise 5 — batch arithmetic. Given my $tests = 237; and a batch size of 16, print how many full batches there are and how many tests are left over. Verify your two numbers multiply and add back to 237.
10. Summary
/never truncates —10 / 4is2.5. Perl has one numeric type and no implicit integer division.int()truncates toward zero, it does not round:int(-2.9)is-2.- Division results are floating point, so they are approximations — don't test two computed values for exact equality.
- Division by zero is a fatal run-time error with exit status 255, after any earlier output has already been written.
perl -ccannot see it. %takes the sign of its right operand.-7 % 3is2in Perl and-1in C and SystemVerilog.**is right associative —2 ** 3 ** 2is512— and binds tighter than unary minus, so-2 ** 2is-4.printfollowed immediately by(takes that as its argument list, which quietly changes what gets printed.
Chapter 3.3 turns to the operators that work on text, including the one that uses the same symbol as multiplication to mean something entirely different.
Where this fits
Part of the Perl curriculum.
