RSS

C/C++

A Simple Function for Formatting Currency


Quirks, Insights, and Obscure Details

Many C library functions come in multiple flavors, one for each type. For example, lround, lroundf, lroundl, llround, llroundf, and llroundl are versions of a single rounding function. The name changes because various versions return different kinds of integers and accept different types of arguments. It is not necessary to have multiple versions of mnyfmt because conversion to the wider integer type is always provided by the compiler.

The relationship between types char and wchar_t is not always clear. The C standard does not specify the exact type for wchar_t. It can be two or four bytes wide, or even one byte wide. Moreover, type char can be signed or unsigned. Because most picture clauses for mnyfmt rely on characters from the 103-character portable character subset that POSIX requires in any character set, it's easiest to use the char type for picture clauses and perform an explicit conversion into wchar_t when necessary:

// Convert a char[] into a wchar_t[]
char *src, chBuff[128];
wchar_t *dst, *wcBuff;
strcpy (chBuff, "Convert me to (wchar_t)");
wcBuff = (wchar_t*)(malloc(strlen(chBuff)*sizeof(wchar_t)));
for (dst=wcBuff,src=chBuff; (*dst=*src); ++dst,++src) {}
// ... C++ will let you use more sophisticated stuff
free(wcBuff);

Most processors use two's complement binary arithmeti, which behaves strangely when dealing with the smallest negative values. That is why mnyfmt fails to calculate the correct formatted result for this case and returns a null pointer:

// test.minus.max
long long_min = -LONG_MAX-1;
assertTrue(long_min<0);
long_min = -long_min;
assertTrue(long_min<0 && "?????");
assertTrue(long_min == -long_min);

The behavior of mnyfmt is different when formatting the integer and the fractional part of the number. In the integer part, all leading non-significant format characters get replaced by 0, but only those that immediately follow the decimal separator get changed. This is why, in the next code sample, the 1 in the format string stops the substitution, leaving the remaining characters unchanged. This code also shows a bad programming practice, as there is no check to ensure that sgn is not null. This error could cause a program failure when the null pointer returned by mnyfmt gets used to change a value in memory. Enclosing every invocation of mnyfmt in an if statement is necessary to avoid this pitfall.

// test.stop
char *sgn, fmtstr[96];
strcpy(fmtstr, "999,999.9999919,one9.");
if ((sgn = mnyfmt(fmtstr, '.', 2455,87)))
  {
     if ((*sgn=='-') && (','==*(sgn+1)))
       {
         ++sgn; *sgn='-';
       }
      assertTrue(eqstr(fmtstr, "002,455.8700019,one9."));
      assertTrue(eqstr(sgn, "2,455.8700019,one9."));
   }

The following code shows that a picture clause can be used to format rupee amounts when words are also included. This code looks for the decimal point and replaces it with a blank space to achieve a more convincing result.

// test.rupee
char *sgn, fmtstr[96]; char *p;
strcpy(fmtstr, "99,99,99,99,99,99,99,999.99");
// LONG_LONG_MAX == 92,23,37,20,36,85,47,758.07
// 3,25,84,729.25
// 19 digits: 12 34 56 78 90 12 34 567 89
if ((sgn = mnyfmt(fmtstr, '.', 32584729,25)))
  {
    assertTrue(eqstr(sgn, "3,25,84,729.25"));
  }
strcpy(fmtstr,"99,99,99,99,99 crores 99 lakhs 99,999 rupees.99 paise");
if ((sgn = mnyfmt(fmtstr, '.', 32584729,25)))
  {
    for (p=sgn; *p!='.'&&*p!=0; ++p)
      {
        // advance p to dec '.'
      }
    *p = ' '; // blank the dot: ".rupees" ==> " rupees"
  }
assertTrue(eqstr(sgn, "3 crores 25 lakhs 84,729 rupees 25 paise"));
// Rp3,25,84,729.25 is read as three crore(s), twenty-five lakh(s),
// eighty-four thousand, seven hundred and twenty-nine rupees and
// twenty-five paise.

Picture clauses can be used to format almost any type of numeric values. The following code shows how to handle dates and hours, but many more applications are possible.

// test.times
char *sgn, fmtstr[96];
strcpy(fmtstr, "99/99/9999");
if ((sgn = mnyfmt(fmtstr, 000, 9272002,0)))
  {
    assertTrue(eqstr(fmtstr, "09/27/2002"));
    assertTrue(eqstr(sgn, "9/27/2002"));
  }
strcpy(fmtstr ,     "99:99:99");
if ((sgn = mnyfmt(fmtstr, '?', 21435,0)))
  {
    assertTrue(eqstr(fmtstr, "02:14:35"));
    assertTrue(eqstr(sgn, "2:14:35"));
  }

The next code sample illustrates a mistake any programmer could make: failure to copy the format string into the formatting variable before invoking mnyfmt. Many programmers will choose to place their currency-formatting logic inside functions that are tailored to each application in order to prevent mistakes like this one.

// test.no.strcpy
char *sgn, fmtstr[96];
strcpy(fmtstr, "9,999.");
if ((sgn = mnyfmt(fmtstr, '.', 2455,87)))
  {
    assertTrue(eqstr(sgn, "2,455."));
  }
if ((sgn = mnyfmt(fmtstr, '.', 1400,87)))
  {
    // never executed: missing strcpy()
    // no char in "2,455." is a format char
  }
else
  {
    assertFalse(eqstr(fmtstr, "1,400."));
    assertTrue(eqstr(fmtstr, "2,455.")); // ???
    assertTrue("BEWARE: missing strcpy()"); // ???
    {
      strcpy(fmtstr, "9,999.");
      sgn = mnyfmt(fmtstr, '.', 1400,87);
      assertTrue(eqstr(sgn, "1,400."));
    }
  }

The double parentheses in the if statement that contains the invocation to mnyfmt might seem odd, but it is a good programming practice suggested by the compiler: "warning: suggest parentheses around assignment used as truth value."

A Good Enough Solution

mnyfmt in its current form does a "good enough" job of formatting international currencies. Further internationalization can be accomplished with a library like ISOMON, which provides simple access to ISO currency data. If you're programming in C++, you'll probably want to pack mnyfmt inside a wrapper that validates input and guards against other usage errors.

For further information, download the complete source code and documentation for mnyfm.


Adolfo Di Mare is a researcher at the Escuela de Ciencias de la Computacion e Informática, Universidad de Costa Rica, where he is full professor. He is a tutor at the Stvdivm Generale in the Universidad Autónoma de Centro America, where he is a Cathedraticum.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Best of the Web

First C Compiler Now on Github

The earliest known C compiler by the legendary Dennis Ritchie has been published on the repository.

Quick Read

HTML5 Mobile Development: Seven Good Ideas (and Three Bad Ones)

HTML5 Mobile Development: Seven Good Ideas (and Three Bad Ones)

Quick Read

Building Bare Metal ARM Systems with GNU

All you need to know to get up and running... and programming on ARM

Quick Read

Amazon's Vogels Challenges IT: Rethink App Dev

Amazon Web Services CTO says promised land of cloud computing requires a new generation of applications that follow different principles.

Quick Read

How to Select a PaaS Partner

Eventually, the vast majority of Web applications will run on a platform-as-a-service, or PaaS, vendor's infrastructure. To help sort out the options, we sent out a matrix with more than 70 decision points to a variety of PaaS providers.

Quick Read


More "Best of the Web" >>

Video