dr-mart

Новости современного программирования. Когда 2+2≠4

Я тут подахренел немного.

Сделали мы портфели на смартлабе (тут). Вот я вбиываю в портфель покупку USDRUB по 56,45.
(я действительно там покупал баксы в мае). 
Бах! А у меня в таблице тут же вбитое число отражается как 56,449999999999996.

Программист мне сказал, что НЕВОЗМОЖНО отражать число 56,45 как 56,45, а можно его только отражать как 56,449999999999996.
И на мои уговоры что-то с этим поделать посоветовал мне пойти поучить матчасть.

Собрали мы учёный совет. начали думать. Несколько дней думали, так и не нашли решения.
В процессе обсуждения звучали такие слова как longint, мантисса, varchar,10^n в нужной степени, операции без радикалов и т.д.

Проблема в том, что число знаков после запятой может быть и 5 (для некоторых акций).
Поэтому просто округлить все числа до 2 знаков после запятой не получится.
Как изящно выйти из ситуации?
★3
85 комментариев
а почему бы перед внесением данных в таблицу не «смотреть» что за тип актива? если акция, смотреть сколько в лоте штук, сделки на бирже не проходят с долями копейки… и потом все показывать в нормальном виде…
avatar
shortillo, ну я ж должен хранить цену акции по которой куплен актив, причем здесь лоты
Тимофей Мартынов, если бы вы писали систему биржи/брокера, где точность — архиважна, пришлось бы юзать longint'ы, с домножением цен на 10^x.
Но для вас, мне кажется, подойдет аналог «мягкого» округления. В R, например, это функция signif(). В других языках — надо смотреть.
avatar
Тимофей Мартынов, простое решение написано постом ниже.
В R есть встроенная функция:
signif(56.4499999996) = 56.45
Реализацию на других языках можно загуглить, пример:
stackoverflow.com/questions/202302/rounding-to-an-arbitrary-number-of-significant-digits
avatar

Смотря какой язык. Вообще-то, тип данных Money есть. Decimal тоже подходит. Пример: http://support.sas.com/kb/51/034.html.

avatar
Округлять перед показом, если число очень длинное)
avatar
п… ц он у тебя гонщик.
пусть делает printf("%.2f", num);
всё номано будет.
весь мир живёт с этим и норм, а он не может.
avatar
ПBМ, весь мир использует Decimal, ну или Money
avatar
Vitty, для того чтобы «отражать число „ 56,449999999999996“ как „56.45“, достаточно воспользоваться функцией printf

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

Vitty, на самом деле даже в этом случае можно обойти округлением до заданной точности.
более чем 18 значных чисел (18 значимых цифр) на смартлабе не будет, что покрывается стандартным double.
у меня на эту тему есть своя прохладная былина
stackoverflow.com/questions/24599498/rounding-double-strangeness

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

avatar
а насколько там важно округление? Можно тупо обрезать лишнее да и все.
sortarray sortarray, проблема в том, что есть например 56,45

а есть ТГК-2 с ценой 0.001655

и одно надо показывать 6 знаков после запятой а другое — тока 2
Тимофей Мартынов, либо самим таблицу точностей заводить, либо где-то из биржевых данных получать.
вы же отображаете разные цены для разных акций. ну будете отображать разные цены для разных акций с разной точностью.
avatar
Тимофей Мартынов, 

сам работаю в IT, но иногда хренею от своих коллег.

1) Где-то уже реализована это и показывает -> значит решение есть.

2) Лот, кол-во акции, цена, бид, аск, в конце концов справочник можно завести на каждую акцию, в котором хранить разрядность....

Тимофей, пушай своего прогера, ставь задачу — у других это сделано.
avatar
Алексей, потому что эта задача тривиальная в общем-то, поэтому и сделана у остальных.
avatar

Тимофей Мартынов, 

имхо дизайн сайта — местами, вырви глаз.

