All streams
Search
Write a publication
Pull to refresh

Comments 101

Кстати, про эту проблему очень часто не задумываются — в том числе там, где идут серьёзные вопросы (деньги). В сентябре 2022 года я сделал такой скриншот интерфейса «СберМегаМаркета». Понятно, что это ни что это не влияет, это просто отображение на фронтенде.

Любопытно, я думал, что деньги всегда считают только целочисленной арифметикой.

Думаю, на бэке обрабатывают как целое число копеек, а при необходимости делят на сто. Очень надеюсь — иначе кто-нибудь опять сожжёт офисное здание из-за конфискованного красного степлера.

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

А на что отсылка с красным степлером?

«Офисное пространство» (1999).

Ни разу не видел хранения в целых копейках, хотя работаю с этим достаточно давно. Почти всегда это DECIMAL(X,Y), причем Y>=4. Изредка, когда нужна не сумма как таковая, а расчет статистического показателя бывают float/double.

Аналогично, при этом всё время читаю статьи про то, как важно копейки хранить отдельным целым

Особого смысла в их хранении отдельно нет. Проще хранить всю сумму не в рублях, а в копейках (или там в десятых/сотых копейки -- зависит от конкретных требований).

Смысл появляется, когда вы конвертируете валюты. Там почти всегда на выходе получается число с множеством цифр после запятой. Округлять до 1/100 копейки? Тогда в масштабах страны на 100+ млн населения получится неплохой доход для того кто внедрит <правильный> алгоритм округления. Поэтому в банковской сфере используется плавучка.

для того кто внедрит <правильный> алгоритм округления

Вот только законным является только один алгоритм округления.

Хранение в копейках как целым числом очень удобно. Начисление и тп можно хранить в тех же double, но после агрегации - уже опять в копейках.

Это всегда гарантирует вам safe сравнение и базовые операции с числами.

Ни разу не видел хранения в целых копейках

А я видел. Сумма хранится в базе в копейках в поле с типом int8. Довольно интересное решение.

Соответственно, чтобы получить сумму в рублях, её надо поделить на 100

Int8 — это максимум 1,27 рубля, что ли? Или имелось в виду, что копейки хранятся в отдельном поле от рублей?..

Или имелось в виду, что копейки хранятся в отдельном поле от рублей?..

Нет, вместе.

Int8 — это максимум 1,27 рубля, что ли?

Это int8 из постгресса, который псевдоним для bigint. Диапазон от -9223372036854775808 до +9223372036854775807, хватит с головой :)

Всего-то 18,5 десятичных цифр... Маловато будет! :) (на мэйнфреймах -- до 31)

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

у postgres даже есть специальный тип данных money где под капотом int8, у которого две правые цифры становятся копейками. вот почему этот тип не используют?) ну классный же для денег

Даже официальные курсы валют обычно имеют 4 знака после запятой. Так что обычно хранится 4 или больше.

Курс, это совсем не количество денег. Т.е. после умножения/деления числа рублей и копеек на курс результат округляется до центов. Понятия не имею, по какому финансовому правилу.

ну для этих случаев есть Decimal в кликхаус и numeric в постгрес. и то и другое под капотом в интах значение хранит, но цифры после запятой можно задать

Ещё от архитектуры процессора зависит. Мэйнфреймы IBM от самого своего рождения (Система 360, 1964 год) имеют поддержку двоично-десятичных чисел (до 31 десятичной цифры плюс знак), и на них "экономическая" информация естественным образом считается в десятичной системе. Вот на ПК (IA-32, AMD64 -- неважно) или, скажем, на ARM любой разновидности такие вещи приходится считать, используя целочисленную арифметику; плавающая запятая -- лишь для задач, где ошибки округления приемлемы (скажем, научные расчёты: там не обязательно, чтоб последняя циферка сходилась, достаточно лишь обеспечить необходимую точность).

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

Первое требование — безопасность при обратном преобразовании:

Да ладно. Такое требование возникает относительно редко – только при сериализации, а не при выводе для пользователя.

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

