mnyfmt.c: Format money or currency amounts
mnyfmt.c
Go to the documentation of this file.
1 // (C) Copyright Adolfo Di Mare 2011
2 // Use, modification and distribution are subject to the
3 // Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE_1_0.txt or copy at
5 // http://www.boost.org/LICENSE_1_0.txt)
6 
7 // Revision history:
8 // Oct 2011 Adolfo Di Mare ==> Initial (non boost) version
9 // Jun+Ago 2012 Adolfo Di Mare ==> CE correction
10 
11 // mnyfmt.c (C) 2011 adolfo@di-mare.com
12 
13 // Define this macro if your compiler does not have a (long long) data type
14 // #define MNYFMT_NO_LONG_LONG
15 
16 #include "mnyfmt.h"
17 
18 #ifdef __cplusplus
19 // To compile the C source with a C++ compiler, override the file's extension.
20 // The GNU gcc compiler does this with option -x: gcc -x c++
21 #endif
22 
23 /** \file mnyfmt.c
24  \brief Implementation for \c mnyfmt().
25 
26  \author Adolfo Di Mare <adolfo@di-mare.com>
27  \date 2011
28 */
29 
30 // Examples and unit test cases are in in file mnyfmtts.c
31 
32 /** Formats and stores in \c fmtstr the money amount.
33 
34  Before invocation, the formatting pattern (picture clause) is stored in
35  result string \c fmtstr. To avoid using \c (double) values that have many
36  round off problems, the parameter for this function is an integer scaled
37  to 10^CE digits. For example, when using CE==2 digits, the monetary value
38  "$2,455.87" is representad by the integer '245587', and if CE==4 digits
39  are used, the integer value would be '24558700'.
40 
41  - The (integer) value to format is \c moneyval.
42  - Overwrites \c fmtstr with the formatted value.
43  - On error, leaves \c fmtstr untouched and returns \c (char*)(0).
44  - If the \c fmtstr does not have enough format characters \c '9' for
45  the integer part to format, of if the \c '-' cannot fit on top of
46  a \c '9' character, \c fmtstr remains untouched and the value
47  returned is \c (char*)(0).
48 
49  - The valid range for CE, the 'currenct exponent', is [0..6]
50  [ a CE of 7 or bigger leaves \c fmtstr untouched and the value
51  returned is \c (char*)(0) ].
52  - The first occurrence of the character \c dec is the decimal fraction
53  separator (usually \c'.' or \c',').
54 
55  - When the decimal fraction separator character \c dec does not appear
56  in \c fmtstr it is assumed to be \c '\0' (end of string character).
57  - After the \c dec separator all the leading consecutive \c '9' format
58  characters are substituted with the corresponding digit from the
59  decimal part in \c moneyval, using digit zero \c '0' as fill character.
60  - All digits that inmediatly follow the decimal fraction separator are
61  changed either to zero or to the corresponding digit taken from the
62  decimal part in \c moneyval.
63  - Both the integer part and the fractional part are filled with the digits
64  that correspond to its position. This means that a format string like
65  \c "9999.9999" wild yield \c "0123.8700" as result when
66  \c moneyval==1238700 and \c CE==4.
67  - Characters trailing after the \c dec separator that are not the \c '9'
68  format digit are left untouched.
69  - All format characters \c '9' appearing before the decimal separator \c
70  dec will be replaced by digit zero \c '0' if the corresponding digit in
71  \c moneyval is not significant.
72  - When \c moneyval is negative, the \c '-' sign will be place over the
73  \c '9' immediately before the more significant digit.
74  - Non format characters in \c fmtstr are left untouched.
75  - The negative sign always is \c '-' and it is always placed on top of
76  the corresponding format character.
77 
78  - Returns \c (char*)(0) when the formatted value does not fit within
79  \c strlen(fmtstr) characters.
80  - Returns a pointer to the first significant digit in the formatted string,
81  within \c fmtstr or to the \c '-' sign if the formatted value is negative.
82  - Before storing the format string in \c fmtstr, the programmer must ensure
83  that \c fmtstr is big enough to hold the format string.
84 
85  \remark
86  - This routine basically substitutes each \c '9' character in \c fmtstr for
87  its corresponding decimal digit, or \c '0' when it is not a significant
88  digit. All other characters within \c fmtstr remain untouched.
89  - As it happens with many C functions, before invocation the programmer
90  must be sure that \c fmtstr is big enough to copy on it the complete
91  format string, as otherwise memory beyond \c fmtstr would be overwritten.
92  - There is no \c (wchart_t) version for this function, as it is meant to
93  place digits in a formatting string. After placement, the result string
94  can be converted to other forms.
95 
96  \dontinclude mnyfmtts.c
97  \skipline test.example
98  \until }}
99 
100  \see mnyfmtts.c
101 */
102 char* mnyfmt(char *fmtstr, char dec, mnyfmt_long moneyval, unsigned CE) {
103  #ifndef __cplusplus
104  const int false = 0; const int true = !false;
105  #endif
106  static mnyfmt_long TENPOW[6+1] = { 1,10,100,1000,10000,100000,1000000 };
107  if ( CE>(-1+sizeof(TENPOW)/sizeof(*TENPOW)) ) { return 0; }
108 
109  char *pDec, *pSign, *p;
110  unsigned nDigits, nFrac;
111  unsigned i, nNines;
112  int isPositive; // (0<=moneyval)
113  char digit[ 8 * ( sizeof(mnyfmt_long) > CE
114  ? sizeof(mnyfmt_long)
115  : CE
116  ) / 3 ];
117  // ensure that digit[] can hold more than 2 digits
118  typedef int check_digit_size[ ( (2<=sizeof(CE)) ? 1 : -1 ) ];
119 
120  if (!( fmtstr) ) { return 0; } // null pointer mistake
121 // if (!(*fmtstr) ) { return 0; } // null string mistake
122 
123  // determine dec position and store it in 'pDec'
124  pDec = fmtstr; // points to the decimal separator (or eos)
125  nNines = 0; // # of 'mnyfmt_format_chars' in fmtstr before decimal separator
126  while ( *pDec!=0 ) {
127  if ( *pDec==mnyfmt_format_char ) { ++nNines; } // count
128  if ( *pDec==dec ) { break; } // mark
129  ++pDec;
130  }
131  if ( pDec==fmtstr || nNines == 0 ) { return 0; } // NULL error
132 
133  // separate the integer and the fractional parts
134  if ( 0 <= moneyval ) { isPositive = true;}
135  else {
136  isPositive = false;
137  moneyval = -moneyval;
138  if ( moneyval<0 ) { return 0; } // -LONG_LONG_MAX-1 case
139  }
140  nFrac = ( moneyval % TENPOW[CE] ); // fractional part
141  moneyval = ( moneyval / TENPOW[CE] ); // integer part
142 
143  // store moneyval's digits in array digit[]
144  if ( moneyval==0 ) {
145  nDigits = 1; // number of digits in integer part
146  digit[0] = 0;
147  }
148  else {
149  nDigits = 0;
150  do { // get all integer part digits
151  digit[nDigits] = moneyval % 10;
152  moneyval /= 10;
153  ++nDigits;
154  } while ( moneyval!=0 );
155  }
156 
157  // check that fmtstr has enough mnyfmt_format_chars
158  if ( isPositive ) {
159  if ( nNines < nDigits ) { return 0; }
160  }
161  else { // use one mnyfmt_format_char for '-' sign
162  if ( nNines < nDigits+1 ) { return 0; }
163  }
164 
165  // store digit[] into fmtstr[]
166  p = pDec;
167  pSign = 0; // pSign && pSignificative
168  for ( i=0; i<nNines; ++i ) {
169  while ( *p!=mnyfmt_format_char ) { --p; } // backtrack to previous one
170  if ( i<nDigits ) {
171  *p = digit[i]+'0';
172  pSign = p; // points to the most significative digit
173  }
174  else if ( ! isPositive ) { // isNegative ==> store '-'
175  *p = '-';
176  pSign = p;
177  isPositive = true;
178  }
179  else {
180  *p = '0'; // replace leading mnyfmt_format_chars with '0'
181  }
182  --p;
183  }
184 
185  // deal with the fractional part
186  if ( *pDec==0 ) {
187  // eos ==> ignore nFrac
188  }
189  else {
190  p = pDec+1;
191  for ( i=0; i<CE; ++i ) {
192  digit[i] = nFrac % 10;
193  nFrac /= 10;
194  }
195 
196  for ( i=CE; (*p)==mnyfmt_format_char ; ++p ) {
197  if ( i>0 ) {
198  --i;
199  *p = digit[i]+'0';
200  }
201  else {
202  *p = '0';
203  }
204  }
205  }
206 
207 
208  // deal with the fractional part
209  if (true) { }
210  else if ( *pDec==0 ) {
211  // eos ==> ignore nFrac
212  }
213  else {
214  p = pDec+1;
215  if ( (*p) != mnyfmt_format_char ) {
216  // no format for fraction ==> ignore nFrac
217  }
218 
219  // find last in 'fmtstr'
220  // store all digits fron nFrac into digit[]
221  for ( i=0; i<CE; ++i ) {
222  digit[i] = nFrac % 10;
223  nFrac = nFrac / 10;
224  }
225  // store digit[] into fmtstr[]
226  i = CE;
227  for(;;) {
228  --i; ++p;
229  if ( *p!=mnyfmt_format_char ) { break; }
230  else {
231  *p = digit[i] +'0';
232  }
233  if ( i==0 ) { break; }
234  }
235  while ( *p==mnyfmt_format_char ) { *p='0'; ++p; }
236  }
237 
238  if (false) {
239  // make sure that nFrac fits within the format string
240  p = ( (*pDec==0) ? pDec : pDec+1 );
241  i = nFrac;
242  while (*p==mnyfmt_format_char) {
243  i /= 10;
244  ++p;
245  }
246  if ( i!=0 ) { return 0; }; // not enough digits in format string
247  }
248 
249  return pSign;
250 }
251 
252 // EOF: mnyfmt.c
Header file for mnyfmt().
long long mnyfmt_long
Definition: mnyfmt.h:17
char * mnyfmt(char *fmtstr, char dec, mnyfmt_long moneyval, unsigned CE)
Formats and stores in fmtstr the money amount.
Definition: mnyfmt.c:102
#define mnyfmt_format_char
Formatting character for mnyfmt().
Definition: mnyfmt.h:26