2012-04-15 115 views
2

在Ruby核心库中,有一个非常有用的Set类。它可以存储任何类型的对象。在浮点数中存储浮点数?

但是如你所知,浮点数(Ruby中的Float)有一些精度问题。 1.2-1.0并不等于0.2

s = Set.new() 
s.add(1.2-1.0) 
s.add(0.2) 
s.size 
=> 2 

是的,我可以使用BigDecimal类型来获得精确的数字。但是是否可以给Set一个特定的比较函数,以便它能够承受一个小错误(例如,1e-9)?

(我知道这个问题是不可知的语言。在其他常用的语言欢迎的解决方案)

+0

的语义是有问题的。例如,假设你已经有了'1.0'和'1.0 + 1.5e-9',允许的错误设置为'1e-9'。现在当你添加'1.0 + 0.7e-9'时会发生什么?该集合是从2个元素变为1个? – 2012-04-15 13:41:39

回答

1

有趣的问题,我想我已经找到了一个潜在的解决方案,这取决于你想要做什么。 Ruby使用引擎盖下的Hash来存储Set的元素。在Ruby中,Hash键等于由方法hasheql?定义。所以,如果你在Float重新定义这些方法(!买者自负),可以使Set考虑合理地接近Float s到等于:

class Float 

    def eql?(other) 
    other.is_a?(Float) && self.round(9) == other.round(9) 
    end 

    alias :old_hash :hash 

    def hash 
    self.round(9).old_hash 
    end 

end 

s = Set.new 
s.add(0.2) 
s.include?(0.2)  # => true 
s.include?(1.2 - 1.0) # => true 
s.include?(0.2001) # => false 
+0

“1.0000000004999999”和“1.0000000005000001”这两个花车怎么样?这两个浮点数尽可能地接近(假设IEEE 754和圆到最近点,则为1 ulp差值)。然而,他们在四舍五入到小数点后9位时会给出不同的结果。 – 2012-04-15 17:04:01

+0

你说得对。这里的问题不是比较,而是'hash'方法。我们可以很容易地将比较结果改为'self - other <1e-9',但在这种情况下,'hash'方法的工作是将实数空间分散为谨慎的块,并且我认为没有这些类型是不可能的的边界案件。由于Ruby中的惯例是'eql?'应该对散列为相同值的对象返回true,所以我们必须相应地更改比较。 – tsherif 2012-04-15 17:28:30

+0

“如果没有这些边界案例,我不认为这是可能的”。同意。一般来说,这是一个难以解决的问题。我的问题是为什么OP将Floats放入一个集合中...... – 2012-04-15 18:11:40