У меня вот так лента выглядит - 
www.dropbox.com/s/n3b660o84nzmcnr/Screenshot%202017-06-28%2020.28.23.png?dl=0

 

могу докинуть CSS, если интересно

avatar
Тимофей Мартынов, с такой проблемой сталкивается рано или поздно ЛЮБОЙ инженер или алго-трейдер. Поздравляю, у Вас это произошло сегодня. После этого дня Вы никогда не будете молиться на компУтер и уж на свой смартфон, как делает это молодёжь — и подавно. Сегодня Вы поняли, что комп-это просто туповатый кусок железа, и сделали его осуждённые гражданским судом (за монополизм) сотрудники Интел и Майкрософт.
По делу: эта проблема существует, но Ваш программист ЭТО ЯВНО ЗАГНУЛ. Ближайшее представление указанного Вами числа:
5.6450000762939453125E1, то есть 56.450000776.....
Проверка тут:
www.binaryconvert.com
С такой проблемой сталкиваются инженеры из многих областей, и в трейдинге тоже. ИМЕННО ПОЭТОМУ в «финансовом» языке программирования Python есть средства для ЛЮБОЙ БОЛЬШОЙ ТОЧНОСТИ чисел.
Такие средства есть и в языке Си (++) путём применения специальных библиотек «arbitrary precision» типа MPFR.
Павел Голобородько (сидя в Японии) ведёт такой проект несколько лет:
www.holoborodko.com/pavel/mpfr/#intro
bitbucket.org/advanpix/mpreal/

