вторник, 19 мая 2015 г.

Числа с плавающей точкой не являются ассоциативными

Ассоциативность  это свойство, при котором для любых трёх элементов множества x, y, z выполняется равенство (x + y) + z = x + (y + z). Многие множества удовлетворяют этому свойству:
  • Целые числа (в том числе машинные целые числа) по операции сложения / умножения.
  • Строки по операции конкатенации.
  • Предикаты по операции конъюнкции / дизъюнкции.  
  • ... и многие многие другие.
Однако некоторые множества не являются ассоциативными, например, числа с плавающей точкой (по сложению / умножению).
Это легко проверить. Для этого мы воспользуемся библиотекой functionaljava, в которой есть возможность тестирования свойств (аля Haskell QuickCheck). Тест будет выглядеть следующим образом:
Property prop = Property.property(Arbitrary.arbDouble, Arbitrary.arbDouble, Arbitrary.arbDouble,
        (x, y, z) -> Property.prop((x + y) + z == x + (y + z)));

CheckResult check = prop.check();

System.out.println("Passed: " + check.isPassed());
Если выполнить этот код, то он выведет false. У класса CheckResult также можно спросить, какие именно числа нарушают ассоциативность:
for (List<Arg<?>> args : check.args()) {
    double x = (Double) args.index(0).value();
    double y = (Double) args.index(1).value();
    double z = (Double) args.index(2).value();
    System.out.printf("(%s + %s) + %s = %s%n", x, y, z, (x + y) + z);
    System.out.printf("%s + (%s + %s) = %s%n", x, y, z, x + (y + z));
}
Этот код выведет примерно следующее:
(5.094895128230203 + -7.839963840335238) + -1.8589779099062191 = -4.604046622011254
5.094895128230203 + (-7.839963840335238 + -1.8589779099062191) = -4.6040466220112535
Как видите, при разной расстановке скобок результаты будут разными.