Remove upfront zeros from floating point lower than 1 in Perl - 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.

Related

Sprintf in Perl doesn't display hexadecimal character properly

I have a code like this.
$entry = &function(); //returns a number between 0 to 20
$var = sprintf("%#.4x", $entry);
if($var=~ /$hex/)
{
//block of statements
}
$hex will be within 0x0000 ..... 0x0014. Now, when function returns from 1 to 20, $var matches $hex. (Like 0x0001 .... 0x0014)
But when $entry is 0, $var becomes 0000. But I want it to be 0x0000. Currently, I am checking if that is 0000, I am changing it through a if loop. Please let me know if that is possible in sprintf itself.
According to the documentation for sprintf:
flags
# prefix non-zero hexadecimal with "0x" or "0X"
Note that it says non-zero, so only non-zero values will be prefixed by 0x.
A simple fix is to add the prefix manually:
sprintf "0x%04x", $entry;
The doc clearly mentions that 0x is appended only for non-zero numbers when # flag is used.This makes sense since zero is zero whether it is in Octal or Hexadecimal. Hence prefixing it with 0x doesn't make sense.
Best way to handle this would be:
if($var=~ /$hex/ or !$var)
Sounds like you are doing things backwards. Wouldn't the following make more sense?
if ($entry == hex($hex))
If you want to compare numbers, compare the numbers, not their text representation.

Learning Perl, One-Liner behaves differently

UPDATE
As pointed out in the answer, this question really has to do with Scalar versus List Context in Perl.
## ## ##
I am learning perl via self-taught crash course (primarily with the Llama book and the web). In attempting some byte swap code, I have found a one liner I do not understand completely. A comment in the script explains what I think is happening in the one-liner.
#!/usr/bin/perl --
#
# Script to print byte-swapped hex values
#
use 5.010;
use warnings;
use strict;
# NOTE: I realize I could use a single variable 'data', but the x- y- z- prefixes may help in
# identification (for clarity) in the code for this SO question.
my $xData;
my $yData;
my $zData;
for ( my $ijk = 998; $ijk < 1001; $ijk++ )
{
printf ( "\n%4d is hex " . (sprintf "0x%04X", $ijk) . "\n", $ijk );
# with byte swap
say "These numbers (bytes swapped) should match...";
# do sprintf, match pattern and store to ($1)($2), now reverse them into ($2)($1).
# BindOp leaves $_ alone, match stuffs $_ & is then used as input for reverse, prints.
say reverse ((sprintf "%04X", $ijk) =~ /(..)(..)/) ; # from perlmonks' webpage
$yData = (reverse ((sprintf "%04X", $ijk) =~ /(..)(..)/) );
say $yData; # does NOT match
$xData = sprintf "%04X", $ijk;
$xData =~ s/(..)(..)/$2$1/ ;
say $xData; # does match
$_ = sprintf "%04X", $ijk;
/(..)(..)/;
$zData = $2 . $1 ;
say $zData; # does match
}
exit 0;
OUTPUT:
998 is hex 0x03E6 These numbers (bytes swapped) should match...
E603
6E30
E603
E603
999 is hex 0x03E7 These numbers (bytes swapped) should match...
E703
7E30
E703
E703
1000 is hex 0x03E8 These numbers (bytes swapped) should match...
E803
8E30
E803
E803
Why does the one liner work, and why doesn't the $yData perform the same way? I'm pretty sure I understand why $xData and $zData work, but I would expect $yData to be the closest equivalent non-one-liner. What is the closest equivalent non-one-liner and why? Where is the discrepancy?
The reverse in your print (say) statement comes in the list context, while when assigned to $yData the context is scalar. This function (may) behave considerably differently based on the context.
From perldoc -f reverse
reverse LIST
In list context, returns a list value consisting of the elements of LIST in the opposite order. In scalar context, concatenates the elements of LIST and returns a string value with all characters in the opposite order.
In this case this produces different results.
When called in list context, it swaps the (two) input bytes, keeping each byte intact (represented by two hexadecimal digits matched in a group). When called in scalar context, it joins the input and returns a character string, running in the opposite order. Taken to represent a hex number this would have each byte changed.