Решение для Вашего случая — их несколько: например хранить такие данные в формате String, а для любых манипуляций тут же преобразовывать их в double (float), а затем показывать результат в ОКРУГЛЁННОМ ВИДЕ — с заранее заданной точностью. Проблема будет в том, чтобы муторно и скучно задавать уровень требуемый точности для всех ВЫВОДИМЫХ полей.
Копая далее математику для финансов Вы будете ещё боьше удивляться почему мир до сих пор не рухнул, если всё (все формулы) рассчитывается по стандарту IEEE754.
Такие проблемы могут возникать у инженеров в самых неожиданных местах.
Я писал об этом на форуме MQL5:
www.mql5.com/ru/forum/165397/page5
Даже при вычислении  казалось бы простого полинома — для разных инженерных целей:
? = (333.75 — a2)b6 + a2 (11a2 b2 — 121b4 — 2) + 5.5b8 + a/(2b).
Из всех компиляторов Си только старый Borland может использовать — хранить и вычислять — все числа в формате 80 бит, используя всю точность процессора. Все остальные режут числовые слова до 64 бит. В Python используется встроенная бибилиотека повышенной точности, которая считает ПОБИТОВО все нужные операции с точностью 128 бит, и конечно делает это в 50-100 раз медленнее. Но в инженерии и финансах лучше медленее чем платить потом за рухнувший мост или за неправильные проценты с бондов, которые рассчитываются ПОСУТОЧНО с сумм типа 200 миллиардов долларов.
В моей личной практике был случай когда баланс областного банка свалился из-за того, что неумёха программист не учёл что-то типа 18...19-разрядной десятичной точности большой суммы баланса области в копейках (купоно-гривня была около 5000 за доллар).
Сергей Подоляк, 
с заранее заданной точностью
тут-то вся и проблема, что точность у всех разная
Тимофей Мартынов, а Вы думаете КАК Ларри наш Эллисон стал миллиардером? В базах Ёракл такой проблемы нет. И поэтому Oracle покупают большие корпорации. Там десятки типов числовых данных и можно считать как угодно точно, хоть астрофизику.
Кстати Джеймс Саймонс из RenTec в одном из интервью сказал, что ЛУЧШЕ всех у него получается у АСТРОФИЗИКОВ.
У них нет проблем с пониманием как точно (насколько высоко точно) требуется высчитывать внутреннюю структуру ценовых рядов.
По Вашему случаю — я предложил наименее затратный и наиболее гибкий метод. Вы всегда сможете его потом заменить/поменять. прикрутить другой движок для расчётов, третий движок для отображения и тому подобное. Это профессиональный подход. Все остальные советы типа «про округление» загонят Вас чуть попозже в угол. И это будет негибко.
Даже в разных версиях компиляторов «от Микрософт» числа округляются по-разному, поэтому там есть опции компилятора типа /fp-precise. Лично я давно использую свою собственную функцию округления double.
Скажите о какой именно «разной точности» идёт речь? О полях в таблицах? Их можно по string хранить как угодно и преобразовывать тоже как угодно. Да, конечно, придётся написать 5-8 подпрограмм проверки ввода и конвертации в String данных. Уверяю Вас, в больших базах типа Oracle именно так и делается. Иначе не получится ни надёжности и цельности данных, ни точности. Там это встроено, а Вам придётся это написать самим. Вот и вся разница. Но Вы же профи, Тимофей. Ну так принимайте в работу проверенные методы профи.
Да, ещё снизится скорость лопачения данных. Но это же не проблема, не так ли?
Тимофей Мартынов, у других получается, как я понимаю
Как выше было правильно сказано, для денег нужен отдельный тип данных. Какой язык программирования-то?
Zweroboi, я подозреваю что JS.  У него есть проблемы с расчетами, так как нет 64 битного типа для цифр пока.
avatar
Zweroboi, не нужен никакой отдельный тип данных, задача решается с помощью целочисленного деления (если абстрагироваться от языка), если частное от целочисленного деления больше нуля при делителе скажем 0.001 (три знака после запятой) то округлять до 3 + 1 знака после запятой. Я удивлен что такая задача вызвала трудности у программистов )
avatar
evgen000, девочка что ты плетёшь ))) какой еще делитель 0.001 в целочисленном делении ))) гуманитарий?
Zweroboi, боже, откуда вы лезете. ru.wikipedia.org/wiki/Целая_часть 
avatar
в с++ есть функция для этого… задаешь число знаков после запятой и получаешь нужный резалт...
msdn.microsoft.com/ru-ru/library/ms131275(v=vs.110).aspx
avatar
Ну раз машина не знает, сколько в цене знаков после запятой, то нужно это указывать самому. Тогда и проблем не будет.
Акция, насколько я понимаю, описана классом. Ну так введите параметр — «разрядность».
avatar
Как изящно выйти из ситуации?
дать взятку программисту
или чего-нибудь ещё… покрепче… дать…
Speculator2016, пиздюлей ему дать…
avatar
храните как int + храните масштаб(INT)
avatar
Преобразовать в string, найти первое вхождение патерна '99' или '00', обрезать, применить функцию что-то типа: if '499' then '5'. Объединить. Работает в 4-6 раз быстрее чем при округлении.
MyKey, что за бред?
avatar
Coder, по существу есть что-нибудь? Если нет, в жопу иди.
MyKey, по существу можно только у виска покрутить. и в шею таких программистов гнать
avatar
Vitty, гони. У тебя же много рабочих из деревни. Я то тут ни на кого не работаю. Это был бесплатный совет. Мне ваша теория и высеры не нужны, я уже наобщался с еб*нутыми IT'шниками.
MyKey, совет насколько бесплатный, настолько и безграмотный. ну и да, с воспитанием у вас так же плохо, как и с познаниями в программировании.
avatar
Vitty, откуда вы знаете что у меня все плохо? Я же не предлагаю данные переформатировать ни на пути в БД, ни из БД; ковырять базу. А тем более в вебе использовать c++. Вы лучше за собой последите и фактами давайте общаться. Ваше личное мнение без них это просто высер. Я это уже писал, вы с первого раза не понимаете.
MyKey, 
Работает в 4-6 раз быстрее чем при округлении
как вы это померили? ассемблерных инструкций в вашем решении гораздо больше
avatar
Андрей К, а что, все инструкции неожиданно стали исполняться с одной скоростью?
а программер что-нибудь слышал про шаг цены актива? по нему пусть и округляет
avatar
число может храниться в любом виде, важно как его отображать — а это полностью во власти вашего программиста.
avatar
Для каждого инструмента хранить кол-во знаков после запятой, вроде иначе никак.
Собрали мы учёный совет. начали думать. Несколько дней думали, так и не нашли решения.
Смотреть в сторону целочисленного деления (выделения целой части).

