in reply to Behaviour of int() unexpected

Internally, all floating points are represented as a binary fraction (effectively), and there is no binary fraction exactly equal to 8.95. For a double-precision floating-point (which is what NV often are in modern perl binaries; see perl -V:nvsize ), 8.95 is internally closer to 8.9499999999999992894572642399, per this calculator. 100 times that will be slightly less than 895.0, which means that int and floor both truncate it down, whereas printing only 6 digits after the decimal will round up to 895.

What Every Computer Scientist Should Know About Floating-Point Arithmetic

Also, if your perl is new enough, the %a or %A sprintf conversion will show the hex version of the internal floating-point binary representation used:

linux shell: perl -f -e 'printf qq(%A\n), $_ for 8.95, 8.95*100, 895' windows cmd.exe: perl -f -e "printf qq(%A\n), $_ for 8.95, 8.95*100, 895"

Mine shows:

0X1.1E66666666666P+3 0X1.BF7FFFFFFFFFFP+9 0X1.BF8P+9

update: the anonymous post slightly beat me to the punch, though showing a single-precision float value, rather than the double-precision float that is more likely for your perl to be using. In this instance, they both are slightly less than 8.95 originally, so both round to 894 after your multiplication; but in some cases, the single and double-precision values fall on different sides of the rouding boundary, so rounding a float and rounding a double give different values. Everyone who is ever going to program a floating-point application should watch Superman III. (Okay, that's showing my age; slightly more recent is Office Space.)

Replies are listed 'Best First'.
Re^2: Behaviour of int() unexpected
by ikegami (Patriarch) on Mar 11, 2025 at 00:31 UTC

    The way I like to explain it is that 895/100 is periodic in binary (and hex), just like 1/3 is periodic in decimal. (That's why you see the repeating 6s.) As such, storing it accurately as a floating point number would take infinite amount of storage. The closest floating point number is used instead. In this case, that's 0x1.1E66666666666 * 2^3 = 8.949999999999999289457264239899814128875732421875.

Re^2: Behaviour of int() unexpected
by harangzsolt33 (Deacon) on Mar 11, 2025 at 00:33 UTC
    Internally, all floating points are represented as a binary fraction (effectively)

    Interesting. To me, this makes no sense at all. Up to this point, I belived that a float point value such as 8.95 is stored as a plain integer like 895 along with an exponent which is -2. So, to turn that into the original number, you print "895" and then move the decimal point to the left by two spaces. But that's apparently not how it's done. But this would be the most logical way to encode a float number if you ask me.

    Convert 8.95 to 8.9499999999999992894572642399 and store it like that. Who came up with this "standard" and WHY?????

      Oh, and how do you imagine 1/3 being stored in your model? There's no pair of integers m and e where m * 10^e gives 1/3. Your model doesn't work for periodic numbers.

      You could approximate it. You could use m = 3333333333 and e = -10, but it's not quite exact.

      And guess what. That's exactly the problem faced here.

      The number is stored as two integers. But it's not a power of 10; it's a power of 2.

      So, in this case, you need a pair of integers m and e where m * 2^e = 895/100. There is no such pair of integers because 895/100 is periodic in binary.

      So the computer used m = 0x11E66666666666 and e = -49, but it's not quite exact.

      $ perl -Mv5.14 -e'say 8.95 == 0x11E66666666666 * 2**(-49) ? 1 : 0' 1 $ perl -Mv5.14 -e'say sprintf "%.751g", 0x11E66666666666 * 2**(-49)' 8.949999999999999289457264239899814128875732421875 $ perl -Mv5.14 -e'say sprintf "%.751g", 8.95' 8.949999999999999289457264239899814128875732421875

      If you want to be picky, an IEEE double is actually stored as follows to get a couple of extra bits:

      ( -1 )^s * ( 1 + ( m * 2^( -52 ) ) * 2^( e - 1023 ) )

        Okay, so if 1/3 gives us a problem, why didn't they introduce another variable such as r which would count the repeating digits at the end?

        So, a number like 23.676767676767 might be represented as m = 2367 e = -2 r = 2 where r tells us to repeat the last two digits to infinity. Or 1/3 would be stored as m = 3 e = -1 r = 1

        In a 64-bit variable, it would be 4 bits for r, 10 bits for e, and 48 bits for m. Then there would be one bit for mantissa's sign and another bit for exponent's sign. Total 64 bits.

        Mantissa would go from -281474976710656 to +281474976710656
        Exp would go from -1024 to +1024
        Rep would go from 0 to 15

      But this would be the most logical way to encode a float number if you ask me.

      What you describe is essentially what Math::BigFloat does.
      If you use Math::BigFloat, then you won't be disappointed by the results you get. (Performance is comparatively sluggish - though perhaps not to an extent that it becomes an issue.)

      Cheers,
      Rob
      > plain integer like 895 along with an exponent which is -2.

      That's decimal logic not binary.

      Number crunching in decimal is a waste of digital resources.°

      And 10 is not a universal constant, just the number of the long things we use to hold our hamburgers.

      See also:

      Humans have too many fingers

      for a more detailed discussion.

      °) Unless you invent affordable micro transistors with 10-ary states.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

        Number crunching in decimal is a waste of digital resources.

        Most of the time, yes. But there are certainly use cases, especially when using BCD (binary-coded decimal). When using very slow hardware, the tradeoff between using BCD and converting to/from binary can be significant, think battery powered digital watch.

        It's also quite useful when dealing with financial data and other "human math problems" (pocket calculators, etc). Yes, you still get rounding errors and stuff, but they are the rounding errors that humans expect in the decimal system we are used to (like on 10/3), not the ones binary calculation generate.

        Even the x86 architecture has (had?) very basic BCD support

        PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
        Also check out my sisters artwork and my weekly webcomics