Perl - Iterating a hash through a foreach loop - unexpected results

i've reworked some code from an earlier post, and it isn't working as expected

i've simplified it to try and find the problem. i spent hours trying to figure out what is wrong, eventually thinking there was a bug in perl or a problem with my computer. but, i've tried it on 3 machines with the same result.

anyway, the hash's keys are filenames; the values are a string found in that file. the value represents a slice position in a 3-D volume. each file is a slice of that volume (btw, the volume is an MRI scan).

i am trying to order the files by their correct slice position. my criteria is as follows:

  1. The slices are supposed to be 3mm apart (slice thickness).
  2. I cannot have gaps more than 3mm.
  3. I can have only one gap smaller than 3mm.

I hope that explains what i'm going for here. The problem, which you should see when you run the script, is that the hash i've given should be a perfect volume. by perfect, i mean every slice is 3mm apart, so there shouldn't be a problem. however, the script seems to think otherwise. it's driving me insane.

thanks in advance for your help

scott

###################################

#! /usr/bin/perl -w

$slice_thickness = 3;

%the_hash = (
"I.0804", "101.2",
"I.0803", "98.2",
"I.0802", "95.2",
"I.0801", "92.2",
"I.0800", "89.2",
"I.0799", "86.2",
"I.0798", "83.2",
"I.0797", "80.2",
"I.0796", "77.2",
"I.0795", "74.2",
"I.0794", "71.2",
"I.0793", "68.2",
"I.0792", "65.2",
"I.0791", "62.2",
"I.0790", "59.2",
"I.0789", "56.2",
"I.0788", "53.2",
"I.0787", "50.2",
"I.0786", "47.2",
"I.0785", "44.2",
"I.0784", "41.2",
"I.0783", "38.2",
"I.0782", "35.2",
"I.0781", "32.2",
"I.0780", "29.2",
"I.0779", "26.2",
"I.0778", "23.2",
"I.0777", "20.2",
"I.0776", "17.2",
"I.0775", "14.2",
"I.0774", "11.2",
"I.0773", "8.2",
"I.0772", "5.2",
"I.0771", "2.2",
"I.0770", "-0.8",
"I.0769", "-3.8",
"I.0768", "-6.8",
"I.0767", "-9.8",
"I.0766", "-12.8",
"I.0765", "-15.8",
"I.0764", "-18.8",
"I.0763", "-21.8",
"I.0762", "-24.8",
"I.0761", "-27.8",
"I.0760", "-30.8",
"I.0759", "-33.8",
"I.0758", "-36.8",
"I.0757", "-39.8",
"I.0756", "-42.8",
"I.0755", "-45.8",
"I.0754", "-48.8",
"I.0753", "-51.8",
"I.0752", "-54.8",
"I.0751", "-57.8",
"I.0750", "-60.8",
"I.0749", "-63.8",
"I.0748", "-66.8",
"I.0747", "-69.8",
"I.0746", "-72.8",
"I.0745", "-75.8",
"I.0744", "-78.8",
"I.0743", "-81.8",
"I.0742", "-84.8",
"I.0741", "-87.8",
);

$count = 0;
$only_once = 0; # a switch to insure that a gap smaller than $slice_thickness occurs only once

foreach $current (sort {$b<=>$a} values %the_hash) { # "<=>" sorts keys by slice position from high to low
$count ++;

if (($count == 1) || ($last-$current == $slice_thickness)) { # It's a keeper cause it's the largest!
if (!$last) {
$last = $current;
print "1 => Current: $current" . "\tLast: $last\tLast - Current: " . ($last-$current) . "\tSlice Thickness: $slice_thickness\n";
}
else {
print "1 => Current: $current" . "\tLast: $last\tLast - Current: " . ($last-$current) . "\tSlice Thickness: $slice_thickness\n";
}
$last = $current;
}
elsif (($last-$current < $slice_thickness) && (!exists $the_hash{$last-$slice_thickness}) && ($only_once == 0)) { # It's a keeper!
$only_once = 1;
print "\n2 => Current: $current" . "\tLast: $last\tLast - Current: " . ($last-$current) . "\tSlice Thickness: $slice_thickness\n";
$last = $current;
}
elsif ($last-$current > $slice_thickness) { # Detects gaps that are more than $slice_thickness
print "\nIt appears a slice is missing between $last and $current.\n";
print "3 => Current: $current" . "\tLast: $last\tLast - Current: " . ($last-$current) . "\tSlice Thickness: $slice_thickness\n";
$last = $current;
}
else { # do nothing
print "\n4 => Current: $current" . "\tLast: $last\tLast - Current: " . ($last-$current) . "\tSlice Thickness: $slice_thickness\n";
$last = $current;
}
}
print "\t~fin~\n"; # ahh, we're finished!!!