Perl - Remove trailing zeroes without exponential value

I am trying to remove trailing zeroes from decimal numbers.
For eg: If the input number is 0.0002340000, I would like the output to be 0.000234
I am using sprintf("%g",$number), but that works for the most part, except sometimes it converts the number into an exponential value with E-. How can I have it only display as a full decimal number?
Numbers don't have trailing zeroes. Trailing zeroes can only occur once you represent the number in decimal, a string. So the first step is to convert the number to a string if it's not already.
my $s = sprintf("%.10f", $n);
(The solution is suppose to work with the OP's inputs, and his inputs appear to have 10 decimal places. If you want more digits to appear, use the number of decimal places you want to appear instead of 10. I thought this was obvious. If you want to be ridiculous like #asjo, use 324 decimal places for the doubles if you want to make sure not to lose any precision you didn't already lose.)
Then you can delete the trailing zeroes.
$s =~ s/0+\z// if $s =~ /\./;
$s =~ s/\.\z//;
or
$s =~ s/\..*?\K0+\z//;
$s =~ s/\.\z//;
or
$s =~ s/\.(?:|.*[^0]\K)0*\z//;
To avoid scientific notation for numbers use the format conversion %f instead of %g.
A lazy way could be simply: $number=~s/0+$// (substitute trailing zeroes by nothing).
The solution is easier than you might think.
Instead of using %g use %f and it will result in the behavior you are looking for. %f will always output your floating decimal in "fixed decimal notation".
What does the documentation say about %g vs %f?
As you may notice in the below table %g will result in either the same as %f or %e (when appropriate).
Ff you'd want to force the use of fixed decimal notation use the appropriate format identifier, which in this case is %f.
sprintf - perldoc.perl.org
%% a percent sign
%c a character with the given number
%s a string
%d a signed integer, in decimal
%u an unsigned integer, in decimal
%o an unsigned integer, in octal
%x an unsigned integer, in hexadecimal
%e a floating-point number, in scientific notation
%f a floating-point number, in fixed decimal notation
%g a floating-point number, in %e or %f notation
What about TIMTOWTDI; aren't we writing perl?
Yes, as always there are more than one ways of doing it.
If you'd just like to trim the trailing decimal-point zeros from a string you could use a regular expression such as the below.
$number = "123000.321000";
$number =~ s/(\.\d+?)0+$/$1/;
$number # is now "12300.321"
Remember that floating point values in perl doesn't have trailing decimals, unless you are dealing with a string. With that said; a string is not a number, even though it can explicitly and implicitly be converted to one.
The simplest way is probably to multiply by 1.
Original:
my $num = sprintf("%.10f", 0.000234000001234);
print($num);
#output
0.0002340000
With multiplying:
my $num = sprintf("%.10f", 0.000234000001234) * 1;
print($num);
#output
0.000234
The whole point of the %g format is to use a fixed point notation when it is reasonable and to use exponential notation when fixed point is not reasonable. So, you need to know the range of values you'll be dealing with.
Clearly, you could write a regular expression to post-process the string from sprintf(), removing the trailing zeroes:
my $str = sprintf("%g", $number);
$str =~ s/0+$//;
If you always want a fixed point number, use '%f', possibly with number of decimal places that you want; you might still need to remove trailing zeroes. If you always want exponential notation, use '%e'.
An easy way:
You could cheat a little. Add 1 to avoid the number breaking into scientific notation. Then manipulate the number as a string (thereby making perl convert it into a string).
$n++;
$n =~ s/^(\d+)(?=\.)/$1 - 1/e;
print $n;
A "proper" way:
For a more "proper" solution, counting the number of decimal places to use with %f would be optimal. It turned out getting the correct number of decimal points is trickier than one would think. Here's an attempt:
use strict;
use warnings;
use v5.10;
my $n = 0.000000234;
say "Original: $n";
my $l = getlen($n);
printf "New : %.${l}f\n", $n;
sub getlen {
my $num = shift;
my $dec = 0;
return 0 unless $num; # no 0 values allowed
return 0 if $num >= 1; # values above 1 don't need this computation
while ($num < 1) {
$num *= 10;
$dec++;
}
$num =~ s/\d+\.?//; # must have \.? to accommodate e.g. 0.01
$dec += length $num;
return $dec;
}
Output:
Original: 2.34e-007
New : 0.000000234
The value can have trailing zeroes only if it is a string.
You can add 0 to it. It will be coverted to a numerical value, not showing any trailing zeroes.

