A Fast Comma Formatter for Integers

 

For formatting integers as strings with commas (e.g. 12345 => '12,345') the library function usually employed is the FormatFloat function. It is located in the SysUtils unit, and is used as follows:

      FormatFloat(',0', someInt);
      

However, since this is a function for floating-point numbers, and thus works with floating-point values internally it is inherently slower than an integer-based function. When an integer is passed to it,  the integer is silently converted to a float (this is known as promotion), and processing continues normally. With today's increasingly faster math coprocessors, the speed difference between integer and floating-point calculations is steadily decreasing; however, even on newer processors, integer arithmetic is still significantly faster.

For comma-formatting integers, using a float function takes up unnecessary computing time. This is not normally a problem when showing a few values, but if you need to do it thousands of times per second, then the wastage adds up. For this, you can use the function presented below. It accepts integers only, and returns a string version of the number with comma-separated thousands groups.

 

function IntToStrComma( Value: integer ): string;
(*
   Very fast inttostr with comma support. Hated using formatfloat()
   handles negative numbers (20Dec01,4:18p)
*)
var len, i: integer;
    buf: array [0..14] of char;
    isneg: boolean;
begin
     if Value = 0 then
     begin
          Result := '0';
          Exit;
     end;

     asm
        (* EDX:EAX = dividend, EAX = quotient
         * ECX = divisor
         * EDX = remainder
         * EBX = buf offset
         * ESI = counter for inserting commas
         *)

        PUSH EBX // save regs
        PUSH ESI


        XOR EBX, EBX

        MOV EAX, Value // get num

        // negative?
        CMP EAX, 0
        JGE @Positive
        MOV IsNeg, True
        NEG EAX
        JMP @WasNeg
        @Positive:
        MOV IsNeg, False
        @WasNeg:

        // first divide
        CDQ
        MOV ECX, 10
        IDIV ECX
        // ans now in EAX, rem in EDX
        ADD DL, 48 // convert to ascii
        MOV Byte(buf[EBX]), DL
        INC EBX

        MOV ESI, 1 // comma
        @WhileTop:  // WHILE EAX > 0 DO
            CMP EAX, 0
            JE @WhileBottom

            CDQ // these must be done again
            MOV ECX, 10
            IDIV ECX
            // ans now in EAX, rem in EDX


            // comma
            INC ESI
            CMP ESI, 4
            JNE @NoComma
            MOV byte(buf[EBX]), ','
            INC EBX
            MOV ESI, 1
            @NoComma:


            ADD DL, 48 // convert to ascii
            // save to string
            MOV byte(buf[EBX]), DL
            INC EBX

            JMP @WhileTop
        @WhileBottom: // END OF WHILE

        MOV [len], EBX
        //INC [len]

        POP ESI // restore regs
        POP EBX
     end;

     if IsNeg then
     begin
          buf[len] := '-';
          inc(len);
     end;

     (* At this point, buf contains REVERSED string; copy and reverse *)
     SetLength( Result, len );
     for i := 1 to len do
     begin
          Result[i] := Char(buf[len-i]);
     end;
end;

On newer, faster processors, FormatFloat is almost twice as slow. On slower processors, the difference can be much larger. On a Pentium 1, for example, FormatFloat is more than three times slower. I'm sure this can be made even faster, though. If you do, please let me know.

 

Download
A unit containing the function can downloaded as a zip file here.

 


(c) 2002 emil santos

codexterity
ems ATSIGN codexterity PERIOD com