How to make numeric warning fatal? - perl

I am not able to make the numeric warning fatal:
use feature qw(say);
use strict;
use warnings;
my $y = "a";
if ($y > 0) {
say "Should not happen";
}
say "P1";
{
say "y = $y";
use warnings FATAL => qw(numeric);
if ($y > 0) {
say "Should not happen";
}
}
say "Done";
Output is:
Argument "a" isn't numeric in numeric gt (>) at p.pl line 6.
P1
y = a
Done
Expected output:
Argument "a" isn't numeric in numeric gt (>) at p.pl line 6.
P1
y = a
Argument "a" isn't numeric in numeric gt (>) at p.pl line 13.
What am I missing?

Once you convert a variable to numeric, that conversion is cached. This means that $y is numeric after the first $y > 0, so the second neither warns nor dies.
Modified test:
use feature qw(say);
use strict;
use warnings;
my $y = my $z = "a";
if ($y > 0) {
say "Should not happen";
}
say "P1";
{
use warnings FATAL => qw(numeric);
if ($z > 0) {
say "Should not happen";
}
}
say "Done";
Output:
Argument "a" isn't numeric in numeric gt (>) at a.pl line 6.
P1
Argument "a" isn't numeric in numeric gt (>) at a.pl line 12.
We can see this caching with Devel::Peek. pIOK indicates an integer value is contained. (It's found in the IV slot.) pNOK indicates a floating point value is contained. (It's found in the NV slot.)
$ perl -MDevel::Peek -we'my $y = "a"; Dump( $y ); 0+$y; Dump( $y );'
SV = PV(0x56336a2a3ea0) at 0x56336a2d2d00
REFCNT = 1
FLAGS = (POK,IsCOW,pPOK)
PV = 0x56336a2db600 "a"\0
CUR = 1
LEN = 10
COW_REFCNT = 1
Argument "a" isn't numeric in addition (+) at -e line 1.
SV = PVNV(0x56336a2a2220) at 0x56336a2d2d00
REFCNT = 1
FLAGS = (POK,IsCOW,pIOK,pNOK,pPOK)
IV = 0
NV = 0
PV = 0x56336a2db600 "a"\0
CUR = 1
LEN = 10
COW_REFCNT = 1

Related

Perl division by zero - need infinity

