Mercurial > hgweb.cgi > pecunia
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pecunia/Money.hpp Sun Jun 17 08:18:48 2018 +0200 @@ -0,0 +1,1574 @@ +/******************************************************************************* +*** 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