Почему double, а не decimal?

Почему double, а не decimal?
Atom
10/31/2010


Заинтересовал вопрос, почему параметры в double а не decimal, у которого фиксированная точка?


< 1 2 3 4  >
Mikhail Sukhov

Avatar
Date: 3/21/2011
Reply


Иванов Андрей

Не хотите разбираться с представлением чисел на компьютере, просто попробуйте. У вас число в примере четыре знака, представляя его в виде double или decimal, результат не изменится. Такие числа можно хоть в float хранить, с семью знаками, ничего не изменится. Просто смысла нет -- рантайм может его запросто хранить в 64 битах, хоть он и 32 бита, и процессор его наверняка будет считать в 64 битах.


Дело то не в максимальной размерности и точности после запятой. А в точности результирующего значения. Ему верить нельзя.

Иванов Андрей

Если я неправ, покажите код или псевдокод, который приводит к 100.99999 в double и не приводит к тому же в decimal.


Да легко:

Code
var priceF = 109.1;

for (var i = 0; i < 9; i++)
{
    priceF += 0.1 * i;
}

priceF /= 0.1;

if (priceF != 1127)
    throw new Exception();


Code

var priceD = 109.1m;

for (var i = 0; i < 9; i++)
{
    priceD += 0.1m * i;
}

priceD /= 0.1m;

if (priceD != 1127)
    throw new Exception();
Thanks:

Иванов Андрей

Avatar
Date: 3/22/2011
Reply


Поставьте не 0.1, а 0.01, посмотрите, что получится.
Угадаете, чему будет равно Math.Round(2.5)? А вы в ShrinkPrice используете Math.Round =) Причина проскальзывания скорее здесь, а не в использовании double.
Это особенности представления вещественных чисел на компьютере, плата за скорость.

Я не понимаю, чем именно вас спасёт представление 0.1 как 0.1, а не как 0.10000000000000001 -- получение среднего арифметического двух цен, например, приводит к ошибке округления, которая всё равно заставляет использовать вашу функцию при выставлении заявки. И не такая уж она тяжёлая, эта ShrinkPrice, потому что выставлять заявки надо очень редко по сравнению с тем, сколько надо считать до выставления заявки.
Thanks:

anothar

Avatar
Date: 3/22/2011
Reply


Иванов Андрей
Поставьте не 0.1, а 0.01, посмотрите, что получится.
Угадаете, чему будет равно Math.Round(2.5)? А вы в ShrinkPrice используете Math.Round =) Причина проскальзывания скорее здесь, а не в использовании double.
Это особенности представления вещественных чисел на компьютере, плата за скорость.

Я не понимаю, чем именно вас спасёт представление 0.1 как 0.1, а не как 0.10000000000000001 -- получение среднего арифметического двух цен, например, приводит к ошибке округления, которая всё равно заставляет использовать вашу функцию при выставлении заявки. И не такая уж она тяжёлая, эта ShrinkPrice, потому что выставлять заявки надо очень редко по сравнению с тем, сколько надо считать до выставления заявки.

Вот неплохой пост:http://stackoverflow.com/questions/803225/when-should-i-use-double-instead-of-decimal. В целом он повторяет Ваши мысли и мысли Михаила. Смысл примерно такой что для финансовых вычислений скорее всего больше подходит именно decimal(кроме тех случаев когда у Вас очень большой расчет) по той причине, которую указал Михаил, а для всех остальных double(или float)-но это как я понял.
Thanks:

Mikhail Sukhov

Avatar
Date: 3/22/2011
Reply


Иванов Андрей
Поставьте не 0.1, а 0.01, посмотрите, что получится.
Угадаете, чему будет равно Math.Round(2.5)? А вы в ShrinkPrice используете Math.Round =) Причина проскальзывания скорее здесь, а не в использовании double.
Это особенности представления вещественных чисел на компьютере, плата за скорость.


При торговле в реальном времени что double что decimal не несет никакой разницы в производительности. Но мне приходится каждый раз делать округление, так как я не могу гарантировать в коде, что double будет сформирован правильно. То, что при 0.01 мой пример работает, а при 0.1 не работает - это еще хуже, чем если бы не работало всегда. Я не могу однозначность сказать - все будет работать. Поэтому лишняя перестраховка в виде выравнивания цены.

Иванов Андрей

Я не понимаю, чем именно вас спасёт представление 0.1 как 0.1, а не как 0.10000000000000001 -- получение среднего арифметического двух цен, например, приводит к ошибке округления, которая всё равно заставляет использовать вашу функцию при выставлении заявки.


Если я вычисляю сред арифметическое, то я точно знаю, что нужно округлять. И я точно знаю, что код, подобный моему примеру, не требует округления. Главное для меня, точно знать как будет работать. Я уже довольно много натыкался на грабли, когда код не работал банально из-за неточности double. Часами выискивал проблему, которая может и вообще сегодня не проявиться. А такая бага самая худшая, когда то работает, то нет (ваш пример с ИИС).

