Version 4 of Delimiting Numbers

Updated 2005-11-29 08:17:16 by LES

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];
     }
 }

LES: I cannot understand the purpose of this proc or it simply doesn't work for me:

 % DelimitNumber 45869797940
 45,869,797,940

OK. But...

 % DelimitNumber 45869797940.4352543
 458,697,979,40...4352543
 % DelimitNumber 45869797940,4352543
 4,586,979,794,0,4,352,543
 % DelimitNumber 45869797940,4352543 .
 4.586.979.794.0,4.352.543
 % DelimitNumber 4586979794043525,43 "."
 4.586.979.794.043.525.,43

The way I understand the idea, this would be my proc:

 proc  DelimitNumber  {_number {_delim ","} {_decimals 0} }          { 

         regsub -all {[,.]} $_number {} _number
         if  ![string is digit $_number]          {error "Not an integer!"}

         set _delim2 [expr {$_delim == "," ? "." : ","}]

         if  {$_decimals > 0}          { 
             regsub -all "(\[0-9]+)(\[0-9]{$_decimals})" $_number "\\1$_delim2\\2"  _number
         }

         set _int [lindex [split $_number $_delim2] 0]

         while          {[regexp {([0-9]+)([0-9]{3})} $_int] == 1}          {
                 regsub  {([0-9]+)([0-9]{3})} $_int "\\1$_delim\\2" _int
         }

        if  {$_decimals > 0}          { 
            return  [join "$_int $_delim2 [lindex [split $_number $_delim2] 1]" {}]
        }

        return $_int
 }

Testing:

 % DelimitNumber 45869797940.4352543 , 7
 45,869,797,940.4352543
 % DelimitNumber 45869797940,4352543 . 7
 45.869.797.940,4352543
 % DelimitNumber 4586979794043525,43 "." 2
 4.586.979.794.043.525,43

Mine seems to work better, but you have to tell it how many decimal digits the original number has (and the result is supposed to have). Improving the proc to guess that automatically must be feasible, but tricky.