Are there ways to get inf or -inf from eval {1/0}, eval {1/-0} instead of Illegal division by zero exception?
Out of the box? No, because it's the wrong answer. Division by zero is undefined not infinite.
You could use something like // to test if the result of your eval is undefined:
my $result = eval { 1/0 } // 'inf';
print $result;
And set a default that way. (Although in the above, 'inf' is the string; a numeric value might be more useful). There are a selection of maths libraries that give you inf type constants, like bignum bigrat bigint. Portability may be an issue though.
If you use bignum, you'll get inf out of 1/0:
use bignum;
print 1 / 0; # produces "inf"
1/-0 is still inf (I guess it's because -0 == 0).
See http://perldoc.perl.org/bignum.html.
What you really need is Data::Float:
#!/usr/bin/env perl
use strict;
use warnings;
use Carp qw( croak );
use Data::Float qw( have_infinite pos_infinity neg_infinity float_is_infinite);
have_infinite() or croak "No support for infinite float values";
my $v = -1;
my $x = 1;
my $y = 0;
my $z = eval { $x/$y };
unless (defined $z) {
$z = ($x >= 0) ? pos_infinity : neg_infinity;
}
print "$z\n";
$z = eval { $v/$y };
unless (defined $z) {
$z = ($v >= 0) ? pos_infinity : neg_infinity;
}
print "$z\n";
Output:
$ ./inf.pl
Inf
-Inf
Of course, if you are using 5.10 or later, you can do:
my $z = eval { $x/$y };
$z //= ($x >= 0) ? pos_infinity : neg_infinity;
print "$z\n";
$z = eval { $v/$y };
$z //= ($v >= 0) ? pos_infinity : neg_infinity;
print "$z\n";

Why does encode raise "Use of uninitialized value within #_"?

With Perl v5.14.2 (provided by Debian Wheezy) this code:
use Encode qw(encode);
no warnings "all";
sub test_encode {
return Encode::encode("utf8", $_[0]);
}
my $a=undef;
my $r=test_encode(substr($a,0,1));
produces an empty string in $r. I'm fine with that.
With Perl 5.18.2 (Ubuntu 14.04), it appears to produces this output:
Use of uninitialized value within #_ in list assignment at
/usr/lib/perl/5.18/Encode.pm line 147.
(even with warnings disabled in the main scop, so apparently it's not a warning. EDIT: per answers, it's definitely a warning):
That list assignment would be, in Encode.pm:
146 sub encode($$;$) {
147 my ( $name, $string, $check ) = #_;
148 return undef unless defined $string;
149 $string .= ''; # stringify;
Tweaking the code, if undef is passed to encode instead of $_[0], it no longer complains. If a copy of $_[0] in a temp variable is passed instead of $_[0], it doesn't complain too.
My question are: What would have changed in Perl between these versions that would explain the new behavior? What exactly does Perl see inside #_ in Encode.pm line 147 ?
ADDENDUM: adding Dump($_[0]); from Devel::Peek at the start of test_encode, it outputs:
Perl 5.14.2:
SV = PVLV(0x23a2c10) at 0x2340998
REFCNT = 1
FLAGS = (GMG,SMG)
IV = 0
NV = 0
PV = 0
MAGIC = 0x235f950
MG_VIRTUAL = &PL_vtbl_substr
MG_TYPE = PERL_MAGIC_substr(x)
TYPE = x
TARGOFF = 0
TARGLEN = 0
TARG = 0x235e370
SV = PV(0x233ec20) at 0x235e370
REFCNT = 2
FLAGS = (PADMY,POK,pPOK)
PV = 0x23576b0 ""\0
CUR = 0
LEN = 16
Perl 5.18.2:
SV = PVLV(0x25c07c0) at 0x2546cb8
REFCNT = 1
FLAGS = (GMG,SMG)
IV = 0
NV = 0
PV = 0
MAGIC = 0x2567dd0
MG_VIRTUAL = &PL_vtbl_substr
MG_TYPE = PERL_MAGIC_substr(x)
TYPE = x
TARGOFF = 0
TARGLEN = 1
TARG = 0x256f328
FLAGS = 0
SV = NULL(0x0) at 0x256f328
REFCNT = 2
FLAGS = (PADMY)
Not sure what to think of that, but the SV part at the end differs significantly, looks like empty string versus NULL(0x0).
It's substr that's warning.
substr warns when its first argument is undefined.
$ perl -we'
my $x;
my $y = substr($x, 0, 1); # Line 3
'
Use of uninitialized value $x in substr at -e line 3.
Since 5.16.0, the warning now happens when the substring operation is actually performed instead of when substr is called. When substr is used as an lvalue, the actual substring operation is performed when the a value is fetched or stored in the returned scalar.
$ perl -we'
my $x;
my $r = \substr($x, 0, 1);
my $y = $$r; # Line 4
'
Use of uninitialized value in scalar assignment at -e line 4.
The substring operation is done then to allow the following to work:
$ perl -wE'$_ = "abc"; substr($_, 0, 1) = "!!!"; say'
!!!bc
Since the warning now happens when the substring operation is done, it's the context of the op in Encode that determines whether the warning will be visible or not.
$ 5.14.2t/bin/perl -e'use warnings; my $r = \substr(my $x, 0, 1); no warnings; my $y = $$r;'
Use of uninitialized value in scalar assignment at -e line 1.
$ 5.14.2t/bin/perl -e'no warnings; my $r = \substr(my $x, 0, 1); use warnings; my $y = $$r;'
$ 5.22.0t/bin/perl -e'use warnings; my $r = \substr(my $x, 0, 1); no warnings; my $y = $$r;'
$ 5.22.0t/bin/perl -e'no warnings; my $r = \substr(my $x, 0, 1); use warnings; my $y = $$r;'
Use of uninitialized value in scalar assignment at -e line 1.
Why did the warning starting happening where the substring operation is actually perform instead of when substr is called? I'm guessing, but it might be to fix the following and similar problems:
$ perl -wE'
my $x = "def";
my $r = \substr($x, 0, 1);
$x = "abc";
say "<$$r>";
'
<a>
$ 5.14.2t/bin/perl -wE'
my $x;
my $r = \substr($x, 0, 1);
$x = "abc";
say "<$$r>";
'
Use of uninitialized value $x in substr at -e line 4.
<>
$ 5.22.0t/bin/perl -wE'
my $x;
my $r = \substr($x, 0, 1);
$x = "abc";
say "<$$r>";
'
<a>
Prefixing substr with scalar calls it as an rvalue, though that's not documented.
$ perl -MO=Concise,-exec -e'1 for substr($_, 0, 1)' 2>&1 | grep substr
7 <#> substr[t4] sKM/3
^
This flag causes the special lvalue behaviour.
$ perl -MO=Concise,-exec -e'1 for scalar substr($_, 0, 1)' 2>&1 | grep substr
7 <#> substr[t2] sK/3
You could also force stringification.
$ perl -MO=Concise,-exec -e'1 for "".substr($_, 0, 1)' 2>&1 | grep substr
8 <#> substr[t2] sK/3
Interesting. If you do almost the same thing but:
my $a=undef;
my $b = substr($a,0,1);
my $r=test_encode($b);
It works fine.
Or:
my $r=test_encode(scalar substr($a,0,1));
So I think I would have to say - this has got to be to do with return values from substr and context.
E.g. #_[0] isn't undefined - #_ is undefined.
The Encode module has:
#
# $Id: Encode.pm,v 2.75 2015/06/30 09:57:15 dankogai Exp $
#
package Encode;
use strict;
use warnings;
Which will be overriding your no warnings directive - but hiding warnings like that isn't really desirable anyway. That's been in for a while though:
2.18 2006/06/03 20:28:48
! bin/enc2xs
overhauled the -C option
- added ascii-ctrl', 'null', 'utf-8-strict' to core
- auto-generated Encode::ConfigLocal no longer use v-string for version
- now searches modules via File::Find so Encode/JP/Mobile is happy
! Byte/Byte.pm CN/CN.pm EBCDIC/EBCDIC.pm JP/JP.pm KR/KR.pm Symbol/Symbol.pm
use strict added; though all they do is load XS, it's
still better a practice
! *.pm
use warnings added to all of them for better practices' sake.
So I would suggest you might be using an older version of Encode when it's working.

Matching a zero with String::Substitution

I am trying to match a digit with String::Substitution; it works fine if the digit is not zero. If the digit is zero it substitutes the empty string instead of the digit. For example:
use strict;
use warnings;
use Data::Dump;
use String::Substitution;
my #data = qw(0 1);
for (#data) {
my $str = $_;
my $regex = qr/(\d)/;
my $replace = '$1';
my $result_str = String::Substitution::gsub_copy($str, $regex, $replace);
my #m = $str =~ /$regex/g;
dd $result_str;
dd #m;
}
The output is:
""
0
1
1
expected output would be:
0
0
1
1
To avoid "uninitialized" warnings, version 1.001 of the module attempts to convert undefined placeholders into empty strings. However, it erroneously uses a truth test rather an defined test to determine which values to replace with an empty string.
map { ($$_) || '' } ( 1 .. $#- )
That code needs to be changed to
map { defined($$_) ? $$_ : '' } ( 1 .. $#- )
A bug report has been submitted.

Misunderstanding of incrementation

In Perl, I have this following code:
my $val = "0";
for(my $z = 0; $z <= 14; $z++)
{
++$val;
if($val == 9) {
$val = "A";
}
print $val;
}
it prints:
1 2 3 4 5 6 7 8 A B 1 2 3 4 5
yet it's supposed to continue from B to C, from C to D and so on, what is the logic behind this?
warnings would have given you a warning message like:
Argument "B" isn't numeric in numeric eq (==)
use warnings;
use strict;
my $val = "0";
for(my $z = 0; $z <= 14; $z++)
{
++$val;
if($val eq '9') { # <------------------
$val = "A";
}
print $val;
}
To quote perlop:
If you increment a variable that is numeric, or that has ever been used in a numeric context, you get a normal increment. If, however, the variable has been used in only string contexts since it was set, and has a value that is not the empty string and matches the pattern /^[a-zA-Z]*[0-9]*\z/, the increment is done as a string, preserving each character within its range, with carry... (emphasis added)
$val == 9 is a numeric context. So it prints A (you just set it), and then you get the magic increment to B (it hasn't been used in a numeric context yet), but then you hit the == (using it in a numeric context), so when you get to ++$val again B is treated as a number (0) and increments to 1.
You could use eq to make a string comparison, thus preserving the magic increment, but you could also just say:
print 1 .. 8, 'A' .. 'F';

How can I disable scientific notation when working with very large numbers in Perl?

Consider the following:
print 3 ** 333; #Yields 7.6098802313206e+158
My question is simple: How can I disable scientific notation when working with very large numbers? Basically, I'd like to see all the digits dumped to stdout verbatim.
Is this possible?
See Math::BigInt
use Math::BigInt;
$x = Math::BigInt->new("3");
print $x ** 333;
Output:
760988023132059809720425867265032780727896356372077865117010037035791631439306199613044145649378522557935351570949952010001833769302566531786879537190794573523
If you want to do it for all integers in your program, you can just add:
use bigint;
If you only want to do it for some integers, you can create Math::BigInt objects.
There is also bignum and Math::BigNum if you are working with floats.
For very small values see the following code:
my $value = 1e-07; # = 0.0000001
# NOPE
print $value; # prints 1e-07, $value is a number
print sprintf("%f", $value); # prints 0, $value is a number
print sprintf("%.10f", $value); # prints 0.0000001000, $value is a number
$value = sprintf("%.10f", $value);
print $value # prints 1e-07, $value is a number
# /NOPE
use bignum;
$value = ($value+0)->bstr();
print $value; # prints 0.0000001, $value is a string
no bignum;
print $value; # prints 0.0000001, $value is a string
# HOORAY
With numbers that large you may have more digits than the precision used to store the numbers. (Seeing a simple runnable example would have resolved this question).
If you really need to see all 150+ digits, you should use the bigint (for integers), bigrat (for rational numbers) and the bignum (for floating point numbers) modules.
Had the same issue with this code:
#!/usr/bin/perl
use strict;
use warnings;
print "Base Exp MAX Signed-Negitive MAX Signed-Positive MAX Unsigned\n";
for( my $x = 1; $x <= 64; $x++ ) {
my $y = (2 ** $x);
printf( "2 ^ %4d = %20d to %-20d or %20d\n",
$x, $y/-2, $y/2, $y );
}
The last two lines where printing:
2 ^ 63 = -4611686018427387904 to 4611686018427387904 or -9223372036854775808
2 ^ 64 = -9223372036854775808 to -9223372036854775808 or -1
Obviously not right, and not realizing the %d conversion was causing the issue, I tried the solution flagged here:
#!/usr/bin/perl
use strict;
use warnings;
use Math::BigInt;
print "Base Exp MAX Signed-Negitive MAX Signed-Positive MAX Unsigned\n";
for( my $x = Math::BigInt->new('1'); $x <= 64; $x++ ) {
my $y = Math::BigInt->new(2 ** $x);
printf( "2 ^ %4d = %20d to %-20d or %20d\n",
$x, $y/-2, $y/2, $y );
}
That's when I realized the printf 'd' conversion was causing an issue. Reading up on Math::BigInt it seems to suggest that these numbers are stored as strings inside, so changing to an 's' conversion, fixed the issue:
#!/usr/bin/perl
use strict;
use warnings;
use Math::BigInt;
print "Base Exp MAX Signed-Negitive MAX Signed-Positive MAX Unsigned\n";
for( my $x = Math::BigInt->new('1'); $x <= 64; $x++ ) {
my $y = Math::BigInt->new(2 ** $x);
printf( "2 ^ %4s = %20s to %-20s or %20s\n",
$x, $y/-2, $y/2, $y );
}
Now the last two lines printed correctly:
2 ^ 63 = -4611686018427387904 to 4611686018427387904 or 9223372036854775808
2 ^ 64 = -9223372036854775808 to 9223372036854775808 or 18446744073709551616
But in regards to Karel's answer, which was almost correct IMHO, this could also be done without the use of BigInt (bigint, BigNum, ...) by using the 'f' conversion but with the precision set to '0' to eliminate those decimals:
#!/usr/bin/perl
use strict;
use warnings;
print "Base Exp MAX Signed-Negitive MAX Signed-Positive MAX Unsigned\n";
for( my $x = 1; $x <= 64; $x++ ) {
my $y = (2 ** $x);
printf( "2 ^ %4d = %20.0f to %-20.0f or %20.0f\n",
$x, $y/-2, $y/2, $y );
}
This also works for the OP's question:
perl -e 'printf "%.0f\n", 3 ** 333'
760988023132059813486251563646478824265752535077884574263917414498578085812167738721447369281049109603746001743233145041176969930222526036520619613114171654144