Иванов Андрей

И не такая уж она тяжёлая, эта ShrinkPrice, потому что выставлять заявки надо очень редко по сравнению с тем, сколько надо считать до выставления заявки.


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

Иванов Андрей

Avatar
Date: 3/22/2011
Reply


Mikhail Sukhov
Иванов Андрей
Поставьте не 0.1, а 0.01, посмотрите, что получится.
Угадаете, чему будет равно Math.Round(2.5)? А вы в ShrinkPrice используете Math.Round =) Причина проскальзывания скорее здесь, а не в использовании double.
Это особенности представления вещественных чисел на компьютере, плата за скорость.


При торговле в реальном времени что double что decimal не несет никакой разницы в производительности. Но мне приходится каждый раз делать округление, так как я не могу гарантировать в коде, что double будет сформирован правильно. То, что при 0.01 мой пример работает, а при 0.1 не работает - это еще хуже, чем если бы не работало всегда. Я не могу однозначность сказать - все будет работать. Поэтому лишняя перестраховка в виде выравнивания цены.

В интернетах есть мнение, что стандарт IEEE 754 убьёт мир =)
http://www.yur.ru/science/computer/IEEE754.htm

Лишняя перестраховка в виде выравнивания цены не хуже лишней перестраховки в виде decimal =)

Quote:

Иванов Андрей

Я не понимаю, чем именно вас спасёт представление 0.1 как 0.1, а не как 0.10000000000000001 -- получение среднего арифметического двух цен, например, приводит к ошибке округления, которая всё равно заставляет использовать вашу функцию при выставлении заявки.


Если я вычисляю сред арифметическое, то я точно знаю, что нужно округлять. И я точно знаю, что код, подобный моему примеру, не требует округления. Главное для меня, точно знать как будет работать. Я уже довольно много натыкался на грабли, когда код не работал банально из-за неточности double. Часами выискивал проблему, которая может и вообще сегодня не проявиться. А такая бага самая худшая, когда то работает, то нет (ваш пример с ИИС).

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

Quote:

Иванов Андрей

И не такая уж она тяжёлая, эта ShrinkPrice, потому что выставлять заявки надо очень редко по сравнению с тем, сколько надо считать до выставления заявки.


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

У меня половину времени занимает чтение лога сделок из файла. На истории [20 декабря, сейчас] по GMKN всё примерно 5.5 секунд, из них секунды 3 это чтение истории из файлов, мегабайтов 90 где-то. Для одного дня программа дольше запускается, чем работает. Вполне возможно, что я использую какие-то очень простые инструменты.

На decimal уже переделали или только планы? Любопытно было бы узнать, как они работают против double. То есть, прогнать одну и ту же стратегию с double и с decimal на месяце сделок. И скорость интересна, и прирост денег на торговле 1 млн без плеча. Если мой способ не подходит, то какой хотите использовать для оценки эффективности перехода? Вам же надо как-то оценивать вложенный труд.

Уверен, что decimal не принесёт денег, зато принесёт хлопоты.
Thanks:

Mikhail Sukhov

Avatar
Date: 3/22/2011
Reply


Иванов Андрей
На decimal уже переделали или только планы? Любопытно было бы узнать, как они работают против double. То есть, прогнать одну и ту же стратегию с double и с decimal на месяце сделок. И скорость интересна, и прирост денег на торговле 1 млн без плеча. Если мой способ не подходит, то какой хотите использовать для оценки эффективности перехода? Вам же надо как-то оценивать вложенный труд.

Уверен, что decimal не принесёт денег, зато принесёт хлопоты.


Я уже переделал до 3.0 еще. Но это не выносил в публичную ветку. Получилось быстрее, потому что из своей стратегии я повыкидывал Shrink.
Thanks:

Иванов Андрей

Avatar
Date: 3/26/2011
Reply


Андрей Ефимов
Иванов Андрей
Поставьте не 0.1, а 0.01, посмотрите, что получится.
Угадаете, чему будет равно Math.Round(2.5)? А вы в ShrinkPrice используете Math.Round =) Причина проскальзывания скорее здесь, а не в использовании double.
Это особенности представления вещественных чисел на компьютере, плата за скорость.

Я не понимаю, чем именно вас спасёт представление 0.1 как 0.1, а не как 0.10000000000000001 -- получение среднего арифметического двух цен, например, приводит к ошибке округления, которая всё равно заставляет использовать вашу функцию при выставлении заявки. И не такая уж она тяжёлая, эта ShrinkPrice, потому что выставлять заявки надо очень редко по сравнению с тем, сколько надо считать до выставления заявки.

Вот неплохой пост:http://stackoverflow.com/questions/803225/when-should-i-use-double-instead-of-decimal. В целом он повторяет Ваши мысли и мысли Михаила. Смысл примерно такой что для финансовых вычислений скорее всего больше подходит именно decimal(кроме тех случаев когда у Вас очень большой расчет) по той причине, которую указал Михаил, а для всех остальных double(или float)-но это как я понял.

Не совсем так. "Финансы" это слишком обще.

Когда вам надо суммировать операции по счетам клиентов, то выбора нет, только decimal. Потому что представить в double 0.1 (10 копеек), например, невозможно. Когда у вас есть арифметика (считаете проценты, например), то double обычно точнее и всегда значительно быстрее. В случае с роботами суммирование операций это вспомогательные вычисления, основные как раз разнообразная арифметика. Мне так кажется =)

По сути, это вопрос религии или требования к производительности -- сложив три раза 1/3 в decimal получишь 0.9999999999, а в double 1.0, но сложив 10 раз 0.1 для decimal будет 1.0 и 0.999999999 для double. То есть, округлять надо независимо от выбора представления и результат будет приемлемый в любом случае. А если бездумно делить, повторюсь в который раз, decimal не поможет.

Mikhail Sukhov
Иванов Андрей
На decimal уже переделали или только планы? Любопытно было бы узнать, как они работают против double. То есть, прогнать одну и ту же стратегию с double и с decimal на месяце сделок. И скорость интересна, и прирост денег на торговле 1 млн без плеча. Если мой способ не подходит, то какой хотите использовать для оценки эффективности перехода? Вам же надо как-то оценивать вложенный труд.

Уверен, что decimal не принесёт денег, зато принесёт хлопоты.


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

Хорошо. "Посмотрим, что скажет стая" =)
Интересно будет послушать про результаты -- стало быстрее и/или больше денег. Может быть выложить параллельно версию с decimal?
Thanks:

Иванов Андрей

Avatar
Date: 3/26/2011
Reply


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

Было
Code

public static double ShrinkPrice(this Security security, double price, ShrinkRules rule = ShrinkRules.Auto)
{
if (security == null)
throw new ArgumentNullException("security");
decimal num = (decimal) price;
decimal minStepSize = (decimal) security.MinStepSize;
if (minStepSize == 0M)
throw new ArgumentException("trololo");
return (double) num.Round(minStepSize, security.Decimals, ((rule == ShrinkRules.Auto) ? null : new MidpointRounding?((rule == ShrinkRules.Less) ? MidpointRounding.AwayFromZero : MidpointRounding.ToEven)));
}

Стало
Code

public static double ShrinkPrice(this Security security, double price, ShrinkRules rule = ShrinkRules.Auto)
{
    if (security == null)
        throw new ArgumentNullException("security");
    if (price <= 0.0)
        throw new ArgumentException("Получена цена меньше либо равная нулю.", "price");
    if (security.MinStepSize <= 0.0)
        throw new InvalidOperationException("Невозможно выровнять цену для инструмента без установленного минимального шага цены."); // security.MinStepSize не параметр метода
    price /= security.MinStepSize;
    if (rule == ShrinkRules.Auto)
        price = Math.Round(price, MidpointRounding.AwayFromZero);
    else if (rule == ShrinkRules.More)
        price = Math.Ceiling(price);
    else if (rule == ShrinkRules.Less)
        price = Math.Floor(price);
    else
        throw new ArgumentOutOfRangeException("rule", rule, "Получено неизвестное правило округления.");
    price *= security.MinStepSize;
    return price;
}

Thanks:

anothar

Avatar
Date: 3/26/2011
Reply


Вот комментарий по производительности:Decimal perfomance
Если ему верить, то у decimal производительность не ахти. А что если внутри представлять все ввиде целого Int64? Ну а наружу-без разницы. ТО есть по сути будет такая структура: фиксированное число знаков после точки, которое зависит только от инструмента и значение ввиде Int64(например 0.0012-это четыре знака после точки и значение 12). Если Вы преимущественно складываете, делите и т.д в рамках одного инструмента то все ок, но насколько это извращенно? По сути это и есть идея того поста.
Thanks:

Иванов Андрей

Avatar
Date: 3/27/2011
Reply


Это не будет быстрее double =) Зато будет больше места для ошибок. И голову надо под такое перестраивать, как под ассемблер.

Если вам не нужна точность за вашими знаками (среднее арифметическое между 0.0012 и 0.0015 при использовании long что даст?), double будет работать не хуже. Чтобы накопить в double ошибку в четвёртом знаке, надо несколько миллионов или десятков миллионов операций. Ошибка быстро накапливается, если используются значения из разных диапазонов -- цена какого-нибудь ИнтерРАО с лотом 100 тысяч и объём торгов за час. То есть, отличаться они должны на несколько порядков. Чем больше порядков различие, тем быстрее накапливается ошибка.
Thanks:
< 1 2 3 4  >

Attach files by dragging & dropping, , or pasting from the clipboard.

loading
clippy