I am new to Perl. An exercise, where I am to create a numeric ruler from which, I size columns for data at 20 characters-width, is proving a little difficult to complete. So far, I have,
printf “%10d” x 5, (1..6);
#ruler = (1..10) x 7;
Print #ruler, “\n”;
It should look something like,
1 2 3 4
1234567890123456789012345678901234567890
What I get for the top row of numbers is an error, ‘Redundant argument in printf at <script.pl> line #; the bottom row produces numbers from 1 to 10, as it ought with the range operator, but I would like it to produce 1 to 9 with a zero on the end. I did think to start the range from 0, but I haven’t figured out how to remove the first index and only the first index.
I would be grateful for your guidance with both issues.
The warning is due to the fact that you pass 6 numbers to printf, but the format only requires 5.
To me,
1 2 3 4
1234567890123456789012345678901234567890
reads as
11, 12, 13, ..., 19, 10, 21, 22, 23, ...
Why does it start with 11? Why is 10 between 19 and 21?
The following makes more sense:
1 2 3 4
01234567890123456789012345678901234567890 0-based
and
1 2 3 4
1234567890123456789012345678901234567890 1-based
I'm not going to give the solution outright.
If you want the numbers 1 to 9 and 0, that would be 1..9, 0.
%10d will add padding on the left. %-10d will add padding on the right.
Nothing says you can't prefix the output with something that doesn't repeat, like a zero or a space.
Provided desired output starts count from 11 instead 1 -- it doesn't look right.
Perhaps OP intended to start count from 1 until some $max value with placing a digit representing tens above main counter.
Please study following code sample for compliance with your requirements.
use strict;
use warnings;
use feature 'say';
my $max = shift || 45;
rule($max);
sub rule {
my $max = shift;
my($a,$b);
$a .= ' ' x 9 . $_ for 1..$max/10;
$b .= $_ % 10 for 1..$max;
say $a . "\n" . $b;
}
Output
1 2 3 4
123456789012345678901234567890123456789012345
Original OP's code requires slight modification to achieve desired output
use strict;
use warnings;
use feature 'say';
my $max = shift || 45;
printf "%10d" x int($max/10) . "\n", (1..$max/10);
print $_ % 10 for 1..$max;
print "\n";
I need to separate the MSB and the rest of the bits from a hex string. So for example: I have a hex string a2, which is equivalent to 1010 0010. I want to separate out the MSB (1 in this case), and rest of the number convert to decimal. I think I can do something like this:
$hex = 'a2';
$dec = hex($hex);
$bin = sprintf("%b", $dec);
$msb = substr $bin, 0, 1;
$rest = substr $bin 1, 7;
$restDec = oct("0b" . $rest);
However, I do not like using strings for bit operations. Is there a better way of doing this?
Trivial using bitwise operators:
$msb = ($dec & 128) >> 7
$rest = ($dec & 127)
Explanation:
Decimal 128 is 0x80 or 0b1000_0000, so using the bitwise "and" operator with 128 masks (sets to zero) all but the top bit, which we then shift down to the LSB where the result ends up being 0 or 1. In actuality you could dispense with the masking operation and just shift right but explicitly masking has two advantages:
It makes the intent crystal clear, and
works even if you inadvertently apply this to a number larger than 255.
Decimal 127 is 0x7F or 0b0111_1111 and bitwise "and-ing" this with $dec sets the MSB to zero while leaving alone the rest of the bits.
Additional note: Perl has hexadecimal numeric literals (0x...) and binary literals (0b...), so the above could also be written
$msb = ($dec & 0x80) >> 7
$rest = ($dec & 0x7F)
Or even
$msb = ($dec & 0b10000000) >> 7
$rest = ($dec & 0b01111111)
For example:
I have $a= -1. If I print it using printf with %.4b or %b, it gives me 32-bit all 1's.
But, I only want to print the least significant 4 bits like 1111 in the file in binary.
Any ideas how to do it?
Thanks
-1 in binary is represented via 2s complement, so it is all 1s. (See here for more: What is “2's Complement”?)
If you want to 'limit' it, then the way you can do this is with a bitwise and.
Switching on 4 bits is
1+2+4+8 = 15.
Therefore:
use strict;
use warnings;
my $val = -1;
printf ( "%b", $val & 15 );
%.4b refers to fractional digits, %04b formats to at least 4 digits, padding leading 0s as needed.
To cater for negative integers, take the modulus by 16 ( 2^<number of least significant bits> ).
my #b = (12, 59, -1, 1 ); # sample of integers
#b = map { $_ % 16; } #b; # take modulus
printf ("4-bits: %04b" . (", %04b" x $#b) . ";\n", #b );
# output with computed number of placeholders
I have the below Perl function to display up to two decimals places. It's not working when the input value is 2.01, and it gives the output as 2 instead of 2.01. I am not sure why it's rounding.
Instead of printf I wrote the output to a file, but still it gives me output1 as 2.
my $ramount = 2.01;
$ramount = int($ramount*100)/100;
printf "output1: $ramount";
If I have values like .2, .23, .2345, 1,23, 23.1, and 9, what function can I use to pad zeros so that it displays 0.2, 0.23, 0.2345, 1, 23, 23.1, and 9?
I think this sequence will answer your question:
DB<1> $a=2.01
DB<2> p $a
2.01
DB<3> printf "%20.10f\n", $a
2.0100000000
DB<4> printf "%20.16f\n", $a
2.0099999999999998
DB<5> printf "%20.16f\n", ($a*100)
200.9999999999999716
DB<6> printf "%20.16f\n", ($a*100)/100
2.0099999999999998
DB<7> printf "%20.16f\n", int($a*100)
200.0000000000000000
DB<8> printf "%20.16f\n", int($a*100)/100
2.0000000000000000
DB<9>
Essentially (and this has been answered many times on SO), 2.01 cannot be represented EXACTLY as a floating point number. The closest possible float is, as you see above, 2.009999999999999716...
As to padding, try
printf "%04d", $number
The leading zero in the format tells printf (or sprintf) to left-pad with zero.
From perldoc perlfaq4:
Why is int() broken?
Your int() is most probably working just fine. It's the numbers that
aren't quite what you think. First, see the answer to "Why am I
getting long decimals (eg, 19.9499999999999) instead of the numbers I
should be getting (eg,
19.95)?".
For example, this
print int(0.6/0.2-2), "\n";
will in most computers print 0, not 1, because even such simple
numbers as 0.6 and 0.2 cannot be presented exactly by floating-point
numbers. What you think in the above as 'three' is really more like
2.9999999999999995559.
How can I round a decimal number (floating point) to the nearest integer?
e.g.
1.2 = 1
1.7 = 2
Output of perldoc -q round
Does Perl have a round() function? What about ceil() and floor()?
Trig functions?
Remember that int() merely truncates toward 0. For rounding to a certain number of digits, sprintf() or printf() is usually the easiest
route.
printf("%.3f", 3.1415926535); # prints 3.142
The POSIX module (part of the standard Perl distribution) implements
ceil(), floor(), and a number of other mathematical and trigonometric
functions.
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
In 5.000 to 5.003 perls, trigonometry was done in the Math::Complex
module. With 5.004, the Math::Trig module (part of the standard Perl
distribution) implements the trigonometric functions. Internally it
uses the Math::Complex module and some functions can break out from the
real axis into the complex plane, for example the inverse sine of 2.
Rounding in financial applications can have serious implications, and
the rounding method used should be specified precisely. In these
cases, it probably pays not to trust whichever system rounding is being
used by Perl, but to instead implement the rounding function you need
yourself.
To see why, notice how you'll still have an issue on half-way-point
alternation:
for ($i = 0; $i < 1.01; $i += 0.05) { printf "%.1f ",$i}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7
0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do
this. Perl numbers whose absolute values are integers under 2**31 (on
32 bit machines) will work pretty much like mathematical integers.
Other numbers are not guaranteed.
Whilst not disagreeing with the complex answers about half-way marks and so on, for the more common (and possibly trivial) use-case:
my $rounded = int($float + 0.5);
UPDATE
If it's possible for your $float to be negative, the following variation will produce the correct result:
my $rounded = int($float + $float/abs($float*2 || 1));
With this calculation -1.4 is rounded to -1, and -1.6 to -2, and zero won't explode.
You can either use a module like Math::Round:
use Math::Round;
my $rounded = round( $float );
Or you can do it the crude way:
my $rounded = sprintf "%.0f", $float;
If you decide to use printf or sprintf, note that they use the Round half to even method.
foreach my $i ( 0.5, 1.5, 2.5, 3.5 ) {
printf "$i -> %.0f\n", $i;
}
__END__
0.5 -> 0
1.5 -> 2
2.5 -> 2
3.5 -> 4
See perldoc/perlfaq:
Remember that int() merely truncates toward 0. For rounding to a
certain number of digits, sprintf() or printf() is usually the
easiest route.
printf("%.3f",3.1415926535);
# prints 3.142
The POSIX module (part of the standard Perl distribution)
implements ceil(), floor(), and a number of other mathematical
and trigonometric functions.
use POSIX;
$ceil = ceil(3.5); # 4
$floor = floor(3.5); # 3
In 5.000 to 5.003 perls, trigonometry was done in the Math::Complex module.
With 5.004, the Math::Trig module (part of the standard Perl distribution) > implements the trigonometric functions.
Internally it uses the Math::Complex module and some functions can break
out from the real axis into the complex plane, for example the inverse sine of 2.
Rounding in financial applications can have serious implications, and the rounding
method used should be specified precisely. In these cases, it probably pays not to
trust whichever system rounding is being used by Perl, but to instead implement the
rounding function you need yourself.
To see why, notice how you'll still have an issue on half-way-point alternation:
for ($i = 0; $i < 1.01; $i += 0.05)
{
printf "%.1f ",$i
}
0.0 0.1 0.1 0.2 0.2 0.2 0.3 0.3 0.4 0.4 0.5 0.5 0.6 0.7 0.7 0.8 0.8 0.9 0.9 1.0 1.0
Don't blame Perl. It's the same as in C. IEEE says we have to do
this. Perl numbers whose absolute values are integers under 2**31 (on
32 bit machines) will work pretty much like mathematical integers.
Other numbers are not guaranteed.
You don't need any external module.
$x[0] = 1.2;
$x[1] = 1.7;
foreach (#x){
print $_.' = '.( ( ($_-int($_))<0.5) ? int($_) : int($_)+1 );
print "\n";
}
I may be missing your point, but I thought this was much cleaner way to do the same job.
What this does is to walk through every positive number in the element, print the number and rounded integer in the format you mentioned. The code concatenates respective rounded positive integer only based on the decimals. int($_) basically round-down the number so ($-int($)) captures the decimals. If the decimals are (by definition) strictly less than 0.5, round-down the number. If not, round-up by adding 1.
The following will round positive or negative numbers to a given decimal position:
sub round ()
{
my ($x, $pow10) = #_;
my $a = 10 ** $pow10;
return (int($x / $a + (($x < 0) ? -0.5 : 0.5)) * $a);
}
Following is a sample of five different ways to summate values. The first is a naive way to perform the summation (and fails). The second attempts to use sprintf(), but it too fails. The third uses sprintf() successfully while the final two (4th & 5th) use floor($value + 0.5).
use strict;
use warnings;
use POSIX;
my #values = (26.67,62.51,62.51,62.51,68.82,79.39,79.39);
my $total1 = 0.00;
my $total2 = 0;
my $total3 = 0;
my $total4 = 0.00;
my $total5 = 0;
my $value1;
my $value2;
my $value3;
my $value4;
my $value5;
foreach $value1 (#values)
{
$value2 = $value1;
$value3 = $value1;
$value4 = $value1;
$value5 = $value1;
$total1 += $value1;
$total2 += sprintf('%d', $value2 * 100);
$value3 = sprintf('%1.2f', $value3);
$value3 =~ s/\.//;
$total3 += $value3;
$total4 += $value4;
$total5 += floor(($value5 * 100.0) + 0.5);
}
$total1 *= 100;
$total4 = floor(($total4 * 100.0) + 0.5);
print '$total1: '.sprintf('%011d', $total1)."\n";
print '$total2: '.sprintf('%011d', $total2)."\n";
print '$total3: '.sprintf('%011d', $total3)."\n";
print '$total4: '.sprintf('%011d', $total4)."\n";
print '$total5: '.sprintf('%011d', $total5)."\n";
exit(0);
#$total1: 00000044179
#$total2: 00000044179
#$total3: 00000044180
#$total4: 00000044180
#$total5: 00000044180
Note that floor($value + 0.5) can be replaced with int($value + 0.5) to remove the dependency on POSIX.
Negative numbers can add some quirks that people need to be aware of.
printf-style approaches give us correct numbers, but they can result in some odd displays. We have discovered that this method (in my opinion, stupidly) puts in a - sign whether or not it should or shouldn't. For example, -0.01 rounded to one decimal place returns a -0.0, rather than just 0. If you are going to do the printf style approach, and you know you want no decimal, use %d and not %f (when you need decimals, it's when the display gets wonky).
While it's correct and for math no big deal, for display it just looks weird showing something like "-0.0".
For the int method, negative numbers can change what you want as a result (though there are some arguments that can be made they are correct).
The int + 0.5 causes real issues with -negative numbers, unless you want it to work that way, but I imagine most people don't. -0.9 should probably round to -1, not 0. If you know that you want negative to be a ceiling rather than a floor then you can do it in one-liner, otherwise, you might want to use the int method with a minor modification (this obviously only works to get back whole numbers:
my $var = -9.1;
my $tmpRounded = int( abs($var) + 0.5));
my $finalRounded = $var >= 0 ? 0 + $tmpRounded : 0 - $tmpRounded;
If you are only concerned with getting an integer value out of a whole floating point number (i.e. 12347.9999 or 54321.0001), this approach (borrowed and modified from above) will do the trick:
my $rounded = floor($float + 0.1);
My solution for sprintf
if ($value =~ m/\d\..*5$/){
$format =~ /.*(\d)f$/;
if (defined $1){
my $coef = "0." . "0" x $1 . "05";
$value = $value + $coef;
}
}
$value = sprintf( "$format", $value );
loads of reading documentation on how to round numbers, many experts suggest writing your own rounding routines, as the 'canned' version provided with your language may not be precise enough, or contain errors. i imagine, however, they're talking many decimal places not just one, two, or three. with that in mind, here is my solution (although not EXACTLY as requested as my needs are to display dollars - the process is not much different, though).
sub asDollars($) {
my ($cost) = #_;
my $rv = 0;
my $negative = 0;
if ($cost =~ /^-/) {
$negative = 1;
$cost =~ s/^-//;
}
my #cost = split(/\./, $cost);
# let's get the first 3 digits of $cost[1]
my ($digit1, $digit2, $digit3) = split("", $cost[1]);
# now, is $digit3 >= 5?
# if yes, plus one to $digit2.
# is $digit2 > 9 now?
# if yes, $digit2 = 0, $digit1++
# is $digit1 > 9 now??
# if yes, $digit1 = 0, $cost[0]++
if ($digit3 >= 5) {
$digit3 = 0;
$digit2++;
if ($digit2 > 9) {
$digit2 = 0;
$digit1++;
if ($digit1 > 9) {
$digit1 = 0;
$cost[0]++;
}
}
}
$cost[1] = $digit1 . $digit2;
if ($digit1 ne "0" and $cost[1] < 10) { $cost[1] .= "0"; }
# and pretty up the left of decimal
if ($cost[0] > 999) { $cost[0] = commafied($cost[0]); }
$rv = join(".", #cost);
if ($negative) { $rv = "-" . $rv; }
return $rv;
}
sub commafied($) {
#*
# to insert commas before every 3rd number (from the right)
# positive or negative numbers
#*
my ($num) = #_; # the number to insert commas into!
my $negative = 0;
if ($num =~ /^-/) {
$negative = 1;
$num =~ s/^-//;
}
$num =~ s/^(0)*//; # strip LEADING zeros from given number!
$num =~ s/0/-/g; # convert zeros to dashes because ... computers!
if ($num) {
my #digits = reverse split("", $num);
$num = "";
for (my $i = 0; $i < #digits; $i += 3) {
$num .= $digits[$i];
if ($digits[$i+1]) { $num .= $digits[$i+1]; }
if ($digits[$i+2]) { $num .= $digits[$i+2]; }
if ($i < (#digits - 3)) { $num .= ","; }
if ($i >= #digits) { last; }
}
#$num =~ s/,$//;
$num = join("", reverse split("", $num));
$num =~ s/-/0/g;
}
if ($negative) { $num = "-" . $num; }
return $num; # a number with commas added
#usage: my $prettyNum = commafied(1234567890);
}
Using Math::BigFloat you can do something like this:
use Math::BigFloat;
print Math::BigFloat->new(1.2)->bfround(1); ## 1
print Math::BigFloat->new(1.7)->bfround(1); ## 2
This can be wrapped in a subroutine
use Math::BigFloat;
sub round {
Math::BigFloat->new(shift)->bfround(1);
}
print round(1.2); ## 1
print round(1.7); ## 2
cat table |
perl -ne '/\d+\s+(\d+)\s+(\S+)/ && print "".**int**(log($1)/log(2))."\t$2\n";'