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
);