Ну вот это предположение, как правило, неверно. Но задача да, остаётся сложной, и много ответвлений. Например, считаем процентные доли чего-то и выводим – получаем 80% +13% + 6% (ну или там с десятыми-сотыми – сколько знаков пользователю нужно). Всегда найдётся кто-то, кто их сложит и будет возмущаться.

Ну как - как правило неверно? Питон, например, по умолчанию выводит именно так...

Поэтому вывод по умолчанию примерно никогда не используется :-)

Отладка? Логирование? Сериализация?

Может для float нужно хранить не только значение, но и погрешность (в том числе накопленную при вычислениях)? Это позволило бы сразу понимать, где значащие разряды, а где мусор.

А в какой форме хранить погрешность? Либо в длинной арифметике, но тогда уж сразу нужно в ней считать, либо во флоатах, и тогда нам нужна будет погрешность на погрешность :)

Ну можно хранить не точное значение, а оценку сверху - порядок погрешности. Тогда хоть в int'е.

Вообще люди уже об этом думали, конечно же. Но это сильно небесплатно, и часто можно обойтись без таких ухищрений. Если не принимать нужных мер, то зачастую интервалы расходятся, таким образом не давая никакой полезной информации. А если принимать нужные меры, то нередко интервалы не нужны.

К сожалению, интервальная арифметика не всегда помогает. Например, в ней x - x != 0. И при сложных вычислениях интервалы быстро становятся гигантскими.

Ну я об этом и написал. К сожалению, панацеи в области представления чисел ещё не придумали (и, скорее всего, не придумают).

А в какой форме хранить погрешность? Либо в длинной арифметике, но тогда уж сразу нужно в ней считать, либо во флоатах, и тогда нам нужна будет погрешность на погрешность :)

Физики у погрешности пишут только первую цифру и порядок. Обычно цель - оценить порядок бедствия и понять, в каком месте теряется точность.
Для этой цели вполне хорошо подходит пара float, где первый float это значение и второй это погрешность.
Формулы для погрешности сложения/умножения/синусов/косинусов и сходны с теми, что получаются при вычислении производных, там ничего сложного нет.
Из минусов только то, что арифметических операций будет больше.

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

Дело в том, что хорошие вычислительные методы, дают предсказуемую погрешность, зачастую значительно меньшую, чем погрешность отдельных операций в составе их алгоритмов. "остаточный член на n-ной итерации", такие вот штуки, и, конечно, кому это надо, они это считают. Смутно конечно помню это с курсов в универе. Но суть представляю: зачем на каждую операцию погрешность считать, тратя на это время, если про многие популярные методы вычислений эти погрешности уже аналитически рассчитаны.

Физики у погрешности пишут только первую цифру и порядок.

А в двоичной системе первая цифра всегда будет 1.

Мы говорим о погрешности, которая по определению ненулевая.

Эм. Покажите, пожалуйста, определение?

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

Нулевую погрешность не рассматриваем как практически невероятную. А если уж так надо её задать, можно предусмотреть одно спецзначение.

Вы первый начали. Физики пишут в десятичной системе.

Но хранить-то данные придётся в двоичной. Вы внатуре не понимаете, о чём я говорю?

Физики у погрешности пишут только первую цифру и порядок

Отнюдь. Гравитационная постоянная

G = (6,67430 ±15) *10E−11 м3/с^2/кг рекомендованное, или

G = (6,674184±78) *10E−11 м3/с^2/кг уточнённое в эксперименте

Или оценка постоянной Хаббла

74,03 ± 1,42 (км/с)/Мпк

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

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

А что научные статьи на эту тему продолжают выходить сегодня, тем более.

Как же без кликбейта. Хорошо что не "ТОП1 проблем, о которых не подозревают 99% программистов!"

Я правильно понял, что что вы предлагаете перестать писать учебники?

вы предлагаете перестать писать учебники?

Я предлагаю начать их читать.

(Прикиньте, я об этой проблеме знал ещё до того, как у меня появился компьютер. Из книжек.)

