<?xml version='1.0' encoding='UTF-8'?>
<rss version='2.0'>
	<channel>
		<title>Хранение не целых чисел в MySQL</title>
		<link>https://lily-software.com</link>
		<description>Лента комментариев страницы</description>
		<generator>Cotonti</generator>
		<language>ru</language>
		<pubDate>Thu, 09 Apr 2026 20:04:21 +0300</pubDate>

		<item>
			<title>Комментируемая страница</title>
			<description><![CDATA[<p style="text-align:justify;">В <strong>MySQL</strong> существует множество типов данных для хранения чисел как целых, так и с плавающей точкой.<br />
Рассмотрим существующие форматы:</p>

<p style="text-align:justify;">Для целых используются: <strong>TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT</strong>. Эти типы используются для хранения целых чисел и занимают от 1 до 8 байт памяти соответственно.</p>

<p style="text-align:justify;">Для чисел с плавающей точкой используются: <strong>FLOAT, DOUBLE</strong>. Главный минус чисел с плавающей точкой (или вещественных чисел) – принцип их представления и хранения. Такие числа хранятся как степени двойки. Так как не все десятичные числа имеют точное представление в виде двоичной дроби, результаты хранения получаются округленными. Даже при элементарных математических операциях возможны расхождения. Более подробно про это можно прочитать на хабре</p>

<p style="text-align:justify;">При хранении важных данных (к примеру, банковский счет) важен каждый символ даже в последнем разряде после точки, поэтому использовать числа с плавающей точкой нельзя.<br />
Избежать проблем хранения и поиска данных можно при помощи типов: <strong>DECIMAL, NUMERIC</strong>.</p>

<p style="text-align:justify;">До версии 5.03 эти типы хранились как строки, при этом использовалось по одному символу строки для каждого разряда числа (при необходимости, тратились дополнительно по одному символу на знак числа и на десятичный разделитель), также была возможна потеря точности из-за некорректной реализации арифметики таких чисел.</p>

<p style="text-align:justify;">Начиная с 5.03, в MySQL появилась новая библиотека для арифметики чисел с фиксированной точкой и изменился подход к хранению таких чисел. Теперь целая часть и часть после точки хранятся как 2 отдельных, целых числа. Оценить расход места можно на основании следующей таблицы:</p>

<table border="1"><tbody><tr><td><strong>Оставшиеся цифры</strong></td>
			<td><strong>Число байт</strong></td>
		</tr><tr><td>0</td>
			<td>0</td>
		</tr><tr><td>1-2</td>
			<td>1</td>
		</tr><tr><td>3-4</td>
			<td>2</td>
		</tr><tr><td>5-6</td>
			<td>3</td>
		</tr><tr><td>7-9</td>
			<td>4</td>
		</tr></tbody></table><p style="text-align:justify;"> </p>

<p style="text-align:justify;">Расчет ведется очень просто: к примеру, нам надо сохранить <strong>DECIMAL</strong>(10,2) – целая часть имеет 8 цифр и занимает 4 байта, часть после точки – 2 цифры и занимает 1 байт. Итого на хранение будет потрачено 5 байт.</p>

<p style="text-align:justify;">В ранних версиях <strong>MySQL</strong> типы <strong>DECIMAL</strong> и <strong>NUMERIC</strong> вели себя по-разному. SQL-стандарт требует, чтобы точность NUMERIC(M,D) была точно M цифр. Для DECIMAL(M,D) стандарт требует точности не меньше M цифр, но позволяет хранить больше. Это означает, что если бы вы захотели сохранить число 1,00005 в DECIMAL(6,4) и NUMERIC(6,4), то по стандарту NUMERIC сохранит 1.0000, а DECIMAL может сохранить 1.00005. Такое поведение при большом количестве математических операций может дать небольшую погрешность.</p>

<p style="text-align:justify;">В последних версиях MySQL DECIMAL и NUMERIC оба типа имеют точность ровно M цифр.</p>

<p style="text-align:justify;">Чтобы переварить все вышеизложенное, рассмотрим пару примеров (Server version: 5.0.77 Gentoo Linux).</p>

<p style="text-align:justify;">Создадим таблицу test с 4 полями разных типов. Внесем одинаковые данные и посмотрим, как будет вести себя MySQL</p>

<pre class="brush:sql;">
CREATE TABLE test (a FLOAT, b DOUBLE, c DECIMAL (6,4), d NUMERIC(6,4)) ENGINE = MyISAM;
INSERT INTO test(a,b,c,d) VALUES (0.3, 0.3, 0.3, 0.3), (0.00003, 0.00003, 0.00003, 0.00003);
 
SELECT a, b, c, d FROM test;
+-------+-------+--------+--------+
| a     | b     | c      | d      |
+-------+-------+--------+--------+
|   0.3 |   0.3 | 0.3000 | 0.3000 |
| 3e-05 | 3e-05 | 0.0000 | 0.0000 |
+-------+-------+--------+--------+</pre>

<pre class="brush:sql;">
SELECT a*1000, b*1000, c*1000, d*1000 FROM test;
+-------------------+--------+----------+----------+
| a*1000            | b*1000 | c*1000   | d*1000   |
+-------------------+--------+----------+----------+
|   300.00001192093 |    300 | 300.0000 | 300.0000 |
| 0.029999999242136 |   0.03 |   0.0000 |   0.0000 |
+-------------------+--------+----------+----------+</pre>

<p>Наглядный пример, почему не стоит доверять важные данные числам с плавающей точкой. Но не стоит думать, что DOUBLE – панацея. FLOAT и DOUBLE – одинаковы по принципу хранения. Оба типа одинаково не точны, просто в DOUBLE неточности появляются при бОльшем числе знаков.</p>

<p>Проблемы могут всплыть даже в более безобидных случаях. Предположим, мы храним данные о балансе клиента с точностью до копеек:</p>

<pre class="brush:sql;">
CREATE TABLE test (money FLOAT) ENGINE = MyISAM;
INSERT INTO test(money) VALUES (123.34),(8691.00),(0.01);
 
SELECT money, 1+money, 1*money FROM test;
+--------+-----------------+--------------------+
| money  | 1+money         | 1*money            |
+--------+-----------------+--------------------+
| 123.34 | 124.33999633789 |    123.33999633789 |
|   8691 |            8692 |               8691 |
|   0.01 | 1.0099999997765 | 0.0099999997764826 |
+--------+-----------------+--------------------+</pre>

<p>Если я вас еще не убедил отказаться от FLOAT , приготовьтесь к проблемам при поиске.</p>

<p>Проблема с поиском:</p>

<pre class="brush:sql;">
SELECT money FROM test WHERE money = 123.34;
Empty set (0.00 sec)</pre>

<p>Если же у вас назрела необходимость в поиске по полю FLOAT – используйте границы для поиска.</p>

<pre class="brush:sql;">
SELECT money FROM test WHERE money &gt; 123.33 AND money &lt;123.35;
+--------+
| money  |
+--------+
| 123.34 |
+--------+
1 row in set (0.00 sec)</pre>

<p> </p>

<p>Вопрос остается открытым. Как же хранить данные?<br />
Предположим, перед нами стоит задача сохранить число с двумя знаками после запятой. Это можно реализовать следующими способами:</p>

<p>1. FLOAT – забиваем на точность, вспоминаем про проблемы с поиском и все равно используем. Диапазон хранимых чисел очень большой: [-3,402823466E+38 .. -1,175494351E-38], 0, и [1,175494351E-38 .. 3,402823466E+38]. Занимает 4 байта.</p>

<p>2. DOUBLE – как уже упоминалось выше, ни чем не отличается от FLOAT, рано или поздно – всплывет погрешность. Диапазон больше, чем звезд во Вселенной: [-1,7976931348623157E+308 .. -2,2250738585072014E-308], 0, и [2,2250738585072014E-308 .. 1,7976931348623157E+308]. Занимает 8 байтов.</p>

<p>3. DECIMAL(X,2) – точное число с десятичной точкой. В зависимости от требуемого числа знаков до точки, меняем X. К примеру, нам надо хранить числа до 10 миллионов. Тогда инициализация поля будет иметь вид DECIMAL(9,2), поле – занимать 5 байтов и хранить числа в диапазоне [-9999999.99 .. 9999999.99].</p>

<p>4. INT – храним сразу в «копейках», а всеми преобразованиями занимаются хранимые процедуры, триггеры, PHP или другой язык программирования. При таком подходе мы сможем сохранить числа в диапазоне: [-21474836.48 .. 21474836.47], или при использовании UNSIGNED [0 .. 42949672.95]. Если вдруг нужен больший диапазон, всегда можно использовать 8 байтовый BIGINT.</p>

<p> </p>

<p><em><strong>Выводы:</strong></em></p>

<p>1. Для хранения важных данных <strong>нельзя использовать типы FLOAT и DOUBLE</strong>;</p>

<p>2. Оптимальный способ для хранения – DECIMAL. В крайнем случае – INT или BIGINT (особенно, если у вас используется ORM или DAO для доступа к данным, и все операции для перевода из целого в дробное и обратно будут прозрачны);</p>

<p>3. <strong>DECIMAL</strong> и <strong>NUMERIC</strong> – идентичны;</p>

<p>4. Если вы используете версию <strong>MySQL до 5.0.3</strong>, настоятельно <strong>рекомендуется обновиться</strong> до что-нибудь посвежее;</p>
]]></description>
			<pubDate>ср, 08 окт 2014 15:44:00 +0300</pubDate>
			<link><![CDATA[https://lily-software.com/articles/xranenie-ne-celyx-chisel-v-mysql]]></link>
		</item>
	</channel>
</rss>