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

  • Learn How A Global Entertainment Company Saw a 448% ROI Every business today uses software to manage systems, deliver products, and empower employees to do their jobs. But software inevitably breaks, and when it does, businesses lose money -- in the form of dissatisfied customers, missed SLAs or lost productivity. PagerDuty, an operations performance platform, solves this problem by helping operations engineers and developers more effectively manage and resolve incidents across a company's global operations. …

  • Live Event Date: December 18, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this upcoming webcast …

Most Popular Programming Stories

More for Developers

RSS Feeds