2012-04-15 6 views
2

В библиотеке ядра Ruby существует очень полезный класс Set. Он может хранить любой тип объекта.Сохранение чисел с плавающей запятой в Set?

Но, как вы знаете, числа с плавающей запятой (Float в Ruby) имеют некоторые проблемы с точностью. 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? –

ответ

1

Интересный вопрос, и я думаю, что я нашел потенциальное решение, в зависимости от того, что вы хотите сделать. Ruby использует Hash под капотом для хранения элемента Set. В Ruby Hash ключевое равенство определяется методами hash и eql?. Так что если вы переопределить эти методы в Float (! РИСКОВАННУЮ ПОКУПКУ), вы можете сделать Set рассмотреть достаточно близко Float с, чтобы быть равными:

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'? Эти два поплавка находятся как можно ближе (разница 1 ulp, предполагая IEEE 754 и от ближайшего к ближайшему). И все же они дают разные результаты при округлении до 9 знаков после запятой. –

+0

Вы правы. Проблема здесь не в сравнении, а в методе «хэш». Мы могли бы легко изменить сравнение на «self-other» <1e-9', но работа метода «hash» в этом случае - это наплевать пространство реальных чисел на сдержанные куски, и я не думаю, что это возможно без такого рода пограничных дел. И поскольку конвенция в Ruby заключается в том, что 'eql?' Должен возвращать true для объектов, которые 'hash' имеют одинаковое значение, мы должны соответствующим образом изменить сравнение. – tsherif

+0

«Я не думаю, что это возможно без такого рода пограничных случаев». Согласовано. В общем, это трудная проблема. Мой вопрос будет в том, почему OP ставит Floats в набор в первую очередь ... –