Я знаю, что число с плавающей запятой не подходит для хранения значений валюты из-за ошибок округления. Есть ли стандартный способ представления денег на C++?
Я поискал в библиотеке ускорения и ничего не нашел. В java кажется, что BigInteger - это способ, но я не смог найти эквивалента в C++. Я мог бы написать свой собственный денежный класс, но предпочел бы не делать этого, если есть что-то протестированное.
@Offirmo: Верно. Однако, если вы выполняете финансовые вычисления, множество ошибок может быть связано с тем, что десятичные валюты должны быть преобразованы в двоичные валюты.





Это зависит от требований вашего бизнеса в отношении округления. Самый безопасный способ - хранить целое число с требуемой точностью и знать, когда и как применять округление.
Это будет дорого стоить с точки зрения проблем с конвертацией. Вы будете выполнять преобразование каждый раз, когда будете делать что-либо со значением, поскольку маловероятно, что каждое значение с плавающей запятой в системе будет таким целым числом.
Как и в моем ответе, точность значения равна точности наименее точного вычисления. Integer * Float будет использовать точность с плавающей запятой. Для C++ вся цепочка должна быть длинной двойной точности.
Что вы, кажется, не понимаете, Orion заключается в том, что не все значения могут быть сохранены в float. Таким образом, небольшие странные математические ошибки могут закрасться в ваши вычисления, если вы не знаете, где и когда вы выполняете округление, чтобы исправить ошибки.
Я бы посоветовал вам сохранить переменную для количества центов, а не долларов. Это должно устранить ошибки округления. Отображение его в стандартном формате долларов / центов должно вызывать беспокойство.
На самом деле это не решает проблему, поскольку вам часто приходится делать больше, чем просто прибавлять к этим числам, и тогда у вас могут возникнуть проблемы, поскольку вы потеряете точность. 100,25 доллара в переводе на 10025 * 0,0745234 APR вызовут проблемы.
Если я правильно помню, где-то есть стандарт, который гласит, что вы должны хранить как минимум 4 цифры для общих операций - поэтому «Валюта» COM дала вам 4. Если речь идет о иностранной валюте, вам, вероятно, понадобится больше.
В своем ответе на этот вопрос я объяснил проблему наименьшей точности в точных вычислениях. В конечном итоге, даже если вы сохраните число в целочисленной форме, вам придется производить вычисления в другом месте. Что бы это ни было, должно быть механизмом хранения.
@Joe: 4 знака после запятой - это на самом деле минимум. Я закончил использовать 6 для своих расчетов, чтобы получить пенсовое разрешение при проверке операций. Но если вы не сделаете всю свою математику в целочисленной форме, у вас будут проблемы, потому что, если вы приведете (неявно или явно), вы попадете в страну с плавающей запятой.
Разобравшись с этим в реальных финансовых системах, я могу сказать, что вы, вероятно, захотите использовать число с точностью не менее 6 десятичных знаков (при условии, что это доллар США). Надеюсь, раз уж вы говорите о валютных ценностях, вы не выйдете из строя. Есть предложения по добавлению десятичных типов в C++, но я пока не знаю ни одного из них.
Лучшим родным типом C++ для использования здесь будет long double.
Проблема с другими подходами, в которых просто используется int, заключается в том, что вам нужно хранить не только свои центы. Часто финансовые транзакции умножаются на нецелочисленные значения, и это доставит вам неприятности, поскольку 100,25 доллара, переведенные в 10025 * 0,000123523 (например, годовая процентная ставка), вызовут проблемы. В конечном итоге вы попадете в страну с плавающей запятой, и преобразования будут вам дорого стоить.
Теперь проблема не возникает в самых простых ситуациях. Приведу точный пример:
Учитывая несколько тысяч денежных значений, если вы умножите каждую на процент, а затем сложите их, вы получите другое число, чем если бы вы умножили общую сумму на этот процент, если у вас недостаточно десятичных знаков. В некоторых ситуациях это может сработать, но зачастую вы довольно быстро получите несколько копеек. По моему общему опыту, я должен убедиться, что вы сохраняете точность до 6 знаков после запятой (следя за тем, чтобы оставшаяся точность была доступна для всей числовой части).
Также поймите, что не имеет значения, с каким типом вы его храните, если вы делаете математику менее точным образом. Если ваша математика выполняется в земле с одинарной точностью, то не имеет значения, храните ли вы ее с двойной точностью. Ваша точность будет верной до наименее точного расчета.
С учетом сказанного, если вы не выполните никаких математических вычислений, кроме простого сложения или вычитания, а затем сохраните число, тогда все будет в порядке, но как только появится что-то более сложное, у вас будут проблемы.
Не могли бы вы расширить свое возражение на ints или дать ссылку? Приведенный вами пример расчета приводит к результату 0,01 доллара США или 1 с использованием целых чисел. Для меня не очевидно, почему это неправильный ответ.
См. Пример выше. Я могу предоставить больше, но в этой ситуации обычно все довольно просто. Я написал программу для финансового прогнозирования, и целые числа и округление никуда не денутся. Вам нужно хранить не только центы, но и дробные центы. В конце концов, вы столкнетесь с проблемами округления.
Я написал некоторое программное обеспечение для точек продаж, и мое решение этой проблемы (выраженное как сумма (скидки-на-позиции)! = Скидка-на-заказ-итого) состоит в том, чтобы убедиться, что вы всегда выполняете расчет, который ты имеешь в виду. Пространство проблемы должно диктовать суммирование небольших процентов или процентов от суммы.
@ Джеффри (и другие) - В дополнение к тому, что уже сказал Орион, финансовые системы должны иметь возможность справляться с очень широким диапазоном чисел. Акции на фондовых рынках (и, в частности, курсы валют) рассчитываются с точностью до долей пенни (0,000001 доллара США), в то время как другие валюты, такие как доллар Зимбабве, испытывают гиперинфляцию (en.wikipedia.org/wiki/Zimbabwean_dollar#Exchange_rate_histo ry) до такой степени, что даже системы, работающие с двойными ставками, не могут справиться с большими используемые значения. Поэтому использование int, long int и т. д. Действительно не вариант.
Я бы рекомендовал использовать long int для хранения валюты наименьшего достоинства (например, американские деньги будут центами), если используется десятичная валюта.
Очень важно: обязательно назовите все ваши денежные значения в соответствии с тем, что они на самом деле содержат. (Пример: account_balance_cents) Это позволит избежать многих проблем в будущем.
(Другой пример, когда это происходит, - это проценты. Никогда не называйте значение «XXX_percent», если оно фактически содержит отношение, не умноженное на сотню.)
Посмотрите на относительно недавний Математическая библиотека Intelr Decimal с плавающей запятой. Это специально для финансовых приложений и реализует некоторые из новые стандарты двоичной арифметики с плавающей запятой (IEEE 754r).
Какой бы тип вы ни выбрали, я бы рекомендовал заключить его в «typedef», чтобы вы могли изменить его в другое время.
Учитывая, что typedef представляет только псевдоним и предоставляет вам возможность неявного преобразования чисел, я бы вместо этого упаковал его в класс.
Один из вариантов - сохранить 10,01 доллара как 1001 и производить все вычисления в пенни с делением на 100D при отображении значений.
Или используйте поплавки и округляйте их только в самый последний момент.
Часто проблемы можно смягчить, изменив порядок операций.
Вместо значения * .10 для 10% скидки используйте (значение * 10) / 100, что значительно поможет. (помните, что .1 - это повторяющийся двоичный файл)
Никогда не используйте поплавки. Попробуйте представить 0,60 доллара в виде числа с плавающей запятой. Финансовый код (также известный как код банка) не может иметь ошибок округления => нет числа с плавающей запятой.
0.6 не может храниться как float или double. Большинство реальных чисел не может быть, числа с плавающей запятой - это всего лишь приближения. Вот результат, который я получаю для пары чисел (0,6 и 8,075): float: 0.60000002384185791000 float: 8.07499980926513670000 double: 0.59999999999999998000 double: 8.07499999999999930000
Целые числа, всегда - сохраняйте их в виде центов (или в другой валюте, в которой вы программируете). Проблема в том, что независимо от того, что вы делаете с плавающей запятой, однажды вы обнаружите ситуацию, когда вычисления будут отличаться, если вы это сделаете. это с плавающей запятой. Округление в последнюю минуту - это не ответ, так как реальные расчеты в валюте округляются по ходу.
Вы также не можете избежать проблемы, изменив порядок операций - это не удается, когда у вас есть процент, который оставляет вас без надлежащего двоичного представления. Бухгалтеры взбесятся, если вы сэкономите ни копейки.
Наше финансовое учреждение использует «двойник». Поскольку мы являемся магазином с фиксированным доходом, у нас есть множество неприятных сложных алгоритмов, которые в любом случае используют double. Уловка состоит в том, чтобы быть уверенным, что ваша презентация для конечного пользователя не выходит за рамки точности double. Например, когда у нас есть список сделок на общую сумму в триллионы долларов, мы должны быть уверены, что не печатаем мусор из-за проблем с округлением.
Не храните их просто как центы, так как вы довольно быстро накапливаете ошибки при умножении на налоги и проценты. По крайней мере, оставьте две лишние значащие цифры: 12,45 доллара будут сохранены как 124 500. Если вы сохраните его в виде 32-битного целого числа со знаком, у вас будет 200000 долларов для работы (положительное или отрицательное). Если вам нужны большие числа или более высокая точность, 64-битное целое число со знаком, вероятно, даст вам все пространство, которое вам понадобится в течение длительного времени.
Может оказаться полезным заключить это значение в класс, чтобы дать вам одно место для создания этих значений, выполнения над ними арифметических операций и их форматирования для отображения. Это также даст вам центральное место для хранения хранимой валюты (доллары США, канадские доллары, евро и т. д.).
как вы попали на 2 000 000? вы можете сохранить до 2 миллиардов центов в 32-битном целом числе со знаком, что составляет около 20 миллионов долларов. отнимите 2 цифры для большей точности, и у вас останется около 200 тысяч долларов.
Насколько большим может удерживаться 64-битное целое число с использованием двух цифр дополнительной точности?
Кроме того, я вижу, что этот пост довольно старый, отражает ли он лучший способ хранения валюты? Или что-то было добавлено в C++ 14 и / или boost, что было бы лучше сейчас?
Напротив. Хранилище должно быть в центах, потому что не существует такого понятия, как суммы денег меньше цента. При расчетах следует следить за тем, чтобы правильно и своевременно использовать соответствующие типы и округлость.
Для центов @einpoklum требуется только до 2 знаков после запятой, но инвестиционные транзакции часто оперируют значениями от 4 до 6 знаков после запятой. Таким образом, для хранения может потребоваться более высокая точность, чем обеспечивает центов.
@RemyLebeau: Я этого не знал. Итак, я думаю, это зависит от того, что пытается сделать OP.
Знайте СВОЙ диапазон данных.
Число с плавающей запятой подходит только для точности от 6 до 7 цифр, так что это означает, что максимальное значение составляет около + -9999,99 без округления. Это бесполезно для большинства финансовых приложений.
Двойное значение подходит для 13 цифр, например: + -99 999 999 999,99. Тем не менее будьте осторожны при использовании больших чисел. Обратите внимание на то, что вычитание двух похожих результатов сильно снижает точность (см. Книгу по числовому анализу, чтобы узнать о потенциальных проблемах).
32-битное целое число хорошо равно + -2 миллиарда (при масштабировании до копейки 2 десятичных разряда упадут)
64-битное целое число будет обрабатывать любые деньги, но, опять же, будьте осторожны при преобразовании и умножении на различные ставки в вашем приложении, которые могут быть числами с плавающей запятой / удвоением.
Ключ в том, чтобы понять вашу проблемную область. Какие юридические требования у вас есть для точности? Как вы отобразите значения? Как часто будет происходить конверсия? Вам нужна интернационализация? Прежде чем принимать решение, убедитесь, что вы можете ответить на эти вопросы.
вперёд и напишите свой собственный класс денег (http://junit.sourceforge.net/doc/testinfected/testing.htm) или currency () (в зависимости от того, что вам нужно). и протестируйте это.
Самая большая проблема - это округление!
19% от 42,50 € = 8,075 €. Согласно немецким правилам округления это 8,08 €. Проблема в том, что (по крайней мере, на моей машине) 8075 не могут быть представлены как двойные. Даже если я изменю переменную в отладчике на это значение, я получу 8,0749999 ....
И здесь моя функция округления (и любая другая логика с плавающей запятой, о которой я могу думать) терпит неудачу, поскольку она дает 8,07 евро. Значащая цифра - 4, поэтому значение округляется в меньшую сторону. И это совершенно неправильно, и вы ничего не можете с этим поделать, если не избегаете использования значений с плавающей запятой везде, где это возможно.
Он отлично работает, если вы представите 42,50 € как целое число 42500000.
42500000 * 19/100 = 8075000. Теперь вы можете применить правило округления выше 8080000. Его можно легко преобразовать в денежное значение для отображения. 8,08 €.
Но я всегда заканчивал это на уроке.
Вы можете попробовать десятичный тип данных:
https://github.com/vpiotr/decimal_for_cpp
Предназначен для хранения денежных ценностей (денежный баланс, курс валюты, процентная ставка), точность, определяемая пользователем. До 19 цифр.
Это решение для C++ только для заголовков.
Решение простое, сохраните с любой требуемой точностью в виде сдвинутого целого числа. Но при чтении конвертировать в двойное число с плавающей запятой, чтобы в вычислениях было меньше ошибок округления. Затем при сохранении в базе данных умножьте на любую требуемую целочисленную точность, но перед усечением в виде целого числа добавьте +/- 1/10 для компенсации ошибок усечения или +/- 51/100 для округления. Очень просто.
Сохраните сумму в долларах и центах как два отдельных целых числа.
Почему голос против? Так хранят цены некоторые крупные финансовые учреждения. ; (
Я бы использовал длинную подпись для 32-битной и подписанную длинную для 64-битной. Это даст вам максимальную емкость для хранения самого основного количества. Затем я разработал два нестандартных манипулятора. Один конвертирует это количество на основе обменных курсов, а другой форматирует это количество в выбранную вами валюту. Вы можете разработать больше манипуляторов для различных финансовых операций / и правил.
Вы говорите, что заглянули в библиотеку ускорения и ничего там не нашли. Но у вас есть multiprecision / cpp_dec_float, который говорит:
The radix of this type is 10. As a result it can behave subtly differently from base-2 types.
Поэтому, если вы уже используете 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
* Я не говорю, что число с плавающей запятой (основание 2) - это плохо, а десятичное (основание 10) - это хорошо. Просто они по-другому ведут себя ...
** Я знаю, что это старый пост, а boost :: multiprecision был представлен в 2013 году, поэтому хотел бы отметить его здесь.
Для информации: нет более или менее ошибок округления при использовании двоичного или десятичного представления (см. 1/3 = 0,333 ...). Использование десятичного представления позволяет получить такие же ошибки округления, как если бы вы делали это вручную. (легче проверить / сопоставить результаты)