How to change exponential number format to the floating-point format in Perl

I have a string in Perl that contains a small number:
$num = "0.00000001";
When I make a numeric operation on it, it becomes number in exponential format:
$num = $num * 100;
print "$num\n";
The result is: 1e-06
The question is,
how to get this number be printed in floating-point format, i.e. 0.000001.
I know I can do it for specific number with sprintf("%.6f", $num),
but I'd like to have a generic solution,
so I won't have to determine each time how many digits to show after the decimal point
(like 6 in the above sprintf example)
When you apply a numeric operation to $num, it becomes a floating-point number. 1e-06 and 0.000001 are textual representations of that number; the stored value doesn't distinguish between them.
If you simply print or stringify the number, it uses a default format which, as you've seen, results in "1e-06". Using sprintf with a format of "%f" will give you a reasonable result; sprintf("%f", $num) yields "0.000001".
But the "%f" format can lose information. For example:
$num = "0.00000001";
printf("%f\n", $num);
prints:
0.000000
You say you want to print without having to determine each time how many digits to show after the decimal point. Something has to make that determination, and there's no universally correct way to do so. The obvious thing to do is print just the significant digits, omitting trailing zeros, but that presents some problems. How many digits do you print for 1.0/3.0, whose decimal representation has an infinite sequence of 3s? And 0.00000001 can't be represented exactly in binary floating-point:
$num = "0.00000001";
printf("%f\n", $num);
printf("%.78f\n", $num);
prints:
0.000000
0.000000010000000000000000209225608301284726753266340892878361046314239501953125
Use rodents of unusual size:
$ perl -Mbigrat -E'$num = 0.00000001; $num *= 100; say $num'
0.000001

Perl pack/unpack/shift

I've been having this problem in Perl for a few days now, and after scouring countless man pages, perldocs and googling too many search terms, hopefully someone here can help me out.
I am given two strings which represent hex values, i.e. "FFFF", not the Perl hex number 0xFFFF. Given two of these strings, I wish to convert them to binary form, perform a bitwise AND of the two, then take the output of this and examine each bit from LSB to MSB.
I have two problems right now; converting the hex string into a hex number, and shifting
the result of the bitwise AND.
For converting the hex string into a hex number, I've tried the following approaches which don't seem to work when I print them out to examine:
$a = unpack("H*", pack("N*", $a));
$a = sprintf("%H", $a);
Using a 'print' to examine each of these does not show a correct value, nor does using 'sprintf' either...
The second problem I have occurs after I perform a bitwise AND, and I want to examine each bit by shifting right by 1. To avoid the previous problem, I used actual Perl hex numbers instead of hex strings (0xffff instead of "ffff"). If I try to perform a shift right as follows:
#Convert from hex number to binary number
$a = sprintf("%B", $a);
$b = sprintf("%B", $b);
$temp = pack("B*", $a) & pack("B*", $b);
$output = unpack("B*", $temp);
At this point everything looks fine, and using a 'print' I can see that the values of the AND operation look right, but when I try to shift as follows:
$output = pack("B*", $output);
$output = $output >> 1;
$output = unpack("B*", $output);
The resulting value I get is in binary form but not correct.
What is the correct way of performing this kind of operation?
There's no such thing as a "hex number". A number is a number, a hexadecimal representation of a number is just that - a representation.
Just turn it into a number and use bitwise and.
my $num = (hex $a) & (hex $b);
print ($num & 1, "\n") while ($num >>= 1)