http://www.perlmonks.org?node_id=730809

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

Hi, I'm having this problem because the value I got is a string in fractional format but I can't seem to convert it to the actual value.
my $strFraction = "1/2" + 0; print "strFraction is actually $strFraction <br>";
#strFraction will keep printing "1" instead of "0.5" even though i've tried to cheat perl into thinking it's a mathematical variable.

Replies are listed 'Best First'.
Re: how to convert fractional string to decimal numbers ?
by ikegami (Patriarch) on Dec 17, 2008 at 02:54 UTC

    Use use warnings;! (And use use strict; if you're not) "1/2" is not a valid number.

    >perl -wle"print '1/2'+0" Argument "1/2" isn't numeric in addition (+) at -e line 1. 1 >perl -wle"print '1abc'+0" Argument "1abc" isn't numeric in addition (+) at -e line 1. 1 >perl -wle"print '123/456'+0" Argument "123/456" isn't numeric in addition (+) at -e line 1. 123

    The simplest safe solution is probably to use Math::BigRat.

    >perl -MMath::BigRat -wle"print Math::BigRat->new('1/2')->numify();" 0.5

    Math::BigRat is part of Perl.

Re: how to convert fractional string to decimal numbers ?
by GrandFather (Saint) on Dec 17, 2008 at 02:58 UTC

    Well, there's the easy (but probably risky) way and the harder (but safer) way to do it. The easy way is just string eval it:

    my $strFraction = eval "1/2"; print "strFraction is actually $strFraction <br>";

    but consider:

    my $strFraction = eval qq|print "this could have been `rm *` - bye bye + files!\n"|; print "strFraction is actually $strFraction <br>";

    which prints:

    this could have been `rm *` - bye bye files! strFraction is actually 1 <br>

    The harder way is to parse the expression, or at the very least untaint it using an appropriate regex if all you accept are simple fractions"


    Perl's payment curve coincides with its learning curve.
Re: how to convert fractional string to decimal numbers ?
by tilly (Archbishop) on Dec 17, 2008 at 02:56 UTC
    If you trust your input, you can use eval.

    Otherwise you will need to parse and evaluate it yourself. Math::Fraction looks like it should be able to parse that.

    If it was for personal use I'd do an eval. If I somewhat trusted my users I'd do a simple sanity check then eval. Otherwise I'd parse it in Perl.

Re: how to convert fractional string to decimal numbers ?
by graff (Chancellor) on Dec 17, 2008 at 04:53 UTC
    No need for any risky sort of eval in this particular case -- use a regex to "untaint" the string:
    my $fractionString = "1/2"; my $fractionValue = ( $fractionString =~ m{^(\d+)/([1-9]\d*)} ) ? $1/$ +2 : "undefined"; print "$fractionString is actually $fractionValue <br>";
    You could even reduce that to an in-place substition that would leave non-valid "fraction" strings unchanged:
    $fractionString =~ s{(\d+)/([1-9]\d*)}{$1/$2}e;

    (update: In view of the (seemingly picky but admittedly valid) concerns expressed below by tilly, I should point out up front that my suggestions here are extrapolating from the particular case given in the OP, to extend to all and only positive fraction strings. If you want to handle negatives or other embellishments, further work is needed. Other solutions are possible that do not extrapolate, or that make different extrapolations, YMMV, etc, and good luck with all that. ;-)

      Apparently -1/2 is undefined.
        Absolutely! Because it doesn't match the particular case given in the OP -- whereas things like "2/3", "234/567", etc, do match that case.

        Another example that won't match that particular case is something like "3-1/2", which I've seen used fairly often to convey the notion of "3.5" rather than "2.5"; things like this can be accommodated, of course, but you really need to be clear about what the conventions are for a given input source (and take the trouble to enforce these conventions by rejecting violations).

Re: how to convert fractional string to decimal numbers ?
by adrive (Scribe) on Dec 17, 2008 at 03:19 UTC
    Thanks guys..of all the forums I've been to (all programming languages), I must say perlmonks is one of the best community with very active membership.

    I left out use strict and warnings on purpose just to directly show the problem, and I've chosen to use Math::BigRat since it's built in, and saves me the nightmare of ppl hacking into the db and inserted some funny harddrive formatting values on the field with eval :D
Re: how to convert fractional string to decimal numbers ?
by adrive (Scribe) on Dec 17, 2008 at 06:16 UTC
     my $fractionValue = ( $str =~ m{^(\d+)/([1-9]\d*)} ) ? $1/$2 : "undefined"; wow..it seems to work. But i've always got some problem understanding regex.. if you don't mind explaining it to me how it works?

    1. =~ = (doesn't this means.."not equal to"?)
    2. m{ } = (no idea)
    3. $1/$2 = (I never knew you could do things like that after a one line condition? What does it actually do?)
      my $fractionValue = ( $str =~ m{^(\d+)/([1-9]\d*)} ) # regex match will evaluate to +true or false ? # the "ternary operator" -- if above condition is tru +e, then... $1/$2 # return this value (using the 1st and 2nd matched +digit strings : # otherwise (if above condition was false), ... "undefined" # return this value ; # end of statement
      The "m{...}" is just another form of the regex match operator; note that all the following expressions mean the same thing in perl:
      /blah/ m/blah/ m=blah= m!blah! m{blah}
      When the regex delimiter is "/", the "m" is optional; but any other character can be used as the delimiter, and in that case, the initial "m" is required. The reason for using a character other than "/" as the delimiter on the regex is to avoid the "match-stick syndrome" that happens when you need to match a literal slash character -- given the following two alternatives, I'd rather use the second one:
      /http:\/\/my\/path\/to\/insanity/ m{my/path/back/to/sanity}
      As for the $1 and $2, these are the "capture" variables set by the regex to contain the strings that were matched within consecutive pairs of parentheses -- another example:
      $_ = "123.456/789-0"; if ( /(\d+)\D(\d+)\D(\d+)/ ) { printf( "Found three numbers: %d was between %d and %d\n", $2, $1, + $3 ); }
      The value assigned to a capture variable will remain available for use until the next time you do a regex match.

      As for "=~" vs. "=", that's a pretty basic perl thing; note that the last example above could have done the match like this:

      if ( $_ =~ /(\d+)\D(\d+)\D(\d+)/ ) { ...
      The "=~" binds a given variable to the regex match "m//" operator (or the substitution "s///" or character replacement "tr///" operators); this can be done implicitly for the global "default string" variable $_, but must be done explicitly for any other variable (so: $str =~ m/blah/).

      The "not equal to" operator in perl (a logical operator, for use in conditionals) is "!=".

      m{...} is the match operator, often seen as /.../. See perlop and perlre

      =~ tell m//, s/// and tr/// on which variable to operate.

      $1 and $2 are set by m//. See perlre

      EXPR ? EXPR : EXPR is the conditional operator. See perlop