avatar
if( price<0,01 ){price=price *1000;} может так обойти?
avatar
Округлять стандартно до 5 или даже 7 знаков. В данном примере получится 56,45.

avatar
Если цена хранится на стороне сервера в БД, то там тип Decimal отлично справится. При выборке из БД, для отображения на странице можно передавать сразу строковое значение (select string(<priceFieldName>)), чтобы не дать скрипту испохабить значение.
avatar
Проблема в том, что число знаков после запятой может быть и 5 (для некоторых акций).

Ну так и округляйте до 5 знаков. При этом варианты с числом знаков 4, 3,… тоже придут в норму. Например, 56,449999999999996 округлится до 56,45000. Если требуется отображать именно 56,45, то придется число знаков для каждого типа хранить.

P.s. Или изначально хранить числа в строках.

P.p.s. Максимум знаков после точки 5? Тогда можно умножать на 10000 и хранить в виде целых чисел.
avatar

считаешь % до которого хочешь округлить. Например, это 0,01%. Берешь цену актива, считаешь этот процент. Например цена 100, тогда шаг при заданной точности 0,01. 

Дальше считаешь число цифр после запятой. В питоне, например это можно сделать так, если наше число точности n. 

str(n).split(',')[-1].count('0')+1

или лучше +2 с запасом. 

 

avatar
несколько вариантов:
1. округлять по минимальному шагу цены для фьючерсов самое оно.
2. так же есть параметр «точность», показывает сколько знаков после запятой используется в инструменте.

не знаю как с буржуйским рынками а с нашими всё прекрасно округляется и ничего лишнего не рисуется.

программист скорей всего ваш, изначально этот момент не продумал, а теперь лень все формы переделывать.
avatar
Любое нецелое число можно представить дробью https://ru.wikipedia.org/wiki/%D0%94%D1%80%D0%BE%D0%B1%D1%8C_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)
Правда придется хранить 2 числа (числитель и знаменатель), но зато не будет копиться ошибка при вычислениях  - все операции проводите с дробью, пользователю отображаете округленный результат.
avatar
Toras, любое? Даже пи? )))

Zweroboi, А почему нет?
sortarray sortarray, по определению. Число пи представляется не дробью, а только и исключительно греческой буковкой. По крайней мере, в мирное время
Zweroboi, это зависит от требуемой точности
22/7 подходит
sortarray sortarray, убедил!
А что за язык такой, не увидел в описании?
Какие препараты употребляет твой прогрммист?
avatar
Есть фильмец про это и книжка «Стальная крыса» там рассказывается что на этом банки делают огромные деньги, на вот этих тысячных и миллионных после запятой ))
Можно считать сколько знаков, кроме нулей в начале числа можно отображать — например, 4 знака:
56.45 (4 знака, в начале числа нет нулей)
0.001655 (3 нуля в начале числа, дальше 4 знака после нулей)
avatar
Кстати, а чем sprintf('%.10g', PX), например, не устраивает? Кажись, будет делать ровно то, что надо, нет?
avatar
Жжоте. Вариантов тьма, я думаю, даже нагуглить легко. Если ЯП обычный, без изысков.
avatar
хранить как longint + разрядность. так цена 54,55 будет храниться как 5455 и 2, что означает 5455/(10^2). а цена 0.01655 как 1655 и 5 (т.е. 1655/(10^5))
avatar
Проблема какая-то надуманная. Решить е1 можно десятком способов от использования правильного типа (мы же не знаем на каком языке вы там «ваяете»), использование сторонних библиотек для js https://mikemcl.github.io/decimal.js/ , хранение данных в виде строки (что у вас за БД?),  преобразование до целого числа (допустим умножение на миллион) при отображении деление и отбрасывание нулей после запятой… и еще дальше можно развивать фантазию…
avatar
Тимофей, давайте сделаем смартлаб open source.
avatar
Тимофей, Вы решили детской задачкой сделать перепись программистов на смартлабе? Не верю вам, что программист не смог сделать.
avatar
Электромонтёр, я думаю это Тимофей коде своего так троллит :-)
avatar
Используй целые числа и отдельно рисуй запятую

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