А писать-то надо, или нет? Переведёте свой первый комментарий с саркастического на понятный?

Писать‑то надо, но не на уровне «если получается Н.Е.Х., надо камлать заклинание import Decimal», а на уровне «На Самом Деле™ в битовом поле хранится N бит мантиссы и M бит порядка, [через 10 страниц] и поэтому надо подключить правильную библиотеку».

Но, конечно, миллениалы — не читатели...

Писать‑то надо, но не на уровне «если получается Н.Е.Х., надо камлать заклинание import Decimal», а на уровне «На Самом Деле™ в битовом поле хранится N бит мантиссы и M бит порядка, [через 10 страниц] и поэтому надо подключить правильную библиотеку».

Но, конечно, миллениалы — не читатели.

Если честно, я так и не понял, что именно вы имете против миллениалов, и кого вы имеете в виду.

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

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

Кажется, я начал догадываться. А вы же самую первую ссылку в моей статье пропустили, да?

И я пропустил. И ссылку, и ту саму статью. Меня еще в 9-м классе учили переводить числа из десятичного представления в двоичное и обратно. Угу, карандишиком на бумажке в клеточку. Ага, 1972 год, тяжелое детство, деревянные игрушки, двухтомник Лорана Шварца (чуть позже, правда). И поэтому заголовок данной статьи меня несколько изумил. Потому что, то, о чем вы пишете. оно в подкорке. Все, как отношение котов/кошек с ежами. Ну нет такого зверя, в упор не вижу. Ну как я могу, хозяин, уколоться, нет же никого тут, кроме нас с тобой.

Миллениалы открыли её минимум 15 лет назад. Теперь, наверно, уже очередь зуммеров.

Миллениалы открыли её минимум 15 лет назад. Теперь, наверно, уже очередь зуммеров.

Зуммеры: «А мы чо? Мы не жужжим!»

В отдельных ЯП встречается тип данных Rational Numerics, где числа хранятся в виде числителя и знаменателя, поэтому в них 0.1 + 0.2 == 0.3 без сюрпризов.

Да, но это цикл статей "санпросвет о плавающей точке".

Mille pardons за мое грубое вмешательство

санпросвет

Уже не первый раз встречаю это слово на Хабре за последнюю неделю в совершенно неподходящем контексте. О каком санитарно-эпидемиологическом просвещении речь? Похоже, стоит вам провести ликбез.

Зачем же так буквально? Метонимии никто не отменял.

Можно было вообще вот это использовать :)

Отличная статья! Но в этом учебнике я дальше плавающей точки не пойду, и так уже хватает головной боли :)

какую бы десятичную строку мы ни вывели

Вопрос - что такое "вывели"? И для чего это "вывели" требуется. Вывели на экран? Вывели сумму в документе? Этот "вывод" требуется для представления человеку. И если человек складывал 0.1+0.2 то выводиться должно 0.3, как бы оно там в двоичном преобразовании туда-сюда не выглядело. Сколько знаков после запятой в слагаемых, столько же должно "выводиться" в сумме. 0.11+0.22=0.33 3.14159+0.00001=3.14160 0.1+0.001=0.1001

Сколько знаков после запятой в слагаемых, столько же должно "выводиться" в сумме.

А сколько их в слагаемых? Что если мы сложили 0.1+0.2 на сервере а потом результат передали на клиент?

А что должен человек увидеть на клиенте? Я говорю о том, что есть внутреннее машинное представление, а есть "вывод на экран" для человека.

поидее можно как-то понять сколько разрядов в a b, и вывести в таком же стиле, но именно в этом случае получается сервер отправил такой ответ, тогда можно пойти дальше, что должно показаться если 0.1 / 0.1234

тоесть 4 или 8 знаков после запятой или все или поидее, можно наверно что-то сделать типо обвести наверное, пока сам не знаю)

Секунду, а как должен выглядеть вывод на экран, например \sin 0? Не все действия сводятся к простым суммам. Да и с суммами быстро проблемы начнутся, не зря в финансовой математике нужны специальные ухищрения.

