Currency






4.11/5 (7 votes)
Apr 15, 2004
5 min read

43789

508
A set of currency manipulation classes
Introduction
While developing some sort of accounting application I ran into a problem of supporting several currencies and performing various computations with them. After digging in MSDN I understood that neither Windows API itself nor MFC (or any other class libraries) doesn't offer proper support for currency management - and that was the reason for developing my own set of classes.
Background
The database, which was the backbone of the application, along with many other tables, has these two tables (we're not interested in Product Categories):
The thing is: we have Suppliers with their Products. Every product has its Purchase Price, Overheads and Shop Price. The latter (namely Shop Price) is independent from Suppliers' Currency ID and all the remaining currency-related fields do depend on it. As program runs, it is required to perform various computations (calculating income, bills, etc.) so this is where the problem was.
The very first idea (not a first-rate one, actually) was of a kind:
- Declare several variables (one for each currency)...
...
double m_dUSD, m_dEuro, m_dRouble;
...
- ...and have following almost identical code pieces here and there:
switch(GetDocument()->GetSupplierCurrencyID(m_pProducts->m_lSupplier)) { case 1: // USD m_dUSD += m_pProducts->m_dOverheads; break; case 2: // Euro m_dEuro += m_pProducts->m_dOverheads; break; case 3: // Roubles m_dRouble += m_pProducts->m_dOverheads; break; default: break; } // switch
It is obvious that this approach has a lot of drawbacks. First off, it gives no flexibility (in case I would like to add one more currency I would have to look through all the source code just to add one more case
to each switch
block. Second, the burden of managing all currency-related stuff (formatting, performing calculations, etc.) rests on programmer's soulders. These four classes appeared to be a solution of the problem (at least in my case). Here we go.
The Reference
CCurrency
Well, the name speaks for itself - a basic currency class with plenty of capabilities.
CCurrency(); CCurrency(CYID cyID, double dValue); CCurrency(CYID cyID, LPCTSTR szCurrencyName, LPCTSTR szCurrencySign, LPCTSTR szStockName, double dValue); CCurrency(const CCurrency& rCy);All kinds of constructors.
CYID
(atypedef
forunsigned long
) is a Currency Identifier. It would be better to use non-clashing identifiers in a program. ThedValue
is the initial numerical value of your Currency object.szCurrencyName
is a human-readable name of the currency represented by the object (US Dollar, for instance)szCurrencySign
is the sign of the currency ($ or whatever).szStockName
is an international name of the currency (I guess USD is the thing). So the construction can look like this:const CYID CY_USD = 1; ... CCurrency cyUSD(); // Empty object - I wonder why you need this kind CCurrency cyUSD(CYID_USD, 1437.6); // This object has $ 1437.6 inside CCurrency cyUSD(CYID_USD, _T("US Dollars"), _T("$"), _T("USD"), 52147.25); // This is a fully-defined // object with 52147.25 USD insidevoid Reset();
Resets
Currency
object (that is, setsm_dValue
andm_lSummands
to 0).IGETSET(m_cyID, CurrencyID, CYID); IGETSET(m_dValue, Value, double); IGETSET(m_lSummands, Summands, long); IGET(m_szName, CurrencyName, LPCTSTR); IGET(m_szSign, CurrencySign, LPCTSTR); IGET(m_szStock, CurrencyStockName, LPCTSTR); CCurrency GetCurrency(CYID cyID);I felt too lazy to write proper accessors/mutators, so this is a quick and dirty solution - but nevertheless it works perfectly. Take a look in
macros.h
.const CCurrency& operator = (const CCurrency& rCy); CCurrency& operator + (const CCurrency& rCy); CCurrency& operator - (const CCurrency& rCy); CCurrency& operator += (const CCurrency& rCy); CCurrency& operator -= (const CCurrency& rCy); CCurrency& operator + (double dValue); CCurrency& operator - (double dValue); CCurrency& operator * (double dValue); CCurrency& operator / (double dValue); CCurrency operator + (const CCurrency& rCyA, const CCurrency& rCyB); CCurrency operator - (const CCurrency& rCyA, const CCurrency& rCyB); CCurrency operator * (const CCurrency& rCyA, double dValue); CCurrency operator / (const CCurrency& rCyA, double dValue); double Average();Arithmetical operations on
CCurrency
objects (surprise, surprise!).
CCurrencyManager
This is actually a container for
CCurrency
objects with somw additional features.BOOL Register(const CCurrency& rCy);
RegistersrCy
within a manager. Currency Manager usesstd::map
, so a copy is inserted.LPCTSTR GetCurrencyName(CYID cyID); LPCTSTR GetCurrencySign(CYID cyID); LPCTSTR GetCurrencyStockName(CYID cyID); double GetCurrencyValue(CYID cyID); long GetCurrencySummands(CYID cyID);Just for information - to see properties of currencies registered within a Manager.LPCTSTR GetSummandsFormat() LPCTSTR GetValueFormat() LPCTSTR GetAverageFormat() void SetSummandsFormat(LPCTSTR szSummandsFormat); void SetValueFormat(LPCTSTR szValueFormat); void SetAverageFormat(LPCTSTR szAverageFormat);These six are used to work with formatting strings. They are usual C-style format strings (remember or refer to
printf()
documentation). The only thing to say about them is that Summands Format expects formatting string suitable to formatlong
values and two remaining strings (Average Format and Value Format) expect adouble
-suitable format string. For instance:CCurrencyManager cmgr; ... cmgr.SetSummandsFormat(_T("%d")); cmgr.SetAverageFormat(_T("%.2f")); cmgr.SetValueFormat(_T("%f"));LPSTR Format(LPSTR szBuffer, LPCTSTR szFormatString, CCurrency &rCy); LPSTR Format(LPSTR szBuffer, LPCTSTR szFormatString, CYID cyID); LPSTR Format(LPSTR szBuffer, LPCTSTR szFormatString, CYID cyID, double dValue);
Three versions of formatting functions. Just like
sprintf()
it expects an output buffer (szBuffer
here), a format string (szFormatString
) and data to be used when formatting. The first version expects aCCurrency
object, whose fields will be used while formatting. The second version needs just Currency Identifier. It looks if such currency is registered in the Currency Manager and if it finds one uses its fields (including Value - see next function). Otherwise it returns empty output buffer. The last version does almost the same thing as the previous one, but it uses user-supplieddValue
instead of a value of a Currency object found. There are specific formatting flags and they are as follows:
So the code snippet can look like:
Flag Meaning %name
Currency Name (see szCurrencyName
inCCurrency
constructor).%sign
Currency Sign %stock
Currency Stock Name %smnd
Number of summands in the Currency object %val
Value of the Currency object %avg
Average value of the Currency object const CYID CYID_USD = 1; ... CCurrencyManager cmgr; cmgr.Register(CCurrency(CYID_USD, _T("US Dollars"), _T("$"), _T("USD"), 52147.25)); cmgr.SetAverageFormat(_T("%d")); cmgr.SetSummandsFormat(_T(".2f")); cmgr.SetValueFormat(_T("%.2f")); ... TCHAR szBuffer[250] = { 0 }; cmgr.Format(szBuffer, _T("The value of %name is %val. " "Average value of %stock %val is %avg " "(totalling %smnd summands)"), CYID_USD);const CCurrencyManager& operator = (const CCurrencyManager& rCm); CCurrencyManager& operator + (const CCurrencyManager& rCm); CCurrencyManager& operator - (const CCurrencyManager& rCm); CCurrencyManager& operator += (const CCurrencyManager& rCm); CCurrencyManager& operator -= (const CCurrencyManager& rCm); CCurrencyManager& operator + (const CCurrency& rCy); CCurrencyManager& operator - (const CCurrency& rCy); CCurrencyManager& operator += (const CCurrency& rCy); CCurrencyManager& operator -= (const CCurrency& rCy);As usual - arithmetical operations. Please note, that if you add or subtract a currency which was not registered in the destination manager a new currency is not registered and the state of a Currency Manager remains intact.
CCurrencyExchange
You would rarely need to use this class directly. It contains information required to perform conversions between two currencies, called Primary and Secondary. Exchange rate used to convert from Primary to Secondary is called Direct Exchange Rate. Exchange rate used to convert from Secondary to Primary is called Reverse Exchange Rate.
CCurrencyExchange(); CCurrencyExchange(CYID cyPrimary, CYID cySecondary, double dDirect, double dReverse);ConstructsCCurrencyExchange
object with given characteristics.IGETSET(m_cyPrimary, PrimaryCurrencyID, CYID); IGETSET(m_cySecondary, SecondaryCurrencyID, CYID); IGETSET(m_dDirect, DirectExchangeRate, double); IGETSET(m_dReverse, ReverseExchangeRate, double);Accessors/mutators - and nothing more.
CCurrencyExchangeManager
Does almost the same thing as
CCurrencyManager
- manages currency exchange operations.BOOL Register(CCurrencyExchange& rCx);Registers Currency Exchange object within the manager.double GetExchangeRate(CYID cySrc, CYID cyDest); BOOL SetExchangeRate(CYID cySrc, CYID cyDest, double dRate);Returns or alters defined exchange rate (in case Currency Exchange object with given IDs is registered in a Currency Exchange Manager).BOOL Exchange(CCurrency& rCy, CYID cyDest); BOOL Exchange(CCurrency& rCySrc, CCurrency& rCyDest);Two variants ofExchange()
method. First one exchanges fromrCy.GetCurrencyID()
tocyDest
and places the result torCy
, altering its Currency ID member variable. Note that it doesn't change any of the string member variables. The second variant exchanges fromrCySrc.GetCurrencyID()
torCyDest.GetCurrencyID()
and places the result inrCyDest
. All exchanges are performed if proper Currency Exchange object is registered within a Currency Exchange Manager. For example:const CYID CYID_USD = 1; const CYID CYID_EURO = 2; ... CCurrencyManager cmgr; cmgr.Register(CCurrency(CYID_USD, _T("US Dollars"), _T("$"), _T("USD"), 52147.25)); cmgr.Register(CCurrency(CYID_EURO, _T("Euro"), _T("€"), _T("EUR"), 87223.2)); CCurrency cyEuro(CYID_EURO, _T("Euro"), _T("€"), _T("EUR"), 87223.2); CCurrencyExchangeManager cxmgr; cxmgr.Register(CCurrencyExchange(1, 2, 1.27, 0.85)); cxmgr.Exchange(cmgr.GetCurrency(CYID_USD), cyEuro); ... TCHAR szBuffer[250] = { 0 }; // Do not forget terminator cmgr.Format(szBuffer, _T("The value of %name is %val."), cyEuro);
Feedback
This set was initially created for my personal use, so it may not fully suit your need. If you liked the stuff, please vote. If you have any problems - mail or post messages over here.
History
- 15 April 2004 - Initial release