То есть 56,45 хранить как 5645 и рядом число 2. Отображать ессно как 56,45 везде.

avatar
Хранить данные не в формате float а decimal с нужной разрядностью (6 или 7 знаков после запятой). А уже при отображение на сайте обрезать не значающиеся  нули после запятой. 
Тип данных float не предназначен для финансовых расчетов.
avatar
Проблема в том, что число знаков после запятой может быть и 5 (для некоторых акций).
Поэтому просто округлить все числа до 2 знаков после запятой не получится.
Как изящно выйти из ситуации?

Все просто.
Если в некоторых акциях до 5-ти знаков после запятой, то и округляйте до пятого знака. Остальные сами округлятся.

 56,449999999999996 =  56,44999 +0.00001 = 56,45

Либо до 8-го… результат будет тот же. А программист какбэ удивляет…
avatar


avatar
Короче. Как программист программисту, есть два способа:
1) Использовать тип Decimal, Money, Короче тот который использует для хранения BCD формат.
2) Для каждого инструмента ввести хранить шаг цены — для всех валют обычно 4 знака, бонды и акции — обычно 6. 
avatar
Если бы программисты строили тюрьму:

КАЖДЫЙ ДЕНЬ В ТЮРЬМУ ЗАВОЗЯТ -10 ЗАКЛЮЧЕННЫХ
ПРИ НЕХВАТКЕ ЗАКЛЮЧЕННЫХ — ПРОИСХОДИТ ОТКАТ К СОСТОЯНИЮ В ПРОШЛОМ МЕСЯЦЕ
ЗЕК-ВЕТЕРАН, ПОЛУЧИВШИЙ ТАКИМ ОБРАЗОМ 50 ХОДОК, ТРАНСФОРМИРУЕТСЯ В ОФИЦЕРА.
ЕСЛИ ОФИЦЕРОВ БОЛЬШЕ, ЧЕМ ЗЕКОВ, ТО ВЕСЬ ПЕРСОНАЛ СТАНОВИТСЯ ЗАКЛЮЧЕННЫМИ
avatar

У Вас ведь есть инфа по всем акциям с указанием шага цены?

Перед выводом округлять до шага цены.

Например, ШЦ = 0.01 --> выводить с точностью до 2-х знаков.

Если ШЦ = 0.0005 --> выводить с точностью до 4-х знаков.

Ещё в сишарп отлично работает что-то типа:

double roundedPx = PriceStep * Math.Round(px / PriceStep);

После такого преобразования подобные проблемы точности выравниваются.

avatar
 кажись понятно почему бардак в медицине  …
