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

Greetings Monks,

I'm currently pondering this question.

So far i have two answers which come with drawbacks however:
1. Run the number through a regexp and check for a period. This seems a bit hackish and overblown to me though. As shown in the answers by zwon and kyle this method is completely unusable due to unexpected traps.
2. Compare $number and int($number). This is a bit nicer, but requires a bit more work, code-wise.

What are your preferred methods?
  • Comment on How to determine whether a number has a decimal component?

Replies are listed 'Best First'.
Re: How to determine whether a number has a decimal component?
by zwon (Abbot) on Jan 04, 2009 at 15:54 UTC

    Note, that period doesn't mean too much:

    my $number = '1.00'; print "contains period\n" if $number =~ /\./; print "integer\n" if $number == int($number);

      Well you could use

      print "contains fractional component\n" if $number =~ /\.(?!0*$)/;

      But that'll fail for large numbers.

      sub r { $_[0] ? 'fractional' : 'integer' } for my $n ( '1.00', 2**50, ) { print( "$n:\n", "method 1: ", r( $n =~ /\./ ), "\n", "method 2: ", r( $n =~ /\.(?!0*$)/ ), "\n", "method 3: ", r( sprintf('%f',$n) =~ /\.(?!0*$)/ ), "\n", "method 4: ", r( int($_)!=$_ ), "\n", "\n", ); }
      1.00: method 1: fractional method 2: integer method 3: integer method 4: integer 1.12589990684262e+015: method 1: fractional method 2: fractional method 3: integer method 4: integer
        Shouldn't one use bignum either ways when going over 32 bit?
      I think (but hope wiser heads will correct me if I err) that zwon's case, above rests on the use of non-interpolating quotes around \$number.

      Compare to this demo (which can be written much more compactly but was deliberately written in this tedious manner for utmost clarity):

      #!/usr/bin/perl -lw use strict; my $number0 = '1.00'; #zwon's version with NON-interpolating quotes my $number1 = 1.00; my $number2 = 1.393939393939393939393939393939393939393939393939393939 +3939393939; # exceeds length for FP precision my $number3 = 123456789.123e5; my $number4 = 123e-5; my $number5 = '123e-5' ; print "\$number0, $number0, contains a period" if $number0 =~ /\./; print "\$number0, $number0, is an Integer" if $number0 == int($number0 +); print "\$number1, $number1, contains a period" if $number1 =~ /\./; print "\$number1, $number1, is an Integer" if $number1 == int($number1 +); print "\$number2, $number2, contains a period" if $number2 =~ /\./; print "\$number2, $number2, is an Integer" if $number2 == int($number2 +); print "\$number3, $number3, contains a period" if $number3 =~ /\./; print "\$number3, $number3, is an Integer" if $number3 == int($number3 +); if ( $number4 =~ /\./ ) { # alt coding of the previous solely to + allow the else print "\$number4, $number4, contains a period"; } else { print "\tNo period in $number4"; } print "\$number4, $number4, is an Integer" if $number4 == int($number4 +); if ( $number5 =~ /\./ ) { print "\$number5, $number5, contains a period"; } else { print "\tNo period in $number5"; } print "\$number5, $number5, is an Integer" if $number5 == int($number5 +); print "\nNext, using ww's regex"; print "\n\$number0: $number0\n\$number1: $number1\n\$number2: $number2 +\n\$number3: $number3\n\$number4: $number4\n"; # CASE 0 $number0 =~ /\d+(\.)\d+/; if ($1) { print "\$number0 contains a decimal point"; } $number0 =~ s/(\d+)(\.)(\d+)/$1/; print "Integer value, if desired: $number0"; # CASE 1 $number1 =~ /\d+(\.)\d+/; if ($1) { print "\$number1 contains a decimal point"; } $number1 =~ s/(\d+)(\.)(\d+)/$1/; print "Integer value of \$number1, if desired: $number1"; # CASE 2 $number2 =~ /\d+(\.)\d+/; if ($1) { print "\$number2 contains a decimal point"; } $number2 =~ s/(\d+)(\.)(\d+)/$1/; print "Integer value of \$number2, if desired: $number2"; # CASE 3 $number3 =~ /\d+(\.)\d+/; if ($1) { print "\$number3 contains a decimal point"; } $number3 =~ s/(\d+)(\.)(\d+)/$1/; print "Integer value of \$number3, if desired: $number3"; # CASE 4 $number4 =~ /(\.)/; # Special case: Scientific notation if ($1) { print "\$number4 contains a decimal point"; } $number4 =~ s/(\d+)(\.)(\d+)/$1/; print "Integer value of \$number4, if desired: $number4"; # CASE 5 $number5 =~ /(\.)/; # Special case, as above if ($1) { print "\$number5 contains a decimal point"; } else { print "NO decimal point in \$number5, $number5"; } $number5 =~ s/(\d=)(\.)(\d+)/$1/; print "Integer value of \$number5, if desired: " . int($number5); # + using int(); otherwise prints the string literal

      Output:

      $number0, 1.00, contains a period $number0, 1.00, is an Integer # --> Note 1 $number1, 1, is an Integer $number2, 1.39393939393939, contains a period $number3, 12345678912300, is an Integer $number4, 0.00123, contains a period No period in 123e-5 Next, using ww's regex $number0: 1.00 $number1: 1 $number2: 1.39393939393939 $number3: 12345678912300 $number4: 0.00123 $number0 contains a decimal point Integer value, if desired: 1 $number1 contains a decimal point Integer value of $number1, if desired: 1 $number2 contains a decimal point Integer value of $number2, if desired: 1 $number3 contains a decimal point Integer value of $number3, if desired: 12345678912300 $number4 contains a decimal point Integer value of $number4, if desired: 0 NO decimal point in $number5, 123e-5 Integer value of $number5, if desired: 0
      --> Note 1: Arguable. Integer is defined "n : any of the natural numbers (positive or negative) or zero syn: whole number" and "whole number" is defined "(Math.), a number which is not a fraction or mixed number; an integer."

      Update: revised phrasing of the parenthetical comment about the tedious (kindergarten) code.

      Thanks to you and also to kyle for demonstrating other reasons to avoid the first method.
