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

Dear Monks,

I have two arrays of floating point numbers. One (@nums) contains the full-length version of these numbers. The other (@short_nums) contains these same numbers shortened to 10 deciimal places.

All i am trying to do is find the full-length numbers of a selection of short numbers that are held in a third array (@found).

e.g. @nums = ('0.0989999999999', '0.6799999999999', '0.0859999999999', '0.0 +239999999999'); @short_nums = ('0.099', '0.68', '0.086', '0.024'); @found = ('0.099', '0.086'); if (defined ($found[0])) { for (my $i =0; $i<@short_nums; $i++) { if ($found[0] == $short_nums[$i]) { push @new_array, $nums[$i]; } } } # etc ....
The problem is that sometimes more than one copy of the numbr is pushed into the array (e.g if two matching numbers in @nums are very similar like 0.005000000000007 and 0.005000000000008).

How can I ensure only one copy of each matching number is pushed into @new_array?? Thanks x

Replies are listed 'Best First'.
Re: testing for numerical equality
by Zaxo (Archbishop) on Jul 14, 2003 at 11:15 UTC

    You can't really distinguish @nums which round to the same. If you need to tell them apart, don't round them. Ignoring that problem, you can construct a hash of short_nums to nums,

    my %unround; @unround{@short_nums} = @nums; my @new_array = map {$unround{$_}} grep {exists $unround{$_}} @found;
    That construction makes the last occurence of nearly equal @nums be returned.

    After Compline,
    Zaxo

Re: testing for numerical equality
by choocroot (Friar) on Jul 14, 2003 at 11:32 UTC
    The problem is that the rounding can give you the same number for two different "large" numbers, so you can't always retrieve the original number from a short one. If you don't care about losing the original number, you can use a hash:
    use strict; use warnings; my @nums = qw( 0.0989999999999 0.0981111111111 0.6799999999999 0.0859999999999 0.0239999999999 0.0239999999993 ); my @found = qw( 0.099 0.086 0.024 ); # create a hash, with the rounded number as the key, and # the original number as the value # Note that it only store a single value, so only the last # original number is stored. my %short_nums = map { sprintf("%.3f", $_) => $_ } @nums; foreach my $short (@found) { if(exists $short_nums{$short}) { print "Long num for $short = ", $short_nums{$short}, "\n"; } else { print "No matching long num for $short ...\n"; } }
    output:
    Long num for 0.099 = 0.0989999999999 Long num for 0.086 = 0.0859999999999 Long num for 0.024 = 0.0239999999993
    ... and if you do care about the lose of the original number, then there is no solution :)
Re: testing for numerical equality
by aquarium (Curate) on Jul 14, 2003 at 11:05 UTC
    either use a hash or loop through your new array to check if entry already exists. i'm sure you've already thought of that...so what else do you want? that's the behaviour of hashes and arrays. if you want to get fancy...then use a pseudo hash, so you can access it like a hash and an array. then you can check for existing entry with hash syntax and add new entry with array syntax. look up "pseudo hash" in any good perl turorial.
      Any good Perl tutorial that mentions pseudo hashes will tell you that pseudo hashes are deprecated, and that they will be gone in 5.10. Bleadperl is already pseudo hash free.

      Abigail

Re: testing for numerical equality
by sgifford (Prior) on Jul 14, 2003 at 16:37 UTC
    If I understand your problem correctly, you want to break out of the loop after you find your number. Try adding last; to the end of your if block:
    if ($found[0] == $short_nums[$i]) { push @new_array, $nums[$i]; last; }
    Another problem you might be having is that it's very hard to check for equality with floating-point numbers with any consistency, because of strange rounding errors. If you always want to use 3 digits to the right of the decimal point, multiplying the numbers by 1000 and storing them as integers might give you better consistency. For example:
    @short_nums = (99, 68, 86, 24); ... if (int($found[0]*1000) == $short_nums[$i])