bug in perl with prefixed 0 numbers - perl

when I try to execute the following(01434.210 instead of 1434.210)
$val=22749.220-(21315.010+01434.210)
print $val
I get these output
output 638.207900000001
But according to me output must be 0.
What am I missing?

A leading 0 in a literal number makes Perl interpret the value I'm base 8:
123 # 123, in decimal
0123 # 123 in octal, but 83 in decimal
This isn't the same for strings converted to numbers. In those Perl ignores the leading 0s. The string-to-number conversion only deals in base-10:
"123" + 0 # 123
"0123" + 0 # still 123
In your example in the comment, you convert a literal number to a string with a leading zero. When you convert that string back to its numeric form you get the same value you started with:
$val=sprintf("%05d",1434); # converting 1434 to the string "01434"
print $val; print "\n"; # still a string
print $val+21315; # "01434" + 21315 => 1434 + 21315
print "\n";
print 01434+21315; # oct(1434) + 21315 => 796 + 21315
The leading zero notation helps with certain builtins that typically use octal numbers, such as those that deal with unix permissions:
chmod 0644, #files

Related

Remove upfront zeros from floating point lower than 1 in Perl

I would like to normalize the variable from ie. 00000000.1, to 0.1 using Perl
my $number = 000000.1;
$number =\~ s/^0+(\.\d+)/0$1/;
Is there any other solution to normalize floats lower than 1 by removing upfront zeros than using regex?
When I try to put those kind of numbers into an example function below
test(00000000.1, 0000000.025);
sub test {
my ($a, $b) = #_;
print $a, "\n";
print $b, "\n";
print $a + $b, "\n";
}
I get
01
021
22
which is not what is expected.
A number with leading zeros is interpreted as octal, e.g. 000000.1 is 01. I presume you have a string as input, e.g. my $number = "000000.1". With this your regex is:
my $number = "000000.1";
$number =~ s/^0+(?=0\.\d+)//;
print $number;
Output:
0.1
Explanation of regex:
^0+ -- 1+ 0 digits
(?=0\.\d+) -- positive lookahead for 0. followed by digits
Learn more about regex: https://twiki.org/cgi-bin/view/Codev/TWikiPresentation2018x10x14Regex
Simplest way, force it to be treated as a number and it will drop the leading zeros since they are meaningless for decimal numbers
my $str = '000.1';
...
my $num = 0 + $str;
An example,† to run from the command-line:
perl -wE'$n = shift; $n = 0 + $n; say $n' 000.1
Prints 0.1
Another, more "proper" way is to format that string ('000.1' and such) using sprintf. Then you do need to make a choice about precision, but that is often a good idea anyway
my $num = sprintf "%f", $str; # default precision
Or, if you know how many decimal places you want to keep
my $num = sprintf "%.3f", $str;
† The example in the question is really invalid. An unquoted string of digits which starts with a zero (077, rather than '077') would be treated as an octal number except that the decimal point (in 000.1) renders that moot as octals can't be fractional; so, Perl being Perl, it is tortured into a number somehow, but possibly yielding unintended values.
I am not sure how one could get an actual input like that. If 000.1 is read from a file or from the command-line or from STDIN ... it will be a string, an equivalent of assigning '000.1'
See Scalar value constructors in perldata, and for far more detail, perlnumber.
As others have noted, in Perl, leading zeros produce octal numbers; 10 is just a decimal number ten but 010 is equal to decimal eight. So yeah, the numbers should be in quotes for the problem to make any sense.
But the other answers don’t explain why the printed results look funny. Contrary to Peter Thoeny’s comment and zdim’s answer, there is nothing ‘invalid’ about the numbers. True, octals can’t be floating point, but Perl does not strip the . to turn 0000000.025 into 025. What happens is this:
Perl reads the run of zeros and recognises it as an octal number.
Perl reads the dot and parses it as the concatenation operator.
Perl reads 025 and again recognises it as an octal number.
Perl coerces the operands to strings, i.e. the decimal value of the numbers in string form; 0000000 is, of course, '0' and 025 is '21'.
Perl concatenates the two strings and returns the result, i.e. '021'.
And without error.
(As an exercise, you can check something like 010.025 which, for the same reason, turns into '821'.)
This is why $a and $b are each printed with a leading zero. Also note that, to evaluate $a + $b, Perl coerces the strings to numbers, but since leading zeros in strings do not produce octals, '01' + '021' is the same as '1' + '21', returning 22.

Why is 00 equal to 0 in perl?

