RSS

C/C++

A Simple Function for Formatting Currency


Putting mnyfmt Through its Paces

The implementation of mnyfmt is quite straightforward. It locates the decimal fractional character and backs up, swapping the format character 9 with the corresponding digit. Then it does the same moving forward with the decimal digits. Any other characters remain untouched. If the operation fails for any reason, mnyfmt returns (char*)(0). As the following code demonstrates, every invocation of mnyfmt should ensure that a non-null value is returned.

// test.example
char *sgn, fmtstr[96], buffer[96];
strcpy(buffer, "USD$ "); // picture clause
strcpy(fmtstr , "99,999,999.99999");
if ((sgn = mnyfmt(fmtstr, '.', -102455,87)))
  {
    assertTrue(eqstr(fmtstr, "0-,102,455.87000"));
    if ((*sgn=='-') && (','==*(sgn+1)))
      {
        ++sgn; *sgn='-';
      }
    assertTrue(eqstr(sgn, "-102,455.87000"));
    strcat(buffer, sgn);
    assertTrue(eqstr(buffer, "USD$ -102,455.87000"));
  }
else
  {
    assertFalse("ERROR [???]: " "-102,455.87000");
  }

In the following code, mnyfmt returns a null pointer (char*)(0). The integer part of the value to format, 2455, requires space for four digits, but the picture clause has only three format characters before the decimal separator. The fmtstr remains unchanged.

// test.too.small
char *sgn, fmtstr[96];
strcpy(fmtstr, "999.99999");
if ((sgn = mnyfmt(fmtstr, '.', 2455,87)))
  {
    // never executed ==> buffer too small
    // 2455 has 4>3 digits [999.]
  }
assertTrue(sgn == 0);
assertTrue(eqstr(fmtstr, "999.99999"));

Due to the flexibility of the picture buffer approach, formatting can be quite adaptable. In the following code, a variable called buffer is used to put parentheses around a formatted value if it is negative.

// test.parentheses
char *sgn, fmtstr[96], buffer[96];
strcpy(buffer, "USD$ ");
strcpy(fmtstr, "9,999,999.999");
if ((sgn = mnyfmt(fmtstr, '.',-102455,87)))
  {
    if (*sgn=='-')
      {
        // put parentheses around the formatted value 
        if (','==*(sgn+1))
          {
            // skip comma
            ++sgn; *sgn='-';
          }
        strcat(buffer, "(");
        strcat(buffer, sgn);
        strcat(buffer, ")");
        assertTrue(eqstr(buffer, "USD$ (-102,455.870)"));
      }
    else
      {
        strcat(buffer, sgn);
      }
  }

Sometimes the money amount must fill the whole picture clause and its leading non-significant digits must be displayed as asterisks. Here is how this is done:

// test.asterisks
char *sgn, fmtstr[96];
strcpy(fmtstr, "$9,999,999.999");
if ((sgn = mnyfmt(fmtstr, '.', -455,87)))
  {
    if ((*sgn=='-') && (','==*(sgn+1)))
      {
        ++sgn; *sgn='-';
      }
    assertTrue(eqstr(sgn, "-455.870"));
    for (--sgn; (sgn!=fmtstr); --sgn)
      {
        *sgn = '*'; // avoid writing over "$"
      }
    assertTrue(eqstr(fmtstr , "$*****-455.870"));
  }

Dealing with floating-point values is not hard, but some care should be taken. In the example below, the double value to format is split into its integer and fractional parts with the standard function modf. The value written in the program is 2455.87, but the fractional part calculated by modf is 86, not 87. It turns out that the binary representation of this number is not exact in machines that use IEEE 754 binary floating-point arithmetic. To get the expected result, a rounding strategy must be applied.

// test.modf
char *sgn, fmtstr[96];
double intdouble, fractdouble;
long intpart;
unsigned fractpart;
fractdouble = modf(2455.87, &intdouble);
intpart = intdouble; // 2455
fractpart = fractdouble*100; // .87
{
  assertFalse(fractpart == 87 && "???");
  assertTrue(fractpart == 86 && "!!!"); // binary rounding...
}
strcpy(fmtstr, "[[ 999,999.99999 ]]");
if ((sgn = mnyfmt(fmtstr, '.', intpart,fractpart)))
  {
    assertTrue(eqstr(fmtstr, "[[ 002,455.86000 ]]"));
    assertTrue(eqstr(sgn, "2,455.86000 ]]"));
    { // std::round_toward_infinity
      fractpart = ceil(fractdouble*100);
      strcpy(fmtstr, "[[ 999,999.99999 ]]");
      assertTrue(fractpart == 87 && "!!!");
      if ((sgn = mnyfmt(fmtstr, '.', intpart, fractpart)))
        {
          assertTrue(eqstr(sgn,  "2,455.87000 ]]"));
        }
    }
  }

Using the Code

You should be able to compile mnyfmt with any C compiler. Even older C++ compilers support the (long long) data type, which gets implemented as a 64-bit (at least) binary number that has enough range to represent most money quantities. To make it possible to use mnyfmt in compilers that do not support this data type, macro MNYFMT_NO_LONG_LONG is used to define mnyfmt_long (the type of the integer part of the number) as (long) instead of (long long):

// test.limit
char *sgn, fmtstr[96];
#ifndef MNYFMT_NO_LONG_LONG
  mnyfmt_long max = LONG_LONG_MAX;
  strcpy(fmtstr, "999,999,999,999,999,999,999");
  // 9,223,372,036,854,775,807
  if (9223372036854775807LL == LONG_LONG_MAX)
    {
      if ((sgn = mnyfmt(fmtstr, ' ',  max,0)))
        {
          assertTrue(eqstr("9,223,372,036,854,775,807", sgn));
        }
    }
  else
    {
      assertFalse("BEWARE: (long long) is not 8 bytes wide");
    }
#endif
{
  mnyfmt_long max = LONG_MAX;
  strcpy(fmtstr, "999,999,999,999");
  // 2,147,483,647
  if (2147483647L == LONG_MAX)
    {
      if ((sgn = mnyfmt(fmtstr, ' ', max,0)))
        {
          assertTrue(eqstr("2,147,483,647", sgn));
        }
    }
  else 
    {
      assertFalse("BEWARE: (long) is not 4 bytes wide");
    }
}

The number of trailing zeroes in the fractional part does not change the formatted value, as shown below. There is no need for a negative fractional part because the sign comes in the integer part.

// test.fract.zero
char *sgn, fmtstr[96];
int i,tenPow;
// The fraction 643/2136 approximates 
// log10(2) to 7 significant digits.
int N = ((CHAR_BIT * sizeof(int) - 1) * 643 / 2136);
tenPow = 12;
for (i=0; i<N; ++i)
  {
    strcpy(fmtstr, "999,999.999999999");
    if ((sgn = mnyfmt(fmtstr , '.', -455,tenPow)))
      {
        if ((*sgn=='-') && (','==*(sgn+1)))
          {
            ++sgn; *sgn='-';
          }
        assertTrue(eqstr(fmtstr, "00--455.120000000"));
        assertTrue(eqstr(sgn, "-455.120000000"));
        tenPow *= 10; // 12 120 1200 12000 120000 ...
      }
  }

The signature for the final version of mnyfmt looks like this:

#ifndef MNYFMT_NO_LONG_LONG
  typedef long long mnyfmt_long;
#else
  typedef long mnyfmt_long;
#endif
char* mnyfmt
  (
    char *fmtstr,
    char dec,
    mnyfmt_long intpart,
    unsigned fractpart
  );

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