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:
m_dUSD += m_pProducts->m_dOverheads;
break;
case 2:
m_dEuro += m_pProducts->m_dOverheads;
break;
case 3:
m_dRouble += m_pProducts->m_dOverheads;
break;
default:
break;
}
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
(a typedef
for unsigned long
) is a Currency Identifier. It would be better to use non-clashing identifiers in a program. The dValue
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();
CCurrency cyUSD(CYID_USD, 1437.6);
CCurrency cyUSD(CYID_USD, _T("US Dollars"),
_T("$"), _T("USD"), 52147.25);
void Reset();
Resets Currency
object (that is, sets m_dValue
and m_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);
Registers rCy
within a manager. Currency Manager uses std::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 format long
values and two remaining strings (Average Format and Value Format) expect a double
-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 a CCurrency
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-supplied dValue
instead of a value of a Currency object found. There are specific formatting flags and they are as follows:
Flag |
Meaning |
%name |
Currency Name (see szCurrencyName in CCurrency 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 |
So the code snippet can look like: 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);
Constructs CCurrencyExchange
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 of Exchange()
method. First one exchanges from rCy.GetCurrencyID()
to cyDest
and places the result to rCy
, altering its Currency ID member variable. Note that it doesn't change any of the string member variables. The second variant exchanges from rCySrc.GetCurrencyID()
to rCyDest.GetCurrencyID()
and places the result in rCyDest
. 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 };
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