Search code examples
perlfloating-point

How to compare floating point numbers in Perl for "equality"


Note that I had set "equality" in double-quotes, so read on about the details:

I wrote some code that stores Perl objects in a file and retrieves them from there. I'm using my own JSON-based code using text representation to be independent from Perl. When I wrote a self-test checking whether the loaded object is the same as the stored object, I got a surprise:

  DB<14> x $self->time() == $other->time()
0  ''
  DB<15> x $self->time()
0  4842276.32536854
  DB<16> x  $other->time()
0  4842276.32536854
  DB<17> x $self->time() == $other->time()
0  ''
  DB<18> x $self->time() - $other->time()
0  '-2.79396772384644e-09'

So while the numbers output look identical, their internal representation is not. However when I create a JSON representation for both, there is no difference (different numbers this time (as time went on); also note that the order of the fields varies as it's a Perl HASH):

  DB<1> x $self->flat
0  '{"time":4842854.29538268,"crc":23407,"use_counter":19}'
  DB<2> x $other->flat
0  '{"use_counter":19,"crc":23407,"time":4842854.29538268}'

(The real objects are more complex, but this shows the principle; the time value comes from clock_gettime(CLOCK_MONOTONIC) # useTime::HiRes qw(clock_gettime CLOCK_MONOTONIC))

Now I wonder: Should I simply try to compare the numbers as strings (as a work-around), or should I use an elaborate ("correct") solution like https://stackoverflow.com/a/33024979/6607497?


Solution

  • Considering different solutions for the original problem (float number is not equal after saving and restoring it via JSON (and using default string conversion)), I wanted to fix JSON save and restore instead of the floating point comparison.

    So after temporarily using string comparison for the floating-point numbers (that would also fix the problem), I switched back to numeric comparison. To make that work, I decided to save the numbers as hexadecimal double representation (Perl's floats are always double internally).

    The code basically became:

    if ($encode) {
        return unpack('H*', pack('d>', $val));
    } else {
        return unpack('d>', pack('H*', $val));
    }
    

    So 5274540.53703853 is saved as 41541eeb225ed6db, for example. I cross-checked with C, and it uses the same representation.