... if we can rely on the third digit after a floating point.

It's a sad, and often forgotten fact: binary floating point is a remarkably poor choice of arithmetic for anything requiring accuracy to a given number of decimals.

The expression ($x * 100) - int($x * 100) suffers at most one rounding error (the same one, twice, unless the optimiser steps in). Addition and subtraction are the most troublesome of the simple operations, but in this case the subtraction is exact. The int is by definition exact.

So from a floating point arithmetic perspective, this is pretty good, really.

The problem, of course, is that few decimal fractions have an exact binary fraction representation, most are, in fact, recurring binary fractions. Consider:

$x = 0.54 ; print "$x: ", ($x * 100) - int($x * 100), "\n" ; # 0.54 +: 0 $x = 0.56 ; print "$x: ", ($x * 100) - int($x * 100), "\n" ; # 0.56 +: 7.105427357601e-15 $x = 0.58 ; print "$x: ", ($x * 100) - int($x * 100), "\n" ; # 0.58 +: 0.999999999999993
none of 0.54, 0.56 and 0.58 has an exact representation in binary floating form, but the rounding in the $x * 100 happens to reverse that error in one of these three cases.

This also shows that "stringification" is rounding to some number of significant decimal figures, which is hiding the error in the binary representation. So, the method offered by ccn, which implicitly "stringifies" the argument (if it is in number form) is, from a floating point perspective, less accurate -- but for what you want, more useful !

So, from an arithmetic perspective, before looking to see if you have any "sub penny" values, you need to decide what number of decimals you expect your values to be accurate to. If that's $D you could use:

int(abs($_[0]) * 10**$D + 0.5) % 10**($D-2)
to give you the value of any part below the 1/100-ths, in units of 1/10**$D -- noting that after the int this is all exact, integer arithmetic. If you don't happen to have a 64-bit processor about your person, then:
use POSIX qw(fmod) ; fmod(int(abs($_[0]) * 10**$D + 0.5), 10**($D-2))
will do the trick. Be aware, however, that the usual floating point (IEEE-754 Double) offers something over 15 decimal digits precision. So, if you set your $D to, say, 6 then you'll be in (or very close to) trouble if your values are 10^9 or more, and you might want to consider 10^8 to be your effective limit (depending on how much arithmetic you've done to reach the value you are testing).

You could run into range issues with the above because it needs an exact value for $x * 10**$D. So rather than round to a fixed number of decimals, it can be better to round to a number of significant decimal digits. However, that's not an easy thing to do. You could use different multipliers as $x gets bigger -- but it's getting messy. But in any case, if you want arithmetic good to, say 4 decimal places, you're limited to 10-11 digits before the decimal point.

Now, as far as I can see, stringification is rounding to 15 significant decimal digits -- I imagine that's documented somewhere. This will mask any representation error and any errors introduced in the conversion from decimal to binary and back again. It will also mask "some" rounding errors, but it is hard to be precise about how many.

You can, of course use sprintf to do rounding stuff for you:

$x = 0+sprintf('%0.6f', $x) ; # 6 decimal places $x = 0+sprintf('%1.5e', $x) ; # 6 significant digits
but if the value is big enough, or small enough, 'f' format will start throwing exponent forms at you, so stay awake !

I haven't tested this exhaustively, but you could use: 0+sprintf('%1.13e', $x) to round to 14 significant decimal digits, which will allow for "quite a lot" of rounding errors.

So, you might be happy with the implicit rounding provided by stringification (and assume it will always be 15 decimals), or you might want to take charge of the problem and do the necessary arithmetic yourself. If you ever come across floating point that is not IEEE-754, you'll need to worry about its precision and range.


In reply to Re^4: modulus and floating point numbers by gone2015
in thread modulus and floating point numbers by dextius

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.