Argument isn't numeric - question - perl

Why doesn't the first example output a warning?
#!/usr/bin/env perl
use warnings;
use 5.012;
my $c = "9\n";
say $c * 2;
my $d = "6a";
say $d * 2;
# 18
# Argument "6a" isn't numeric in multiplication (*) at ./perl8.pl line 9.
# 12

When converting a number from a string, trailing whitespace is ignored, and newline counts as whitespace, so a warning isn't generated. Converting "9 " doesn't generate a warning either.

Because Perl thinks it's a number:
use Scalar::Util 'looks_like_number';
for ("9\n", "6a") {
say looks_like_number($_);
}
1
0

Related

truncate string in perl into substring with trailing elipses

I'm trying to truncate a string in a select input option using perl if it is longer than a set value, though i can't get it to work correctly.
my $value = defined $option->{value} ? $option->{value} : '';
my $maxValueLength = 50;
if ($value.length > $maxValueLength) {
$value = substr $value, 0, $maxValueLength + '...';
}
Another option is regex
$string =~ s/.{$maxLength}\K.*/.../;
It matches any character (.) given number of times ({N}, here $maxLength), what is the first $maxLength characters in $string; then \K makes it "forget" all previous matches so those won't get replaced later. The rest of the string that is matched is then replaced by ...
See Lookaround assertions in perlre for \K.
This does start the regex engine for a simple task but it doesn't need any conditionals -- if the string is shorter than the maximum length the regex won't match and nothing happens.
Your code has several syntax errors. Turn on use strict and use warnings if you don't have it, and then read the error messages it tells you about. This is a bit tricky because of Perl's very complex syntax (see also Damian Conway's keynote from the 2020 Perl and Raku Conference), but it boils down to these:
Use of uninitialized value in concatenation (.) or string at line 7
Argument "..." isn't numeric in addition (+) at line 8
I've used the following adaption of your code to produce these
use strict;
use warnings;
my $value = '1234567890' x 10;
my $maxValueLength = 50;
if ( $value.length > $maxValueLength ) {
$value = substr $value, 0, $maxValueLength + '...';
}
print $value;
Now let's see what they mean.
The . operator in Perl is a concatenation. You cannot use it to call methods, and length is not a method on a string. Perl thinks you are using the built-in length (a function, not a method) without an argument, which makes it default to $_. Most built-ins do this, to make one-liners shorter. But $_ is not defined. Now the . tries to concatenate the length of undef to $value. And using undef in a string operation leads to this warning.
The correct way of doing this is length $value (or with parentheses if you prefer them, length($value)).
The + operator is not concatenation (we just learned that the . is). It's a numerical addition. Perl is pretty good at converting between strings and numbers as there aren't really any types, so saying 1 + "5" would give you 6 without problems, but it cannot do that for a couple of dots in a string. Hence it complains about a non-number value in an addition.
You want the substring with a given length, and then you want to attach the three dots. Because of associativity (or stickyness) of operators you will need to use parentheses () for your substr call.
$value = substr($value, 0, $maxValueLength) . '...';
To find a length of the string use length(STRING)
Here is the code snippet how you can modify the script.
#!/usr/bin/perl
use strict;
use warnings;
use feature qw(say);
my $string = "abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz";
say "length of original string is:".length($string);
my $value = defined $string ? $string : '';
my $maxValueLength = 50;
if (length($value) > $maxValueLength) {
$value = substr $value, 0, $maxValueLength;
say "value:$value";
say "value's length:".length($value);
}
Output:
length of original string is:80
value:abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvw
value's length:50

How to apply perl range operator on input from <STDIN>?

I'm not able to apply perl range operator over an input taken from Standard Input. Here's the code:
#!/usr/bin/perl
#range = (1..5, 6, 7, 8..10);
print "#range\n";
print "Enter your range. Esc by CTRL+D.\n";
chomp(#user_range = <STDIN>); # input 1..5<LF> 6<LF> 7..10<LF> CTRL+D
print "#user_range\n";
The first block of code (#range) works fine and outputs: 1 2 3 4 5 6 7 8 9 10;
In the second (#user_range) I input 1..5 6 7..10 CTRL+D. The output I get is 1..5 6 7..10 instead of 1 2 3 4 5 6 7 8 9 10.
Please help.
Anything you read from STDIN or any other filehandle comes in as just a string. This is a basic security measure, you don't want your programming language evaluating input as code.
You could run it through eval, but that's a security hole; it lets users run arbitrary code. Don't use eval STRING unless you know what you're doing. eval BLOCK is fine, it's totally different.
Instead, if you want something done to input you'll have to do it yourself. If it's just simple numbers, then it can be fairly straightforward.
use 5.010;
use strict;
use warnings;
# Read the string from input and remove the newline.
my $list_string = <STDIN>;
chomp $list_string;
# Split it into a list on commas.
# "1..5, 6, 7..10" becomes "1..5", "6", "7..10".
my #list = split /\s*,\s*/, $list_string;
# Go through each element checking for a range operator.
# If there's a range operator, replace it with the range.
# Otherwise leave it alone.
#list = map { range_transform($_) } #list;
print join ", ", #list;
sub range_transform {
my $string = shift;
# Match the X..Y. If it doesn't match, just return it.
return $string unless $string =~ m{^(\d+)\.\.(\d+)$};
# Perform the range operation.
return $1..$2;
}
This doesn't have any error checking, it should probably also check that the input is either a number OR a range operator and puke if it's anything else. But that should get you started.

How to force long double in Perl

I lose precision when doing arithmetic or trying to print (debug) numbers this big:
1234567890.123456789
I think my problems are with $d (result of arithmetic) and the formatted print of $e.
How can I force long doubles? My Perl version (5.8.4 on SUN) says it's possible.
sprintf has a size option for long doubles (q or L or ll), but I haven't figured out how to use it, and don't know if it would work with printf.
Edit: I added BigFloat, which works! But I'd still like to force long doubles.
Try to add 1234567890 + 0.123456789
and subtract 1234567890 - 0.123456789.
use Config;
use Math::BigFloat;
$a = 1234567890;
$b = 123456789;
$c = $b/1e9; # 0.123456789
$d = $a + $c; # not enough precision (32-bit or double?)
$e = sprintf("%d.%.9d",$a,$b); # combine as strings
$f = 1234567890.123456789; # for reference (not enough precision)
# Use BigFloat to bypass lack of longdbl
$aBig = Math::BigFloat->new("$a");
$dSum = $aBig->fadd("$c"); # $dSum = $a + $c
$aBig = Math::BigFloat->new("$a"); # <-- Need a new one for every operation?
$dDif = $aBig->fsub(abs("$c")); # $dDif = $a - $c
print "a $a\n"; # 1234567890
print "c $c\n"; # 0.123456789
print "d=a+c $d\n"; # 1234567890.12346 <-- **Problem**
print "dSum=a+c $dSum\n"; # 1234567890.123456789 <-- Solution
print "dDif=a-c $dDif\n"; # 1234567890.876543211 <-- Solution
print "e $e\n"; # 1234567890.123456789
print "f $f\n"; # 1234567890.12346 <-- double, 52-bit, not longdbl?
printf ("printf e 20.9f %20.9f\n",$e); # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20.9f %20.9f\n",$dSum); # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20s %20s\n",$dSum); # 1234567890.123456789
printf ("printf dDif 20.9f %20.9f\n",$dDif); # 1234567890.876543283 <-- **Problem**
printf ("printf dDif 20s %20s\n",$dDif); # 1234567890.876543211
print "uselongdouble $Config{uselongdouble}\n"; # empty. No long doubles by default
print "d_longdbl $Config{d_longdbl}\n"; # "define". Supports long doubles
print "size double longdbl $Config{doublesize} $Config{longdblsize}\n"; # Ans 8 16
I also used this code to try to understand the types, but it didn't help much. Has anyone used it to explain problems like this?
use Devel::Peek 'Dump';
Dump ($dSum); # Wow, it's complicated
Dump ($f);
Perl has one size of float, and it's called NV. The size of an NV is decided when Perl is built.
$ perl -V:nvsize
nvsize='8';
This information is also accessible within a Perl program via the Config module.
$ perl -MConfig -E'say $Config{nvsize}'
8
The size of NV cannot be changed after Perl is built.
You can force Perl to be built to use long double floats as follows when when building Perl:
sh Configure -Duselongdouble ...
-or-
perlbrew install -Duselongdouble ...
-or-
perlbrew install --ld ...
If you don't want to rebuild your perl or make a new one, you will need to use a module. I recommend Math::LongDouble as it provides access to native long double floats, and it does so as transparently as possible.
Another option is to use an arbitrary-precision library such as Math::BigFloat, but that will be slower that necessary if all you need is a long double.
bignum will overload all operators in the current scope to use arbitrary precision integers and floating point operations.
use bignum;
my $f = 123456789.123456789;
print "$f\n"; # 123456789.123456789
print $f + $f, "\n"; # 246913578.246913578
Behind the scenes, bignum turns all numeric constants into Math::BigInt and Math::BigNum objects as appropriate.
It's important to note that bignum is lexically scoped and does not effect the whole program. For example...
{
use bignum;
$f = 123456789.123456789; # $f is a Math::BigNum object
}
$g = 123456789.123456789; # $g is a regular NV
print "$f\n"; # 123456789.123456789
print "$g\n"; # 123456789.123457
# This will use Math::BigNum's addition method, but $g has already lost precision.
print $f + $g, "\n"; # 246913578.246913789
You can get a bit better performance out of this by using the Math::BigInt::GMP plugin to use the GNU Multiple Precision Arithmetic Library for some operations. You have to install that module first using the normal CPAN install process (you don't need GMP). Then tell bignum to use GMP.
use bignum lib => "GMP";

How to print the string using foreach using with addition function? in perl

use warnings;
use strict;
my #a = qw(1 2 3 4 'c');
my #b = qw(5 6 7 8);
my $i;
for ($i=0; $i < scalar #a; $i++)
{
my $ax = $a[$i] + $b[$i];
print "$ax\n";
}
How to print the string variable c when using the add function.
# I expect output
6
8
10
12
c
In other languages, e.g., Javascript and, somewhat, in Python, + does both numeric addition and string concatenation.
But not in Perl.
In Perl, string concatenation is done with . and numeric addition with +
That is why this script produces the "isn't numeric in addition" warning.
The "use of uninitialized value" warning comes from going beyond the end of the #b array.
These are warnings. Execution did not stop. Note that the script decided that "c" was equivalent to 0 and undefined was equivalent to 0, and produced 0 as the addition on the last line.
6
8
10
12
Use of uninitialized value in addition (+) at test.pl line 8.
Argument "'c'" isn't numeric in addition (+) at test.pl line 8.
0
It is possible to overload '+' within a custom class, and it would be possible to write a custom class that adds scalars that match a numeric regex with + and concats others. But modifying core functionality would be unusual. In any event, these techniques are beyond the scope of this answer.

How can I include a variable in a Perl printf expression?

How can I include a variable in a printf expression?
Here's my example:
printf "%${cols}s", $_;
Where $cols is the number of columns
and $_ is a string.
The statement results in an "Invalid conversion" warning.
The problem ended up being that I forgot to chomp the variable. Gah. Thanks everyone.
Your interpolated variable $cols looks like its supposed to be a number, say 10, so
"%${cols}s"
should interpolate and be equivalent to
"%10s"
which is a valid format string.
If however $cols was something other than a number or valid format string, you'd get the warning.
For example, if:
$cols = "w";
that would result in "%ws" as a format string - giving the error you quote:
Invalid conversion in printf: "%w"
Valid format information can be found here.
I figured out your specific problem. Your code is correct. However, I suppose $cols might be a number read from user input, say like this:
my $cols = <STDIN>;
This works, and in numeric context $cols will appear to be a number, but the problem is that $cols isn't appearing in numeric context here. It's in string context, which means that instead of expanding to "%5s", your format string expands to "%5\ns". The newline there is mucking up the format string.
Change the code where you read $cols to this:
chomp(my $cols = <STDIN>);
See the documentation on chomp, as you may want to use it for other input reading as well.
Always use * in your format specifier to unambiguously indicate variable width! This is similar to the advice to use printf "%s", $str rather than printf $str.
From the perlfunc documentation on sprintf:
(minimum) width
Arguments are usually formatted to be only as wide as required to display the given value. You can override the width by putting a number here, or get the width from the next argument (with *) or from a specified argument (with e.g. *2$):
printf '<%s>', "a"; # prints "<a>"
printf '<%6s>', "a"; # prints "< a>"
printf '<%*s>', 6, "a"; # prints "< a>"
printf '<%*2$s>', "a", 6; # prints "< a>"
printf '<%2s>', "long"; # prints "<long>" (does not truncate)
If a field width obtained through * is negative, it has the same effect as the - flag: left-justification.
For example:
#! /usr/bin/perl
use warnings;
use strict;
my $cols = 10;
$_ = "foo!";
printf "%*s\n", $cols, $_;
print "0123456789\n";
Output:
foo!
0123456789
With the warnings pragma enabled, you'll see warnings for non-numeric width arguments.
Your current method should work
perl -e'my $cols=500; $_="foo"; printf "%${cols}s\n\n", $_;'
The following seems to work for me:
#!/bin/perl5.8 -w
use strict;
my $cols = 5;
my $a = "3";
printf "%${cols}d\n", $a;
yields
28$ ./test.pl
3
29$
I cannot reproduce your problem. The following code works fine:
use strict;
use warnings;
my $cols=40;
while (<>) {
printf "%${cols}s\n", $_;
}
It prints any input line using at least 40 columns of width.