|
perlfaq4 - Data Manipulation ($Revision: 10394 $)
This section of the FAQ answers questions related to manipulating
numbers, dates, strings, arrays, hashes, and miscellaneous data issues.
Internally, your computer represents floating-point numbers in binary.
Digital (as in powers of two) computers cannot store all numbers
exactly. Some real numbers lose precision in the process. This is a
problem with how computers store numbers and affects all computer
languages, not just Perl.
the perlnumber manpage shows the gory details of number representations and
conversions.
To limit the number of decimal places in your numbers, you can use the
printf or sprintf function. See the "Floating Point Arithmetic" for more details.
printf "%.2f", 10/3;
my $number = sprintf "%.2f", 10/3;
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.
Perl only understands octal and hex numbers as such when they occur as
literals in your program. Octal literals in perl must start with a
leading 0 and hexadecimal literals must start with a leading 0x.
If they are read in from somewhere and assigned, no automatic
conversion takes place. You must explicitly use oct() or hex() if you
want the values converted to decimal. oct() interprets hexadecimal (0x350),
octal (0350 or even without the leading 0, like 377) and binary
(0b1010) numbers, while hex() only converts hexadecimal ones, with
or without a leading 0x, such as 0x255, 3A, ff, or deadbeef.
The inverse mapping from decimal to octal can be done with either the
<%o> or %O sprintf() formats.
This problem shows up most often when people try using chmod(),
mkdir(), umask(), or sysopen(), which by widespread tradition
typically take permissions in octal.
chmod(644, $file);
chmod(0644, $file);
Note the mistake in the first line was specifying the decimal literal
644, rather than the intended octal literal 0644. The problem can
be seen with:
printf("%#o",644);
Surely you had not intended chmod(01204, $file); - did you? If you
want to use numeric literals as arguments to chmod() et al. then please
try to express them as octal constants, that is with a leading zero and
with the following digits restricted to the set 0..7.
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);
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);
$floor = floor(3.5);
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.
As always with Perl there is more than one way to do it. Below are a
few examples of approaches to making common conversions between number
representations. This is intended to be representational rather than
exhaustive.
Some of the examples later in the perlfaq4 manpage use the Bit::Vector
module from CPAN. The reason you might choose Bit::Vector over the
perl built in functions is that it works with numbers of ANY size,
that it is optimized for speed on some operations, and for at least
some programmers the notation might be familiar.
- How do I convert hexadecimal into decimal
-
Using perl's built in conversion of 0x notation:
-
$dec = 0xDEADBEEF;
-
Using the hex function:
-
$dec = hex("DEADBEEF");
-
Using pack:
-
$dec = unpack("N", pack("H8", substr("0" x 8 . "DEADBEEF", -8)));
-
Using the CPAN module Bit::Vector:
-
use Bit::Vector;
$vec = Bit::Vector->new_Hex(32, "DEADBEEF");
$dec = $vec->to_Dec();
- How do I convert from decimal to hexadecimal
-
Using sprintf:
-
$hex = sprintf("%X", 3735928559);
$hex = sprintf("%x", 3735928559);
-
Using unpack:
-
$hex = unpack("H*", pack("N", 3735928559));
-
Using Bit::Vector:
-
use Bit::Vector;
$vec = Bit::Vector->new_Dec(32, -559038737);
$hex = $vec->to_Hex();
-
And Bit::Vector supports odd bit counts:
-
use Bit::Vector;
$vec = Bit::Vector->new_Dec(33, 3735928559);
$vec->Resize(32);
$hex = $vec->to_Hex();
- How do I convert from octal to decimal
-
Using Perl's built in conversion of numbers with leading zeros:
-
$dec = 033653337357;
-
Using the oct function:
-
$dec = oct("33653337357");
-
Using Bit::Vector:
-
use Bit::Vector;
$vec = Bit::Vector->new(32);
$vec->Chunk_List_Store(3, split(//, reverse "33653337357"));
$dec = $vec->to_Dec();
- How do I convert from decimal to octal
-
Using sprintf:
-
$oct = sprintf("%o", 3735928559);
-
Using Bit::Vector:
-
use Bit::Vector;
$vec = Bit::Vector->new_Dec(32 |