chrism01 has asked for the wisdom of the Perl Monks concerning the following question:

Monks,
Came across this case today:
# int vs float $str1 = 0.00; $str2 = 0; $str1 += $str2; print "str1 $str1\n"; Result: str1 0
Given that Perl is written in C and iirc follows the same rules, I'd expect $str2 to be promototed to float/double during the calc, then, given the tgt is float/double, I'd expect a float/double result, NOT an integer.
Any explanations?
Cheers
Chris
PS: using $str1 = $str1 + $str2; produces the same result

Replies are listed 'Best First'.
Re: Integer vs Float during addition
by Zaxo (Archbishop) on Jun 29, 2006 at 02:00 UTC

    It's even simpler than that,

    $str1 = 0.00; print $str1, "\n"; __END__ 0
    Perl doesn't enforce any native strong typing, and it freely converts between types. Once experience tells you what to expect, it's liberating to forget about casts, atof and friends.

    Zero is zero, and Perl has its own ideas about how to display it.

    After Compline,
    Zaxo

Re: Integer vs Float during addition
by blokhead (Monsignor) on Jun 29, 2006 at 02:08 UTC
    You'd get the same output without the addition:
    my $float = 0.00; print "$float\n"; # prints "0"
    Note that what you're doing when printing is coercing a number to a string. Perl's principle is to do this in the most basic way it can. If the number is technically a float internally, but its decimal expansion is .0000..., its string conversion will just look like an int. If you want it to look different, consider sprintf.

    blokhead

Re: Integer vs Float during addition
by chrism01 (Friar) on Jun 29, 2006 at 04:27 UTC
    Actually, that was a cleaned-up example, the actual one was using
    if( $cost) { .... }
    but I was getting different results (ie if() was true/false) depending on whether the $cost of 0.00 was extracted via DBI::MYSQL and then left untouched, or extracted via DBI::MYSQL and then added to an integer zero.
    Took me a while to figure out why the optional code path was /was not being followed.
    ie not just an academic qn and nothing to do with printing as such.
    Cheers
    Chris

      Ok, you took it the opposite direction. Boolean context doesn't force anything. Print forces to string. 0+ forces to number.

      The string '0.00' is true, but numeric zero is false.

      After Compline,
      Zaxo

        More specifically, string '0.00' is true but string '0' is false. This tends to surprise C-to-Perl newcomers. So if someone got a string and they weren't sure if it was natural or sprintf-formatted, it could lead to bugs in logic.
        use Data::Dumper; for (0, '', '0', '00', '0.0', '0.00', undef) { print (($_? 'TRUE' : 'FALSE'), ': ', Dumper($_)); }

        --
        [ e d @ h a l l e y . c c ]

      One way to do that is:
      if ( NumericTrue( $cost ) ) { ... } sub NumericTrue{ return shift()*1.0; }
      (Updated to allow e.g. 99 cents to be a cost.)

      -M

      Free your mind

        Or return 0+shift();
Re: Integer vs Float during addition
by Ieronim (Friar) on Jun 29, 2006 at 12:07 UTC
    According to Perl Cookbook, Perl interpolates the floating-point numbers in stings using the "%.15g" format; and
    $str1 = 0.00; print "str1 $str1\n";
    is equal to
    printf "str1 %.15g\n", 0.00;
    and prints the same
    str1 0
    . Updated to look cleaner.
Re: Integer vs Float during addition
by Moron (Curate) on Jun 29, 2006 at 11:44 UTC
    Expect instead that Perl will behave according to Perl documentation not C documentation. The requirement you describe (conformance to a fixed number of decimal places, rather than significant digits) conforms to the notion of fixed point arithmetic - floating point means that only significant digits are shown stored, not any zeroes after the point. (COBOL, not C, is the legacy language that most readily springs to mind for supporting fixed point arithmetic.)

    Perl is quite capable of handling such storage formats however. If you have such specific storage requirements for a Perl program, it is usually best to take charge of how data is stored and converted using the pack and unpack functions. But if it is just a matter of formatting, the printf and sprintf functions can do that for you.

    -M

    Free your mind