I am not a heavy perl user. I am just learning. However, recently I am working with an old script written by someone else.
Essentially, the script reads an input file from which the variable "index_a" is defined such that when doing a simple print:
print "$index_a";
00
My confusion comes from the following:
When a comparison to "zero" is made, it holds true:
if ($index_a == 0) { print "Found first index: 0" };
Found first index: 0
I was expecting the comparison to be false. Since the value of index_a = 00 is obtained from reading a line from an input file. Hence to my understanding index_a is a string which value is "00", therefore the if statement should not be executed.
Could some perl expert help me understand this? Also, could you let me know what is the proper way to check for data types in perl? I have experience with python and C and hence this is behavior is very confusing to me.
Thanks!
In Perl, the context is important when considering the value. A scalar variable can contain all sorts of data types, but what you are testing for determines what you find. For example:
if ("00" == 0) # string "00" is numerically identical to 0
if ("a" == 0) # string "a" is numerically identical to 0 (gives a warning)
# Argument "a" isn't numeric in numeric eq (==)
if ("00" eq "0") # string "00" is not stringwise identical to "0".
if (#a == 0) # if the array #a is empty (length 0)
In a numerical equality check with a string, Perl will try to cast the string to a number. If the string begins with a number, it will be cast to that number (for the duration of the test), else it will be cast to 0.
You have to (sort of) know what you have in a variable when you test what it contains. Or else you have to check more thoroughly. In your case, when reading from a file, you might want to use eq to test for string equality.
When you use a Perl string in a numeric context, such as a numeric comparison, it recognizes the starting part that looks like a decimal number and ignores the stuff after that. Whatever it recognizes becomes the number.
It follows these basic rules:
Ignore leading whitespace. This is handy when you extract a field out of columnar data and the number doesn't take up the entire column.
Allow for a single leading sign (+ or -)
Skip leading zeros (so, no octal)
Capture decimal ASCII digits (0,1,2,3,4,5,6,7,8,9), allowing for a single decimal point (so, no semantic versioning numbers)
Stop at the first non-decimal-digit character
Whatever you have so far is the number. If you have nothing, the number is 0.
There are some things to note, though:
Adding 0 to a string is a common way to force numeric mode.
Underscores in strings are just underscores. You can use underscores to separate digits in number literals but not in strings. So, 123_456 is 123456 because the perl parser handles the _, but '123_456' is just 123.
However, if the first characters (excluding the sign) are "Inf", in any case, the string converts to "Inf" and includes the - sign but not the + sign. This is the special IEEE value of Infinity.
However, if the first characters (excluding the sign) are "NaN", in any case, the string converts to "Nan" and excludes either sign. This is the special "Not a number" value.
Here are some examples:
#!perl
use v5.10;
use utf8;
use open qw(:std :utf8);
my #strings = qw(
-5 +7 +006 -01 00
+.1234 -.9876 .657 ..890 1.2.3
١٢٣
- + +-657
12a34 a987 123fred 0456barney
0x12 12_34 0177
NaN -NaN NAN
Inf +Inf -Inf INF Infamy Infinity
);
push #strings, " 432", "+ 123", " -987", " +063"," NaN", " Inf", '';
foreach my $string ( #strings ) {
printf "%-12s => %s\n", qq('$string'), 0 + $string;
}
And the output:
'-5' => -5
'+7' => 7
'+006' => 6
'-01' => -1
'00' => 0
'+.1234' => 0.1234
'-.9876' => -0.9876
'.657' => 0.657
'..890' => 0
'1.2.3' => 1.2
'١٢٣' => 0
'-' => 0
'+' => 0
'+-657' => 0
'12a34' => 12
'a987' => 0
'123fred' => 123
'0456barney' => 456
'0x12' => 0
'12_34' => 12
'0177' => 177
'NaN' => NaN
'-NaN' => NaN
'NAN' => NaN
'Inf' => Inf
'+Inf' => Inf
'-Inf' => -Inf
'INF' => Inf
'Infamy' => Inf
'Infinity' => Inf
' 432' => 432
'+ 123' => 0
' -987' => -987
' +063' => 63
' NaN' => NaN
' Inf' => Inf
'' => 0
Finally, there's another interesting thing about Perl scalars. When you use a string as a number, Perl converts the string to a number and remembers the conversion. It does not, however, change the string. Devel::Peek shows you the innards of Perl data structures.
#!perl
use v5.10;
use Devel::Peek;
select(STDERR); $|++; # just to order the output from Dump
my $string = '123fred';
say "string is <$string>";
Dump( $string );
say '-' x 40;
my $n = $string + 0;
say "string is <$string>";
Dump( $string );
Here's the output. At first, $string has a PV (pointer value) for a string. After the numeric operation, it also has IV and NV values for numbers.
string is <123fred>
SV = PV(0x7f8be980cab0) at 0x7f8bea0160f8
REFCNT = 1
FLAGS = (POK,IsCOW,pPOK)
PV = 0x7f8be9610ca0 "123fred"\0
CUR = 7
LEN = 10
COW_REFCNT = 1
----------------------------------------
string is <123fred>
SV = PVNV(0x7f8be980ac50) at 0x7f8bea0160f8
REFCNT = 1
FLAGS = (POK,IsCOW,pIOK,pNOK,pPOK)
IV = 123
NV = 123
PV = 0x7f8be9610ca0 "123fred"\0
CUR = 7
LEN = 10
COW_REFCNT = 1

