Fast Numerical Formatting

.

Environment: VC6 SP4, W2k SP2

Display of the most financial data is required to be formated with a thousand separator. Like: 1000000 to be formated and displayed as "1,000,000"

There are lots of ways to do this formating, but there is no default crt function to do this for you. Here I try to present 2 functions, with very good performance that can be used for this this type of formating. Here is the most simple implementation.

static char sDecimalSeparator = '.'; // dot by default
static char sThousantSeparator =','; // comma by default

// call this to format a long to a string
static char* __stdcall long2str( char* szBuffer, long lValue )
{
  char *p;                // pointer to traverse string
  char *firstdig;         // pointer to first digit
  char temp;              // temp char
  unsigned digval;        // value of digit
  unsigned long val;

  p = szBuffer;

  if (lValue < 0 ) {
      // negative, so output '-' and negate
     *p++ = '-';
     val = (unsigned long)(-(long)lValue); // make it positive!!
  } else
     val = lValue;

  firstdig = p;    // save pointer to first digit

  int iDecimalPos = 0;

  do {
     iDecimalPos ++;
     if (iDecimalPos != 4)
     {
       digval = (unsigned) (val % 10);
       val /= 10;                     // get next digit
       *p++ = (char) (digval + '0');  // and store the digit
     } else
     {
       *p++ = sThousantSeparator;
       iDecimalPos = 0;
     }
  } while (val > 0);

  //  We now have the digit of the number in the buffer, 
  // but in reverse order.  Thus we reverse them now.

  *p-- = '\0';    // terminate string; p points to last digit

  do {
      temp = *p;
      *p = *firstdig;
      *firstdig = temp;   // swap *p and *firstdig
      --p;
      ++firstdig;         
  } while (firstdig < p); // repeat until halfway

  return szBuffer;
}

Now, most of the financial data is displayed with a sign as a number trailer.
eg: 1000+ or 1000-
Here is a new implementation one with this new functionality: adding the sign after the number, regarding it's positive or negative.

static char sDecimalSeparator = '.'; // dot by default
static char sThousantSeparator =','; // comma by default

// call this to format a long to a string
// iShowSign = 0 - show only the default "-" sign
// iShowSign = 1 - show minus/plus signs always at the 
//                 begining of the number (eg: +1000, -1000)
// iShowSign = 2 - show minus/plus signs always at the 
//                 end of the number (eg: 1000+, 1000-)
static char* __stdcall long2str( char* szBuffer,
                                 long lValue,
                                 int iShowSign = 0 )
{
  char *p;               // pointer to traverse string
  char *firstdig;        // pointer to first digit
  char temp;             // temp char
  unsigned digval;       // value of digit
  unsigned long val;

  p = szBuffer;

  if (lValue < 0 ) {
     // negative, so output '-' and negate
     if (iShowSign < 2)
       	*p++ = '-';
     val = (unsigned long)(-(long)lValue); // make it positive!!
  } else
  {
     if  ( iShowSign == 1 )
       *p++ = '+';
     val = lValue;
  }

  firstdig = p;     // save pointer to first digit

  int iDecimalPos = 0;

  do {
     iDecimalPos ++;
     if (iDecimalPos != 4)
     {
       digval = (unsigned) (val % 10);
       val /= 10;                    // get next digit
       *p++ = (char) (digval + '0'); // and store the digit
     } else
     {
       *p++ = sThousantSeparator;
       iDecimalPos = 0;
     }
  } while (val > 0);

  //  We now have the digit of the number in the buffer, 
  // but in reverse order.  Thus we reverse them now.

  // Check if we have to add signs
  if ( iShowSign == 2 )
  {
     if ( lValue < 0 )
       *p++ = '-';
     else
       *p++ = '+';
     *p-- = '\0'; // terminate string; p points to last digit
     *p--; // go back one more step to leave the sign sign intact!
  } else
  {
     *p-- = '\0'; // terminate string; p points to last digit
  }

  do {
      temp = *p;
      *p = *firstdig;
      *firstdig = temp;   // swap *p and *firstdig
      --p;
      ++firstdig;
  } while (firstdig < p); // repeat until halfway

  return szBuffer;
}

I hope that this is as useful to you, as it would have been for me some time ago. If you have implementations that are faster than those two, please send them to me.



