1

How can I tell perl that:

print (log(1000)/log(10)."\n");
print int(log(1000)/log(10))."\n";

should both give 3 (and not 2 which the last one surprisingly gives)?

I know why this happens (binary floats are not decimal floats). So I am not asking WHY it happens. I am asking HOW to tell perl to "do the right thing".

Ole Tange
  • 31,768
  • 5
  • 86
  • 104
  • It's entirely possible that the _actual_ value is `2.9999999999999` (i.e. within epsilon of 3, but not exactly 3) and `print` just recognizes that and converts it to `3` for the sake of prettiness. Then, `int` truncates the _actual_ value to `2`, giving you the erroneous result. I'm suggesting this because changing `1000` to `1001` gives the expected answer, and that would likely be just slightly _over_ 3. Note that I'm not sure why it wouldn't be stored as 3 exactly; to my knowledge, Perl's floating point numbers should be able to. – Nic Feb 05 '17 at 01:48
  • Oh, wait, I found a [related bug report](http://list.perl5-porters.narkive.com/2Z0keja9/perl-49872-bad-rounding-on-int-log). Looks like the intermediate values of `log(1000)` and `log(10)` are causing issues. From that link: "*the internal floating point value is actually the next representable value below 3.*" Specifically, try printing `log(1000)` and `log(10)`; you'll notice that `log(1000)` is just barely less than `log(10) * 3`. – Nic Feb 05 '17 at 01:51
  • 2
    `printf "%.51f\n", log(1000)/log(10)` prints `2.999999999999999555910790149937383830547332763671875`. `int()` doesn't round, it truncates, but `print` rounds, which is why the first line prints `3`. – Keith Thompson Feb 05 '17 at 02:04
  • 2
    Possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – John Dvorak Feb 05 '17 at 02:14
  • I know why this happens (binary floats are not decimal floats). So I am not asking WHY it happens. I am asking HOW to tell perl to "do the right thing". – Ole Tange Feb 05 '17 at 02:42
  • @OleTange: *"HOW to tell perl to 'do the right thing'"* Remove the call to `int`. – Borodin Feb 05 '17 at 12:11

1 Answers1

5

Neither log(1000) nor log(10) can be accurately represented by a floating point number, so you end up with something a little less than 3.

$ perl -e'CORE::say sprintf "%.20f", log(1000)/log(10)'
2.99999999999999955591

You want to round instead of truncating.

A solution is to abuse stringification.

say int("".(log(1000)/log(10)));
say int("".(log(1000000)/log(10)));

Or you could use sprintf (slower than stringification above).

say sprintf("%.0f", log(1000)/log(10));
say sprintf("%.0f", log(1000000)/log(10));
Ole Tange
  • 31,768
  • 5
  • 86
  • 104