What does "0 but true" mean in Perl?

PerlIntegerBoolean

Perl Problem Overview


Can someone explain what exactly the string "0 but true" means in Perl? As far as I understand, it equals zero in an integer comparison, but evaluates to true when used as a boolean. Is this correct? Is this a normal behavior of the language or is this a special string treated as a special case in the interpreter?

Perl Solutions


Solution 1 - Perl

It's normal behaviour of the language. Quoting the perlsyn manpage:

> The number 0, the strings '0' and "", the empty list (), and undef > are all false in a boolean context. All other values are true. Negation > of a true value by ! or not returns a special false value. > When evaluated as a string it is treated as "", but as a number, it is > treated as 0.

Because of this, there needs to be a way to return 0 from a system call that expects to return 0 as a (successful) return value, and leave a way to signal a failure case by actually returning a false value. "0 but true" serves that purpose.

Solution 2 - Perl

Because it's hardcoded in the Perl core to treat it as a number. This is a hack to make Perl's conventions and ioctl's conventions play together; from perldoc -f ioctl:

> The return value of ioctl (and fcntl) is as follows: > > if OS returns: then Perl returns: >
> -1 undefined value > 0 string "0 but true" > anything else that number > > Thus Perl returns true on success and false on failure, yet you > can still easily determine the actual value returned by the > operating system: > > $retval = ioctl(...) || -1; > printf "System returned %d\n", $retval; > > The special string "0 but true" is exempt from -w complaints > about improper numeric conversions.

Solution 3 - Perl

Additionally to what others said, "0 but true" is special-cased in that it doesn't warn in numeric context:

$ perl -wle 'print "0 but true" + 3'
3
$ perl -wle 'print "0 but crazy" + 3'
Argument "0 but crazy" isn't numeric in addition (+) at -e line 1.
3

Solution 4 - Perl

The value 0 but true is a special case in Perl. Although to your mere mortal eyes, it doesn't look like a number, wise and all knowing Perl understands it really is a number.

It has to do with the fact that when a Perl subroutine returns a 0 value, it is assumed that the routine failed or returned a false value.

Imagine I have a subroutine that returns the sum of two numbers:

die "You can only add two numbers\n" if (not add(3, -2));
die "You can only add two numbers\n" if (not add("cow", "dog"));
die "You can only add two numbers\n" if (not add(3, -3));

The first statement won't die because the subroutine will return a 1. That's good. The second statement will die because the subroutine won't be able to add cow to dog.

And, the third statement?

Hmmm, I can add 3 to -3. I just get 0, but then my program will die even though the add subroutine worked!

To get around this, Perl considers 0 but true to be a number. If my add subroutine returns not merely 0, but 0 but true, my third statement will work.

But is 0 but true a numeric zero? Try these:

my $value = "0 but true";
print qq(Add 1,000,000 to it: ) . (1_000_000 + $value) . "\n";
print "Multiply it by 1,000,000: " . 1_000_000 * $value . "\n";

Yup, it's zero!

The index subroutine is a very old piece of Perl and existed before the concept of 0 but true was around. It is suppose to return the position of the substring located in the string:

index("barfoo", "foo");   #This returns 3
index("barfoo", "bar");   #This returns 0
index("barfoo", "fu");    #This returns ...uh...

The last statment returns a -1. Which means if I did this:

if ($position = index($string, $substring)) {
   print "It worked!\n";
}
else {
   print "If failed!\n";
}

As I normally do with standard functions, it wouldn't work. If I used "barfoo" and "bar" like I did in the second statement, The else clause would execute, but if I used "barfoo" and "fu" as in the third, the if clause would execute. Not what I want.

However, if the index subroutine returned 0 but true for the second statement and undef for the third statement, my if/else clause would have worked.

Solution 5 - Perl

You may also see the string "0E0" used in Perl code, and it means the same thing, where 0E0 just means 0 written in exponential notation. However, since Perl only considers "0", '' or undef as false, it evaluates to true in a boolean context.

Solution 6 - Perl

It's hard-coded in Perl's source code, specifically in Perl_grok_number_flags in numeric.c.

Reading that code I discovered that the string "infinity" (case insensitive) passes the looks_like_number test too. I hadn't known that.

Solution 7 - Perl

In an integer context, it evaluates to 0 (the numeric part at the beginning of the string) and is zero. In a scalar context, it's a non-empty value, so it is true.

  • if (int("0 but true")) { print "zero"; }

    (no output)

  • if ("0 but true") { print "true"; }

    (prints true)

Solution 8 - Perl

0 means false in Perl (and other languages related to C). For the most part, that's a reasonable behavior. Other languages (Lua for instance) treat 0 as true and provide another token (often nil or false) to represent a non-true value.

One case where the Perl way doesn't work so well is when you want to return either a number or, if the function fails for some reason, a false value. For instance, if you write a function that reads a line from a file and returns the number of characters on the line. A common usage of the function might be something like:

while($c = characters_in_line($file)){
    ...
};

