http://qs1969.pair.com?node_id=11109035

harangzsolt33 has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks,

I have a sub called TEST() which converts every sort of input to a number. It should return 0 if the input value is undefined or uninitialized or blank or contains letters only. But if it contains digits, it should return those digits only.

So, if I ask to convert "55a" then it returns "55" which is fine. But if I try to convert "a55" then it returns 0, which baffles me! It should return 55. Why does this happen?

Edit: It seems like I am able to access the value of $N from within the warning exception, but I cannot modify it. The changes do not stick. So, I guess the question is how can I export the value of $N from within the warning sub { } so that it would overwrite the value of $N in the TEST sub?

#!/usr/bin/perl -w use strict; use warnings; my @TEST_VALUES = ('55a', 'a55'); foreach my $i (@TEST_VALUES) { print "Calling TEST('$i'): "; print "\nTEST return value = ", TEST($i), "\n"; } exit; ####################################### sub TEST { print " TEST() was called "; defined $_[0] or return 0; my $N = shift; print "with argument: '$N'\n"; # If we get a warning, it's probably because $N # is not a number. So, we remove all non-digits # and then it becomes a number! local $SIG{__WARN__} = sub { print "\t\t(((warning '$N'->"; $N =~ tr +|0-9||cd; $N = "0$N"; print "'$N')))\n"; }; $N += 0; print "\nThe value of N is now '$N'\n"; return int($N); }

Replies are listed 'Best First'.
Re: Converting to number doesn't always work... (updated)
by haukex (Archbishop) on Nov 22, 2019 at 06:52 UTC
    So, if I ask to convert "55a" then it returns "55" which is fine. But if I try to convert "a55" then it returns 0, which baffles me! It should return 55. Why does this happen?

    This is simply how Perl's string-to-number conversion works, it only takes the first portion of the string that looks like a number (although interestingly, I'm having trouble finding a reference in the Perl documentation at the moment*). The tr/0-9//cd solution is one way to work around that, if you are ok with strings such as "a1b2c3.4d" being converted to 1234.

    local $SIG{__WARN__}

    Instead of trying to trap the warning, it's better to use looks_like_number from Scalar::Util, as this gives you the exact internal function that Perl uses to check strings and generate that warning in the first place. (Update 2: We've been over this before.)

    * Update: The Camel says "To convert from string to number, Perl internally uses something like the C library’s atof(3) function.", and atof(3) says "The atof() function converts the initial portion of the string" (emphasis mine).

      Instead of trying to trap the warning, it's better to use looks_like_number from Scalar::Util, as this gives you the exact internal function that Perl uses to check strings and generate that warning in the first place

      Seems that perl doesn't always issue a warning when a variable that doesn't look like a number is used in numeric context:
      C:\>perl -MScalar::Util="looks_like_number" -wle "$r = ''; $x = \$r; p +rint 'lln' if looks_like_number($x); $x += 1" C:\>perl -MScalar::Util="looks_like_number" -wle "$x = 'hello'; print +'lln' if looks_like_number($x); $x += 1" Argument "hello" isn't numeric in addition (+) at -e line 1. C:\>
      For both of those one liners, Scalar::Util::looks_like_number($x) returns a false value, but it's only the second one liner that warns when $x is used in numeric context.

      Cheers,
      Rob
        $x = \$r;

        I am confused why you're taking this step? This means that $x is a reference, and a reference is like a dual-valued variable*: As a string, it's "SCALAR(0xabc)", which I would guess is what looks_like_number is looking at˛, and in numeric context, it's the memory address, which is why $x += 1 doesn't warn.

        * Update: Triple-valued? It's a reference, string, and number ;-) (Or rather: It's a reference, that gets converted to different values depending on context.)

        ˛ Update 2: Hmmm, nope, looks like it's checking the flags in this case.

