Surprising output for the descending range ’01’..’-1′

[This is fixed in Perl v5.32]

The range operator has some interesting features to do more numbers, and one of them looks like it has some unintended special cases.

A couple of months ago, Hauke D reported some strange behavior with the range operator. Some of this is likely to be clarified in the v5.30 documentation but the odd cases remain. One part of this is useful and the others are probably surprising. Don’t depend on this feature for anything that’s important. Even though it acts like this, the undesigned feature may disappear.

You already know that it counts up in whole numbers from the left-hand argument to the right-hand one, inclusively:

$ perl -le '$, = q( ); print 1..3'
1 2 3

It can handle negative numbers just fine:

$ perl -le '$, = q( ); print -1..3'
-1 0 1 2 3

And in the case where the right-hand side is numerically smaller, you get the empty list:

$ perl -le '$, = q( ); print -1..-3'

You may also know that the range operator has some special string magic. Want the letters from a to z? No problem:

$ perl -le "\$, = q( ); print q(a)..q(z)"
a b c d e f g h i j k l m n o p q r s t u v w x y z

Here’s a trickier one. How about the strings from a to az? It goes up to z and then starts in with the doubles. It changes the last character until it exhausts the range and then moves back one character and does it again. It repeats this until it gets to the element on the right-hand side (inclusively).

This doesn’t work with everything. Try it with alpha to omega:

$ perl -le "\$, = q( ); print q(α)..q(ω)"
α

There’s actually a hard-coded endpoint for these ranges. This goes back to the day where we didn’t think anyone would do more than a to z.

Try it with a (Latin small letter) to omega and you get a to z again:

$ perl -le "use utf8; use feature qw(unicode_strings); \$, = q( ); print q(a)..q(ω)"
a b c d e f g h i j k l m n o p q r s t u v w x y z

Put both of those together. Suppose that you want all of the two-digit dates for a month, including leading zeros. There’s another special case for that. If both sides look like a number even though they are strings, do something a bit more fancy that I hadn’t known before:

$ perl -le "\$, = q( ); print q(01)..q(31)"
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

As numbers you don’t get the leading zeros:

$ perl -le "\$, = q( ); print 01..31"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

And this brings you to the odd behavior. You know that range must increase, so 1 to -1 is the empty list no matter the leading zero:

$ perl -le "\$, = q( ); print 1..-1"

$ perl -le "\$, = q( ); print 01..-1"

However, in the string case with the leading zero and a lower number output all of the numbers of the same length as the right-hand argument. With -1 you get 01 to 99:

$ perl -le "\$, = q( ); print q(01)..q(-1)"
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

It’s not the – that’s special. It’s anything that looks like a number and has a string length that’s the same or larger:

$ perl -le "\$, = q( ); print q(01)..q(00)"
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

Make the right-hand string longer and that length determines when the sequence stops:

$ perl -le "\$, = q( ); print q(01)..q(000)"
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
... lots of output ...
996 997 998 999