SQLinfo.ru - Все о MySQL

Форум пользователей MySQL

Задавайте вопросы, мы ответим

Вы не зашли.

#1 16.11.2021 23:55:01

LazY
_cмельчак
MySQL Authorized Developer and DBA
Зарегистрирован: 02.04.2007
Сообщений: 849

Небольшая история о преобразовании числовых строк в числа

Всегда знал, что 'число' и число - разные вещи, но за всё время своей работы с MySQL ни разу не бывал в ситуации, когда эта разница играет роль. И вот недавно такое случилось, о чем хочу рассказать.

Если вкратце: разница может сыграть роль, когда такие величины участвуют в арифметических операциях умножения или деления.

В моём случае нужно было рассчитать цену на товар со скидкой 55%. Что, как известно, делается по простой формуле:

Код:

цена со скидкой = ROUND( цена без скидки * (1 - скидка/100) )

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

Код:

mysql> SELECT 55/100, 1 - 55/100, '55'/100, 1 - '55'/100;
+--------+------------+----------+---------------------+
| 55/100 | 1 - 55/100 | '55'/100 | 1 - '55'/100        |
+--------+------------+----------+---------------------+
| 0.5500 |     0.4500 |     0.55 | 0.44999999999999996 |
+--------+------------+----------+---------------------+

Как я понял, операция с числами дала результат типа DECIMAL - точное число, а операция с числами и строкой - FLOAT, число с плавающей точкой. Из-за различий в механизме их хранения возникло расхождение фактических величин.

На практике это привело к двум разным ценам:

Код:

29990 * 0.4500 = 13495.5000; после округления - 13496
29990 * 0.44999999999999996 = 13495.499999999998; после округления - 13495

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

Если подобная величина отправляется в делитель, то и здесь возникают различия:

Код:

mysql> SELECT 1/55, 1/'55';
+--------+---------------------+
| 1/55   | 1/'55'              |
+--------+---------------------+
| 0.0182 | 0.01818181818181818 |
+--------+---------------------+

Вот такая история.

Неактивен

 

#2 30.11.2021 18:48:08

rgbeast
Администратор
MySQL Authorized Developer and DBA
Откуда: Москва
Зарегистрирован: 21.01.2007
Сообщений: 3880

Re: Небольшая история о преобразовании числовых строк в числа

Спасибо! Интересный эффект. Действительно, явное преобразование во FLOAT дает такой же результат.

Код:

mysql> select 1-CAST(55 AS FLOAT)/100.;
+--------------------------+
| 1-CAST(55 AS FLOAT)/100. |
+--------------------------+
|      0.44999999999999996 |
+--------------------------+

Неактивен

 

#3 15.02.2023 18:48:58

Римович
Участник
Зарегистрирован: 25.01.2023
Сообщений: 15

Re: Небольшая история о преобразовании числовых строк в числа

В некой базе данных имеется таблица "Пример":
+---------+--------+
| Число1 | Число2 |
+---------+--------+
| 9891.88 |   4723   |
+---------+--------+
Полям "Число1" и "Число2" назначен тип поля FLOAT.
Запрос на перемножение полей возвращает следующий результат вычисления:
+-------------------+
| Число1 * Число2     |
+-------------------+
| 46719348.68652344  |
+-------------------+
А вот калькулятор показывает чуть другой результат произведения:
9891.88 * 4723 = 46 719 349,24

Вопрос - как и в каких случаях можно работать с этим "опасным" типом FLOAT? Только для хранения данных, не предусматривающего в дальнейшем математических операций с ними?


Прикрепленные файлы:
Attachment Icon Скриншот 15.02.23_16.10.44.png, Размер: 30,438 байт, Скачано: 107

Неактивен

 

#4 15.02.2023 21:49:20

deadka
Администратор
Зарегистрирован: 14.11.2007
Сообщений: 2421

Re: Небольшая история о преобразовании числовых строк в числа

У double точность повыше,

Код:

mysql> create table t_8912(c1 double, c2 double);
Query OK, 0 rows affected (1.30 sec)

mysql> insert into t_8912 values(9891.88, 4723);
Query OK, 1 row affected (0.34 sec)

mysql> select c1*c2 from t_8912;
+--------------------+
| c1*c2              |
+--------------------+
| 46719349.239999995 |
+--------------------+
1 row in set (0.01 sec)

Зеленый свет для слабаков, долги отдают только трусы, тру гики работают только в консоли...

Неактивен

 

#5 16.02.2023 20:57:30

Римович
Участник
Зарегистрирован: 25.01.2023
Сообщений: 15

Re: Небольшая история о преобразовании числовых строк в числа

DOUBLE и занимает вдвое больше пространства.
Вопрос остаётся - когда пользоваться FLOAT безопасно? Или, например, забыть этот тип и использовать теперь только DOUBLE? )
Я полагал, что разница в этих типах только в точности числа хранения. Разница при умножении, например, должна бы быть в пределах округления - но не такая, как в моём примере...

Неактивен

 

#6 17.02.2023 00:12:11

paulus
Администратор
MySQL Authorized Developer and DBA
Зарегистрирован: 22.01.2007
Сообщений: 6757

Re: Небольшая история о преобразовании числовых строк в числа

Я боюсь, что это больше вопрос не про MySQL, а в принципе про хранение и работу чисел с одинарной точностью.

Неплохая статья на эту тему есть на википедии: https://en.wikipedia.org/wiki/Single-pr … int_format (русскоязычная в этом месте — плохая). В частности, из-за особенностей формата хранения точность не будет теряться на числах, у которых в десятичном представлении не больше 6 цифр.

Про то, использовать ли FLOAT или DOUBLE — вопрос плохо поставленный. И тот и другой форматы хранят лишь приблизительное значение. У double точность безусловно выше, но легко можно придумать сценарии, когда и он будет округлять. Соответственно, ответ про формат такой: если вам критичны вычисления с какой-то точностью (например, деньги), то нужно использовать DECIMAL или INT нужных размеров (подразумевая, что вы храните в копейках, например). Если вас устраивает приблизительный результат, то FLOAT или DOUBLE сгодятся (второй делает точность, разумеется, сильно выше).

Ну и заодно собрал вот такой пример, в котором видно, что даже первое число уже хранится не так, как вы ожидаете, что уж говорить о произведении:


test> create table x (f1 float, f2 float, d1 double, d2 double);
Query OK, 0 rows affected (0,04 sec)

test> insert into x values (9891.88, 4723, 9891.88, 4723);
Query OK, 1 row affected (0,00 sec)

test> select f1, cast(f1 as double), f1 * f2, d1 * d2, 9891.88 * 4723 from x;
+---------+--------------------+-------------------+--------------------+----------------+
| f1      | cast(f1 as double) | f1 * f2           | d1 * d2            | 9891.88 * 4723 |
+---------+--------------------+-------------------+--------------------+----------------+
| 9891.88 |    9891.8798828125 | 46719348.68652344 | 46719349.239999995 |    46719349.24 |
+---------+--------------------+-------------------+--------------------+----------------+
1 row in set (0,01 sec)
 

Последний столбец в выборке — DECIMAL(10,2).

Неактивен

 

#7 17.02.2023 20:50:31

Римович
Участник
Зарегистрирован: 25.01.2023
Сообщений: 15

Re: Небольшая история о преобразовании числовых строк в числа

Спасибо большое!
Для начала уже хорошо понимать, что оно вот так всё. )
Попробую разобраться, какую неточность можно ожидать, если придётся работать с плавающей точкой...

Неактивен

 

Board footer

Работает на PunBB
© Copyright 2002–2008 Rickard Andersson