Вот вы все смеетесь, а в квике немного другая проблема, но тоже с округлением, не везде решена — в «состоянии счета» «балансовая цена» нифига не округляется. Но вопрос, само собой, выеденного яйца не стоит — количество знаков в цене конкретной бумаги заранее известно, и даже если не известно — можно предположить, что не больше 5.
Найти нормального прогера.
Тимофей, эту задачу должен решать разработчик а не ты. Если не может — найти другого. Все задачи решаемые! Например, можно хранить цену и точность цены. Затем имея точность ява скриптом на месте делать цифру нужную, конвертировать в строку и показывать.
avatar
Дели М на 10 Е раз, будет ровно то, что ты хочешь. Если бы вопрос стоял, как получить результат быстрее всего, коментариев тут было бы еще больше. Но поскольку для тебя скорость не важна, все заметно упрощается.
avatar
Cristopher Robin, что на что делить? можно пример?
Мдаа, почитал советы и понял, пока такие советчики есть, работы у меня всегда будет много :-) Лишь пара норм советов была.Явно же написали суть проблемы, даже читать ТЗ не могут правильно. Чего только не насоветовали и стринги обрезать и пользоваться встроенными средствами С для округления, нюню))) Большая половина не работала никогда с котировками в онлайне, к примеру с Финама или ЯхуФинанса, повеселились бы.Допустим число требует округления до 2 знаков после запятой:
  1. На входе 0,314, округляем до двух, получаем 0,31. Ура, Круто. А если шаг торгов 0,05? :-) Это касается и любителя предлагать printf("%.2f", num); — и типо все номано будет :-) Любитель Оракла, округли 0,359 до двух разрядов мега средствами если шаг 0,05 и он еще и варьируется ;-)
  2. Для любителя стрингов — мелькал тут, а проделай ка такую операцию хотя бы с одной акцией как ты советовал, если при считывании Стринга в дабл ты получить можешь для одного SECCODE 127, ;127,1; 127,11 ;-)
  3. Можно намоделировать массу примеров когда позиция, к примеру тейк профит  будет при разных вариациях математического или бухгалтерского округления выдавать «шляпу».
 Сам недавно решал эту проблему. Стандартные округления тут не подойдут, потому как помимо количества знаков после запятой есть еще и шаг торговли а он может быть для одного количества после запятой разным, поэтому формулы тут нет. И нагуглить «легко» не получится, т.к. это округление специфичное. Поэтому писаки сверху пальцем в небо тычут, тк результатом округления может стать х, ххх1 что не будет в некоторых случаях удовлетворять шагу торгов либо x,xxx5, что не позволит тебе закрыть позицию там где бы ты хотел. Поэтому, все советы которые предлагают для старта брать число СЖИГАЙ, по числу тут ничего не поймешь, для старта надо брать код бумаги.
1. Необходимо формировать массив (к примеру) по бумагам, загрузить его легко, в Квике есть к примеру текущая таблица параметров содержащая, Код площадки, Код бумаги, количество знаков после запятой, шаг. Вот такой вот вид у нее:+МосЭнерго,TQBR,MSNG,,52848350,,2.3120,,,,,0.0000,0.0000,,,2079,,,1000,,,AGRO-гдр,TQBR,AGRO,,17453854,,610,,,,,0,0,,,439,,,1,,,Polymetal,TQBR,POLY,,38150043,,728.5,,,,,0.0,0.0,,,1681,,,1,,, Далее делай с ней что хочешь, парси а потом  хош в массив загрузи и поиском по нему бегай, хош бегай поиском сразу по файлу(это нехорошо).2. Пишите функцию которой отправляете Площадку, Код бумаги, число а она  возвращает число необходимое для подстановки по параметрам из массива.  Пишется все за один рабочий день, два часа кода, 6 часов тестов :-). Можно и без площадки, я передаю ее на всякий случай, мало ли SECCODE совпадет с каким то на амер бирже. У меня ежесекундно тащатся порядка 120 инструментов, на каждом по 3 таймфрейма (итого 360 файлов), поиск и подстановка нужных чисел занимает для всей этой фермы секунды (использую массив). 2 ядра на сервере не быстрых и 2 Гб оперативки, диски обычные.
 PS. Привет ребятам с Финама отвечающим за котировки. Откройте хотя бы один день то что вы выгружаете по Татнефти.Но спасибо Вам за информацию :-) И за знания которые пришлось приобрести. Что делать с программистом я не знаю, но совета что делать с ним и не спрашивали.
avatar

Возможно ты его не так уговариваешь. намекни что за такое и уволить бездельника можно! )

Кстати может это знак что нужно продавать а не покупать? )

avatar

теги блога Тимофей Мартынов

....все тэги



UPDONW
Новый дизайн