Αυτό το άρθρο είναι προς το παρόν στα ρωσικά — η αγγλική μετάφραση βρίσκεται σε εξέλιξη.
ROUND округляет число до ближайшего целого: вы вызываете его, когда нужно превратить дробный итог суммы, среднего или процента в целое значение для отчёта, лимита или платёжки. За простым названием прячутся две разные стратегии округления половинок и коварная разница между точным типом numeric и приблизительным double precision, на которой регулярно горят финансовые отчёты.
Округление ROUND редко бывает «просто косметикой»: его результат попадает в суммы оплат, комиссии, лимиты, отчёты по зарплатам и сверки с внешними системами. Если PostgreSQL и приложение округляют половинку 0.5 по-разному, расхождение в одну копейку будет редким — и именно поэтому такие редкие копейки потом труднее всего объяснять.
Базовое округление до целого
С одним аргументом ROUND отбрасывает дробную часть и приближает значение к ближайшему целому. Ровные половинки (.5) уходят от нуля: 2.5 становится 3, а -2.5 становится -3. То есть ROUND всегда выбирает сторону, дальше от нуля, как только дробная часть равна ровно половине.
SELECT
ROUND(3.14159) AS a,
ROUND(2.5) AS b,
ROUND(3.5) AS c,
ROUND(-2.5) AS d;
Ключевые свойства ROUND с одним аргументом:
- Для
numeric результат сохраняет тот же тип, дробная часть просто обнуляется.
- Стратегия для литералов вроде
2.5 — half-away-from-zero, симметричная относительно нуля.
- Чтобы оставить знаки после запятой, у
ROUND есть второй аргумент ROUND(x, n) — он разбирается в отдельной статье.
numeric против double: где прячется банковское округление
Главная ловушка ROUND в том, что PostgreSQL округляет половинки по-разному в зависимости от типа аргумента. Литерал 2.5 — это numeric, и здесь ROUND идёт от нуля. А вот для double precision тот же ROUND использует банковское округление (round-half-to-even): половинки уходят к ближайшему чётному.
SELECT
ROUND(2.5::numeric) AS num_25,
ROUND(3.5::numeric) AS num_35,
ROUND(2.5::double precision) AS dbl_25,
ROUND(3.5::double precision) AS dbl_35;
Для double значения 2.5 и 3.5 округляются ROUND к 2 и 4 соответственно — потому что по правилу half-to-even выигрывает чётный сосед. Это не баг, а способ уменьшить систематическое смещение, когда суммируется множество округлённых значений.
Грабли: одно и то же число даёт у ROUND разный результат только из-за типа. Если в запросе встречаются и константы numeric, и колонки float, итоги могут «не сойтись» на копейку. Приводите данные к numeric явно, когда от ROUND нужна предсказуемость.
Отдельно проверьте границу между PostgreSQL и приложением. Если ROUND срабатывает на этапе агрегации, а сервис потом ещё раз округляет уже полученную сумму, вы получаете двойное правило и труднообъяснимые расхождения. Лучше зафиксировать один слой, один тип и одну стратегию округления.
Округление денег
Когда ROUND применяется к деньгам, double опасен ещё и потому, что не все десятичные дроби представимы в двоичной плавающей точке. Сравните накопленную сумму, которую вы потом передадите в ROUND:
SELECT
SUM(amount) AS raw_total,
ROUND(SUM(amount)) AS rounded_total
FROM orders
WHERE status = 'paid';
Если колонка amount объявлена как numeric(12,2), SUM накапливается точно и ROUND отрабатывает честно. Если как double precision, у суммы уже могут появиться «хвосты» вроде 19.999999998, и ROUND округлит именно их. Правило простое: храните деньги в numeric, а ROUND применяйте к numeric.
Округлить через ROUND среднюю зарплату по отделу до целого:
SELECT
dept,
ROUND(AVG(salary)) AS avg_salary
FROM employees
GROUP BY dept
ORDER BY avg_salary DESC;
AVG над numeric-колонкой возвращает numeric, поэтому здесь ROUND предсказуемо использует half-away-from-zero, без сюрпризов с банковским округлением.
Отличия MySQL и ClickHouse
Функция ROUND есть везде, но стратегия для половинок различается, и переносить логику округления между СУБД вслепую нельзя.
- MySQL:
ROUND(x) для точных типов (DECIMAL) округляет от нуля, а для приблизительных (DOUBLE) поведение зависит от платформенной реализации C — обычно банковское. Тот же раскол по типу аргумента, что и в PostgreSQL.
SELECT ROUND(2.5), ROUND(2.5e0);
- ClickHouse: здесь главное отличие —
round() по умолчанию делает банковское округление к ближайшему чётному, а не уходит от нуля, как ROUND над numeric в PostgreSQL. Поэтому round(2.5) даёт 2, а не 3. Если нужно явно подчеркнуть банковскую стратегию, есть roundBankers(), а за привычным округлением от нуля придётся идти к floor/ceil с поправкой. Не переносите финансовую логику из PostgreSQL вслепую: проверьте тип аргумента и нужную стратегию на контрольных половинках.
SELECT round(2.5), round(3.5), roundBankers(2.5);
Запомните одно: для ROUND «округлить до целого» — это не единственная операция, а семейство стратегий. Прежде чем доверять ROUND финансовый отчёт, проверьте тип аргумента и то, как именно ваша СУБД обходится с ровными половинками.
ROUNDокругляет число до ближайшего целого: вы вызываете его, когда нужно превратить дробный итог суммы, среднего или процента в целое значение для отчёта, лимита или платёжки. За простым названием прячутся две разные стратегии округления половинок и коварная разница между точным типомnumericи приблизительнымdouble precision, на которой регулярно горят финансовые отчёты.Округление
ROUNDредко бывает «просто косметикой»: его результат попадает в суммы оплат, комиссии, лимиты, отчёты по зарплатам и сверки с внешними системами. Если PostgreSQL и приложение округляют половинку0.5по-разному, расхождение в одну копейку будет редким — и именно поэтому такие редкие копейки потом труднее всего объяснять.Базовое округление до целого
С одним аргументом
ROUNDотбрасывает дробную часть и приближает значение к ближайшему целому. Ровные половинки (.5) уходят от нуля:2.5становится3, а-2.5становится-3. То естьROUNDвсегда выбирает сторону, дальше от нуля, как только дробная часть равна ровно половине.SELECT ROUND(3.14159) AS a, -- 3 ROUND(2.5) AS b, -- 3 ROUND(3.5) AS c, -- 4 ROUND(-2.5) AS d; -- -3Ключевые свойства
ROUNDс одним аргументом:numericрезультат сохраняет тот же тип, дробная часть просто обнуляется.2.5— half-away-from-zero, симметричная относительно нуля.ROUNDесть второй аргументROUND(x, n)— он разбирается в отдельной статье.numeric против double: где прячется банковское округление
Главная ловушка
ROUNDв том, что PostgreSQL округляет половинки по-разному в зависимости от типа аргумента. Литерал2.5— этоnumeric, и здесьROUNDидёт от нуля. А вот дляdouble precisionтот жеROUNDиспользует банковское округление (round-half-to-even): половинки уходят к ближайшему чётному.SELECT ROUND(2.5::numeric) AS num_25, -- 3 ROUND(3.5::numeric) AS num_35, -- 4 ROUND(2.5::double precision) AS dbl_25, -- 2 ROUND(3.5::double precision) AS dbl_35; -- 4Для
doubleзначения2.5и3.5округляютсяROUNDк2и4соответственно — потому что по правилу half-to-even выигрывает чётный сосед. Это не баг, а способ уменьшить систематическое смещение, когда суммируется множество округлённых значений.Грабли: одно и то же число даёт у
ROUNDразный результат только из-за типа. Если в запросе встречаются и константыnumeric, и колонкиfloat, итоги могут «не сойтись» на копейку. Приводите данные кnumericявно, когда отROUNDнужна предсказуемость.Отдельно проверьте границу между PostgreSQL и приложением. Если
ROUNDсрабатывает на этапе агрегации, а сервис потом ещё раз округляет уже полученную сумму, вы получаете двойное правило и труднообъяснимые расхождения. Лучше зафиксировать один слой, один тип и одну стратегию округления.Округление денег
Когда
ROUNDприменяется к деньгам,doubleопасен ещё и потому, что не все десятичные дроби представимы в двоичной плавающей точке. Сравните накопленную сумму, которую вы потом передадите вROUND:SELECT SUM(amount) AS raw_total, ROUND(SUM(amount)) AS rounded_total FROM orders WHERE status = 'paid';Если колонка
amountобъявлена какnumeric(12,2),SUMнакапливается точно иROUNDотрабатывает честно. Если какdouble precision, у суммы уже могут появиться «хвосты» вроде19.999999998, иROUNDокруглит именно их. Правило простое: храните деньги вnumeric, аROUNDприменяйте кnumeric.Округлить через
ROUNDсреднюю зарплату по отделу до целого:SELECT dept, ROUND(AVG(salary)) AS avg_salary FROM employees GROUP BY dept ORDER BY avg_salary DESC;AVGнадnumeric-колонкой возвращаетnumeric, поэтому здесьROUNDпредсказуемо использует half-away-from-zero, без сюрпризов с банковским округлением.Отличия MySQL и ClickHouse
Функция
ROUNDесть везде, но стратегия для половинок различается, и переносить логику округления между СУБД вслепую нельзя.ROUND(x)для точных типов (DECIMAL) округляет от нуля, а для приблизительных (DOUBLE) поведение зависит от платформенной реализации C — обычно банковское. Тот же раскол по типу аргумента, что и в PostgreSQL.SELECT ROUND(2.5), ROUND(2.5e0); -- 3 (DECIMAL, away from zero), often 2 (DOUBLE, to even)round()по умолчанию делает банковское округление к ближайшему чётному, а не уходит от нуля, какROUNDнадnumericв PostgreSQL. Поэтомуround(2.5)даёт2, а не3. Если нужно явно подчеркнуть банковскую стратегию, естьroundBankers(), а за привычным округлением от нуля придётся идти кfloor/ceilс поправкой. Не переносите финансовую логику из PostgreSQL вслепую: проверьте тип аргумента и нужную стратегию на контрольных половинках.SELECT round(2.5), round(3.5), roundBankers(2.5); -- 2, 4, 2Запомните одно: для
ROUND«округлить до целого» — это не единственная операция, а семейство стратегий. Прежде чем доверятьROUNDфинансовый отчёт, проверьте тип аргумента и то, как именно ваша СУБД обходится с ровными половинками.