Your skill will accomplishwhat the force of many cannot PerlMonks

### log() and int() problem

 on Dec 25, 2012 at 16:47 UTC Need Help??
Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to write a program to check if a number is a power of 5, but I came across a weird problem. Bellow is a code which illustrates my issue:
```my \$x = 125;
my \$l = log(\$x) / log(5);

print "LOG      == \$l\n";
print "int(LOG) == ", int(\$l), "\n";

print "Are equal: ", (\$l == int(\$l)) ? "true\n" : "false\n";
It prints:
```LOG      == 3
int(LOG) == 3
Are equal: false
For \$x = 25 it works as expected, but for  \$x = 125 (and probably other numbers), it acts really weird. It says that 3 != int(3). I'm wondering how is this possible... Is there some explanation for this?

Thank you

Replies are listed 'Best First'.
Re: log() and int() problem
by moritz (Cardinal) on Dec 25, 2012 at 17:02 UTC

log returns a floating-point number, so the results are prone to floating-point inaccuracies:

```\$ perl -wE 'printf "%.20f\n", log(125)/log(5)'
3.00000000000000044409

A safer test is something like this here, which only uses integers:

```use strict;
use warnings;
use 5.010;

sub is_power_of {
my (\$num, \$base) = @_;

while (\$num > 1) {
return 0 if \$num % \$base != 0;
\$num /= \$base;
}
return 1;
}

say is_power_of(125, 5);
Idly curious why a loop and "/=" are needed. Not questioning but curious. Example?
Add  print "\$num\n"; and you can see how the loop works
Re: log() and int() problem
by toolic (Bishop) on Dec 25, 2012 at 17:02 UTC
Re: log() and int() problem
by LanX (Bishop) on Dec 25, 2012 at 17:17 UTC
when using Data::Dumper you will see that \$l is seen as a string not an integer

print "LOG      == ",\$l,Dumper \\$l,"\n";

prints

LOG      == 3\$VAR1 = \'3';

As others pointed out log only returns floats up to certain precision, which IIRC also depends on the current implementation of Perl (that is compiling options). And internally floats are stored in the string-slot of a variable.

That means the slight calculation error might be too small to be shown by a print but is still sufficient to let == fail. Even if print would show the discrepancy, your approach wouldn't always work.

So better go the other way round and check if 125 == 5**\$x with \$x=int(\$l+\$tolerance)

EDIT: The approach Moritz updated into his post is even better.

Cheers Rolf

And internally floats are stored in the string-slot of a variable
No they're not. An xpvnv has separate slots for an int, a float and a string:
```[davem@pigeon bleed]\$ p -MDevel::Peek -e'\$x=1; \$x=2.2; \$x = "three"; D
+ump \$x'
SV = PVNV(0x1ae3150) at 0x1b021d0
REFCNT = 1
FLAGS = (POK,pPOK)
IV = 1
NV = 2.2
PV = 0x1afb360 "three"\0
CUR = 5
LEN = 16

Dave.

OK, thanks for correcting!

(I already suspected that I should place one more "IIRC". :)

Maybe I remembered something different or Scalar::Util::dualvar confused me or the reality in perl is even more complex. (and the latter wouldn't surprise me)

Cheers Rolf

PS: or was it JS ... ?!?

UPDATE:

For the records, here the source of my misunderstanding

from perlnumber:

Perl can internally represent numbers in 3 different ways: as native integers, as native floating point numbers, and as decimal strings. Decimal strings may have an exponential notation part, as in "12.34e-56" . Native here means "a format supported by the C compiler which was used to build perl".

Thank you very much. I solved my issue with adding and subtracting 1 from the returned value.
I think that perl should do this internally.
```my \$l = log(125) / log(5);

\$l += 1;
\$l -= 1;

print \$l == int(\$l);

Thank you all for the great answers.

I solved my issue with adding and subtracting 1 from the returned value
Sorry, but that's a terrible "solution". Your example floating point inaccuracy happens to be slightly greater than three. Since the int function truncates, consider what happens if the inaccuracy happens to be slightly less than three. Though a crude fix would be to add 0.5 (i.e. int(\$l+0.5) instead of int(\$l)), the perl documentation advises against using int for rounding and suggests sprintf and the POSIX floor and ceil functions as sounder alternatives.

Still don't understand why you don't go with moritz's solution.

> I solved my issue

you're very optimistic.

I delved into Devel::Peek and I'm still not sure which type casting or crossed thresholds produced different results for 2 and 3.

Anyway you're voyaging dangerous grounds, don't be surprised if your Float to Int conversions breaks again.

Better rely on on already shown pure integer arithmetic for your integer (sic) tests. (rule of thumb!)

Cheers Rolf

PS:

```lanx@nc10-ubuntu:~\$ perl -MDevel::Peek -e'\$|=1;\$e=2;\$x=log(5**\$e)/log(
+5); Dump \$x; print ("\n", (\$x==\$e) ? "" : "not " ,"equal\n\n");  Dump
+ \$x;'

SV = NV(0x9c86840) at 0x9c70f68
REFCNT = 1
FLAGS = (NOK,pNOK)
NV = 2

equal

SV = PVNV(0x9c50a60) at 0x9c70f68
REFCNT = 1
FLAGS = (IOK,NOK,pIOK,pNOK)
IV = 2
NV = 2
PV = 0

lanx@nc10-ubuntu:~\$ perl -MDevel::Peek -e'\$|=1;\$e=3;\$x=log(5**\$e)/log(
+5); Dump \$x; print ("\n", (\$x==\$e) ? "" : "not " ,"equal\n\n");  Dump
+ \$x;'

SV = NV(0x8fde840) at 0x8fc8f68
REFCNT = 1
FLAGS = (NOK,pNOK)
NV = 3

not equal

SV = PVNV(0x8fa8a60) at 0x8fc8f68
REFCNT = 1
FLAGS = (NOK,pIOK,pNOK)
IV = 3
NV = 3
PV = 0

UPDATE: improved code

Create A New User
Node Status?
node history
Node Type: perlquestion [id://1010275]
Approved by toolic
Front-paged by LanX
help
Chatterbox?
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2018-02-20 06:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
Voting Booth?
When it is dark outside I am happiest to see ...

Results (267 votes). Check out past polls.

Notices?