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

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?



Solution 1:[1]

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'";
}

Checks:

  • Signed integer (IV): ($sv->FLAGS & SVf_IOK) && !($sv->FLAGS & SVf_IVisUV)
  • Unsigned integer (IV): ($sv->FLAGS & SVf_IOK) && ($sv->FLAGS & SVf_IVisUV)
  • Floating-point number (NV): $sv->FLAGS & SVf_NOK
  • Downgraded string (PV): ($sv->FLAGS & SVf_POK) && !($sv->FLAGS & SVf_UTF8)
  • Upgraded string (PV): ($sv->FLAGS & SVf_POK) && ($sv->FLAGS & SVf_UTF8)

You could use similar tricks. But keep in mind,

  • It'll be very hard to stringify floating point numbers without loss.

  • 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 for 0+$!, "".$!;'
      2
      No such file or directory
    

Solution 2:[2]

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

Solution 3:[3]

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.

Solution 4:[4]

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.

Solution 5:[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 

Solution 6:[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.

Solution 7:[7]

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

use Params::Util qw<_NUMBER>;

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

Solution 8:[8]

The following function returns true (1) if the input is numeric and false ("") if it is a string. The function also returns true (-1) if the input is a numeric Inf or NaN. Similar code can be found in the JSON::PP module.

sub is_numeric {
    my $value = shift;
    no warnings 'numeric';
    # string & "" -> ""
    # number & "" -> 0 (with warning)
    # nan and inf can detect as numbers, so check with * 0
    return unless length((my $dummy = "") & $value);
    return unless 0 + $value eq $value;
    return 1 if $value * 0 == 0;  # finite number
    return -1;                    # inf or nan
}

Solution 9:[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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 bubaflub
Solution 5 chocolateboy
Solution 6 dan1111
Solution 7 Axeman
Solution 8 Peter John Acklam
Solution 9 tuxuday