// (C) Copyright Adolfo Di Mare 2011 // Use, modification and distribution are subject to the // Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) // Revision history: // Oct 2011 Adolfo Di Mare ==> Initial (non boost) version // Jun+Ago 2012 Adolfo Di Mare ==> CE correction // mnyfmt.c (C) 2011 adolfo@di-mare.com // Define this macro if your compiler does not have a (long long) data type // #define MNYFMT_NO_LONG_LONG #include "mnyfmt.h" #ifdef __cplusplus // To compile the C source with a C++ compiler, override the file's extension. // The GNU gcc compiler does this with option -x: gcc -x c++ #endif /** \file mnyfmt.c \brief Implementation for \c mnyfmt(). \author Adolfo Di Mare \date 2011 */ // Examples and unit test cases are in in file mnyfmtts.c /** Formats and stores in \c fmtstr the money amount. Before invocation, the formatting pattern (picture clause) is stored in result string \c fmtstr. To avoid using \c (double) values that have many round off problems, the parameter for this function is an integer scaled to 10^CE digits. For example, when using CE==2 digits, the monetary value "$2,455.87" is representad by the integer '245587', and if CE==4 digits are used, the integer value would be '24558700'. - The (integer) value to format is \c moneyval. - Overwrites \c fmtstr with the formatted value. - On error, leaves \c fmtstr untouched and returns \c (char*)(0). - If the \c fmtstr does not have enough format characters \c '9' for the integer part to format, of if the \c '-' cannot fit on top of a \c '9' character, \c fmtstr remains untouched and the value returned is \c (char*)(0). - The valid range for CE, the 'currenct exponent', is [0..6] [ a CE of 7 or bigger leaves \c fmtstr untouched and the value returned is \c (char*)(0) ]. - The first occurrence of the character \c dec is the decimal fraction separator (usually \c'.' or \c','). - When the decimal fraction separator character \c dec does not appear in \c fmtstr it is assumed to be \c '\0' (end of string character). - After the \c dec separator all the leading consecutive \c '9' format characters are substituted with the corresponding digit from the decimal part in \c moneyval, using digit zero \c '0' as fill character. - All digits that inmediatly follow the decimal fraction separator are changed either to zero or to the corresponding digit taken from the decimal part in \c moneyval. - Both the integer part and the fractional part are filled with the digits that correspond to its position. This means that a format string like \c "9999.9999" wild yield \c "0123.8700" as result when \c moneyval==1238700 and \c CE==4. - Characters trailing after the \c dec separator that are not the \c '9' format digit are left untouched. - All format characters \c '9' appearing before the decimal separator \c dec will be replaced by digit zero \c '0' if the corresponding digit in \c moneyval is not significant. - When \c moneyval is negative, the \c '-' sign will be place over the \c '9' immediately before the more significant digit. - Non format characters in \c fmtstr are left untouched. - The negative sign always is \c '-' and it is always placed on top of the corresponding format character. - Returns \c (char*)(0) when the formatted value does not fit within \c strlen(fmtstr) characters. - Returns a pointer to the first significant digit in the formatted string, within \c fmtstr or to the \c '-' sign if the formatted value is negative. - Before storing the format string in \c fmtstr, the programmer must ensure that \c fmtstr is big enough to hold the format string. \remark - This routine basically substitutes each \c '9' character in \c fmtstr for its corresponding decimal digit, or \c '0' when it is not a significant digit. All other characters within \c fmtstr remain untouched. - As it happens with many C functions, before invocation the programmer must be sure that \c fmtstr is big enough to copy on it the complete format string, as otherwise memory beyond \c fmtstr would be overwritten. - There is no \c (wchart_t) version for this function, as it is meant to place digits in a formatting string. After placement, the result string can be converted to other forms. \dontinclude mnyfmtts.c \skipline test.example \until }} \see mnyfmtts.c */ char* mnyfmt(char *fmtstr, char dec, mnyfmt_long moneyval, unsigned CE) { #ifndef __cplusplus const int false = 0; const int true = !false; #endif static mnyfmt_long TENPOW[6+1] = { 1,10,100,1000,10000,100000,1000000 }; if ( CE>(-1+sizeof(TENPOW)/sizeof(*TENPOW)) ) { return 0; } char *pDec, *pSign, *p; unsigned nDigits, nFrac; unsigned i, nNines; int isPositive; // (0<=moneyval) char digit[ 8 * ( sizeof(mnyfmt_long) > CE ? sizeof(mnyfmt_long) : CE ) / 3 ]; // ensure that digit[] can hold more than 2 digits typedef int check_digit_size[ ( (2<=sizeof(CE)) ? 1 : -1 ) ]; if (!( fmtstr) ) { return 0; } // null pointer mistake // if (!(*fmtstr) ) { return 0; } // null string mistake // determine dec position and store it in 'pDec' pDec = fmtstr; // points to the decimal separator (or eos) nNines = 0; // # of 'mnyfmt_format_chars' in fmtstr before decimal separator while ( *pDec!=0 ) { if ( *pDec==mnyfmt_format_char ) { ++nNines; } // count if ( *pDec==dec ) { break; } // mark ++pDec; } if ( pDec==fmtstr || nNines == 0 ) { return 0; } // NULL error // separate the integer and the fractional parts if ( 0 <= moneyval ) { isPositive = true;} else { isPositive = false; moneyval = -moneyval; if ( moneyval<0 ) { return 0; } // -LONG_LONG_MAX-1 case } nFrac = ( moneyval % TENPOW[CE] ); // fractional part moneyval = ( moneyval / TENPOW[CE] ); // integer part // store moneyval's digits in array digit[] if ( moneyval==0 ) { nDigits = 1; // number of digits in integer part digit[0] = 0; } else { nDigits = 0; do { // get all integer part digits digit[nDigits] = moneyval % 10; moneyval /= 10; ++nDigits; } while ( moneyval!=0 ); } // check that fmtstr has enough mnyfmt_format_chars if ( isPositive ) { if ( nNines < nDigits ) { return 0; } } else { // use one mnyfmt_format_char for '-' sign if ( nNines < nDigits+1 ) { return 0; } } // store digit[] into fmtstr[] p = pDec; pSign = 0; // pSign && pSignificative for ( i=0; i store '-' *p = '-'; pSign = p; isPositive = true; } else { *p = '0'; // replace leading mnyfmt_format_chars with '0' } --p; } // deal with the fractional part if ( *pDec==0 ) { // eos ==> ignore nFrac } else { p = pDec+1; for ( i=0; i0 ) { --i; *p = digit[i]+'0'; } else { *p = '0'; } } } // deal with the fractional part if (true) { } else if ( *pDec==0 ) { // eos ==> ignore nFrac } else { p = pDec+1; if ( (*p) != mnyfmt_format_char ) { // no format for fraction ==> ignore nFrac } // find last in 'fmtstr' // store all digits fron nFrac into digit[] for ( i=0; i