Mercurial > pecunia
view src/pecunia/Money.hpp @ 69:e106fadfad66
Selected the final name for the library and updated everything accordingly.
author | John Schneiderman <JohnMS@member.fsf.org> |
---|---|
date | Sun, 17 Jun 2018 08:18:48 +0200 |
parents | src/iso-currency/Money.hpp@b11cc8d9547e |
children | 924a06bf271d |
line wrap: on
line source
/******************************************************************************* *** This file is part of Pecunia. *** *** *** *** Copyright (C) 2016 *** *** John Schneiderman <Licensing _AT_ Schneiderman _DOT_ me> *** *** *** *** This program is free software: you can redistribute it and/or modify it *** *** under the terms of the GNU Lesser General Public License as published *** *** by the Free Software Foundation, either version 3 of the License, or *** *** (at your option) any later version. *** *** *** *** This program is distributed in the hope that it will be useful, but *** *** WITHOUT ANY WARRANTY; without even the implied warranty of *** *** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *** *** See the GNU Lesser General Public License for more details. *** *** *** *** You should have received a copy of the GNU Lesser General Public License*** *** along with this program. If not, see <http://www.gnu.org/licenses/>. *** *******************************************************************************/ #ifndef PECUNIA_CURRENCY_MONEY_HPP_ #define PECUNIA_CURRENCY_MONEY_HPP_ #include <cassert> #include <cstdint> #include <cmath> #include <functional> #include <iomanip> #include <istream> #include <limits> #include <locale> #include <ostream> #include <stdexcept> #include <string> #include <sstream> #include <type_traits> #include "Adjustments.hpp" #include "Codes.h" #include "MoneyManip.h" #include "MoneyTypes.h" #include "internal/Verification.hpp" namespace pecunia { namespace currency { /** * A representation of a country's currency supporting basic arithmetic usage and guaranteeing all * operations succeed without a loss of precision, overflow, or underflow. This version allows for * reading in an arbitrary currency. * * @tparam precision The number of digits after a minor unit to guarantee are always correct. * @tparam MinorUnit The storage type for the minor unit of the monetary value. This type must * be an unsigned internal type. * @tparam UnitStorage The internal storage container for the monetary and major value. * @tparam FloatingPoint The floating-point type to accept and convert into. This type must be * a floating-point type. */ template < std::uint8_t precision = 2, typename MinorUnit = std::uint16_t, typename UnitStorage = std::int64_t, typename FloatingPoint = double > class Money { static_assert(std::is_unsigned<MinorUnit>::value, "The MinorUnit must be unsigned."); static_assert(std::is_integral<MinorUnit>::value, "The MinorUnit must be an integer."); static_assert( std::is_floating_point<FloatingPoint>::value, "The FloatingPoint is not a floating-point type." ); /** * Calculates the largest storable monetary major value. */ UnitStorage maximumMajorValue(); /** * Calculates the smallest storable monetary major value. */ UnitStorage minimumMajorValue(); /** * Calculates the largest storable monetary minor value. */ MinorUnit maximumMinorValue(); /** * Calculates the largest storable monetary amount value. */ UnitStorage maximumAmountValue(); /** * Calculates the smallest storable monetary amount value. */ UnitStorage minimumAmountValue(); /// The entire representation of the monetary amount using the currency's minor unit, e.g. cents /// for USD, plus any extra digits of precision. UnitStorage amount_; /// The ISO 4217 currency code the monetary amount represents. Codes iso4217Code_; public: /** * Constructor for a fully formed object that initialises the stored amount to zero. * * @param[in] code The ISO 4217 currency code the amount represents. */ Money(const Codes code = Codes::XXX); /** * Constructor for setting an exact minor monetary value whose major value is zero. The minor * value is expected to be adjusted for the level of precision the object is expecting, e.g. * U.S.A. dollars with a precision of two would enter twelve cents as 1200. * * @pre When the minor value is zero, the sign must be Sign::Neither. * * @exception overflow_error When the minor unit is too large to be stored. * * @param[in] minor The amount of minor currency to store, e.g. U.S.A. cents. * @param[in] sign The associated signedness of the minor value, e.g. negative. * @param[in] code The ISO 4217 currency code the amount represents. */ Money(const MinorUnit minor, const MinorSign sign, const Codes code = Codes::XXX); /** * Constructor for setting an exact monetary value of a fully formed object. The minor value is * expected to be adjusted for the level of precision the object is expecting, e.g. U.S.A. * dollars with a precision of two would enter twelve cents as 1200. * * @pre The maximum minor value must fit within the MinorUnit. * * @exception overflow_error When the major unit is too large to be stored in a positive direction. * @exception overflow_error When the major unit is too small to be stored in a negative direction. * @exception overflow_error When the minor unit is too large to be stored. * * @param[in] major The amount of major currency to store, e.g. U.S.A. dollars. * @param[in] minor The amount of minor currency to store, e.g. U.S.A. cents. * @param[in] code The ISO 4217 currency code the amount represents. */ Money(const UnitStorage major, const MinorUnit minor, const Codes code = Codes::XXX); /** * Constructor for setting a monetary value using the native floating-point type. @note The * minor units in a floating-point value cannot be verified and will always fit within the * currency digits and precision specified, e.g. U.S.A. dollar value with a precision of 0 will * store the value 1.555 as 1.55 when no rounder is supplied. * * @exception overflow_error When the major unit is too large to be stored in a positive direction. * @exception overflow_error When the major unit is too small to be stored in a negative direction. * @exception domain_error When the conversion results in the floating-point value dividing by * zero. * @exception underflow_error When the floating-point value is too small to fit within the * FloatingPoint. * @exception overflow_error When the floating-point value is too large to fit within the * FloatingPoint. * @exception domain_error When the conversion results in the floating-point value not being * valid. * * @param[in] value The floating-point value to convert. * @param[in] code The ISO 4217 currency code the amount represents. * @param[in] rounder The method for rounding the number when converting the floating-point * number. */ Money( const FloatingPoint value, const Codes code, RounderFunction<FloatingPoint>&& rounder = [] (const FloatingPoint value, const std::uint8_t) { return value; } ); Money(const Money& other) =default; /** * Conversion operator to a native floating-point type. * * @exception domain_error When the conversion results in the floating-point value dividing by * zero. * @exception underflow_error When the floating-point value is too small to fit within the * FloatingPoint. * @exception overflow_error When the floating-point value is too large to fit within the * FloatingPoint. * @exception domain_error When the conversion results in the floating-point value not being * valid. */ explicit operator FloatingPoint() const; ~Money() =default; /** * Negation operator. * * @return The negated value of the current object. */ Money operator-() const; /** * Additive operator. When the supplied monetary object is of a different currency, the amount * is converted into the current object's currency using the pecunia::currency::converter function. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] other The additional value to add to the current object's value. * * @return A new object containing the summation of the two values. */ Money operator+(const Money& other) const; /** * Subtractive operator. When the supplied monetary object is of a different currency, the * amount is converted into the current object's currency using the pecunia::currency::converter * function. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] other The additional value to subtract from the current object's value. * * @return A new object containing the difference of the two values. */ Money operator-(const Money& other) const; Money operator*(const Money&) const =delete; /** * Multiplicative operator. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] value The additional value to multiply to the current object's value. * * @return A new object containing the product of the two values. */ Money operator*(const FloatingPoint value) const; /** * Multiplicative operator. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @tparam p The number of digits after a minor unit to guarantee are always correct. * @tparam MU The storage type for the minor unit of the monetary value. This type must * be an unsigned internal type. * @tparam US The internal storage container for the monetary and major value. * @tparam FP The floating-point type to accept and convert into. This type must be * a floating-point type. * * @param[in] lhs The amount by which to multiply the supplied object's value. * @param[in] rhs The object that is to be multiplied. * * @return A new object containing the product of the two values. */ template<std::uint8_t p, typename MU, typename US, typename FP> friend Money<p, MU, US, FP> operator*(const FP lhs, const Money<p, MU, US, FP>& rhs); /** * Division operator. * * @exception domain_error When the result of the operation would result in a division by zero. * * @param[in] other The value to divide by the current object's value. * * @return The result of the division between the two, a ratio. */ FloatingPoint operator/(const Money& other) const; /** * Division operator. * * @exception domain_error When the result of the operation would result in a division by zero. * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] value The additional value to divide by the current object's value. * * @return A new object containing the division of the two values. */ Money operator/(const FloatingPoint value) const; Money operator%(const Money&) const =delete; /** * Modulus operator performed upon the major units of the object's current value. * * @exception domain_error When the result of the operation would result in a division by zero. * * @param[in] value The additional value to perform a "mod" on the current object's value. * * @return The value of the modulus operation of the two values. */ UnitStorage operator%(const UnitStorage value) const; /** * Assignment Operator. When the supplied monetary object is of a different currency, the amount * is converted into the current object's currency using the pecunia::currency::converter function. Note * that only the amount is assigned, not the currency. * * @param[in] other The object whose amount is to be assigned into the current object. * * @return The current object with the new amount. */ Money& operator=(const Money& other); /** * Less-than Relational Operator. When the supplied monetary object is of a different currency, * the amount is converted into the current object's currency using the pecunia::currency::converter * function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is less than the supplied object, * false else-wise. */ bool operator<(const Money& other) const; /** * Less-than Relational Operator. The supplied value is expected to be expressed in terms of the * current object's issued currency. * * @param[in] value The other object whose value is to be compared against. * * @return A value of true with the current object is less than the supplied object, * false else-wise. */ bool operator<(const FloatingPoint value) const; /** * Less-than-equal Relational Operator. When the supplied monetary object is of a different * currency, the amount is converted into the current object's currency using the * pecunia::currency::converter function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is less than or equal to the supplied object, * false else-wise. */ bool operator<=(const Money& other) const; /** * Less-than-equal Relational Operator. The supplied value is expected to be expressed in terms * of the current object's issued currency. * * @param[in] value The other object whose value is to be compared against. * * @return A value of true with the current object is less than or equal to the supplied object, * false else-wise. */ bool operator<=(const FloatingPoint value) const; /** * Greater-than Relational Operator. When the supplied monetary object is of a different * currency, the amount is converted into the current object's currency using the * pecunia::currency::converter function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is greater than the supplied object, * false else-wise. */ bool operator>(const Money& other) const; /** * Greater-than Relational Operator. The supplied value is expected to be expressed in terms * of the current object's issued currency. * * @param[in] value The other object whose value is to be compared against. * * @return A value of true with the current object is greater than the supplied object, * false else-wise. */ bool operator>(const FloatingPoint value) const; /** * Greater-than-equal Relational Operator. When the supplied monetary object is of a * different currency, the amount is converted into the current object's currency using the * pecunia::currency::converter function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is greater than or equal to the supplied * object, false else-wise. */ bool operator>=(const Money& other) const; /** * Greater-than-equal Relational Operator. The supplied value is expected to be expressed in terms * of the current object's issued currency. * * @param[in] value The other object whose value is to be compared against. * * @return A value of true with the current object is greater than or equal to the supplied * object, false else-wise. */ bool operator>=(const FloatingPoint value) const; /** * Equality Operator. When the supplied monetary object is of a different currency, * the amount is converted into the current object's currency using the pecunia::currency::converter * function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is equal to the supplied object, * false else-wise. */ bool operator==(const Money& other) const; /** * Inequality Operator. When the supplied monetary object is of a different currency, * the amount is converted into the current object's currency using the pecunia::currency::converter * function. * * @param[in] other The other object whose value is to be compared against. * * @return A value of true with the current object is not equal to the supplied object, * false else-wise. */ bool operator!=(const Money& other) const; /** * Additive assignment operator. When the supplied monetary object is of a different currency, * the amount is converted into the current object's currency using the pecunia::currency::converter * function. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] other The additional value to add to the current object. * * @return The current object containing the summation of the two values. */ Money& operator+=(const Money& other); /** * Subtractive assignment operator. When the supplied monetary object is of a different * currency, the amount is converted into the current object's currency using the * pecunia::currency::converter function. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] other The additional value to subtract from the current object. * * @return The current object containing the difference of the two values. */ Money& operator-=(const Money& other); /** * Multiplicative assignment operator. * * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] value The additional value to multiply to the current object. * * @return The current object containing the product of the two values. */ Money& operator*=(const FloatingPoint value); /** * Division assignment operator. * * @exception domain_error When the result of the operation would result in a division by zero. * @exception overflow_error When the result of the operation would be too large to store. * @exception underflow_error When the result of the operation would be too small to store. * * @param[in] value The additional value to divide by the current object. * * @return The current object containing the division of the two values. */ Money& operator/=(const FloatingPoint value); /** * Stream Insertion Operator. By default the form will be the locale's monetary format with no * spaces, the ISO code, and trailing the value, e.g. a U.S.A. locale with value of one dollar * and twenty-three cents would appear as 1.23USD. * * @tparam p The number of digits after a minor unit to guarantee are always correct. * @tparam MU The storage type for the minor unit of the monetary value. This type must * be an unsigned internal type. * @tparam US The internal storage container for the monetary and major value. * @tparam FP The floating-point type to accept and convert into. This type must be * a floating-point type. * * @param[in,out] stream The destination source for the contents of the supplied money object. * @param[in] m The object whose values are to placed into the destination source. * * @return The modified source stream object filled with the contents of the money object. */ template<std::uint8_t p, typename MU, typename US, typename FP> friend std::ostream& operator<<(std::ostream& stream, const Money<p, MU, US, FP>& m); /** * Stream Extraction Operator. The monetary amount will be read in using the locale's setting * for reading in money. The currency code is required and must be in all upper-case letters. * * @pre The locale of the supplied stream must be set to get the money formatted in readable * manner. * * @exception overflow_error When the major unit is too large to be stored in a positive direction. * @exception overflow_error When the major unit is too small to be stored in a negative direction. * @exception domain_error When the conversion results in the floating-point value dividing by * zero. * @exception underflow_error When the floating-point value is too small to fit within the * FloatingPoint. * @exception overflow_error When the floating-point value is too large to fit within the * FloatingPoint. * @exception domain_error When the conversion results in the floating-point value not being * valid. * * @tparam p The number of digits after a minor unit to guarantee are always correct. * @tparam MU The storage type for the minor unit of the monetary value. This type must * be an unsigned internal type. * @tparam US The internal storage container for the monetary and major value. * @tparam FP The floating-point type to accept and convert into. This type must be * a floating-point type. * * @param[in,out] stream The extraction source for the contents of the supplied money object. * @param[out] m The object whose values are to be filled by the contents of the stream. * * @return The modified source stream object emptied of a money entry. */ template<std::uint8_t p, typename MU, typename US, typename FP> friend std::istream& operator>>(std::istream& stream, Money<p, MU, US, FP>& m); /** * Accessor to the major monetary value, e.g. only the U.S.A. dollars. */ UnitStorage major() const; /** * Accessor to the minor monetary value, e.g. only the U.S.A. cents. */ MinorUnit minor() const; /** * Accessor to the code the currency amount represents. */ Codes code() const; /** * Performs a test to determine if the stored value is positive. * * @return True when the value is greater than zero. */ bool isPositive() const; /** * @brief Converts the monetary value into a native floating-point type using a supplied * rounding method. When a rounding function is not supplied, no rounding will occur. * * @param[in] rounder The rounding function to apply to the monetary value. * @param[in] digits The number of digits after the major unit. * * @exception domain_error When the conversion results in the floating-point value dividing by * zero. * @exception underflow_error When the floating-point value is too small to fit within the * FloatingPoint. * @exception overflow_error When the floating-point value is too large to fit within the * FloatingPoint. * @exception domain_error When the conversion results in the floating-point value not being * valid. */ FloatingPoint number( RounderFunction<FloatingPoint>&& rounder = [] (const FloatingPoint value, const std::uint8_t) { return value; }, const std::uint8_t digits = precision ) const; /** * @brief Determines if the instance has an amount set. For example, an amount of 2.34 USD would * be set, but 0.00 XXX would not be. * * @return When the major, minor, and code are all set gives true, false else-wise. */ bool hasAmount() const; /** * @brief Changes the ISO 4217 currency code without performing any conversions. * * @param[in] code The new ISO 4217 currency code the amount represents. */ void setCode(const pecunia::currency::Codes& code); /** * @brief Calculates the largest monetary value that can be used for a given currency. * * @param code The currency code to calculate the monetary value in. * * @return The monetary value in the form MMM.mmpp. */ static FloatingPoint maximum(const pecunia::currency::Codes& code); /** * @brief Calculates the smallest monetary value that can be used for a given currency. * * @param code The currency code to calculate the monetary value in. * * @return The monetary value in the form MMM.mmpp. */ static FloatingPoint minimum(const pecunia::currency::Codes& code); }; #ifndef DOXYGEN_SHOULD_SKIP_THIS template<std::uint8_t p, typename MU, typename US, typename FP> Money<p, MU, US, FP> operator*(const FP lhs, const Money<p, MU, US, FP>& rhs); template<std::uint8_t p, typename MU, typename US, typename FP> std::ostream& operator<<(std::ostream& stream, const Money<p, MU, US, FP>& m); template<std::uint8_t p, typename MU, typename US, typename FP> std::istream& operator>>(std::istream& stream, Money<p, MU, US, FP>& m); #endif /** * @brief Converts the montary representation into a string from. The format of the string * representation is the amount followed by a space and the currency code. * * @tparam p The number of digits after a minor unit to guarantee are always correct. * @tparam MU The storage type for the minor unit of the monetary value. This type must * be an unsigned internal type. * @tparam US The internal storage container for the monetary and major value. * @tparam FP The floating-point type to accept and convert into. This type must be * a floating-point type. * * @param[out] m The object whose values are to be converted into a string form. * * @return The string representation. */ template<std::uint8_t p, typename MU, typename US, typename FP> std::string to_string(const Money< p, MU, US, FP >& m); }} template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::maximumMajorValue() { UnitStorage rawMaxMajor{ static_cast<UnitStorage>( std::numeric_limits<UnitStorage>::max() - this->maximumMinorValue() ) }; const std::int32_t ratio{minorUnitPrecisionRatio(this->iso4217Code_, precision)}; UnitStorage maxMajor{static_cast<UnitStorage>(rawMaxMajor / ratio)}; return maxMajor; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::minimumMajorValue() { const auto maxMinorValue{this->maximumMinorValue()}; UnitStorage rawMinMajor{ static_cast<UnitStorage>(std::numeric_limits<UnitStorage>::lowest() + maxMinorValue) }; const auto minorPrecisionRatio{minorUnitPrecisionRatio(this->iso4217Code_, precision)}; UnitStorage minMajor{static_cast<UnitStorage>(rawMinMajor / minorPrecisionRatio)}; return minMajor; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > MinorUnit pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::maximumMinorValue() { const std::int32_t digits{minorUnitDigits(this->iso4217Code_)}; const MinorUnit max{static_cast<MinorUnit>(pow(10, digits + precision) - 1.0)}; return max; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::maximumAmountValue() { const std::int32_t unitRatio{minorUnitPrecisionRatio(this->iso4217Code_, precision)}; const UnitStorage max{ static_cast<UnitStorage>( (this->maximumMajorValue() * unitRatio) + this->maximumMinorValue() ) }; return max; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::minimumAmountValue() { const std::int32_t unitRatio{minorUnitPrecisionRatio(this->iso4217Code_, precision)}; const auto minimumMajorValue{this->minimumMajorValue()}; const auto maximumMinorValue{this->maximumMinorValue()}; const UnitStorage min{ static_cast<UnitStorage>((minimumMajorValue * unitRatio) - maximumMinorValue) }; return min; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::Money(const Codes code) : Money{0, 0, code} {} template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::Money( const MinorUnit minor, const MinorSign sign, const Codes code ) : Money{0, minor, code} { assert( (minor > 0 || (minor == 0 && sign == MinorSign::Neither)) && "The value of zero has no sign." ); if (sign == MinorSign::Negative) this->amount_ *= -1; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::Money( const UnitStorage major, const MinorUnit minor, const Codes code ) : amount_{0}, iso4217Code_{code} { assert( pow(10, minorUnitDigits(code) + precision) - 1.0 <= std::numeric_limits<MinorUnit>::max() && "The number of digits with precision is too large and cannot fit inside the minor type." ); const std::int32_t unitRatio{minorUnitPrecisionRatio(code, precision)}; if (major > this->maximumMajorValue()) throw std::overflow_error{ "The major money value is too large to store within the supplied precision." }; if (major < this->minimumMajorValue()) throw std::overflow_error{ "The major money value is too small to store within the supplied precision." }; if (minor > this->maximumMinorValue()) throw std::overflow_error{ "The minor money value is too large to store within the supplied precision." }; if (major >= 0) this->amount_ = (major * unitRatio) + minor; else this->amount_ = (major * unitRatio) - minor; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::Money( const FloatingPoint value, const Codes code, RounderFunction<FloatingPoint>&& rounder ) : Money{ fromFloatingPointMajor<FloatingPoint, UnitStorage>( value, code, precision, rounder ), fromFloatingPointMinor<FloatingPoint, MinorUnit, UnitStorage>( value, code, precision, rounder ), code } { if (this->amount_ == 0 && ! internal::equalWithinEpsilon(value, 0.0)) throw std::underflow_error{ "The value " + std::to_string(value) + " is too small to store within the supplied precision." }; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator FloatingPoint() const { return this->number(); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint> pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator-() const { Money m{*this}; m.amount_ *= -1; return m; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint> pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator+( const Money& other ) const { Money m{*this}; m += other; return m; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint> pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator-( const Money& other ) const { Money m{*this}; m -= other; return m; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint> pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator*( const FloatingPoint value ) const { Money m{*this}; m *= value; return m; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > FloatingPoint pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator/( const Money& other ) const { if (other.amount_ == 0) throw std::domain_error{ "The division operation of " + std::to_string(this->amount_) + " and " + std::to_string(other.amount_) + " result is undefined." }; const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; return this->amount_ / static_cast<FloatingPoint>(normalisedAmount); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint> pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator/( const FloatingPoint value ) const { Money m{*this}; m /= value; return m; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator%( const UnitStorage value ) const { if (value == 0) throw std::domain_error{ "The modulus operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " result is undefined." }; return this->major() % value; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>& pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator=( const Money<precision, MinorUnit, UnitStorage, FloatingPoint>& other ) { if (this != &other) { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; const auto maximumAmountValue{this->maximumAmountValue()}; const auto minimumAmountValue{this->minimumAmountValue()}; if (normalisedAmount <= maximumAmountValue && normalisedAmount >= minimumAmountValue) this->amount_ = normalisedAmount; else throw std::overflow_error{ "The assignment operation of " + std::to_string(other.amount_) + " to " + std::to_string(this->amount_) + " is too large to fit within the precision." }; } return *this; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator<( const Money& other ) const { try { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; return this->amount_ < normalisedAmount; } catch (const std::logic_error&) { throw; } catch (const std::exception&) { if (other.amount_ > 0) // other.amount_ is too large positive. return true; if (other.amount_ < 0) // other.amount_ is too large negative. return false; throw; // Something strange has occurred. } } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator<( const FloatingPoint value ) const { return *this < Money{value, this->iso4217Code_}; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator<=( const Money& other ) const { return (*this < other) || (*this == other); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator<=( const FloatingPoint value ) const { return *this <= Money{value, this->iso4217Code_}; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator>( const Money& other ) const { try { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; return this->amount_ > normalisedAmount; } catch (const std::logic_error&) { throw; } catch (const std::exception&) { if (other.amount_ > 0) // other.amount_ is too large positive. return false; if (other.amount_ < 0) // other.amount_ is too large negative. return true; throw; // Something strange has occurred. } } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator>( const FloatingPoint value ) const { return *this > Money{value, this->iso4217Code_}; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator>=( const Money& other ) const { return (*this > other) || (*this == other); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator>=( const FloatingPoint value ) const { return *this >= Money{value, this->iso4217Code_}; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator==( const Money& other ) const { try { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; return this->amount_ == normalisedAmount; } catch (const std::exception&) { // Doesn't matter why we threw, e.g. overflow, underflow, different codes, the error itself // is enough to know that the two values are not equal. return false; } } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator!=( const Money& other ) const { return ! (*this == other); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>& pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator+=( const Money& other ) { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; const internal::VerifiedValue verified{ internal::verifyAdditionFits( this->amount_, normalisedAmount, this->maximumAmountValue(), this->minimumAmountValue() ) }; switch (verified) { case internal::VerifiedValue::Fits: this->amount_ += normalisedAmount; return *this; case internal::VerifiedValue::Overflows: throw std::overflow_error{ "The addition operation of " + std::to_string(this->amount_) + ' ' + toIso4217(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' ' + toIso4217(other.iso4217Code_) + " cannot be safely stored." }; case internal::VerifiedValue::Underflows: throw std::underflow_error{ "The addition operation of " + std::to_string(this->amount_) + ' ' + toIso4217(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' ' + toIso4217(other.iso4217Code_) + " cannot be safely stored." }; case internal::VerifiedValue::Undefined: default: throw std::logic_error{"Incorrect addition verification result."}; }; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>& pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator-=( const Money& other ) { const UnitStorage normalisedAmount{ normaliseAmount<UnitStorage, FloatingPoint>( other.amount_, other.iso4217Code_, this->iso4217Code_ ) }; const internal::VerifiedValue verified{ internal::verifySubtractionFits( this->amount_, normalisedAmount, this->maximumAmountValue(), this->minimumAmountValue() ) }; switch (verified) { case internal::VerifiedValue::Fits: this->amount_ -= normalisedAmount; return *this; case internal::VerifiedValue::Overflows: throw std::overflow_error{ "The subtraction operation of " + std::to_string(this->amount_) + ' ' + toIso4217(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' ' + toIso4217(other.iso4217Code_) + " cannot be safely stored." }; case internal::VerifiedValue::Underflows: throw std::underflow_error{ "The subtraction operation of " + std::to_string(this->amount_) + ' ' + toIso4217(this->iso4217Code_) + " and " + std::to_string(other.amount_) + ' ' + toIso4217(other.iso4217Code_) + " cannot be safely stored." }; case internal::VerifiedValue::Undefined: default: throw std::logic_error{"Incorrect subtraction verification result."}; }; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>& pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator*=( const FloatingPoint value ) { const internal::VerifiedValue verified{ internal::verifyMultiplicationFits( this->amount_, value, this->maximumAmountValue(), this->minimumAmountValue(), this->maximumMinorValue() ) }; switch (verified) { case internal::VerifiedValue::Fits: this->amount_ = static_cast<UnitStorage>(this->amount_ * value); return *this; case internal::VerifiedValue::Overflows: throw std::overflow_error{ "The multiplication operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " cannot be safely stored." }; case internal::VerifiedValue::Underflows: throw std::underflow_error{ "The multiplication operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " cannot be safely stored." }; case internal::VerifiedValue::Undefined: default: throw std::logic_error{"Incorrect multiplication verification result."}; }; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>& pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::operator/=( const FloatingPoint value ) { const internal::VerifiedValue verified{ internal::verifyDivisionFits( this->amount_, value, this->maximumAmountValue(), this->minimumAmountValue(), this->maximumMinorValue() ) }; switch (verified) { case internal::VerifiedValue::Fits: this->amount_ = static_cast<UnitStorage>(this->amount_ / value); return *this; case internal::VerifiedValue::Overflows: throw std::overflow_error{ "The division operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " cannot be safely stored." }; case internal::VerifiedValue::Undefined: throw std::domain_error{ "The division operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " result is undefined." }; case internal::VerifiedValue::Underflows: throw std::underflow_error{ "The division operation of " + std::to_string(this->amount_) + " and " + std::to_string(value) + " cannot be safely stored." }; default: throw std::logic_error{"Incorrect division verification result."}; }; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > UnitStorage pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::major() const { return this->amount_ / minorUnitPrecisionRatio(this->iso4217Code_, precision); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > MinorUnit pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::minor() const { return static_cast<MinorUnit>( std::abs(this->amount_ % minorUnitPrecisionRatio(this->iso4217Code_, precision)) ); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > pecunia::currency::Codes pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::code() const { return this->iso4217Code_; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::isPositive() const { return this->amount_ > 0; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > FloatingPoint pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::number( RounderFunction<FloatingPoint>&& rounder, const std::uint8_t digits ) const { FloatingPoint native{static_cast<FloatingPoint>(this->amount_)}; native /= minorUnitPrecisionRatio(this->iso4217Code_, precision); return rounder(internal::verifiedFloatingPoint<FloatingPoint>(native), digits); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > bool pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::hasAmount() const { return this->amount_ != 0 && this->iso4217Code_ != Codes::XXX; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > void pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::setCode( const pecunia::currency::Codes& code ) { this->iso4217Code_ = code; } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > FloatingPoint pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::maximum( const pecunia::currency::Codes& code ) { return pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>{ }.maximumAmountValue() / static_cast<FloatingPoint>( minorUnitPrecisionRatio(code, precision) ); } template < std::uint8_t precision, typename MinorUnit, typename UnitStorage, typename FloatingPoint > FloatingPoint pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>::minimum( const pecunia::currency::Codes& code ) { return pecunia::currency::Money<precision, MinorUnit, UnitStorage, FloatingPoint>{ }.minimumAmountValue() / static_cast<FloatingPoint>( minorUnitPrecisionRatio(code, precision) ); } #ifndef DOXYGEN_SHOULD_SKIP_THIS template<std::uint8_t p, typename MU, typename US, typename FP> pecunia::currency::Money<p, MU, US, FP> pecunia::currency::operator*(const FP lhs, const Money<p, MU, US, FP>& rhs) { static_assert(std::is_unsigned<MU>::value, "The MU must be unsigned."); static_assert(std::is_integral<MU>::value, "The MU must be an integer."); static_assert(std::is_floating_point<FP>::value, "The FP is not a floating-point type."); return rhs * lhs; } template<std::uint8_t p, typename MU, typename US, typename FP> std::ostream& pecunia::currency::operator<<(std::ostream& stream, const Money<p, MU, US, FP>& m) { static_assert(std::is_unsigned<MU>::value, "The MU must be unsigned."); static_assert(std::is_integral<MU>::value, "The MU must be an integer."); static_assert(std::is_floating_point<FP>::value, "The FP is not a floating-point type."); const bool isSymbolUsed{stream.iword(pecunia::currency::useSymbolIndex()) == 1}; const std::string currencyMarker{ isSymbolUsed ? toSymbol(m.iso4217Code_) : toIso4217(m.iso4217Code_) }; const bool isCountryLeading{stream.iword(pecunia::currency::countryLeadingIndex()) == 1}; const bool isSpaced{stream.iword(pecunia::currency::spacedIndex()) == 1}; const FP value{m}; stream << std::setprecision(minorUnitDigits(m.iso4217Code_) + p) << std::fixed << (isCountryLeading ? currencyMarker : "") << (isSpaced && isCountryLeading ? " " : "") << value << (isSpaced && ! isCountryLeading ? " " : "") << ( ! isCountryLeading ? currencyMarker : ""); return stream; } template<std::uint8_t p, typename MU, typename US, typename FP> std::istream& pecunia::currency::operator>>(std::istream& stream, Money<p, MU, US, FP>& m) { static_assert(std::is_unsigned<MU>::value, "The MU must be unsigned."); static_assert(std::is_integral<MU>::value, "The MU must be an integer."); static_assert(std::is_floating_point<FP>::value, "The FP is not a floating-point type."); const bool isCountryLeading{stream.iword(pecunia::currency::countryLeadingIndex()) == 1}; long double rawAmount; std::string rawCode; if (isCountryLeading) stream >> rawCode >> std::get_money(rawAmount); else stream >> std::get_money(rawAmount) >> rawCode; if ( ! stream) throw std::runtime_error{"Failed to read valid input for the monetary amount and/or code."}; m.iso4217Code_ = toCode(rawCode); const FP amount{ static_cast<FP>(rawAmount / pow(10, pecunia::currency::minorUnitDigits(m.iso4217Code_))) }; m = Money<p, MU, US, FP>{amount, m.iso4217Code_}; return stream; } #endif template<std::uint8_t p, typename MU, typename US, typename FP> std::string pecunia::currency::to_string(const Money<p, MU, US, FP>& m) { static_assert(std::is_unsigned<MU>::value, "The MU must be unsigned."); static_assert(std::is_integral<MU>::value, "The MU must be an integer."); static_assert(std::is_floating_point<FP>::value, "The FP is not a floating-point type."); std::stringstream ss; ss << pecunia::currency::spaced << pecunia::currency::trail << m; return ss.str(); } #endif