Notice that if the number of characters on a particular line is 0, the while loop will end before the end of the file. So the characters_in_line function should special case 0 characters and return '0 but true' instead. That way the function will work as intended in the while loop, but also return the correct answer should it be used as a number.

Note that this isn't a built in part of the language. Rather it takes advantage of Perl's ability to interpret a string as a number. So other stings are sometimes used instead. DBI uses "0E0", for instance. When evaluated in numeric context, they return 0, but in boolean context, false.

Solution 9 - Perl

Things that are false:

  • "".
  • "0".
  • Things that stringify to those.

"0 but true" is not one of those, so it's not false.

Furthermore, Perl returns "0 but true" where a number is expected in order to signal that a function succeeded even though it returned zero. sysseek is an example of such a function. Since the value is expected to be used as a number, Perl is coded to consider it to be a number. As a result, no warnings are issued when it's used as a number, and looks_like_number("0 but true") returns true.

Other "true zeroes" can be found at http://www.perlmonks.org/?node_id=464548.

Solution 10 - Perl

Another example of "0 but true":

The DBI module uses "0E0" as a return value for UPDATE or DELETE queries that didn't affect any records. It evaluates to true in a boolean context (indicating that the query was executed properly) and to 0 in a numeric context indicating that no records were changed by the query.

Solution 11 - Perl

I just found proof that the string "0 but true" is actially built into the interpreter, like some people here already answered:

$ strings /usr/lib/perl5/5.10.0/linux/CORE/libperl.so | grep -i true
Perl_sv_true
%-p did not return a true value
0 but true
0 but true

Solution 12 - Perl

When you want to write a function that returns either an integer value, or false or undef (i.e. for the error case) then you have to watch out for the value zero. Returning it is false and shouldn't indicate the error condition, so returning "0 but true" makes the function return value true while still passing back the value zero when math is done on it.

Solution 13 - Perl

"0 but true" is a string just like any other but because of perl's syntax it can serve a useful purpose, namely returning integer zero from a function without the result being "false"(in perl's eyes).

And the string need not be "0 but true". "0 but false" is still "true"in the boolean sense.

consider:

if(x)

for x:        yields:
1             -> true
0             -> false
-1            -> true
"true"        -> true
"false"       -> true
"0 but true"  -> true
int("0 but true") ->false

The upshot of all of this is you can have:

sub find_x()

and have this code be able to print "0" as its output:

if($x = find_x)
{
   print int($x) . "\n";
}

Solution 14 - Perl

The string ``0 but true'' is still a special case:

for arg in "'0 but true'" "1.0*('0 but true')" \
           "1.0*('0 but false')" 0 1 "''" "0.0" \
           "'false'" "'Ja'" "'Nein'" "'Oui'" \
           "'Non'" "'Yes'" "'No'" ;do
    printf "%-32s: %s\n" "$arg" "$(
        perl -we '
            my $ans=eval $ARGV[0];
            $ans=~s/^(Non?|Nein)$//;
            if ($ans) {
                printf "true:  |%s|\n",$ans
            } else {
                printf "false: |%s|", $ans
          };' "$arg"
        )"
    done

give the following: (note the ``warning''!)

'0 but true'                    : true:  |0 but true|
1.0*('0 but true')              : false: |0|
Argument "0 but false" isn't numeric in multiplication (*) at (eval 1) line 1.
1.0*('0 but false')             : false: |0|
0                               : false: |0|
1                               : true:  |1|
''                              : false: ||
0.0                             : false: |0|
'false'                         : true:  |false|
'Ja'                            : true:  |Ja|
'Nein'                          : false: ||
'Oui'                           : true:  |Oui|
'Non'                           : false: ||
'Yes'                           : true:  |Yes|
'No'                            : false: ||

... and don't forget to RTFM!

man -P'less +"/0 but [a-z]*"' perlfunc

       ... "fcntl".  Like "ioctl", it maps a 0 return from the system call
           into "0 but true" in Perl.  This string is true in boolean
           context and 0 in numeric context.  It is also exempt from the
           normal -w warnings on improper numeric conversions. ...

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjkramerView Question on Stackoverflow
Solution 1 - PerlChris Jester-YoungView Answer on Stackoverflow
Solution 2 - PerlgeekosaurView Answer on Stackoverflow
Solution 3 - PerlmoritzView Answer on Stackoverflow
Solution 4 - PerlDavid W.View Answer on Stackoverflow
Solution 5 - PerlAndy LesterView Answer on Stackoverflow
Solution 6 - PerlSimon WhitakerView Answer on Stackoverflow
Solution 7 - PerlDaniel PapasianView Answer on Stackoverflow
Solution 8 - PerlJon EricsonView Answer on Stackoverflow
Solution 9 - PerlikegamiView Answer on Stackoverflow
Solution 10 - PerlkixxView Answer on Stackoverflow
Solution 11 - PerljkramerView Answer on Stackoverflow
Solution 12 - PerlandyView Answer on Stackoverflow
Solution 13 - PerlFrostyView Answer on Stackoverflow
Solution 14 - PerlF. HauriView Answer on Stackoverflow