Ну, а чему равен синус 0? Sin0=0.

Хорошо, давайте я с другого края зайду. Как выводить на экран значение \sin 1?

Да, сколько знаков после запятой выводить должен определять человек.

Синусы как раз прекрасно сводятся к простым суммам (ряды Тейлора), но не понятно, как это вообще должно быть связано с отображением результата. В sin(1) ни конечной в десятичной записи рациональной дроби, ни даже периодической вы не увидите, поэтому, по сути вопрос в том, на каком знаке после запятой его округлить. А это примерно "как пользователю нужно". Меня бы 0.01745 бы устроило, кроме тех случаев, когда нужно дальше с этим что-то считать.

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

Кому должно? преобразование в двоичное представление необратимо вносит ошибку для десятичных дробей.

И что? Сложите в калькуляторе Windows 0.1+0.001 - будет он на экране результат отображать с ошибкой? А если команда print выводит на экран черти что - то кто в этом виноват?

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

Да, вы правы.

зы.

Сколько знаков после запятой в слагаемых, столько же должно "выводиться" в сумме. 0.11+0.22=0.33

В общем случае "программа не знает" конечно сколько знаков после запятой было в слагаемых.

a=0.1
b=0.2
c=a+b
print c

В этом случае print не знает, сколько знаков выводить.Программист должен это явно как-то указать.

по стандарту получается знает сколько бит у float задействовано в счете

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

по стандарту получается знает сколько бит у float задействовано в счете

числа в памяти храняться определенным образом там стандарт есть

Вот поэтому я и пишу свои статьи: чтобы их читали. При счёте у флоата задействованы все биты.

по вашему примеру я вижу что память выделится под все, а биты будут определенные, в стандарте тоже на сколько помню мы видим сколько в целой части и мантиссе разве нет?

есть формат float вот например как у вас

https://godbolt.org/z/35j944xzM

может можно разобрать float на метаданные

например те данные, которые будут удобны для понимания

целое число

мантисса

количество какоето чтобы вернуть обратно

За статью спасибо, пишете хорошо, о существовании прямо нескольких и замороченных алгоритмов для такого лично я не подозревал. О существовании типов decimal и rational действительно знают достаточно малое людей только потому, что они не на слуху. Ещё меньше людей знают, что формат с плавающей точкой был придуман не для дробных чисел, а для инженеров и физиков, у которых есть как очень большие, так и очень маленькие фундаментальные константы.

Спасибо на добром слове. Знаете, что меня удивляет*? Эти статьи о замороченных алгоритмах имеют крохи цитирований. Например, один из основоположников процитирован 34 раза..

И это при том, что во всех рантаймах всех толковых языков есть один из этих алгоритмов...

Скрытый текст

На самом деле, не удивляет, поскольку эту кухню я знаю слишком хорошо.

С помощью decimal.getcontext().prec = (число знаков после запятой) можно получить точность намного больше 64 знаков после запятой

и соответствующие тормоза

я тоже заметил кое-что щас меня закидают тапками, возможно, кароче можно легко задаться вопросом от всего этого почему в ПК и как появились числа, если их надо так и так 1 функцией прогонять туда-сюда, я столкнулся с этим при написании, пускай и на аски текстового редактора, и где-то день просто философски размышлял зачем в пк нужны числа, если по итогу функция для всего этого 1

самая простая функция по взятию числа в строку или самописный делитель или sprintf/snprintf

и вот, да я видел стандарт тот, что-то тоже читал, и всё равно задам вопрос почему в пк есть типы чисел если есть строки

получается ситуация, мы видим строки, но считаем алгоритмы числами используя строки

Потому что скорость расчёта в "десятичном текстовом" виде будет многократно меньше, чем при естественном для машины двоичном числовом, а схемы для всего этого будут существенно сложней.

Я бы Вас закидал тапками, за то что в начале предложения "Shift" ленитесь нажать. Палец, блин, отвалится.

Как говорится,
Sign up to leave a comment.

Articles