Version 2 of Delimiting Numbers

Updated 2005-11-28 04:42:20

WJP I find large numbers hard to read without group delimiters, but Tcl's format command doesn't provide automatic delimiter insertion the way some recent versions of C printf do. Here's a Tcl procedure that does the job. It defaults to a group size of 3 and comma as the delimiter but has optional arguments that allow other choices for locales that use other delimiters and group sizes.


See also commas added to numbers.


 # Given a number represented as a string, insert delimiters to break it up for
 # readability. Normally, the delimiter will be a comma which will be inserted every
 # three digits. However, the delimiter and groupsize are optional arguments,
 # permitting use in other locales.
 #
 # The string is assumed to consist of digits, possibly preceded by spaces,
 # and possibly containing a decimal point, i.e.: [:space:]*[:digit:]*\.[:digit:]*

 proc DelimitNumber {number {delim ","} {GroupSize 3}} {
     # First, extract right hand part of number, up to and including decimal point
     set point [string last "." $number];
     if {$point >= 0} {
         set PostDecimal [string range $number $point end];
         set PostDecimalP 1;
     } else {
         set point end;
         set PostDecimal "";
         set PostDecimalP 0;
     }

     # Now extract any leading spaces.
     set ind 0;
     while {[string equal [string index $number $ind] \u0020]} {
         incr ind;
     }
     set FirstNonSpace $ind;
     set LastSpace [expr $FirstNonSpace - 1];
     set LeadingSpaces [string range $number 0 $LastSpace];

     # Now extract the non-fractional part of the number, omitting leading spaces.
     set MainNumber [string range $number $FirstNonSpace $point];

     # Insert commas into the non-fractional part.
     set Length [string length $MainNumber];
     set Phase  [expr $Length % $GroupSize]
     set PhaseMinusOne  [expr $Phase -1];
     set DelimitedMain "";

     #First we deal with the extra stuff.
     if {$Phase > 0} {
         append DelimitedMain [string range $MainNumber 0 $PhaseMinusOne];
     }
     set FirstInGroup $Phase;
     set LastInGroup [expr $FirstInGroup + $GroupSize -1];
     while {$LastInGroup < $Length} {
         if {$FirstInGroup > 0} {
             append DelimitedMain $delim;
         }
         append DelimitedMain [string range $MainNumber $FirstInGroup $LastInGroup];
         incr FirstInGroup $GroupSize
         incr LastInGroup  $GroupSize
     }

     # Reassemble the number.
     if {$PostDecimalP} {
         return [format "%s%s.%s" $LeadingSpaces $DelimitedMain $PostDecimal];
     } else {
         return [format "%s%s" $LeadingSpaces $DelimitedMain];
     }
 }