Perl inconsistently prints strings that contain specific combinations of '%' - perl

Can anyone please explain this perl behavior that i came across?
printf("%what_the\n");
printf("\%what_the\n");
Prints:
%what_the
%what_the
WHILE...
printf("%tomorrow\n");
printf("\%tomorrow\n");
Prints:
0morrow
0morrow
...EVEN WITH warnings and strict:
use strict;
use warnings;
printf("\%tomorrow\n");
Prints:
Missing argument in printf at - line 3.
0morrow

printf is different from regular print. You might be thinking it is the same, it is not. printf takes a pattern, which includes %. For example:
printf "%s\n", "tomorrow"; # prints "tomorrow\n"
%s is a placeholder for a string, which should be the second argument to printf.
The warning you get shows you the problem
Missing argument in printf at - line 3.
printf expects a second argument, because you have supplied a placeholder.
Not all letters following a percent sign is a valid combination, here's a few from the documentation from sprintf
%% 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
.... more
I do not see %to in there, but it seems to be what is being triggered. It prints a 0 because it casts the empty string (missing argument) to 0.
Documentation here.

The way to escape a % sign is to double it, not by a backslash. %o is the format for printing an octal number. Try doing printf "%tomorrow", 255;. The t is a modifier flag on %o to set the integer type.
https://perldoc.perl.org/functions/sprintf#size
HTH

Related

Need explanation for `~0` vs. `2**64` with and without `use integer`

