TheLegend TheLegend - 4 months ago 26
Ruby Question

Ruby Enumerable can't compare BigDecimal NaN in min function

If I compare

a
,
b
and
c
like so

[a,b,c].min


where

a = BigDecimal.new("NaN")
b = BigDecimal.new("NaN")
c = BigDecimal.new("0.0")


I get:

ArgumentError: comparison of BigDecimal with BigDecimal failed


But if I was to use the comparison operator that ruby's Enumerable min uses then I get this:

irb(main):001:0> a <=> b
=> nil

irb(main):002:0> a <=> c
=> nil


And no errors are rendered. Is this an issue within Ruby or am I misunderstanding
min
, is there something else I can use to achieve the same effect as enumerable's
min
that will not explode?

Answer

From the Ruby language documentation:

NaN is never considered to be the same as any other value, even NaN itself:

n = ::new(‘NaN’)

n == 0.0 -> nil

n == n -> nil

As I understand it, any instance of the NaN value is unique and incomparable.

Consider this code snippet:

require 'bigdecimal'

a = BigDecimal('NaN')
b = BigDecimal('NaN')

puts a == b
puts a > b
puts a < b

You'll get false for each of these comparisons. min and max depend sorting to produce a result, and as @phoffer pointed out, sort produces values of 0, 1 and -1 based on whether the second number is equal to, greater than or less than the first number.

TL;DR: Any time you're using NaN in an operation, you can't expect meaningful results.

If the API you're working with returns NaN, you can at least protect yourself against this corner case by detecting it. NaN is actually an instance of Float, so you can test it by using the nan? method.

2.2.0 :002 > require 'bigdecimal'
 => true 
2.2.0 :003 > a = BigDecimal('NaN')
 => #<BigDecimal:7fae1b3bd050,'NaN',9(9)> 
2.2.0 :004 > a.nan?
 => true 
Comments