Adding two numbers in unix

I tried to add two numbers by below logic:
num=0001
newnum=`expr $num + 1`
echo $newnum
But it returns '2', my desired output is '0002'.
num=0001
newnum=`expr $num + 0001`
echo $newnum
I used above logic also,but no use. What is needed here to get my desired output.
Thanks in advance.
Use printf to print numbers with leading zeroes:
printf "%04d\n" $num
You shouldn't do arithmetic with numbers with leading zeroes, because many applications treat an initial zero as meaning that the number is octal, not decimal.
Use printf:
$ num=0001
$ printf "%04d" $(expr $num + 1)
0002
In order to assign the result to a variable, say:
$ newnum=$(printf "%04d" $(expr $num + 1))
$ echo $newnum
0002
Numbers with leading zeros are interpreted as octal numbers. In order to treat them as decimals you need to prepend 10# to the number. Finally, use printf to pad the number with zeros.
num=0001
newNum=$((10#$num + 1))
paddedNum=$(printf %04d $newNum)

Perl pack/unpack and length of binary string

Consider this short example:
$a = pack("d",255);
print length($a)."\n";
# Prints 8
$aa = pack("ddddd", 255,123,0,45,123);
print length($aa)."\n";
# Prints 40
#unparray = unpack("d "x5, $aa);
print scalar(#unparray)."\n";
# Prints 5
print length($unparray[0])."\n"
# Prints 3
printf "%d\n", $unparray[0] '
# Prints 255
# As a one-liner:
# perl -e '$a = pack("d",255); print length($a)."\n"; $aa = pack("dd", 255,123,0,45,123); print length($aa)."\n"; #unparray = unpack("d "x5, $aa); print scalar(#unparray)."\n"; print length($unparray[0])."\n"; printf "%d\n", $unparray[0] '
Now, I'd expect a double-precision float to be eight bytes, so the first length($a) is correct. But why is the length after the unpack (length($unparray[0])) reporting 3 - when I'm trying to go back the exact same way (double-precision, i.e. eight bytes) - and the value of the item (255) is correctly preserved?
By unpacking what you packed, you've gotten back the original values, and the first value is 255. The stringification of 255 is "255", which is 3 characters long, and that's what length tells you.

How can I convert hex strings into numbers in Perl?

I recently wrote a script which parsed a text representation of a single binary byte month field.
(Don't ask :-{ )
After fiddling with sprintf for a while I gave up and did this;
our %months = qw / x01 1
x02 2
x03 3
x04 4
x05 5
x06 6
x07 7
x08 8
x09 9
x0a 10
x0b 11
x0c 12 /;
...
my $month = $months{$text};
Which I get away with, because I'm only using 12 numbers, but is there a better way of doing this?
If you have
$hex_string = "0x10";
you can use:
$hex_val = hex($hex_string);
And you'll get: $hex_val == 16
hex doesn't require the "0x" at the beginning of the string. If it's missing it will still translate a hex string to a number.
You can also use oct to translate binary, octal or hex strings to numbers based on the prefix:
0b - binary
0 - octal
0x - hex
See hex and/or oct.
#!/usr/bin/perl
use strict;
use warnings;
my #months = map hex, qw/x01 x02 x03 x04 x05 x06 x07 x08 x09 x0a x0b x0c/;
print "$_\n" for #months;
If I understand correctly you have 1 byte per month - not string "0x10", but rather byte with 10 in it.
In this way, you should use unpack:
my $in = "\x0a";
print length($in), "\n";
my ($out) = unpack("c", $in);
print length($out), "\n", $out, "\n"
output:
1
2
10
If the input are 3 characters, like "x05", then changing is also quite simple:
my $in = "x0a";
my $out = hex($in);
Here's another way that may be more practical for directly converting the hexadecimals contained in a string.
This make use of the /e (e for eval) regex modifier on s///.
Starting from this string:
$hello_world = "\\x48\\x65\\x6c\\x6c\\x6f\\x20\\x57\\x6f\\x72\\x6c\\x64";
Hexadecimals to characters :
print $hello_world =~ s/\\x([0-9a-fA-F]{2})/chr hex $1/gre;
Hexadecimals to decimal numbers :
print $hello_world =~ s/\\x([0-9a-fA-F]{2})/hex $1/gre;
Drop the /r modifier to substitute the string in-place.
One day I used a python script that did stuff with a binary file and I was stuck with a bytes literal (b'\x09\xff...') containing only hexadecimal digits.
I managed to get back my bytes with a one-liner that was a variant of the above.