I wrote some test program printing the values of ~0 and 2**64:
#!/usr/bin/perl
use warnings;
use strict;
#use integer;
print ~0, "\n";
print 2**64, "\n";
Without use integer the program outputs
118446744073709551615
1.184467440737096e+19
With use integer the program outputs:
-1
1.184467440737096e+19
The other odd thing is that even when using print int(2**64) the number is output in scientific format still, just as if int(...) wasn't there (Still ~0 without use integer is output in "integer format").
I can force integer output using printf("%u\n", ...), however.
(Perl being used in 5.18.2 of SLES12 SP5 on x86_64)
Questions:
So why is 2**64 a "float" with and without use integer, while ~0 never is?
And with use integer when ~0 is print as -1, it still satisfies the condition ~0 > 2**63 (when I'd expect -1 not to be greater than any positive value (like 2**63).
Update
There seems to be another odd effect seen in the Perl debugger:
2^64 is an odd integer, and 2^64-1 is -2.
DB<22> if (1) { use integer; print 2**64, "\n" }
1.84467440737096e+19
DB<23> if (1) { use integer; print 2**64 - 1, "\n" }
-2
DB<13> if (1) { use integer; printf '%x', 2**64-1, "\n" }
fffffffffffffffe
DB<14> if (1) { use integer; printf '%x', 2**64, "\n" }
ffffffffffffffff
DB<15> if (1) { no integer; printf '%x', 2**64, "\n" }
ffffffffffffffff
DB<16> if (1) { no integer; printf '%x', 2**63, "\n" }
8000000000000000
So why is 2**64 a "float" with and without use integer
Exponentiation is calculated using floating point numbers and thus produces a float. I don't know why use integer doesn't force the result to be cast to a signed integer, but it doesn't. This is consistent with its documentation, which states that the pragma only affects the operands and results of:
the arithmetic operators (+, -, *, /, %, +=, -=, *=, /=, %=, and unary minus)
the comparison operators (<, <=, >, >=, ==, !=, <=>), and
the bitwise operators (|, &, ^, <<, >>, |=, &=, ^=, <<=, >>=)
In fact, it specifically excludes **.
The power operator ** is also not affected, so that 2 ** .5 is always the square root of 2.
while ~0 never is?
The machine only has operations for performing bitwise operations on integer types, and they return integer types. There's no point in converting the number to a float (and plenty of reasons not to on a build with 64-bit ints).
And with use integer when ~0 is print as -1, it still satisfies the condition ~0 > 2**63 (when I'd expect -1 not to be greater than any positive value (like 2**63).
use integer causes many operators to cast values to IV, and < is such an operator. Casting 2**63 produces -9223372036854775808 on my machine.
$ perl -M5.010 -Minteger -e'say 0 + 2**63'
-9223372036854775808
Manual page perlop(1) explains in section "Integer Arithmetic" that use integer will cause a signed interpretation of integer results, so that will explain 118446744073709551615 vs. -1.
The other thing is that with 64-bit integers 2**64 actually is a 65-bit number than cannot be presented as integer.
So that's interpreted as floating-point number.
And maybe, most important:
~0 is not 2**64, but 2**64 - 1.
One only effect I cannot explain is why int floor(2**64 - 1) isn't output as integer number like ~0 or 0xffffffffffffffff are.

How to preserve number of decimal places in perl after multiplication

I have one floating point number in Perl and after multiplying with negative integer, all trailing zeroes are getting removed automatically. However I still need those extra zeroes.
Example:
my $float = 1.40000;
my $multiply = -1 * $float;
print "Negative number: $multiply"; //-1.4
Is there any way to get -1.40000?
You need to format your output using printf.
printf( "Negative number: %.4f\n", $multiply );
Learn more about printf with perldoc -f printf or here.

Perl - convert hexadecimal to binary and use it as string

I am new to Perl and I have difficulties using the different types.
I am trying to get an hexadecimal register, transform it to binary, use it a string and get substrings from the binary string.
I have done a few searches and what I tried is :
my $hex = 0xFA1F;
print "$hex\n";
result was "64031" . First surprise : can't I print the hex value in Perl and not just the decimal value ?
$hex = hex($hex);
print "$hex\n";
Result was 409649. Second surprise : I would expect the result to be also 64031 since "hex" converts hexadecimal to decimal.
my $bin = printf("%b", $hex);
It prints the binary value. Is there a way to transform the hex to bin without printing it ?
Thanks,
SLP
Decimal, binary, and hexadecimal are all text representations of a number (i.e. ways of writing a number). Computers can't deal with these as numbers.
my $num = 0xFA1F; stores the specified number (sixty-four thousand and thirty-one) into $num. It's stored in a format the hardware understands, but that's not very important. What's important is that it's stored as a number, not text.
When print is asked to print a number, it prints it out in decimal (or scientific notation if large/small enough). It has no idea how the number of created (from a hex constant? from addition? etc), so it can't determine how to output the number based on that.
To print an number as hex, you can use
my $hex = 'FA1F'; # $hex contains the hex representation of the number.
print $hex; # Prints the hex representation of the number.
or
my $num = 0xFA1F; # $num contains the number.
printf "%X", $num; # Prints the hex representation of the number.
You are assigning a integer value using hexadecimal format. print by default prints numbers in decimal format, so you are getting 64031.
You can verify this using the printf() by giving different formats.
$ perl -e ' my $num = 0xFA1F; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$ perl -e ' my $num = 64031; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$ perl -e ' my $num = 0b1111101000011111; printf("%d %X %b\n", ($num) x 3 ) '
64031 FA1F 1111101000011111
$
To get the binary format of 0xFA1F in string, you can use sprintf()
$ perl -e ' my $hex = 0xFA1F; my $bin=sprintf("%b",$hex) ; print "$bin\n" '
1111101000011111
$
lets take each bit of confusion in order
my $hex = 0xFA1F;
This stores a hex constant in $hex, but Perl doesn't have a hex data type so although you can write hex constants, and binary and octal constants for that matter, Perl converts them all to decimal. Note that there is a big difference between
my $hex = 0xFA1F;
and
my $hex = '0xFA1F';
The first stores a number into $hex, which when you print it out you get a decimal number, the second stores a string which when printed out will give 0xFAF1 but can be passed to the hex() function to be converted to decimal.
$hex = hex($hex);
The hex function converts a string as if it was a hex number and returns the decimal value and, as up to this point, $hex has only ever been used as a number Perl will first stringify $hex then pass the string to the hex() function to convert that value from hex to decimal.
So to the solution. You are almost there with printf(),there is a function called sprintf() which takes the same parameters as printf() but instead of printing the formatted value returns it as a string. So what you need is.
my $hex = 0xFA1F;
my $bin = sprintf("%b", $hex);
print $bin;
Technical note:
Yes I know that Perl stores all its numbers internally as binary, but lets not go there for this answer, OK?
If you're ok with using a distribution, I wrote Bit::Manip to make my prototyping a bit easier when dealing with registers (There's also a Pure Perl version available if you have problems compiling the XS code).
Not only can it fetch out bits from a number, it can toggle, clear, set etc:
use warnings;
use strict;
use Bit::Manip qw(:all);
my $register = 0xFA1F;
# fetch the bits from register using msb, lsb
my $msbyte = bit_get($register, 15, 8);
print "value: $msbyte\n";
print "bin: " . bit_bin($msbyte) . "\n";
# or simply:
# printf "bin: %b\n", $msbyte;
Output:
value: 250
bin: 11111010
Here's a blog post I wrote that shows how to use some of the software's functionality with an example datasheet register.

Is %s the only format that will allow printf to display big integers correctly?

I just spent an epoch figuring out that my big integer was fine and that printf's %d/%u were not up to the task of displaying it:
use strict;
use warnings;
use bigint;
use List::Gen;
*factorial = do {use bigint; <[..*] 1, 1..>->code};
my $value = factorial(32);
printf "%d\n", $value; # -1
printf "%u\n", $value; # 18446744073709551615
printf "%s\n", $value; # 263130836933693530167218012160000000
I wouldn't be surprised if the answer is no, just wanted to confirm it.
This was surprisingly hard to track down. I didn't see anything obvious in the docs, so I went to the source. The printf function is implemented (at the bottom of a long call stack) by the C function Perl_sv_vcatpvfn_flags. It appears as if this function assumes the number will fit in an IV or a UV. They are defined by
typedef IVTYPE IV;
typedef UVTYPE UV;
Which is in turn defined by (at least on my Perl, I think this is configurable)
#define IVTYPE long /**/
#define UVTYPE unsigned long /**/
So, if your number won't fit in a long, then (%d) or in an unsigned long (%u), then a string (%s) is your only option. A bigger quesiton is why you are using printf in the first place. Are you formatting these numbers in some way other than printing them? If not, then print should just do the right thing.
Yes. %s is the only format for strings.
%%: Obviously no.
%c: Obviously no.
%s: Yes.
%d: Only if the number fits in a signed integer.
%u, %o, %x, %X, %b, %B: Only if the number fits in a unsigned integer.
%e, %E, %f, %g, %G, %a, %A: Only if the number fits in a floating point number.
%p: Obviously no.
%n: Obviously no.
Given that you have to use a specific library for the purpose of manipulating such numbers, there is no reason to expect that the builtin printf would handle them. Instead, you can use the library's conversion functions such as as_hex or bnstr. If you just want to print the number, use print "$value\n";.

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.