Лучший способ хранения значений валюты в С++

Я знаю, что float не подходит для хранения значений валюты из-за ошибок округления. Существует ли стандартный способ представления денег на С++?

Я посмотрел в библиотеку boost и ничего не нашел. В java кажется, что BigInteger - это способ, но я не смог найти эквивалент в С++. Я мог бы написать свой собственный денежный класс, но предпочитаю не делать этого, если что-то проверено.

Ответ 1

Не храните его точно так же, как цента, так как вы аккумулируете ошибки при умножении на налоги и проценты довольно быстро. По крайней мере, сохраните еще две значащие цифры: 12,45 долларов США будут храниться как 124 500. Если вы сохраните его в 32-битном целочисленном значении, у вас будет 200 000 долларов для работы (положительных или отрицательных). Если вам нужны большие числа или больше точности, подписанное 64-битное целое число, вероятно, даст вам все необходимое пространство в течение длительного времени.

Возможно, это поможет обернуть это значение в классе, чтобы дать вам одно место для создания этих значений, сделать арифметику на них и форматировать их для отображения. Это также даст вам центральное место для хранения той валюты, которую она хранит (USD, CAD, EURO и т.д.).

Ответ 2

Имея дело с этим в реальных финансовых системах, могу вам сказать, что вы, вероятно, хотите использовать число с не менее 6 десятичными знаками точности (при условии, что доллар США). Надеюсь, что, поскольку вы говорите о валютных ценностях, вы здесь не пойдете. Есть предложения по добавлению десятичных типов в С++, но я не знаю ни одного, что на самом деле там пока.

Лучший родной тип С++ для использования здесь будет длинным.

Проблема с другими подходами, которые просто используют int, заключается в том, что вам нужно хранить больше, чем только ваши центы. Часто финансовые транзакции умножаются на нецелые значения, и это приведет к неприятностям, поскольку 100.25 долларов, переведенных на 10025 * 0.000123523 (например, APR), вызывает проблемы. Вы в конечном итоге окажетесь в плавающей точке, и конверсии будут стоить вам дорого.

Теперь проблема не возникает в большинстве простых ситуаций. Я дам вам точный пример:

Учитывая несколько тысяч значений валюты, если вы умножаете каждый на процент, а затем добавляете их, у вас будет другое число, чем если бы вы умножили общее количество на этот процент, если вы не сохранили достаточное количество знаков после запятой. Теперь это может сработать в некоторых ситуациях, но вы часто будете несколько пенни довольно быстро. В моем общем опыте убедитесь, что вы сохраняете точность до 6 знаков после запятой (убедитесь, что остальная точность доступна для всей части номера).

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


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

Ответ 4

Самая большая проблема заключается в скруглении!

19% от 42,50 € = 8,075 €. Из-за немецких правил округления это 8,08 €. Проблема в том, что (по крайней мере, на моей машине) 8 075 не может быть представлен как двойной. Даже если я изменю переменную в отладчике на это значение, я получаю 8 0749999....

И здесь моя функция округления (и любая другая логика с плавающей запятой, о которой я могу думать) терпит неудачу, так как она производит 8,07 €. Значимая цифра равна 4, поэтому значение округляется вниз. И это неправильно, и вы ничего не можете с этим поделать, если вы не сможете использовать значения с плавающей запятой, где это возможно.

Он отлично работает, если вы представляете 42,50 € как Integer 42500000.

42500000 * 19/100 = 8075000. Теперь вы можете применить правило округления выше 8080000. Это легко может быть преобразовано в значение валюты для отображения причин. 8,08 €.

Но я всегда буду обертывать это в классе.

Ответ 5

Я бы предположил, что вы храните переменную для количества центов вместо долларов. Это должно устранить ошибки округления. Отображение его в формате стандартов доллар/цента должно рассматриваться как проблема.

Ответ 6

Какой бы тип вы ни выбрали, я бы рекомендовал его обернуть в "typedef", чтобы вы могли изменить его в другое время.

Ответ 7

Знайте свой диапазон данных.

Поплавок хорош только для 6-7 цифр точности, поэтому это означает максимум около -9999,99 без округления. Это бесполезно для большинства финансовых приложений.

Двойник хорош для 13 цифр, таким образом: + -99,999,999,999.99, Будьте осторожны при использовании больших чисел. Признать вычитание двух аналогичных результатов удаляет большую часть точности (см. Книгу по численному анализу для потенциальных проблем).

32-битное целое хорошо + -2 миллиарда (масштабирование до пенни упадет на 2 десятичных знака)

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

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

Ответ 8

Вы можете попробовать десятичный тип данных:

https://github.com/vpiotr/decimal_for_cpp

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

Это решение только для заголовков для С++.

Ответ 9

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

Ответ 10

Ты говоришь, что посмотрел в библиотеку форсирования и ничего не нашел. Но там вы multiprecision/cpp_dec_float, который гласит:

Радиус этого типа равен 10. В результате он может вести себя по-разному по сравнению с базовыми типами.

Итак, если вы уже используете Boost, это должно быть хорошо для валютных значений и операций, поскольку его базовый номер 10 и точность 50 или 100 цифр (много).

См:

#include <iostream>
#include <iomanip>
#include <boost/multiprecision/cpp_dec_float.hpp>

int main()
{
    float bogus = 1.0 / 3.0;
    boost::multiprecision::cpp_dec_float_50 correct = 1.0 / 3.0;

    std::cout << std::setprecision(16) << std::fixed 
              << "float: " << bogus << std::endl
              << "cpp_dec_float: " << correct << std::endl;

    return 0;
}

Вывод:

float: 0.3333333432674408

cpp_dec_float: 0.3333333333333333

* Я не говорю, что float (base 2) плохой и десятичный (база 10) хорош. Они просто ведут себя по-другому...

** Я знаю, что это старый пост и boost:: multiprecision был введен в 2013 году, поэтому хотел бы отметить его здесь.

Ответ 11

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

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

Ответ 12

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

Очень важно: не забудьте указать все ваши валютные значения в соответствии с тем, что они на самом деле содержат. (Пример: account_balance_cents) Это позволит избежать множества проблем по линии.

(Другой пример, где это происходит, - это проценты. Никогда не называйте значение "XXX_percent", когда оно фактически содержит коэффициент, не умноженный на сто.)

Ответ 13

В библиотеке GMP реализованы реализации "bignum", которые можно использовать для вычислений целочисленного размера, необходимых для работы с деньгами. См. Документацию для mpz_class (предупреждение: это ужасно неполно, хотя предоставляется полный диапазон арифметических операторов).

Ответ 14

Один из вариантов заключается в том, чтобы хранить $10.01 как 1001 и делать все вычисления в гроши, делясь на 100D при отображении значений.

Или, используйте поплавки и только раунд в последний возможный момент.

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

Вместо значения *.10 за 10% скидку используйте (значение * 10)/100, что значительно поможет. (помните, что .1 - повторяющийся двоичный файл)

Ответ 15

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

Ответ 17

Решение прост, сохраняйте любую точность, как сдвинутое целое число. Но при чтении в конвертировании в двойной float, так что расчеты имеют меньше ошибок округления. Затем, когда хранение в базе данных умножается на любую целую точность, но перед усечением как целое число добавляет +/- 1/10 для компенсации ошибок усечения или +/- 51/100 для округления. Легкий peasy.

Ответ 18

Я бы использовал подписанный long для 32-битного и подписанного long long для 64-битного. Это даст вам максимальную емкость для самого базового количества. Тогда я бы разработал два пользовательских манипулятора. Один, который преобразует это количество, основанное на обменных курсах, и тот, который форматирует это количество в выбранной вами валюте. Вы можете разработать больше манипуляторов для различных финансовых операций и правил.

Ответ 19

Сохраните сумму доллара и цента в виде двух отдельных целых чисел.