Comments 20
Что есть "огрубления результатов в operator std::string() const" ?
Если результат произведения дробной части на вес разряда не помещается в uint64_t, то происходит вот это
while ((std::numeric_limits<uint64_t>::max() / current_fraction) < current_unit)
{
uint8_t least_bit = current_fraction & least_bit_mask;
current_unit /= 5;
current_fraction = (current_fraction + least_bit)/ 2;
}
С фиксированной запятой, а не точкой
А BCD в C++ уже отменили? Насколько помню, арифметика с фиксированной точкой реализуется через BCD.
И еще. В случае переполнения, по-хорошему, нужно бы исключение генерировать. Ибо дальше результат становится непредсказуемым и некорректным.
а) не отменили, не знаю кто так реализовывает - будет неудобно
б) при работе с каким-нить int и его переполнением компилятор вам ничего не скажет, во всяком, без включения санитайзеров, я тоже решил так не делать - можно ли это сделать? конечно, и не особо сложно
не отменили, не знаю кто так реализовывает - будет неудобно
Дело в том, что современные процессоры, если не ошибаюсь, BCD арифметику поддерживают.
Насчет "кто" - например, IBM. Тип DecimalT<n,m>, совпадающий с SQL типом DECIMAL(N,M) реализован именно через BCD.
И это будет ничуть не неудобнее, чем вот это все вот что выше.
при работе с каким-нить int и его переполнением компилятор вам ничего не скажет
Вы считаете это нормальным? Я - нет. Потому что результат дальнейших вычислений становится некорректным, а вы про это знать не знаете.
А поскольку fixed point широко используется в финансовых расчетах, к вам очень быстро придут с претензией - "у меня на счете должно быть 1 000 000 000 а по факту всего 1 000 - где остальное?" И там "компилятор мне ничего не сказал" за отмазку не сканает, поверьте...
а) если вы хорошо разбирайтесь в теме - давайте ссылки где написано, что этот Decimal реализован через BCD. Подозреваю, что там сделано так из-за изначальной заточенности именно под десятичную систему счисления, я себе таких ограничений не ставил. Современные процессоры обычную двоичную арифметику, тоже поддерживают, кстати
б) проблема мне понятная и известная, но выберите за базовый тип достаточно жирный что б избежать переполнения
в) статья носит иллюстративный характер, главный посыл: "есть double/float они не очевидные и сложно устроены, а смотрите как можно было б"
если вы хорошо разбирайтесь в теме - давайте ссылки где написано, что этот Decimal реализован через BCD
Я в этой систем работаю. IBM i.
Чтобы понять как это работает, достаточно посмотреть что такое BCD и что такое packed decimal у IBM (это тот самый DECIMAL в SQL, _DecimalT<> в C++) Улавливаете сходство (мягко говоря)?
Современные процессоры обычную двоичную арифметику, тоже поддерживают, кстати
Речь о том, для современного процессора вы можете написать реализацию, которая будет работать с фиксированной точкой (BCD) без конвертаций туда-сюда, на уровне инструкций процессора.
Т.е. не эмуляцию, а полноценную реализацию. Что и реализовано у IBM (правда, мы не с x86 работаем, а с рисковыми Power процессорами - подозреваю, что там возможности процессора по работе с BCD побогаче). Т.е. там нет каких-то специальных библиотек. Все это поддерживается на уровне "машинных инструкций" (низкоуровневые примитивные команды)
Там, кстати, еще есть полезные вещи - операции с округлением. Скажем, есть у вас есть два переменных:
A типа decimal(5.3)
и
B типа decimal(4,2)
Так вот, если A = 3.245, то присвоение B = A без округления даст B = 3.24, а присвоение с округлением - B = 3.25
проблема мне понятная и известная, но выберите за базовый тип достаточно жирный что б избежать переполнения
Переполнение - нештатная ситуация. И может возникнуть вне зависимости от жирноты базового типа.
Вопрос в том - заметите вы его сразу, или потащите кривой результат дальше с видом что "так и надо"?
То, как это сделано в С++ не отмазка. Там много чего оставлено на усмотрение конечного разработчика.
Понимаете, автор не работал с системами IBM и с SQL тоже не каждый день работает, я предположил почему там используют BCD - в принципе, идея интересная (может я её даже реализую). Но очевидно, это не является каким-то единственно верным и божественным вариантом реализации. Далее, этот код не взят из какого-либо продукта, буду я контролировать переполнения или нет - мое дело, как автора, если вам хочется - скопируйте исходники и добавьте контроль переполнений.
Ну вообще я привык что когда что-то делается, оно делается под конкретные сценарии использования. А не "вааще шоб было".
Пример сценария я привел - работа с БД (поддержка типа DECIMAL в БД) и финансовые расчеты. Возможно, есть еще какие-то сценарии с которыми я лично не сталкивался.
Но если понятны сценарии, тогда можно уже рисовать как удобные для потенциальных сценариев API, так и эффективную в рамках данного сценария реализацию.
И да, мне хочется. Не то чтобы хочется, мне это необходимо чтобы (как писал выше) не иметь потенциальных проблем с клиентами банка (репутационные риски, жалобы и риски нежелательного внимания со стороны регулятора, штрафы и т.п.) Но, слава богу, у меня все это есть - я (мы) работаю с языком, где поддержка такой арифметики нативна - я языке есть соответствующие типы и все операции с ними (и в реализации С/С++ на этой платформе есть _Decimal для С и _DecimalT<n,p> для С++). И там да, переполнение всегда вызывает системное исключение, которое можно легко перехватить и обработать (а не обработаешь - ну упадет, оставив дампы и записи в joblog - будет "дефект промсреды").
Самое главное, какая производительность этого дела? Сравнима ли она с double, float? Было бы интересно это увидеть, иначе смысл этого всего немного теряется. И насколько быстро растёт погрешность, например, при выполнении редукции по сумме скажем 1000000 чисел?
Смотрите, замеров нету, но это в целом не очень простое дело, ведь вопрос на какой платформе мерить. Насчёт погрешностей - при суммировании их быть не может - может быть переполнение, но погрешности именно при суммировании взяться неоткуда. Но она может быть на умножение или делении.
не такое уж и сложное дело, берем обычный процессор, который у вас есть, далее пишем какой-нибудь не сложный вычислительный код и смотрим, сколько нужно времени для double, сколько для предложенной реализации. Если окажется, что предложенная реализация быстрее, но при этом выдает приемлемую точность, то можно было бы попробовать использовать этот тип в более серьезных приложениях. У меня интерес есть к этому, так как в CFG коде не всегда можно "нормально" перейти с FP64 на FP32, если бы получилось что то интересное с предложенным кодом, то можно было бы попробовать использовать его, так как точности в FP32 не всегда хватает.
Опробовать можно на методе Якоби, например, хотя бы на последовательной реализации. Например, на таком коде, нужно только выкинуть наши директивы распараллеливания, если смущают, а так - ни будут проигнорированы. http://dvmh-server.ddns.net:3000/Alexander_KS/SAPFOR/src/branch/master/dvm/tools/tester/trunk/test-suite/Performance/jac3d.cdv
Самое главное, какая производительность этого дела? Сравнима ли она с double, float?
Производительность в каких приложениях?
Все это делается не для производительности. А, например, для повышения устойчивости к накоплению ошибок округления. Для этого есть специальные тесты
Второй аспект - работа с БД. Там есть типы DECIMAL и NUMERIC. С фиксированной точкой. Широко используются в финансовых расчетах.
Если вам удастся хорошо реализовать "нативную" арифметику с этими типами, то производительность "на круг" будет выше - вам не потребуется каждый раз при чтении из БД конвертировать эти типы в плавающую точку, потом заморачиваться с ошибками округления, а потом конвертировать обратно чтобы записать в БД.
Т.е. вся эта история делается под конкретные сценарии.
У нас в коболе все флоаты это инт128 + точность. Если операция над числами разной точности, то конвертируем в максимальную из них. Числа максимум в 31 символ, но никто пока не жаловался
Имплементация чисел с фиксированной точкой (часть 2)