@Dmaw

Почему так работает SQL выборка из поля типа float?

Почему так работает SQL выборка из поля типа float?
SELECT * FROM products WHERE price='0.8'  (нет результатов)
SELECT * FROM products WHERE price>='0.8'  (есть результаты)
SELECT * FROM products WHERE price LIKE '0.8'  (есть результаты)

Второй запрос работает быстро, третий медленно, как заставить работать первый вариант?
  • Вопрос задан
  • 1449 просмотров
Пригласить эксперта
Ответы на вопрос 4
trapwalker
@trapwalker
Программист, энтузиаст
Я отвечу чуть более развёрнуто.
Дело в том, что float хранит дробные числа в двоичном формате.
Кроме двоичных мы работаем с десятичными числами и тут возникает проблема.
К примеру, число 1/3 (одна третья) в десятичной системе счисления будет записываться как бесконечная (периодическая) десятичная дробь 0.33333(3). А вот в троичной системе счисления это число запишется конечной троичной дробью: 0.1 (читается, как "ноль целых и одна третья").
По такой же схеме у нас десятичное число 0.5 (ноль целых и пять десятых) равно в двоичной 0.1 (ноль целых и одна вторая). 0.25 [10] == 0.01 [2]; 0.75 [10] == 0.5 [10] + 0.25 [10] == 0.11 [2].

А теперь, внимание, на десятичное число 0.2 (ноль целых и две десятых), для перевода в двоичную систему счисления нам нужно сложить это число из членов двоичного ряда:
==1/2, 1/4, 1/8, 1/16, 1/32, 1/64, 1/128, 1/256, 1/512, 1/1024 ...
0. 0    0    1    1     0     0   ( 1      1      0      0 )

В круглых скобках обозначен период двоичной дроби. То есть двоичной дробью в конечном количестве знаков после двоичной запятой задать десятичное число 0.2 (ноль целых и две десятых) НЕЛЬЗЯ!

Если у нас есть лишь конечное количество знаков, то ими представить в точности 2/10 в двоичной системе счисления одним числом невозможно.
Чему конкретно получится равным "обрзанное" (округлённое конечно) число будет зависеть от того по какой именно двоичный разряд мы его округлили.

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

Итак, чтобы сравнить два числа на равенство, скажем a и b, нужно выбрать некое e=0.0000001, которое будет определять точность сравнения и проверять условие: abs(a-b)<e - модуль разности сравниваемых чисел должен быть меньше выбранной точности. Если условие соблюдается, то числа считаем условно равными с заданной точностью. Если нет - не равными.

Добавлю, что, как отметили выше, цены и денежные суммы вообще в формате с плавающей точкой хранить "плохая примета"=). Для этого есть специальные форматы, которые не создают таких сюрпризов при переводе из/в десятичную систему.
Вот замечательная статья на хабре про это: Потеря точности из Double во Float или «Куда пропа...
Ну и еще есть: Что нужно знать про арифметику с плавающей запятой, Наглядное объяснение чисел с плавающей запятой, Разбираемся в числах с плавающей точкой, и т.д.
Ответ написан
Комментировать
FanatPHP
@FanatPHP
Чебуратор тега РНР
НИКОГДА не использовать для цен тип float. Только int или decimal
Ответ написан
Комментировать
tsklab
@tsklab
Здесь отвечаю на вопросы.
SELECT * FROM products WHERE ABS(price - 0.8) < 0.01 -- (будут результаты) второе число — ед. из. цены
Ответ написан
Комментировать
@Dmaw Автор вопроса
Сделал поле типа double, пока работает.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы