*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

пятница, 16 апреля 2010 г.

Можно ли memset'ить float и double?

В финансовой области постоянно приходится иметь дело с ценами, а цены удобно держать как float или double. Также финансовой сфере много старого когда, написанного на С или Фортране.

А в мире языке С практика инициализации структур нулем через memset является весьма распространенной и в целом не самой плохой практикой.

Вопрос: а что, если в структуре есть поля типа double или float. Что будет, если поля этих типов будут тупо забиты нулями, каково будет значение этих полей?

Для начала я проверил у себя на Солярисе и в Visual Studio 9 - все вроде нормально. После memset'а нулем и float и double тоже равны нулю.

Хотя в целом правильный ответ такой: если ваш компилятор гарантирует хранение вещественных чисел в форматe IEEE 754, то вы в безопасности. Если нет (стандарт языка не гарантирует, что должен использоваться именно IEEE 754), то могут быть неожиданности.

14 комментариев:

  1. Я видел платформу, где binary representation для float и double была не в IEEE формате.

    "Нулевое" значение памяти для float'а соответствовало -1.0f

    ОтветитьУдалить
  2. а классы memset-ить можно или нельзя?

    ОтветитьУдалить
  3. деньги с плавающей точкой... Поубывалбы

    ОтветитьУдалить
  4. bialix: нет, вот тут все четко: в С++ memset-ить можно только POD типы. Если в типе есть хоть минимальная примесь ООП, то нельзя делать никаких предположений о формате хранения типа в памяти, и тем более в эту память лазить руками. Например "struct A { int a; }" это POD-тип, а вот "struct A { public: int a; } - уже нет, хоть по сути одно и тоже.

    Конечно, во многих компиляторах будет работать memset для класса, но это не по страндарту.

    ОтветитьУдалить
  5. malgarr: А какие проблемы с хранением сумм с плавающей точкой? разве что сравнивать надо не через "==", а через "abs(a-b) < e". Просто float и double - это просто и быстро. Правильнее, конечно, делать свой класс для чисел произвольной длины и точности, но если надо обрабатывать огромное количество сумм (например, цен) в секунду, то тут уже надо идти на компромисс.

    ОтветитьУдалить
  6. А как решается проблема невозможности полностью точного представления десятичных дробей? Например, 0.3 это ж 0.2(9) даже в double. Всякие ошибки округления не мучают? Или у вас просто сравнение цен, без дальнейшей обработки их?

    ОтветитьУдалить
  7. @malgarr: согласен, даже double заставляет меня нервничать.

    Я у себя использую тип Currency (хранится в 64-бит целом, фиксированная точка, 4 десятичных знака после запятой). Скорость сравнения для целых, мне кажется, даже пошустрее double будет.

    ОтветитьУдалить
  8. Хранение цены как float дает следующее:
    - поддержка дробности как таковой
    - атомарность (float - это 4 байта, что на большинстве современных платформ атомарная единица), можно при многопотоковой обработке не использовать mutex'ы, "взятие" которых съедает все ухищрения по производительности. Атомарность double (8 байт) у нас на Sparc'ах не принимается. В double переводится только после критических по времени участков.

    Согласен, тут есть и плюсы и минусы.

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

    ОтветитьУдалить
  9. Использоване float и double для финансовых операций неверно! Ни float, ни double не могут представить елементарные 10 сентов (0.1)

    пример:

    float cent = 0.01f;
    if (cent < 0.01)
    std::cout << "cent < 0.01" << std::endl;
    if (cent > 0.01)
    std::cout << "cent > 0.01" << std::endl;
    if (cent == 0.01)
    std::cout << "cent == 0.01" << std::endl;

    Для большинства финансовых инструментов достаточно типов int или long long. При этом переменная должна хранить количество центов (или дробную часть центов в зависимости от нужной точности)

    Надеятся на атомарность действительных чисел - неправильно в принципе. И размер переменной тут непричём. Это уже оптимизация, а я очень сомневаюсь, что в конкретно вашем случае производительность была критической.

    ОтветитьУдалить
  10. Другой пример

    // we have 1 cent
    double cent = 0.01f;
    // we can use fantastic multiply machine to
    // reproduce it multiple tames
    double lot_of_cents = cent * 100000000;
    // lets put all big beg of our cents
    // to the bank account
    double account = lot_of_cents;
    // we can estimate that we already have
    // 1 million US on our account
    double million = 1000000;
    // lets try to use them
    account -= million;
    // now bank will check our account
    // to be sure we are even one
    double epsilon = 0.01; // too big epsilon !!!
    if (account < -epsilon)
    std::cout << "You are bankrupt" << std::endl;

    // Ups !!!
    // Don't use devices you are not sure about!
    // double and float are not good
    // for financial operations
    std::cout << "Account = " << account << std::endl;

    ОтветитьУдалить
  11. Все, сдаюсь! ;-)

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

    Скорее всего интерфейсы через float и double - это наследие Фортрана, на котором написана туча финансовой аналитики.

    ОтветитьУдалить
  12. Этот комментарий был удален автором.

    ОтветитьУдалить
  13. У нас именно аналитика. Там, где происходит исполнение трейдов, часто применяет вообще символьное представление при передаче сумм туда сюда. Например, один из самых распространенных протоколов в мире finance - FIX, вообще текстовый.

    ОтветитьУдалить
  14. float и double для аналитики вполне подходят. +/- два цента сути не меняют. Аналитика - это прогноз, он изначально неточен.
    А вот в бухгалтерии все центы надо учитывать точно. В бухгалтерии даже сложный процент считают по "неправильной" формуле.
    http://dxdy.ru/topic13064.html

    ОтветитьУдалить