Ian
Ian

Reputation: 13842

Perl numeric comparison of numeric strings understanding and debugging

I have 2 variables, x, y with "numeric" data. Note, both of these come from different sources (mysql data and parsed file data), so I am assuming firstly that they have ended up as strings.

## this will work fine with any data that I assign manually, so can't reproduce the issue for you here...

## $x sourced from an sql query, $y sourced from a parsed file
 
warn "test x == y, $x == $y is " . ($x == $y);
warn "test x != y, $x != $y is " . ($x != $y);
warn "test x eq y, $x eq $y is " . ($x eq $y);
warn "test x ne y, $x ne $y is " . ($x ne $y);
warn "unpack x, " . unpack 'H*', $x;
warn "unpack y, " . unpack 'H*', $y;

output

test x == y, 14 == 14 is  
test x != y, 14 != 14 is 1
test x eq y, 14 eq 14 is 1 
test x ne y, 14 ne 14 is
unpack x, 3134
unpack y, 3134

Unless I'm having a bad hair day, which is possible. The first two tests "appear" incorrect, as I would expect the strings to be converted to numbers and compared.

I can get around it by using "ne" rather than !=, but I want to try and understand, so I don't introduce bugs elsewhere as there's some issue I don't understand.

Note: If I assign 14 or "14" to $y to test beforehand, all works as I'd expect. Doing the same with $x makes no difference, so I assume something is "funny" with $y. If I assign $y = $y + 0 or similar to make sure it's a number, it makes no difference.

So as I can't reproduce the example nicely, I'm trying to understand what may be wrong, and how I can isolate what is "different" about $y or poke about at it.

Edit: If I Devel::Peek at $x (doesn't change after comparison)

SV = PVNV(0xe2de4c0) at 0x101ccad8
  REFCNT = 1
  FLAGS = (IOK,NOK,POK,pIOK,pNOK,pPOK)
  IV = 14
  NV = 14
  PV = 0x447ab580 "14"\0
  CUR = 2
  LEN = 10

If I Devel::Peek at $y

SV = PVNV(0xcf94880) at 0xf039770
  REFCNT = 1
  FLAGS = (NOK,pIOK,pNOK)
  IV = 14
  NV = 14
  PV = 0

I also note:

warn ($x - $y);
output: -1.77635683940025e-15

Which suggests choroba is maybe correct (although why does $y need stringifying for a numeric comparison, I can see that may happen when adding to a string in debug, but not in the original). I'm a little surprised by the whole thing though, in both the fact it doesn't display the float, or any easy way to figure if that was the case.

Edit2: Interestingly, this only happens for the number 14 in the test data, number 12 for example gives the correct result, which again may make sense if there is some rounding happening. I've got around it by $x=int($x + 0.5) and $y=int($y + 0.5) as that's actually what I want the values to be.

Edit: ikegamis sprintf on $y produces

14.0000000000000017763568394002504646778106689453125

Just out of interest, I traced this all the way back to a XML::LibXML::Reader find_value() call, where the number was originally a 0.14 string and multiplied, so my mistake for not doing some proper data checking in the code.

Shouldn't Devel::Peek show the number as 14.0000000000000017763568394002504646778106689453125 under the hood ?

Edit: Just for those interested, I can manually replicate it now with setting the following, which makes the problem a bit more obvious.

my $x = "0.14";
$x *= 100;
my $y = 14;

Upvotes: 4

Views: 140

Answers (1)

choroba
choroba

Reputation: 241848

my $x = 14.000000000000001;
my $y = 14;

When stringifying a value, Perl rounds the number a bit if needed.

See also What Every Computer Scientist Should Know About Floating-Point Arithmetic

Upvotes: 2

Related Questions