###################################

here is my output:

1 => Current: 101.2 Last: 101.2 Last - Current: 0 Slice Thickness: 3
1 => Current: 98.2 Last: 101.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 95.2 Last: 98.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 92.2 Last: 95.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 89.2 Last: 92.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 86.2 Last: 89.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 83.2 Last: 86.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 80.2 Last: 83.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 77.2 Last: 80.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 74.2 Last: 77.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 71.2 Last: 74.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 68.2 Last: 71.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 65.2 Last: 68.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 62.2 Last: 65.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 59.2 Last: 62.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 56.2 Last: 59.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 53.2 Last: 56.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 50.2 Last: 53.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 47.2 Last: 50.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 44.2 Last: 47.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 41.2 Last: 44.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 38.2 Last: 41.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 35.2 Last: 38.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 32.2 Last: 35.2 Last - Current: 3 Slice Thickness: 3

It appears a slice is missing between 32.2 and 29.2.
3 => Current: 29.2 Last: 32.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 26.2 Last: 29.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 23.2 Last: 26.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 20.2 Last: 23.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 17.2 Last: 20.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 14.2 Last: 17.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 11.2 Last: 14.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 8.2 Last: 11.2 Last - Current: 3 Slice Thickness: 3

2 => Current: 5.2 Last: 8.2 Last - Current: 3 Slice Thickness: 3
1 => Current: 2.2 Last: 5.2 Last - Current: 3 Slice Thickness: 3
1 => Current: -0.8 Last: 2.2 Last - Current: 3 Slice Thickness: 3
1 => Current: -3.8 Last: -0.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -6.8 Last: -3.8 Last - Current: 3 Slice Thickness: 3

It appears a slice is missing between -6.8 and -9.8.
3 => Current: -9.8 Last: -6.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -12.8 Last: -9.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -15.8 Last: -12.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -18.8 Last: -15.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -21.8 Last: -18.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -24.8 Last: -21.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -27.8 Last: -24.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -30.8 Last: -27.8 Last - Current: 3 Slice Thickness: 3

4 => Current: -33.8 Last: -30.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -36.8 Last: -33.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -39.8 Last: -36.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -42.8 Last: -39.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -45.8 Last: -42.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -48.8 Last: -45.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -51.8 Last: -48.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -54.8 Last: -51.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -57.8 Last: -54.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -60.8 Last: -57.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -63.8 Last: -60.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -66.8 Last: -63.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -69.8 Last: -66.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -72.8 Last: -69.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -75.8 Last: -72.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -78.8 Last: -75.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -81.8 Last: -78.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -84.8 Last: -81.8 Last - Current: 3 Slice Thickness: 3
1 => Current: -87.8 Last: -84.8 Last - Current: 3 Slice Thickness: 3
~fin~

now, this is bizarre!!! have i gone insane or is 32.2-29.2 not equal to 3.0????

code:
################################
#! /usr/local/bin/perl -w

$slice_thickness = 3.0;

if (31.2-28.2 == $slice_thickness) {
print "It worked!\n";
}
else {
print "ughhhh!\n";
}

if (32.2-29.2 == $slice_thickness) {
print "It worked!\n";
}
else {
print "ughhhh!\n";
}
################################

result:
################################
It worked!
ughhhh!
################################

Due to the way floating point numbers are represented internally (e.g. IEEE754 standard), they can never be stored in the exact manner. They are stored in the form of an exponent and a mantissa and together they spell out what the number is. However, unlike integral arithmetic, this representation is not exact and is subject to errors when you are trying to compare a floating pt number from another. In daily arithmetic both 32.2-29.2 and 3.0 should be identical, but they are derived in a different manner internally and can arrive at a slightly different internal representation, so the comparison fails due to round-up errors.

I forgot which perl FAQ number it is, but I'm pretty sure there is an FAQ entry that suggests how you should implement floating-pt number comparison. Please try to dig it out yourself.

Old-school teachers tend to recommend something like this (I prefer this for it's simple enough and practically works quite well):

sub equal {
	my ($A, $B) = @_;
	return abs($A - $B) < 1E-10;
}

and replace all your floating-point == with equal(). This is one method.

Perl Cookbook gives you another method, using sprintf() to convert both operands to compare to fixed number of decimal places before comparing them, stringwise (using eq instead of ==).

thanks. it really was frustrating. but, at least i learned something. i got it all working now.

scott