Version 33 of format

Updated 2013-04-22 17:39:01 by pooryorick

Summary

Format a string in the style of sprintf

Synopsis

format formatString ?arg arg ...?

Description

This command generates a formatted string in the same way as the ANSI C sprintf procedure (it uses sprintf in its implementation). FormatString indicates how to format the result, using % conversion specifiers as in sprintf, and the additional arguments, if any, provide values to be substituted into the result. The return value from format is the formatted string.

Documentation

man page

Maximum Width for Numbers

Note that format, by default, doesn't provide a way to use one of the number formats (like %d, etc.) AND specify a maximum number of digits. You could specify the format as a %s and then provide a maximum number of characters, or you could write tcl code to check for maximum. Jonathan Bromley posted, on comp.lang.tcl during early Sep 2007, this code, which provides a first cut at a "-strict" initial arguement to format.

proc strictformat {fmt value} {
   set f [format $fmt $value]
   regexp {%(\d+)} $fmt -> maxwidth
   if {[string length $f]>$maxwidth} {
      return [string repeat * $maxwidth]
   } else {return $f}
}
rename format _format
proc format {args} {
   if {[string equal [lindex $args 0] -strict]} {
      eval strictformat $args
   } else {
      eval _format $args
   }
}

Make Unsigned Values

You can use [format] to produce unsigned integers for display (but don't reckon with them - for [[expr] they're still signed!):

% format %u -1
4294967295

See floating-point formatting for discussion on how to write format strings to handle floats...

DKF: Note that 8.5 makes this sort of thing much less necessary as we can now handle arbitrary width integers.

Nice Looking Floats

To make numbers look nice:

set fah [format "%0.2f" [expr $temperature_cel * 9 / 5 + 32]]

Color Formatting

set color [format #%02x%02x%02x $r $g $b]

Converting Characters

A limited formatting of decimals to characters is available in other languages, e.g. CHR() in Basic. If you use that more often, here's a cute shortcut:

interp alias {} chr {} format %c
% set a [chr 49][chr 48]
10

Abbreviating Integers

See Narrow formatting for short rendering of big integers, with powers of 1024:

% fixform 12345678
11.7M

Understanding Formats

This method should get format string and explain the format structure. This is a fast scatch:

proc explainFormat {formatStr vars} {
    set index 1
    foreach frm [split $formatStr "%"] {
        set extra ""
        set size 0
        regexp {([0-9]+)([duioxXcsfegG])(.*)} $frm => size type extra
        if {$size==0} {
            set size [string length $frm]
        } else {
            set frm "%$size$type [lindex $vars 0]"
            set vars [lrange $vars 1 end]
            set size [string trimleft $size 0]
        }
        for {set i 0} {$i<2} {incr i} {
            set newIndex [expr {$size +$index -1}]
            puts "$index-$newIndex '$frm'"
            set index [expr {$newIndex +1}]
            if {$extra==""} {
                break
            } else {
                set frm $extra
                set size [string length $extra]
            }
        }
    }
}
% explainFormat hello%02s000%3d $a $b
1-5 'hello'
6-7 '%02s $a'
8-10 '000'
11-13 '%3d $b'

Emulating Fortran

RS 2007-09-04: Here's emulating the FORTRAN behavior that numbers too large for the format are marked as an asterisk string:

proc strictformat {fmt value} {
    set f [format $fmt $value]
    regexp {%(\d+)} $fmt -> maxwidth
    if {[string length $f]>$maxwidth} {
        return [string repeat * $maxwidth]
    } else {return $f}
}

Testing:

% strictformat %5.2f 12.345
12.35
% strictformat %5.2f 123.45
*****
% strictformat %5.2f 12345.67
*****

Restricting Floats

While using Tcl 8.5, you will begin to see strings like 0.0052499999999999995 where before you were seeing values like 0.00525. To round the value to a shorter value, try something like:

format %.3g 0.0052499999999999995

Rebasing

On comp.lang.tcl, Don Porter writes, in reply to a question about how to go from base 10 to another base, such as 2 or 16, using arbitrarily large numbers in Tcl 8.5:

> I would have guessed that "format %x" should do the job, but apparently
> it's currently limited to 64 bits...

% format %llx 1234567890123456789012345
1056e0f36a6443de2df79

(Note: Those are ELLs above in '%11x'. Not {percent eleven lower-case-x}, but rather {percent, ell, ell, lower-case-x}. DrASK)

Digit Grouping

Digit grouping can make numbers with many digits easier to read.

ET: While I never liked the language ADA, it did have an idea that I wish had caught on, the optional use of an underscore character in large numerical constants, to make the numbers readable. (And trival for a compiler or interpreter to scan/parse).

So, 1_234_567 is a number that is as readable as 1,234,567 and is much better than 1234567. I have a handy little converter, (which I stole from somewhere on this wiki and modifed):

proc {commas} {var {num 3} {char ","}} {
    set len   [string length $var]
    set first [expr $len - $num]
    set x     ""
    while { $len > 0} {
        # grab left num chars
        set lef [string range $var $first end] 
        if {[string length $x] > 0} {
            set x   "${lef}$char${x}"
        } else {
            set x   ${lef}
        }
        # grab everything except left num chars
        set var [string range  $var 0 [expr $first -1]]
        set len   [string length $var]
        set first [expr $len - $num]
    }
    return $x
}

Here are some examples of its use:

dec2bin 987654
11110001001000000110

% commas [dec2bin 987654] 4 _
1111_0001_0010_0000_0110

% commas [dec2bin 987654] 4 { }
1111 0001 0010 0000 0110

% commas [dec2bin 987654] 1 { }
1 1 1 1 0 0 0 1 0 0 1 0 0 0 0 0 0 1 1 0

%commas 123456789     ;# naturally, it defaults for use with large decimal integers
123,456,789

% commas 123456789 3 _  ;# and here's how I wish numbers could be entered, in tcl and in C etc.
123_456_789

% puts "0x[commas [format %08X 123456789] 4 _]"    ;# and for hex numbers as well
0x075B_CD15   

Misc

LV: The man page for 8.4 is missing examples. 8.5 is better, but I'm looking for an example of the following. I have a report line that I am trying to fill out. It consists of a time stamp, a date stamp, and 2 text strings. each of these items must begin in a specific column.

set g "OHIO"
set fmtg [format "%-25.25s" $g]
puts [string length "$fmtg"]

The man page is complex enough that I want to be certain that I am not missing something. This seems to ensure that if g is longer than 25 characters, it is truncated, and if it is shorter than 25 characters, that it is left justified and blank padded. Are there any gotchas of which I need to be aware?

[TODO: Explain XPG positional format specifiers.]