How to convert number to one type in perl [duplicate] - perl

How do I fix this code so that 1.1 + 2.2 == 3.3? What is actually happening here that's causing this behavior? I'm vaguely familiar with rounding problems and floating point math, but I thought that applied to division and multiplication only and would be visible in the output.
[me#unixbox1:~/perltests]> cat testmathsimple.pl
#!/usr/bin/perl
use strict;
use warnings;
check_math(1, 2, 3);
check_math(1.1, 2.2, 3.3);
sub check_math {
my $one = shift;
my $two = shift;
my $three = shift;
if ($one + $two == $three) {
print "$one + $two == $three\n";
} else {
print "$one + $two != $three\n";
}
}
[me#unixbox1:~/perltests]> perl testmathsimple.pl
1 + 2 == 3
1.1 + 2.2 != 3.3
Edit:
Most of the answers thus far are along the lines of "it's a floating point problem, duh" and are providing workarounds for it. I already suspect that to be the problem. How do I demonstrate it? How do I get Perl to output the long form of the variables? Storing the $one + $two computation in a temp variable and printing it doesn't demonstrate the problem.
Edit:
Using the sprintf technique demonstrated by aschepler, I'm now able to "see" the problem. Further, using bignum, as recommended by mscha and rafl, fixes the problem of the comparison not being equal. However, the sprintf output still indicates that the numbers aren't "correct". That's leaving a modicum of doubt about this solution.
Is bignum a good way to resolve this? Are there any possible side effects of bignum that we should look out for when integrating this into a larger, existing, program?

See What Every Computer Scientist Should Know About Floating-Point Arithmetic.
None of this is Perl specific: There are an uncountably infinite number of real numbers and, obviously, all of them cannot be represented using only a finite number of bits.
The specific "solution" to use depends on your specific problem. Are you trying to track monetary amounts? If so, use the arbitrary precision numbers (use more memory and more CPU, get more accurate results) provided by bignum. Are you doing numeric analysis? Then, decide on the precision you want to use, and use sprintf (as shown below) and eq to compare.
You can always use:
use strict; use warnings;
check_summation(1, $_) for [1, 2, 3], [1.1, 2.2, 3.3];
sub check_summation {
my $precision = shift;
my ($x, $y, $expected) = #{ $_[0] };
my $result = $x + $y;
for my $n ( $x, $y, $expected, $result) {
$n = sprintf('%.*f', $precision, $n);
}
if ( $expected eq $result ) {
printf "%s + %s = %s\n", $x, $y, $expected;
}
else {
printf "%s + %s != %s\n", $x, $y, $expected;
}
return;
}
Output:
1.0 + 2.0 = 3.0
1.1 + 2.2 = 3.3

"What Every Computer Scientist Should Know About Floating-Point Arithmetic"
Basically, Perl is dealing with floating-point numbers, while you are probably expecting it to use fixed-point. The simplest way to handle this situation is to modify your code so that you are using whole integers everywhere except, perhaps, in a final display routine. For example, if you're dealing with USD currency, store all dollar amounts in pennies. 123 dollars and 45 cents becomes "12345". That way there is no floating point ambiguity during add and subtract operations.
If that's not an option, consider Matt Kane's comment. Find a good epsilon value and use it whenever you need to compare values.
I'd venture to guess that most tasks don't really need floating point, however, and I'd strongly suggest carefully considering whether or not it is the right tool for your task.

A quick way to fix floating points is to use bignum. Simply add a line
use bignum;
to the top of your script.
There are performance implications, obviously, so this may not be a good solution for you.
A more localized solution is to use Math::BigFloat explicitly where you need better accuracy.

From The Floating-Point Guide:
Why don’t my numbers, like 0.1 + 0.2 add up to a nice round 0.3, and
instead I get a weird result like
0.30000000000000004?
Because internally, computers use a
format (binary floating-point) that
cannot accurately represent a number
like 0.1, 0.2 or 0.3 at all.
When the code is compiled or
interpreted, your “0.1” is already
rounded to the nearest number in that
format, which results in a small
rounding error even before the
calculation happens.
What can I do to avoid this problem?
That depends on what kind of
calculations you’re doing.
If you really need your results to add up exactly, especially when you
work with money: use a special decimal
datatype.
If you just don’t want to see all those extra decimal places: simply
format your result rounded to a fixed
number of decimal places when
displaying it.
If you have no decimal datatype available, an alternative is to work
with integers, e.g. do money
calculations entirely in cents. But
this is more work and has some
drawbacks.
Youz could also use a "fuzzy compare" to determine whether two numbers are close enough to assume they'd be the same using exact math.

To see precise values for your floating-point scalars, give a big precision to sprintf:
print sprintf("%.60f", 1.1), $/;
print sprintf("%.60f", 2.2), $/;
print sprintf("%.60f", 3.3), $/;
I get:
1.100000000000000088817841970012523233890533447265625000000000
2.200000000000000177635683940025046467781066894531250000000000
3.299999999999999822364316059974953532218933105468750000000000
Unfortunately C99's %a conversion doesn't seem to work. perlvar mentions an obsolete variable $# which changes the default format for printing a number, but it breaks if I give it a %f, and %g refuses to print "non-significant" digits.

abs($three - ($one + $two)) < $some_Very_small_number

Use sprintf to convert your variable into a formatted string, and then compare the resulting string.
# equal( $x, $y, $d );
# compare the equality of $x and $y with precision of $d digits below the decimal point.
sub equal {
my ($x, $y, $d) = #_;
return sprintf("%.${d}g", $x) eq sprintf("%.${d}g", $y);
}
This kind of problem occurs because there is no perfect fixed-point representation for your fractions (0.1, 0.2, etc). So the value 1.1 and 2.2 are actually stored as something like 1.10000000000000...1 and 2.2000000....1, respectively (I am not sure if it becomes slightly bigger or slightly smaller. In my example I assume they become slightly bigger). When you add them together, it becomes 3.300000000...3, which is larger than 3.3 which is converted to 3.300000...1.

Number::Fraction lets you work with rational numbers (fractions) instead of decimals, something like this (':constants' is imported to automatically convert strings like '11/10' into Number::Fraction objects):
use strict;
use warnings;
use Number::Fraction ':constants';
check_math(1, 2, 3);
check_math('11/10', '22/10', '33/10');
sub check_math {
my $one = shift;
my $two = shift;
my $three = shift;
if ($one + $two == $three) {
print "$one + $two == $three\n";
} else {
print "$one + $two != $three\n";
}
}
which prints:
1 + 2 == 3
11/10 + 11/5 == 33/10

Related

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

How to do formatted print of hex number with bigint/Math::BigInt?

I want to print hex numbers with a defined length, padded with zeros. The code below:
use warnings;
use bigint;
$x = 0x1_0000;
$y = 0x2_0000_12345;
print $x->as_hex(),"\n";
printf("%0#12x\n", $y);
produces the output:
0x10000
0x00ffffffff
But I would like the 2nd number to be 0x02000012345. It seems that bigint does not overload the format specifiers (if that is possible at all). Is there a good workaround?
Update: this seems to be a problem of the Perl installation. It should work as expected on a 64-bit version. This can be determined like below (from perldoc sprintf):
use Config;
if ($Config{use64bitint} eq "define" || $Config{longsize} >= 8) {
print "Nice quads!\n";
}
A colleague pointed me to a solution using the BigInt::as_hex() function:
printf("0x%016s\n", substr($x->as_hex(), 2));
will print a 16-digit hex-number with padded 0s.

Unexpected result using POSIX ceil() in Perl

I can't for the life of me figure out why the following produces the result it does.
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";
Sigil-tastic, I know — sorry.
I've solved this for my app as follows:
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil("$n");
print "$c ($n)\n";
...but I'm bewildered as to why that's necessary here.
What's happening is this: $n contains a floating point value, and thus is not exactly equal to 3, on my computer it's 3.00000000000000044409. Perl is smart enough to round that to 3 when printing it, but when you explicitly use a floating point function, it will do exactly what it advertises: ceil that to the next integral number: 4.
This is a reality of working with floating point numbers, and by no means Perl specific. You shouldn't rely on their exact value.
use strict;
use warnings;
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g; # Should be exactly 3.
# But it's not.
print "Equals 3\n" if $n == 3;
# Check it more closely.
printf "%.18f\n", $n;
# So ceil() is doing the right thing after all.
my $c = ceil($n);
print "g=$g t=$t r=$r n=$n c=$c\n";
Obligatory Goldberg reference: What Every Computer Scientist Should Know About Floating-Point Arithmetic.
Using Perl, the ability to treat a string as a number in a numerical operation turns into an advantage because you can easily use sprintf to explicitly specify the amount of precision you want:
use strict; use warnings;
use POSIX qw( ceil );
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil( sprintf '%.6f', $n );
print "$c ($n)\n";
Output:
C:\Temp> g
3 (3)
The problem comes about because, given the finite number of bits available to represent a number, there are only a large but finite number of numbers that can be represented in floating point. Given that there are uncountably many numbers on the real line, this will result in approximation and rounding errors in the presence of intermediate operations.
Some numbers (like 6.65) have an exact representation in decimal, but cannot be represented exactly in the binary floating point that computers use (just like 1/3 has no exact decimal representation). As a result, floating point numbers are frequently slightly different than what you would expect. The result of your calculation is not 3, but about 3.000000000000000444.
The traditional way of handling this is to define some small number (called epsilon), and then consider two numbers equal if they differ by less than epsilon.
Your solution of ceil("$n") works because Perl rounds a floating point number to around 14 decimal places when converting it to a string (thus converting 3.000000000000000444 back to 3). But a faster solution would be to subtract epsilon (since ceil will round up) before computing ceil:
my $epsilon = 5e-15; # Or whatever small number you feel is appropriate
my $c = ceil($n - $epsilon);
A floating point subtraction should be faster than converting to a string and back (which involves a lot of division).
Not a Perl problem, as such
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
main()
{
double n = (6.65 * 4.0 - 6.65) / 6.65;
double c = ceil(n);
printf("c is %g, n was %.18f\n", c, n);
}
c is 4, n was 3.000000000000000444
The other answers have explained why the problem is there, there's two ways to make it go away.
If you can, you can compile Perl to use higher precision types. Configuring with -Duse64bitint -Duselongdouble will make Perl use 64 bit integers and long doubles. These have high enough precision to make most floating point problems go away.
Another alternative is to use bignum which will turn on transparent arbitrary precision number support. This is slower, but precise, and can be used lexically.
{
use bignum;
use POSIX;
my $g = 6.65;
my $t = $g * 4;
my $r = $t - $g;
my $n = $r / $g;
my $c = ceil($n);
print "$c ($n)\n";
}
You can also declare individual arbitrary precision numbers using Math::BigFloat

How do I set the floating point precision in Perl?

Is there a way to set a Perl script's floating point precision (to 3 digits), without having to change it specifically for every variable?
Something similar to TCL's:
global tcl_precision
set tcl_precision 3
Use Math::BigFloat or bignum:
use Math::BigFloat;
Math::BigFloat->precision(-3);
my $x = Math::BigFloat->new(1.123566);
my $y = Math::BigFloat->new(3.333333);
Or with bignum instead do:
use bignum ( p => -3 );
my $x = 1.123566;
my $y = 3.333333;
Then in both cases:
say $x; # => 1.124
say $y; # => 3.333
say $x + $y; # => 4.457
There is no way to globally change this.
If it is just for display purposes then use sprintf("%.3f", $value);.
For mathematical purposes, use (int(($value * 1000.0) + 0.5) / 1000.0). This would work for positive numbers. You would need to change it to work with negative numbers though.
I wouldn't recommend to use sprintf("%.3f", $value).
Please look at the following example:
(6.02*1.25 = 7.525)
printf("%.2f", 6.02 * 1.25) = 7.52
printf("%.2f", 7.525) = 7.53
Treat the result as a string and use substr. Like this:
$result = substr($result,0,3);
If you want to do rounding, do it as string too. Just get the next character and decide.
Or you could use the following to truncate whatever comes after the third digit after the decimal point:
if ($val =~ m/([-]?[\d]*\.[\d]{3})/) {
$val = $1;
}

How do I tell if a variable has a numeric value in Perl?

Is there a simple way in Perl that will allow me to determine if a given variable is numeric? Something along the lines of:
if (is_number($x))
{ ... }
would be ideal. A technique that won't throw warnings when the -w switch is being used is certainly preferred.
Use Scalar::Util::looks_like_number() which uses the internal Perl C API's looks_like_number() function, which is probably the most efficient way to do this.
Note that the strings "inf" and "infinity" are treated as numbers.
Example:
#!/usr/bin/perl
use warnings;
use strict;
use Scalar::Util qw(looks_like_number);
my #exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);
foreach my $expr (#exprs) {
print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}
Gives this output:
1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number
See also:
perldoc Scalar::Util
perldoc perlapi for looks_like_number
The original question was how to tell if a variable was numeric, not if it "has a numeric value".
There are a few operators that have separate modes of operation for numeric and string operands, where "numeric" means anything that was originally a number or was ever used in a numeric context (e.g. in $x = "123"; 0+$x, before the addition, $x is a string, afterwards it is considered numeric).
One way to tell is this:
if ( length( do { no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
If the bitwise feature is enabled, that makes & only a numeric operator and adds a separate string &. operator, you must disable it:
if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
(bitwise is available in perl 5.022 and above, and enabled by default if you use 5.028; or above.)
Check out the CPAN module Regexp::Common. I think it does exactly what you need and handles all the edge cases (e.g. real numbers, scientific notation, etc). e.g.
use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }
Usually number validation is done with regular expressions. This code will determine if something is numeric as well as check for undefined variables as to not throw warnings:
sub is_integer {
defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}
sub is_float {
defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}
Here's some reading material you should look at.
A simple (and maybe simplistic) answer to the question is the content of $x numeric is the following:
if ($x eq $x+0) { .... }
It does a textual comparison of the original $x with the $x converted to a numeric value.
Not perfect, but you can use a regex:
sub isnumber
{
shift =~ /^-?\d+\.?\d*$/;
}
A slightly more robust regex can be found in Regexp::Common.
It sounds like you want to know if Perl thinks a variable is numeric. Here's a function that traps that warning:
sub is_number{
my $n = shift;
my $ret = 1;
$SIG{"__WARN__"} = sub {$ret = 0};
eval { my $x = $n + 1 };
return $ret
}
Another option is to turn off the warning locally:
{
no warnings "numeric"; # Ignore "isn't numeric" warning
... # Use a variable that might not be numeric
}
Note that non-numeric variables will be silently converted to 0, which is probably what you wanted anyway.
rexep not perfect... this is:
use Try::Tiny;
sub is_numeric {
my ($x) = #_;
my $numeric = 1;
try {
use warnings FATAL => qw/numeric/;
0 + $x;
}
catch {
$numeric = 0;
};
return $numeric;
}
Try this:
If (($x !~ /\D/) && ($x ne "")) { ... }
I found this interesting though
if ( $value + 0 eq $value) {
# A number
push #args, $value;
} else {
# A string
push #args, "'$value'";
}
Personally I think that the way to go is to rely on Perl's internal context to make the solution bullet-proof. A good regexp could match all the valid numeric values and none of the non-numeric ones (or vice versa), but as there is a way of employing the same logic the interpreter is using it should be safer to rely on that directly.
As I tend to run my scripts with -w, I had to combine the idea of comparing the result of "value plus zero" to the original value with the no warnings based approach of #ysth:
do {
no warnings "numeric";
if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
You can use Regular Expressions to determine if $foo is a number (or not).
Take a look here:
How do I determine whether a scalar is a number
There is a highly upvoted accepted answer around using a library function, but it includes the caveat that "inf" and "infinity" are accepted as numbers. I see some regex stuff for answers too, but they seem to have issues. I tried my hand at writing some regex that would work better (I'm sorry it's long)...
/^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/
That's really 5 patterns separated by "or"...
Zero: ^0$
It's a kind of special case. It's the only integer that can start with 0.
Integers: ^[+-]?[1-9][0-9]*$
That makes sure the first digit is 1 to 9 and allows 0 to 9 for any of the following digits.
Scientific Numbers: ^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$
Uses the same idea that the base number can't start with zero since in proper scientific notation you start with the highest significant bit (meaning the first number won't be zero). However, my pattern allows for multiple digits left of the decimal point. That's incorrect, but I've already spent too much time on this... you could replace the [1-9][0-9]* with just [0-9] to force a single digit before the decimal point and allow for zeroes.
Short Float Numbers: ^[+-]?[0-9]?\.[0-9]+$
This is like a zero integer. It's special in that it can start with 0 if there is only one digit left of the decimal point. It does overlap the next pattern though...
Long Float Numbers: ^[+-]?[1-9][0-9]*\.[0-9]+$
This handles most float numbers and allows more than one digit left of the decimal point while still enforcing that the higher number of digits can't start with 0.
The simple function...
sub is_number {
my $testVal = shift;
return $testVal =~ /^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/;
}
if ( defined $x && $x !~ m/\D/ ) {}
or
$x = 0 if ! $x;
if ( $x !~ m/\D/) {}
This is a slight variation on Veekay's answer but let me explain my reasoning for the change.
Performing a regex on an undefined value will cause error spew and will cause the code to exit in many if not most environments. Testing if the value is defined or setting a default case like i did in the alternative example before running the expression will, at a minimum, save your error log.