Perl Part I · Module 3 · Operators and Expressions
The Three-Way Comparators: <=> and cmp
Every comparison so far has answered yes or no. These two answer a different question: does the left value come before the right one, after it, or in the same place? That three-way answer is exactly what sorting needs, which is where you will use them. They split into numeric and textual forms like every other comparison in this module, and on the same two operands they can return opposite signs. One of them also has a property no other operator in the module has — there is an input for which it returns neither a comparison nor an error, but nothing at all, and a sort built on it then produces a confidently ordered list from an answer that was never given.
Foundation13 min readPerlPart IFundamentalsComparisonSortingOperators
Part I · Module 3 · Chapter 3.7 · The Three-Way Comparators: <=> and cmp
1. A Three-Way Answer
| Compare as | Operator | Returns |
|---|---|---|
| numbers | <=> | -1, 0 or 1 |
| text | cmp | -1, 0 or 1 |
<=> is called the spaceship operator, from its shape. Both operators report a position, not a verdict:
-1— the left value comes first.0— they belong in the same place.1— the right value comes first.
use strict;
use warnings;
print "5 <=> 3 : ", (5 <=> 3), "\n";
print "3 <=> 5 : ", (3 <=> 5), "\n";
print "5 <=> 5 : ", (5 <=> 5), "\n";
print "'a' cmp 'b' : ", ("a" cmp "b"), "\n";
print "'b' cmp 'a' : ", ("b" cmp "a"), "\n";
print "'a' cmp 'a' : ", ("a" cmp "a"), "\n";5 <=> 3 : 1
3 <=> 5 : -1
5 <=> 5 : 0
'a' cmp 'b' : -1
'b' cmp 'a' : 1
'a' cmp 'a' : 0The result is a sign, not a distance. This catches people arriving from C, where strcmp may return a difference:
use strict;
use warnings;
print "1 <=> 1000000 : ", (1 <=> 1000000), "\n";
print "1000000 <=> 1 : ", (1000000 <=> 1), "\n";
print "'a' cmp 'zzzz': ", ("a" cmp "zzzz"), "\n";1 <=> 1000000 : -1
1000000 <=> 1 : 1
'a' cmp 'zzzz': -1A gap of one million gives the same -1 as a gap of one. Do not use the return value as a measure of how far apart two values are — it only tells you which way round they go.
2. The Same Split, the Opposite Answer
These two operators divide numeric from textual exactly as Chapter 3.5 and Chapter 3.6 did, and here the consequence fits on two lines:
use strict;
use warnings;
print "'10' cmp '9' : ", ("10" cmp "9"), "\n";
print "10 <=> 9 : ", (10 <=> 9), "\n";'10' cmp '9' : -1
10 <=> 9 : 1Same two operands. Opposite signs. cmp says "10" comes first, which is correct text ordering — the character 1 precedes the character 9. <=> says 9 comes first, which is correct numeric ordering.
Since these operators exist mainly to drive sort, an opposite sign means a reversed list. That is Chapter 3.6 §3's default-sort trap seen from the comparator's side, and it is the reason the two chapters belong next to each other.
3. Why 0 Is the Useful Part
Returning 0 on a tie is what makes multi-key sorting work. The idiom is to try one key, and if it ties, fall through to another:
use strict;
use warnings;
my @rows = (
"alu 3",
"fifo 1",
"decoder 3",
"alu 1",
);
for my $r (sort {
my ($an, $ac) = split / /, $a;
my ($bn, $bc) = split / /, $b;
$bc <=> $ac || $an cmp $bn
} @rows) {
print "$r\n";
}alu 3
decoder 3
alu 1
fifo 1Failure count descending, and within an equal count, name ascending. alu 3 and decoder 3 tie on the count — $bc <=> $ac gives 0 — so the || moves on to $an cmp $bn, which puts alu before decoder.
That works because 0 is false in Perl, by Chapter 2.8, and -1 and 1 are both true. So "they tie" and "keep looking" are the same value. || and its short-circuit behaviour are Chapter 3.8's subject; it appears here because the multi-key sort is the reason the three-way return matters at all.
sort itself belongs to Module 6, which covers the $a and $b variables and split properly. What this chapter owns is the comparator in the middle.
4. <=> Can Return undef
Every other comparison operator in this module returns something you can treat as true or false. <=> has one input where it does not.
NaN — "not a number" — is a floating-point value that arises from an undefined operation. It is not an error and it does not stop the program:
use strict;
use warnings;
my $nan = 9**9**9 - 9**9**9;
print "the value prints as : $nan\n";
my $r = ($nan <=> 1);
print "nan <=> 1 defined? : ", (defined $r ? "yes ($r)" : "NO - undef"), "\n";
print "nan == 1 : ", ($nan == 1 ? "true" : "false"), "\n";
print "nan != 1 : ", ($nan != 1 ? "true" : "false"), "\n";
print "nan < 1 : ", ($nan < 1 ? "true" : "false"), "\n";
print "nan > 1 : ", ($nan > 1 ? "true" : "false"), "\n";
print "nan == nan : ", ($nan == $nan ? "true" : "false"), "\n";
my $c = ($nan cmp 1);
print "nan cmp 1 defined? : ", (defined $c ? "yes ($c)" : "NO - undef"), "\n";the value prints as : NaN
nan <=> 1 defined? : NO - undef
nan == 1 : false
nan != 1 : true
nan < 1 : false
nan > 1 : false
nan == nan : false
nan cmp 1 defined? : yes (1)Four things in that output are worth stopping on.
$nan <=> 1isundef. Not0, not-1, not1. NaN has no position relative to1, so the operator declines to give one. This is the distinction from Chapter 2.7 appearing where you would least expect it: an operator returning "no answer" rather than an answer.<and>are both false. Normally exactly one of them is true for two different values. Here neither is, which breaks the reasoning "it is not less than, so it must be greater than or equal".$nan == $nanis false. A value that is not equal to itself. Any code that assumes$x == $xholds — a cache check, a change detector, a convergence test — is wrong for this value.cmpworks fine and returns1.cmpis a text comparison, and as text the value is the three charactersNaN, which compare perfectly well against1. That is notcmpbeing safer; it iscmpanswering a different question.
What that does to a sort
use strict;
use warnings;
my $nan = 9**9**9 - 9**9**9;
my @values = (3, $nan, 1, 2);
print join(" ", sort { $a <=> $b } @values), "\n";1 2 3 NaNUse of uninitialized value in sort at sort-with-nan.pl line 7.0Where a NaN comes from in real work
You are unlikely to type 9**9**9 - 9**9**9. You are quite likely to read a field that a tool wrote as nan or NaN when a measurement was unavailable — and that text becomes a real NaN, without a warning:
use strict;
use warnings;
for my $field ("nan", "NaN", "NAN", "-nan", "inf") {
my $n = $field + 0;
print "$field + 0 = $n\n";
}nan + 0 = NaN
NaN + 0 = NaN
NAN + 0 = NaN
-nan + 0 = NaN
inf + 0 = InfNothing on STDERR. This is the one place Chapter 2.13's rule does not apply: most non-numeric text numifies to 0 and warns, but nan in any capitalisation is recognised as a genuine floating-point value and converts silently. inf likewise becomes Inf.
So a NaN enters your data as an ordinary-looking field from an ordinary-looking log line, with no diagnostic at the point of entry. The first sign of it is the sort warning above, a long way from the cause.
Note also that dividing by zero does not produce NaN in Perl — Chapter 3.2 §3 showed it is a fatal error. That is arguably the friendlier behaviour: it stops rather than poisoning a value that then sorts oddly three hundred lines later.
5. Why This Matters in Engineering Work
Report ordering is a correctness question. A failure summary sorted by count descending is read top-down and acted on from the top. The comparator decides which test gets looked at first, so cmp where <=> was meant does not merely untidy the report — it changes which work gets done.
Multi-key ordering is the normal case. "Worst slack first, then by instance name" is the shape of most useful engineering reports, and it depends entirely on the tie returning 0. Writing the comparator with < instead of <=> cannot express it: < has no way to say "same".
A NaN in your data is a silent reordering. The specific risk is not a crash, it is a list that looks right. One uninitialized-value warning from inside sort is the only evidence, and it names the sort line rather than the data that caused it. Checking a parsed number before you sort on it is the fix, and Module 8's conditionals give you the tools.
6. Common Mistakes
Reading the return value as a magnitude. It is -1, 0 or 1 regardless of the gap.
Using cmp on numbers in a comparator. "10" cmp "9" is -1 and 10 <=> 9 is 1, so the list comes out reversed where digit counts differ.
Assuming <=> always returns a number. With NaN it returns undef, and sort silently treats that as a tie.
Assuming exactly one of < and > must be true. For NaN both are false.
Assuming $x == $x. False for NaN.
Thinking cmp is the safe one because it survived NaN. It survived by answering a textual question. That is only correct if a textual answer is what you wanted.
Ignoring Use of uninitialized value in sort. It means your comparator returned nothing for at least one pair, and the resulting order is not trustworthy.
7. Exercise
Exercise 1 — the three answers. Run the §1 script and record all six values. Then state in one sentence what distinguishes these operators from < and lt.
Exercise 2 — sign, not distance. Compute 3 <=> 4 and 3 <=> 4000000. Record both and say what that rules out as a use for the return value.
Exercise 3 — the opposite sign. Predict "100" cmp "99" and 100 <=> 99 before running them. Then, using a four-element list of numbers with differing digit counts, sort it with each comparator and record the two orders.
Exercise 4 — the tie-break. Build a list of four strings of the form name count where two share a count, and sort by count descending then name ascending as in §3. Then delete the || $an cmp $bn part and record what changes about the two tied rows.
Exercise 5 — NaN, carefully. Reproduce the §4 script. Then sort a list containing the NaN and capture STDOUT and STDERR into separate files. Confirm that STDOUT alone gives no indication of a problem, and write one sentence on what that means for a nightly flow that logs only STDOUT.
Exercise 6 — the self-comparison. Write two lines that print whether $nan == $nan and whether $nan eq $nan. Explain the difference between the two results, naming the operator family each belongs to.
8. Summary
<=>andcmpreturn-1,0or1— a position, not a verdict, and not a distance.<=>compares quantities;cmpcompares characters. On10and9they return opposite signs.0on a tie is the useful part, because0is false and so a||can fall through to a second key. That is how multi-key sorting is written.<=>returnsundeffor NaN — the one comparison in this module whose answer is neither true nor false.- With NaN,
<and>are both false, and$nan == $nanis false. - A
sortover a NaN warns once and still produces a list.Use of uninitialized value in sortis the only signal, and the resulting order is an artefact. cmpis not the safe alternative — it answers a textual question, which happens to be well-defined for the charactersNaN.
Chapter 3.8 moves from comparing values to combining the answers, and starts with the property that makes || work as a tie-break at all: short-circuit evaluation.
Where this fits
Part of the Perl curriculum.