Comments

  • it's a nice program

    Posted by lexito on 05/23/2008 11:07am

    how can you display ex. 234578E, in parts of as
    23
    45
    78
    E
      when it is pases to a function

    Reply
  • it's a nice program

    Posted by lexito on 05/23/2008 10:55am

    how can you display ex. 234578E, in parts of as 23 45 78 E when it is pases to a function

    Reply
  • How about this approach.

    Posted by Legacy on 12/20/2002 12:00am

    Originally posted by: Kenny Tsai

    typedef unsigned long DWORD;
    
    const char szFail[]="FAIL!";

    char *Long2Str (long lv,char *Buf,int BufSize)
    {
    char *p;
    if(Buf && 0<BufSize) {
    DWORD va=(DWORD)((lv<0)?-lv:lv);
    p=&Buf[BufSize];
    *--p=0;//Null terminate;
    if(!va && Buf<p) {//Zero case.
    *--p='0';
    }
    else {
    int cnt=0;
    while(va && Buf<p) {
    if(4==++cnt) {
    cnt=(*--p=',',0);
    }
    else {
    *--p=(char)(va%10+'0');
    va/=10;
    }
    }
    }
    if(va||(lv<0 && (Buf==p || 0==(*--p='-')))) {
    p=(char *)szFail;
    }
    }
    else {
    p=(char *)szFail;
    }
    return p;
    }

    void main (void)
    {
    char Buf[30];
    puts(Long2Str(0,Buf,30));
    puts(Long2Str(-987654321,Buf,12));
    puts(Long2Str(-987654321,Buf,30));
    }

    Reply
  • W32 API GetNumberFormat?

    Posted by Legacy on 07/30/2002 12:00am

    Originally posted by: Peter Ritchie

    What's wrong with the Win32 API Function GetNumberFormat?
    
    

    It allows to simple convert a number ([-]{0-9}[.{0-9}]) using the users current international settings:

    char szValue[] = "1000000.00";
    char szNewValue[1024];
    ::GetNumberFormat(LOCALE_USER_DEFAULT
    ,NULL
    , szValue
    , NULL
    , szNewValue
    , sizeof(szNewValue) / sizeof(szNewValue[0]));

    One thousand iterations of the above will take roughly 60 (sixty) milliseconds on a 266 Pentium I with Windows 2000.
    And, it's one line of code...

    It also allows you to override the number format to anything you want with parameter four (NUMBERFMT).

    See also GetCurrencyFormat, GetDateFormat, and GetTimeFormat

    http://msdn.microsoft.com/library/en-us/intl/nls_8a0k.asp
    http://msdn.microsoft.com/library/en-us/intl/nls_0rec.asp
    http://msdn.microsoft.com/library/en-us/intl/nls_5w6s.asp
    http://msdn.microsoft.com/library/en-us/intl/nls_6at0.asp


    Reply
  • just inserting comma

    Posted by Legacy on 02/20/2002 12:00am

    Originally posted by: soichi

    If I just put comma, I can use STL facet/locale to do that, but it's slow.. Before I wrote function which just to put commas in the numbers.. Here is what I've got
    
    

    // convert 123456789 to 123,456,789 (for example)
    char *MG_Utility::putcomma(char *s)
    {
    int i;


    if(len>3) {
    if(len%3 != 0) {
    for(i=0;i<len%3;i++) {
    *(ip++) = *(rp++);
    }
    *(ip++) = ',';
    }
    while(1) {
    for(i=0;i<3;i++) {
    *(ip++) = *(rp++);
    }
    if(rp != s+len) *(ip++) = ',';
    else break;
    }
    *(ip++) = 0;
    strcpy(s, n);
    }
    return s;
    }

    I haven't optimized or anything, but it's fast enough for what I was trying to do...

    Reply
  • STL way

    Posted by Legacy on 11/09/2001 12:00am

    Originally posted by: Yuriy Georgadze

    template<typename T>
    
    std::ostream& nifty_print(std::ostream s, const T& t)
    {
    //
    // change locale to the USA one to set
    // thousands separator to ','
    //
    std::locale old_locale(s.imbue(std::local("American_USA.1252")));
    s << t;
    s.imbue(old_locale); // restore locale
    }


    //
    // than you can do something like
    //

    nifty_print(std::cout, 32445679111L) << " result of 32445679111L" << std::endl;

    //
    // or...
    //
    std::stringstream ss;
    nifty_print(ss, 32445679111L);
    std::string s = ss.str();

    Reply
  • ERROR HANDLING

    Posted by Legacy on 11/08/2001 12:00am

    Originally posted by: Radu

    works well but some nice error handling if szBuffer goes out of its limits would be nicer (return NULL or extend szBuffer to hold the string).

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Flash technology is becoming more prominent in the storage industry. Offering superior speed and reliability when compared to traditional hard disk drives – flash storage is a flexible and increasingly cost-effective technology that can be used to optimize enterprise storage environments. This ebook explores the many uses and benefits of flash storage technology in the enterprise. Check it out to discover and learn all you need to: Optimize storage performance Leverage server flash as storage cache …

  • Managing your company's financials is the backbone of your business and is vital to the long-term health and viability of your company. To continue applying the necessary financial rigor to support rapid growth, the accounting department needs the right tools to most efficiently do their job. Read this white paper to understand the 10 essentials of a complete financial management system and how the right solution can help you keep up with the rapidly changing business world.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds