Лучший способ хранить валютные значения в C ++ - PullRequest
54 голосов
/ 29 сентября 2008

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

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

Ответы [ 19 ]

30 голосов
/ 29 сентября 2008

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

Может оказаться полезным обернуть это значение в классе, чтобы дать вам одно место для создания этих значений, выполнения арифметических действий над ними и форматирования их для отображения. Это также даст вам центральное место для хранения валюты, в которой оно хранится (доллары США, канадские доллары, евро и т. Д.).

21 голосов
/ 29 сентября 2008

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

Лучший нативный тип C ++ для использования здесь будет long double.

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

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

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

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


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

20 голосов
/ 29 сентября 2008

Посмотрите на относительно недавнюю десятичную математическую библиотеку с плавающей запятой Intelr . Он специально для финансовых приложений и реализует некоторые из новых стандартов для двоичной арифметики с плавающей запятой (IEEE 754r) .

10 голосов
/ 08 июля 2010

Самая большая проблема - это само округление!

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

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

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

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

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

8 голосов
/ 29 сентября 2008

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

5 голосов
/ 29 сентября 2008

Знайте ВАШ диапазон данных.

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

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

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

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

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

5 голосов
/ 29 сентября 2008

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

5 голосов
/ 04 января 2011

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

https://github.com/vpiotr/decimal_for_cpp

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

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

4 голосов
/ 29 сентября 2008

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

3 голосов
/ 28 августа 2017

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

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

Так что, если вы уже используете 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 (база 2) плохая, а десятичная (база 10) хорошая. Они просто ведут себя по-разному ...

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

...