Re: How to determine whether a number has a decimal component?
by kyle (Abbot) on Jan 04, 2009 at 15:55 UTC

    I would compare to int because it says what I mean, and it will work in some strange cases where the regexp won't.

    use Test::More 'tests' => 2; my $x = 1 - 2**-52; ok( $x != int $x, 'int method works' ); # this test fails ok( $x =~ /\./, 'RegExp method works' );

    Basically, don't treat your numbers as strings if you don't have to.

Re: How to determine whether a number has a decimal component?
by Perlbotics (Archbishop) on Jan 04, 2009 at 16:12 UTC

    What is the origin of $number?

    If it is read from a textual representation (STDIN, File), personally I would chose method 1. to scan for a dot followed by digits where at least one digit is not zero (something along $number =~ /\.0*[1-9]\d*/). Scanning for a dot allone is not sufficient.

    If the origin of $number is a computation, than you have to take care of rounding issues when you apply method 2. Comparing $number with int($number) for equalness might fail due to rounding issues. So it might be more robust to do a comparison along abs($number - int($number)) < $epsilon (see Why do floating point numeric equality/inequality tests fail mysteriously?).

    Update: Please note GrandFather's correct comment about the special case of the comparison $number == int($number). Indeed, abs($number - int($number)) itself may lead to false positives when $epsilon is ill chosen (see below).

      Comparing $number with int($number) for equalness might fail due to rounding issues.

      Not true in the narrow context of the equality test. If you have already rounded $number to a suitable precision then the equality test will always be correct. When they can be represented at all, integers are stored and manipulated precisely.


      Perl's payment curve coincides with its learning curve.
      Wow, that's the first time i've seen Perl do something truly dumb.

      Anyhow, the origin shouldn't matter. I'm looking for methods that work in all circumstances on the very simple question of "Is this an int or a float?" As such the comparison thing doesn't matter much either, since i doubt it'll store pre-decimal components in a manner that would make them come out as a decimal when retrieved from ram.

      Edit: So why do people vote this down? Can't handle it when people speak their mind about Perl? Did i say something really stupid without realizing? I'm not trolling here, so at least let me know if and when I'm wrong.

        The problem with floating point (and binary floating point and decimal fractions) is not strictly Perl's fault.

        Any calculation in floating point can end up accumulating rounding errors. This will happen in any language.

        Conversion of a decimal fraction to binary floating point introduces a small "representation error" most of the time, because most decimal fractions turn out to be recurring binary ones. Conversion from binary to decimal, for example in print tends to round to some number of decimal digits, so "representation error" is usually hidden. This rounding on output can also mask some rounding errors. Any system that uses binary floaing point will suffer from this issue -- and most do !

        If all you want to know is whether the value you have in your hands has a non-zero fractional part, then $x - int($x) will tell you. But, depending on how the value $x was arrived at, be prepared for this to tell you there is a fraction, despite print $x showing none... for example:

        my $x = (1 - 0.99) * 100 ; print $x, ($x - int($x)) ? " has fraction" : " no fraction", "\n" ;
        ...answering your later question, you may get false positives -- things which don't look as if they have fraction bits may be reported as having some.

        Incidentally int($x) in Perl does not force its result into an integer form. If $x is a very large floating value, so large that there cannot be any fraction bits, it will simply return the original value of $x. If $x is a large floating value, with one or more fraction bits, but an integer part too big for integer form, it will discard the fraction bits but still return a floating point value. You are in no danger of things going wrong (as they would do if int tried to return, say, a 32-bit integer).

        If by "something truly dumb" you mean "equalness might fail due to rounding issues"

        that ain't Perl, it's a function of performing "real" arithmetic using finite precision and is unavoidable (most numbers can't be represented using finite precision).

        Why the down votes? I don't know, but it may be people are assuming you didn't read Why do floating point numeric equality/inequality tests fail mysteriously? and the material linked from that node and said something "truly dumb" in consequence. ;)


        Perl's payment curve coincides with its learning curve.
        A reply falls below the community's threshold of quality. You may see it by logging in.
