Stefan Majewsky
Stefan Majewsky

Reputation: 5555

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?

Upvotes: 20

Views: 4514

Answers (10)

Peter John Acklam
Peter John Acklam

Reputation: 363

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
}

Upvotes: 0

ikegami
ikegami

Reputation: 386491

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
    

Upvotes: 19

chocolateboy
chocolateboy

Reputation: 1816

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 

Upvotes: 4

bubaflub
bubaflub

Reputation: 41

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.

Upvotes: 4

Axeman
Axeman

Reputation: 29854

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

use Params::Util qw<_NUMBER>;

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

Upvotes: 3

TLP
TLP

Reputation: 67910

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.

Upvotes: 6

dan1111
dan1111

Reputation: 6566

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.

Upvotes: 3

choroba
choroba

Reputation: 242103

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

Upvotes: 16

Bohdan
Bohdan

Reputation: 414

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
        ];

Upvotes: 11

tuxuday
tuxuday

Reputation: 3037

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.

Upvotes: -1

Related Questions