Re: Integer vs Float during addition
by chrism01 (Friar) on Jun 30, 2006 at 01:07 UTC
    To Zaxo & Halley
    Actually, I already knew all that 0 vs 0.0, true vs false stuff. :-)
    My query was that given $cost starts as a float and then sometimes has an integer added to it, why is the result integer, not float? I'm sure that in the background int is promoted to float for the actual calc, and given the tgt is already a float, I'd expect a float result?
    Surely that's a reduction of the variable?
    As implied above, this code I inherited(!) doesn't print the value, it's just used for a boolean test.
    Cheers
    Chris
    I'm just trying to understand the internals a bit more.
      I'm just trying to understand the internals a bit more

      It's often enlightening (and fun) to use Devel::Peek to get a glimpse of what the scalar actually looks like:
      use warnings; use Devel::Peek; $int = 17; #print "\$int: $int\n"; Dump($int); print "##############\n\n"; $double_1 = 1.23456789; #print "\$double_1: $double_1\n"; Dump($double_1); print "##############\n\n"; $double_2 = 1.0000; #print "\$double_2: $double_2\n"; Dump($double_2); print "##############\n\n"; $ul = 2 ** 31 + 16; # too big for an IV #print "\$ul: $ul\n"; Dump($ul); print "##############\n\n"; $double_3 = 2 ** 46 + 19; # too big for a UV or IV #print "\$double_3: $double_3\n"; Dump($double_3); print "##############\n\n"; $int += 0.0123; # $int no longer an IV #print "\$int: $int\n"; Dump($int); print "##############\n\n";
      Which outputs:
      D:\pscrpt>perl try.pl SV = IV(0x89c7cc) at 0x3f5d3c REFCNT = 1 FLAGS = (IOK,pIOK) IV = 17 ############## SV = NV(0x8afcb4) at 0x3f5d24 REFCNT = 1 FLAGS = (NOK,pNOK) NV = 1.23456789 ############## SV = NV(0x8afcbc) at 0x8a3204 REFCNT = 1 FLAGS = (NOK,pNOK) NV = 1 ############## SV = IV(0x89c7c8) at 0x8ba4d8 REFCNT = 1 FLAGS = (IOK,pIOK,IsUV) UV = 2147483664 ############## SV = NV(0x8afcc4) at 0x8a2fdc REFCNT = 1 FLAGS = (NOK,pNOK) NV = 70368744177683 ############## SV = PVNV(0x3f8714) at 0x3f5d3c REFCNT = 1 FLAGS = (NOK,pNOK) IV = 17 NV = 17.0123 PV = 0 ##############
      Note that $double_2 (assigned a value of 1.000) is an NV (perl's idea of a double) as you expected, but the actual value in the NV slot has been truncated to simply '1' ... when you 'print $double_2;', it's the value in the NV slot that gets printed.

      $ul is assigned a value that's too big to be a signed int, so it gets assigned to the UV slot, and the IsUV flag gets set. And $double_3 is assigned an integer value that can only fit into a double ... so, naturally, it has to be an NV.

      Note also that $int, which starts out as an IV, becomes an NV as soon as you add a float to it - and that the original IV value remains in the IV slot. It's the fact that the NOK and pNOK flags are set that determines that the NV value (rather than the value in the IV slot) will be displayed if you then 'print $int;'.

      Furthermore, if you enable those print statements that are currently commented out, you'll see that the various Dump() outputs change.

      Devel::Peek doesn't really explain anything, but it does let you see how the scalars are changing - and from that you can often come to a "good enough" understanding of what's happening. If you want a thorough understanding then you probably need to examine the perl source code.

      (And, of course, I always find that Devel::Peek throws up something unexpected - eg I don't know why doing '$int += 0.0123;' sets the PV slot to 0.)

      Cheers,
      Rob