Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

log() and int() problem

by Anonymous Monk
on Dec 25, 2012 at 16:47 UTC ( #1010275=perlquestion: print w/ replies, xml ) 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

Comment on log() and int() problem
Select or Download Code
Re: log() and int() problem
by toolic (Chancellor) on Dec 25, 2012 at 17:02 UTC
Re: log() and int() problem
by moritz (Cardinal) on Dec 25, 2012 at 17:02 UTC
      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 LanX (Canon) 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".

        emphasis added.

      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

        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

        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.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1010275]
Approved by toolic
Front-paged by LanX
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (5)
As of 2014-09-20 10:27 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    How do you remember the number of days in each month?











    Results (158 votes), past polls