Re: Converting to number doesn't always work...
by AnomalousMonk (Archbishop) on Nov 22, 2019 at 05:24 UTC
    $N =~ tr|[0-9]||cd;

    NB: The first field of the  tr/// operator (see Quote-Like Operators in perlop) is a search list, and it does not "know" about  [...] character classes (the search list is itself a kind of character class), so the  [ ] characters are among the "digit" characters which are not deleted.

    c:\@Work\Perl\monks>perl -wMstrict -le "my $n = 'abc[]1p2q3][xyz'; $n =~ tr|[0-9]||cd; print qq{'$n'}; " '[]123]['
    Don't know about the rest of the question.


    Give a man a fish:  <%-{-{-{-<

      Oh, thanks for catching that!

      my $A = 'abcd0123456789ABCDE[]{}()_=|?<>!@~%$/.-'; print "\nBEFORE: $A"; $A =~ tr|[0-9]||cd; print "\nAFTER: $A"; # Output: 0123456789[]
Re: Converting to number doesn't always work...
by AnomalousMonk (Archbishop) on Nov 22, 2019 at 20:35 UTC

    Consideration of the way Perl converts a string to a number is interesting, but I was puzzled by why all the other things that are done to $N in TEST() seem to have no effect. Here's the narrative I've imagined for the critical part of what's happening:

    1. In the  $N += 0; statement, the number 0 is added to the string $N (after its conversion to a temporary number according to the rules already discussed in this thread) to generate a number that is assigned to a temporary, intermediate variable, but not yet to $N.
    2. In the course of numifying the string and adding, the "... isn't numeric in addition ..." warning is triggered, and this is trapped by the  local $SIG{__WARN__} = sub { ... } handler temporarily installed by TEST().
    3. The  $SIG{__WARN__} handler assigns a lot of stuff to $N and prints a lot of stuff, but none of it makes any difference because...
    4. When the  $N += 0; statement completes, it assigns the contents of its temporary, intermediate variable to $N and that's all, folks; any changes to $N prior to this final assignment are wiped out.
    Here's the code I used to convince myself of the validity of this narrative:
    c:\@Work\Perl\monks>perl use strict; use warnings; sub TEST { defined $_[0] or return 0; my $N = shift; printf "TEST('$N') called - "; local $SIG{__WARN__} = sub { # doing anything or nothing to $N has no effect here # return; # try this printf "(in warn '$N' -> "; $N =~ tr|0-9||cd; $N = "0$N"; $N = 'garbage'; printf "'$N') - "; }; printf "\$N is '$N' before += - "; $N += 0; printf "\$N is '$N' after += \n"; return int($N); } for my $n (qw(55 55x x55)) { my $m = TEST($n); print "TEST('$n') returns $m \n\n"; } __END__ TEST('55') called - $N is '55' before += - $N is '55' after += TEST('55') returns 55 TEST('55x') called - $N is '55x' before += - (in warn '55x' -> 'garbag +e') - $N is '55' after += TEST('55x') returns 55 TEST('x55') called - $N is 'x55' before += - (in warn 'x55' -> 'garbag +e') - $N is '0' after += TEST('x55') returns 0
    I guess an opcode decompilation could confirm my conjecture, but I'm too lazy to do this right now.

    Update: Re-writing the  $N += 0; statement as  $N = $N + 0; may allow better visualization of what I think happens: The  $N + 0 expression must be evaluated first, and it's in this evaluation that the warning is triggered and a bunch of irrelevant things are done to $N; then the  $N = ...; assignment is done, overwriting any changes to $N made by the  $SIG{__WARN__} handler.


    Give a man a fish:  <%-{-{-{-<

      Yes, this is how it SHOULD work, but it doesn't! Let me simplify this script a little bit to demonstrate the problem. Run this script below. It should say TEST return value = 77 (at least it does on my computer which is Microsoft Windows 7 running TinyPerl 5.8.) But then you change the "return $N;" to "return $N + 0;" (and remember, at this point, $N is 77, so adding zero to it should leave it the same. But instead of returning 77, it flips its value BACK TO THE ORIGINAL!! which is totally WEIRD behavior! The return value should be 77, not 55.
      #!/usr/bin/perl -w use strict; use warnings; my @TEST_VALUES = ('55a', 'a55'); foreach my $i (@TEST_VALUES) { print "Calling TEST('$i'): "; print "\nTEST return value = ", TEST($i), "\n\n\n"; } exit; ####################################### sub TEST { my $N = shift; local $SIG{__WARN__} = sub { print "\nWarning was triggered. N=$N "; + $N = 77; print " Now N is $N **END OF WARNING**\n"; }; print "\n N=$N N+0= "; print "ADDITION: ", $N + 0; print "\n N=$N "; return $N; }

      I think, I found a BUG in the Perl interpreter. Lol ;)

        Here's something to ponder:

        c:\@Work\Perl\monks>perl -wMstrict -e "my @TEST_VALUES = ('55a', 'a55'); foreach my $i (@TEST_VALUES) { print qq{Calling TEST('$i') / }; print qq{TEST return values = @{[ TEST($i) ]} \n\n}; } ;; sub TEST { my $N = shift; local $SIG{__WARN__} = sub { print qq{(warn: N=$N -> }; $N = 77; print qq{N=$N) / }; }; print qq{N=$N / N+0=}, $N + 0; print qq{ / N.''=}, $N.'', qq{\n}; return $N, $N+0, $N.''; } " Calling TEST('55a') / (warn: N=55a -> N=77) / N=55a / N+0=55 / N.''=77 TEST return values = 77 55 77 Calling TEST('a55') / (warn: N=a55 -> N=77) / N=a55 / N+0=0 / N.''=77 TEST return values = 77 0 77
        Don't ask me what this means or if it's a bug (it's late and I need to think about this with a clear head), but I suspect it has something to do with the inherent dualvar nature of scalars (see Scalar::Util::dualvar): a scalar caches a string or numeric representation of its value if it was ever used as a string or number, and both representations may be present in the scalar at the same time and may represent entirely unrelated values! Runs the same under ActiveState 5.8.9 and Strawberry 5.14.4.1.


        Give a man a fish:  <%-{-{-{-<

Re: Converting to number doesn't always work...
by Don Coyote (Hermit) on Nov 22, 2019 at 16:32 UTC

    The question of whether perl will return a string or number, which is thouroughly interesting, is a bit of a side issue.

    What you seem to be attempting to do is change a lexically scoped variable from a different scope.

    use strict; use warnings; #use vars ('$M','$N'); # local $SIG{__WARN__} = sub { $N=24 }; my $t_num = sub { $N=24 }; printf "\nThe value of N is now %s %d %o\n",$N,$N,$N; $M = sprintf "%x", 41; printf "\nThe value of M is now %s %d %o\n",$M,$M,$M; my $t_var = sub { $M = 42; # printf "\nThe value of M is now %s %d %o\n",$M,$M,$M; sub { $M }; }; printf "\nThe value of t_var is now %s %d %o\n",$t_var->(),$t_var->( +),$t_var->(); printf "\nThe value of M is now %s %d %o\n",$M,$M,$M; =head1 output Global symbol "$N" requires explicit package name at v42_13.pl line 10 +. Global symbol "$N" requires explicit package name at v42_13.pl line 11 +. ... Global symbol "$M" requires explicit package name at v42_13.pl line 23 +. Global symbol "$M" requires explicit package name at v42_13.pl line 23 +. Execution of v42_13.pl aborted due to compilation errors. =cut =head uncommented 'use vars ('$M','$N');' output Use of uninitialized value $N in printf at v42_13.pl line 11. Use of uninitialized value $N in printf at v42_13.pl line 11. Use of uninitialized value $N in printf at v42_13.pl line 11. The value of N is now 0 0 The value of M is now 29 29 35 The value of t_var is now CODE(0xxxxxxxxxx) dddddddd ddddddddddd The value of M is now 42 42 52 =cut

    If you change the scope then you can probably put your local pc repair shop on speed-dial. I would advise not doing this with environment variables.

    additional not sure about this really, though, I'd have thought if this was an issue here, there would have been some more responses of the kind. I may have misunderstood this a little.

Re: Converting to number doesn't always work...
by parv (Parson) on Nov 22, 2019 at 05:25 UTC

    Ignore my earlier useless reply please, for I had missed your effort to extract a number inside the obtuse print statement.