Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Currency

0.00/5 (No votes)
14 Apr 2004 1  
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):

Database Structure

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 (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(); // 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 inside
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 };
// 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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here