Re: How to determine whether a number has a decimal component?
by swampyankee (Parson) on Jan 04, 2009 at 17:38 UTC

    Because of limitations on integer vs floating point representations in computing, comparing $number vs int($number) may cause problems with integer overflow (use bigint may alleviate this). For input data, checking for a decimal point should work; for the results of computation, well, remember that on most computers (1.0/3.0) * 3.0 won't return exactly 1, but something like 0.99999…, so, as mentioned by others, you'll have to check for $number - int($number) ≤ &epsilon. You'll also have to realize that it won't always work.


    Information about American English usage here and here. Floating point issues? Please read this before posting. — emc

      Does this make it possible to get false negatives, i.e. $x is 0.999 and comparing it against int $x gives the result that they ARE equal? If not, this is of no interest to me at all, since false positives would not have any adverse impact if they only result into action that ensures that $x does not contain a decimal component.

        Well, that particular case won't unless the tolerance (ε) is very large, as int(0.999) is 0. ;-) More generally, it is, of course, possible, although when it happens will be dependent on the details of the C compiler and compiler options used to build a particular installation of Perl. Alas, this means that it's possible that different Perl installations on a given platform, e.g., ActiveState and Strawberry Perl on Windows, could yield different results.

        Given your last statement, "false positives would not have any adverse impact if they only result into action that ensures that $x does not contain a decimal component." you may want to tell us why you're interested in this test, at all. It may be superfluous.


        Information about American English usage here and here. Floating point issues? Please read this before posting. — emc

Re: How to determine whether a number has a decimal component?
by CountZero (Bishop) on Jan 04, 2009 at 21:10 UTC
    Why not using Regexp::Common::number?

    CountZero

    A program should be light and agile, its subroutines connected like a string of pearls. The spirit and intent of the program should be retained throughout. There should be neither too little or too much, neither needless loops nor useless variables, neither lack of structure nor overwhelming rigidity." - The Tao of Programming, 4.1 - Geoffrey James

      Thanks for the suggestion, but as mentioned earlier, doing this with RegExp is hackish and a bit too much of "shooting with cannons at flies".