my $num = log(1_000_000) / log(10);
print "num: $num\n";
print "int(num): " . int($num) . "\n";
print "sprintf(num): " . sprintf("%0.16f", $num) . "\n";
produces:
num: 6
int(num): 5
sprintf(num): 5.9999999999999991
To what precision does perl print floating-point numbers?
Using: v5.8.8 built for x86_64-linux-thread-multi
When stringifying floating point numbers, whether to print or otherwise, Perl generally uses the value of DBL_DIG or LDBL_DIG from the float.h or limits.h file where it was compiled.
This is typically the precision of the floating point type perl will use rounded down. For instance, if using a typical double type, the precision is 53 bits = 15.95 digits, and Perl will usually stringify with 15 digits of precision.
Related
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.
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.
I'm trying to write a script to find hex strings in a text file and convert them to their reverse byte order. The trouble I'm having is that some of the hex strings are 16 bit and some are 64 bits. I've used Perl's pack to pack and unpack the 16 bit hex numbers and that works fine, but the 64 bit does not.
print unpack("H*", (pack('I!', 0x20202032))). "\n"; #This works, gives 32202020
#This does not
print unpack("H*", (pack('I!', 0x4f423230313430343239303030636334))). "\n";
I've tried the second with the q and Q (where I get ffffffffffffffff). Am I approaching this all wrong?
As bit of background, I've got a multi-gigabyte pipe-delimited text file that has hex strings in reverse byte order as explained above. Also, the columns of the file are not standard; sometimes the hex strings appear in one column, and sometimes in another. I need to convert the hex strings to their reverse byte order.
Always use warnings;. If you do, you'll get the following message:
Integer overflow in hexadecimal number at scratch.pl line 8.
Hexadecimal number > 0xffffffff non-portable at scratch.pl line 8.
These can be resolved by use bigint; and by changing your second number declaration to hex('0x4f423230313430343239303030636334').
However, that number is still too large for pack 'I' to be able to handle.
Perhaps this can be done using simple string manipulation:
use strict;
use warnings;
my #nums = qw(
0x20202032
0x4f423230313430343239303030636334
);
for (#nums) {
my $rev = join '', reverse m/([[:xdigit:]]{2})/g;
print "$_ -> 0x$rev\n"
}
__END__
Outputs:
0x20202032 -> 0x32202020
0x4f423230313430343239303030636334 -> 0x3463633030303932343034313032424f
Or to handle digits of non-even length:
my $rev = $_;
$rev =~ s{0x\K([[:xdigit:]]*)}{
my $hex = $1;
$hex = "0$hex" if length($hex) % 2;
join '', reverse $hex =~ m/(..)/g;
}e;
print "$_ -> $rev\n"
To be pedantic, the hex numbers in your example are 32-bit and 128-bit long, not 16 and 64. If the longest one was only 64-bit long, you could successfully use the Q pack template as you supposed (provided hat your perl has been compiled to support 64-bit integers).
The pack/unpack solution can be used anyway (if with the addition of a reverse - you also have to remove the leading 0x from the hex strings or trim the last two characters from the results):
print unpack "H*", reverse pack "H*", $hex_string;
Example with your values:
perl -le 'print unpack "H*", reverse pack "H*", "4f423230313430343239303030636334"'
3463633030303932343034313032424f
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.
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