поиск:
Полезные ссылки

  • Форум по MySQL

  • Статьи по MySQL

  • Вопросы по MySQL

  • MySQL.com


  • Базы данных

  • MySQL

  • PostgreSQL


  • PHP конференция 2005
    Подробности!

    A.5.7. Проблемы со сравнением чисел с плавающей точкой

    Числа с плавающей точкой иногда служат источником неприятностей, поскольку эти числа архитектурно хранятся в компьютере не как точные числа. То, что обычно мы видим на экране, не является точным значением числа.

    Поля типов FLOAT, DOUBLE и DECIMAL следующие.

    CREATE TABLE t1 (i INT, d1 DECIMAL(9,2), d2 DECIMAL(9,2));
    INSERT INTO t1 VALUES (1, 101.40, 21.40), (1, -80.00, 0.00),
    (2, 0.00, 0.00), (2, -13.20, 0.00), (2, 59.60, 46.40),
    (2, 30.40, 30.40), (3, 37.00, 7.40), (3, -29.60, 0.00),
    (4, 60.00, 15.40), (4, -10.60, 0.00), (4, -34.00, 0.00),
    (5, 33.00, 0.00), (5, -25.80, 0.00), (5, 0.00, 7.20),
    (6, 0.00, 0.00), (6, -51.40, 0.00);
    mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b
        -> FROM t1 GROUP BY i HAVING a <> b;
    +------+--------+-------+
    | i    | a      | b     |
    +------+--------+-------+
    |    1 |  21.40 | 21.40 |
    |    2 |  76.80 | 76.80 |
    |    3 |   7.40 |  7.40 |
    |    4 |  15.40 | 15.40 |
    |    5 |   7.20 |  7.20 |
    |    6 | -51.40 |  0.00 |
    +------+--------+-------+
    

    Результат правильный. Хотя кажется, что первые пять записей должны быть отброшены, не выдержав проверки на сравнение, тем не менее, они могут удовлетворить условиям по той причине, что в зависимости от архитектуры компьютера между числами проявляются отличия примерно на уровне десятого знака.

    С помощью ROUND() (или подобной функции) проблема не может быть решена, поскольку результат все равно будет числом с плавающей запятой, например:

    mysql> SELECT i, ROUND(SUM(d1), 2) AS a, ROUND(SUM(d2), 2) AS b
        -> FROM t1 GROUP BY i HAVING a <> b;
    +------+--------+-------+
    | i    | a      | b     |
    +------+--------+-------+
    |    1 |  21.40 | 21.40 |
    |    2 |  76.80 | 76.80 |
    |    3 |   7.40 |  7.40 |
    |    4 |  15.40 | 15.40 |
    |    5 |   7.20 |  7.20 |
    |    6 | -51.40 |  0.00 |
    +------+--------+-------+
    

    Вот как выглядят числа в столбце 'a':

    mysql> SELECT i, ROUND(SUM(d1), 2)*1.0000000000000000 AS a,
        -> ROUND(SUM(d2), 2) AS b FROM t1 GROUP BY i HAVING a <> b;
    +------+----------------------+-------+
    | i    | a                    | b     |
    +------+----------------------+-------+
    |    1 |  21.3999999999999986 | 21.40 |
    |    2 |  76.7999999999999972 | 76.80 |
    |    3 |   7.4000000000000004 |  7.40 |
    |    4 |  15.4000000000000004 | 15.40 |
    |    5 |   7.2000000000000002 |  7.20 |
    |    6 | -51.3999999999999986 |  0.00 |
    +------+----------------------+-------+
    

    В вашей системе результаты могут либо такими, либо нет - это зависит от архитектуры компьютера. Каждый процессор выполняет вычисления с плавающей точкой по-своему. Например, на некоторых машинах можно получить ``правильные'' результаты, если умножить оба аргумента на 1 (см. пример ниже).

    ПРЕДУПРЕЖДЕНИЕ: НИКОГДА НЕ ПОЛАГАЙТЕСЬ НА ДАННЫЙ МЕТОД В СВОЕМ ПРИЛОЖЕНИИ, ЭТО ПРИМЕР ТОГО, КАКИЕ МЕТОДЫ НЕ СЛЕДУЕТ ИСПОЛЬЗОВАТЬ!!!

    mysql> SELECT i, ROUND(SUM(d1), 2)*1 AS a, ROUND(SUM(d2), 2)*1 AS b
        -> FROM t1 GROUP BY i HAVING a <> b;
    +------+--------+------+
    | i    | a      | b    |
    +------+--------+------+
    |    6 | -51.40 | 0.00 |
    +------+--------+------+
    

    Приведенный выше пример вроде бы работает. Но это случайность, поскольку именно на той конкретной машине, где выполнялась проверка, процессор выполняет арифметические операции с плавающей точкой таким образом, что числа округляются до одинаковых значений. Однако полагаться на то, что так будут работать все без исключения процессоры, нельзя.

    Правильный способ сравнения чисел с плавающей запятой заключается в том, чтобы сначала определиться с допустимым отклонением одного числа от другого, а затем при сравнении учитывать этот допуск. Например, если мы договоримся, что числа должны считаться одинаковыми, если они равны с точностью до одной десятитысячной (0,0001), то сравнение должно проводиться следующим образом:

    mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
        -> GROUP BY i HAVING ABS(a - b) > 0.0001;
    +------+--------+------+
    | i    | a      | b    |
    +------+--------+------+
    |    6 | -51.40 | 0.00 |
    +------+--------+------+
    1 row in set (0.00 sec)
    

    И наоборот, если мы хотим оставить строки, в которых числа одинаковы, то проверка должна быть следующей:

    mysql> SELECT i, SUM(d1) AS a, SUM(d2) AS b FROM t1
        -> GROUP BY i HAVING ABS(a - b) < 0.0001;
    +------+-------+-------+
    | i    | a     | b     |
    +------+-------+-------+
    |    1 | 21.40 | 21.40 |
    |    2 | 76.80 | 76.80 |
    |    3 |  7.40 |  7.40 |
    |    4 | 15.40 | 15.40 |
    |    5 |  7.20 |  7.20 |
    +------+-------+-------+
    
     
    © 1997-2005 PHP Club Team
    Rambler's Top100