How to tell apart numeric scalars and string scalars in Perl?

走远了吗. 提交于 2019-12-18 03:52:10

问题


Perl usually converts numeric to string values and vice versa transparently. Yet there must be something which allows e.g. Data::Dumper to discriminate between both, as in this example:

use Data::Dumper;
print Dumper('1', 1);

# output:
$VAR1 = '1';
$VAR2 = 1;

Is there a Perl function which allows me to discriminate in a similar way whether a scalar's value is stored as number or as string?


回答1:


There's no way to find this out using pure perl. Data::Dumper uses a C library to achieve it. If forced to use Perl it doesn't discriminate strings from numbers if they look like decimal numbers.

use Data::Dumper;
$Data::Dumper::Useperl = 1;
print Dumper(['1',1])."\n";

#output
$VAR1 = [
          1,
          1
        ];



回答2:


A scalar has a number of different fields. When using Perl 5.8 or higher, Data::Dumper inspects if there's anything in the IV (integer value) field. Specifically, it uses something similar to the following:

use B qw( svref_2object SVf_IOK );

sub create_data_dumper_literal {
    my ($x) = @_;  # This copying is important as it "resolves" magic.
    return "undef" if !defined($x);

    my $sv = svref_2object(\$x);
    my $iok = $sv->FLAGS & SVf_IOK;
    return "$x" if $iok;

    $x =~ s/(['\\])/\\$1/g;
    return "'$x'";
}

You could use similar tricks. But keep in mind,

  • It'll be very hard to stringify floating point numbers without loss. (Floating pointer numbers are identified using $sv->FLAGS & SVf_NOK.)

  • You need to properly escape certain bytes (e.g. NUL) in string literals.

  • A scalar can have more than one value stored in it. For example, !!0 contains a string (the empty string), a floating point number (0) and a signed integer (0). As you can see, the different values aren't even always equivalent. For a more dramatic example, check out the following:

    $ perl -E'open($fh, "non-existent"); say 0+$!; say "".$!;'
    2
    No such file or directory
    



回答3:


It is more complicated. Perl changes the internal representation of a variable depending on the context the variable is used in:

perl -MDevel::Peek -e '
    $x = 1;    print Dump $x;
    $x eq "a"; print Dump $x;
    $x .= q(); print Dump $x;
'
SV = IV(0x794c68) at 0x794c78
  REFCNT = 1
  FLAGS = (IOK,pIOK)
  IV = 1
SV = PVIV(0x7800b8) at 0x794c78
  REFCNT = 1
  FLAGS = (IOK,POK,pIOK,pPOK)
  IV = 1
  PV = 0x785320 "1"\0
  CUR = 1
  LEN = 16
SV = PVIV(0x7800b8) at 0x794c78
  REFCNT = 1
  FLAGS = (POK,pPOK)
  IV = 1
  PV = 0x785320 "1"\0
  CUR = 1
  LEN = 16



回答4:


Based on your comment that this is to determine whether quoting is needed for an SQL statement, I would say that the correct solution is to use placeholders, which are described in the DBI documentation.

As a rule, you should not interpolate variables directly in your query string.




回答5:


The autobox::universal module, which comes with autobox, provides a type function which can be used for this purpose:

use autobox::universal qw(type);

say type("42");  # STRING
say type(42);    # INTEGER
say type(42.0);  # FLOAT 
say type(undef); # UNDEF 



回答6:


When a variable is used as a number, that causes the variable to be presumed numeric in subsequent contexts. However, the reverse isn't exactly true, as this example shows:

use Data::Dumper;

my $foo = '1';
print Dumper $foo;  #character
my $bar = $foo + 0;
print Dumper $foo;  #numeric
$bar = $foo . ' ';
print Dumper $foo;  #still numeric!
$foo = $foo . '';
print Dumper $foo;  #character

One might expect the third operation to put $foo back in a string context (reversing $foo + 0), but it does not.

If you want to check whether something is a number, the standard way is to use a regex. What you check for varies based on what kind of number you want:

if ($foo =~ /^\d+$/)      { print "positive integer" }
if ($foo =~ /^-?\d+$/)    { print "integer"          }
if ($foo =~ /^\d+\.\d+$/) { print "Decimal"          }

And so on.

It is not generally useful to check how something is stored internally--you typically don't need to worry about this. However, if you want to duplicate what Dumper is doing here, that's no problem:

if ((Dumper $foo) =~ /'/) {print "character";}

If the output of Dumper contains a single quote, that means it is showing a variable that is represented in string form.




回答7:


You might want to try Params::Util::_NUMBER:

use Params::Util qw<_NUMBER>;

unless ( _NUMBER( $scalar ) or $scalar =~ /^'.*'$/ ) { 
   $scalar =~ s/'/''/g;
   $scalar = "'$scalar'";
}



回答8:


One simple solution that wasn't mentioned was Scalar::Util's looks_like_number. Scalar::Util is a core module since 5.7.3 and looks_like_number uses the perlapi to determine if the scalar is numeric.




回答9:


I don't think there is perl function to find type of value. One can find type of DS(scalar,array,hash). Can use regex to find type of value.



来源:https://stackoverflow.com/questions/12686335/how-to-tell-apart-numeric-scalars-and-string-scalars-in-perl

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!