Updated 2014-10-27 21:01:10 by Jorge

Summary  edit

Methods for converting numbers between non-negative decimal and binary (we're talking about the printable textual representation of binary numbers here, e.g., 6 -> 110, and the inverse 110 -> 6).

See Also  edit

Big bitstring operations
Converting an integer to a base 2 string
to.binary
Math on binary strings
To little endian
[two's complement]

Converting Decimal to Binary  edit

proc dec2bin {i} {
    #returns a string, e.g. dec2bin 10 => 1010 
    set res {} 
    while {$i>0} {
        set res [expr {$i%2}]$res
        set i [expr {$i/2}]
    }
    if {$res == {}} {set res 0}
    return $res
}

In the following example, which requires Tcl 8.6 or later, %lb or %b could be used, but $int would then be truncated if necessary. Also, in the case of %lb or %b, if $int is too large, the result could be nonsensical, as a truncated version of the machine representation of a negative integer would be returned.
package require Tcl 8.6
proc dec2bin {int} {
    return [format %llb $int]
}

The following example includes a "width" option to set the width of the resulting string
proc dec2bin {i {width {}}} {
    #returns the binary representation of $i
    # width determines the length of the returned string (left truncated or added left 0)
    # use of width allows concatenation of bits sub-fields

    set res {}
    if {$i<0} {
        set sign -
        set i [expr {abs($i)}]
    } else {
        set sign {}
    }
    while {$i>0} {
        set res [expr {$i%2}]$res
        set i [expr {$i/2}]
    }
    if {$res == {}} {set res 0}

    if {$width != {}} {
        append d [string repeat 0 $width] $res
        set res [string range $d [string length $res] end]
    }
    return $sign$res
}

Another variation: use [format] to convert to octal (or hexadecimal, for

that matter), and [string map] to transform octal digits to bit patterns.
proc dec2bin x {
    if {[string index $x 0] eq {-}} {
        set sign -
        set x [string range $x 1 end]
    } else {
        set sign {}
    }
    return $sign[string trimleft [string map {
    0 {000} 1 {001} 2 {010} 3 {011} 4 {100} 5 {101} 6 {110} 7 {111}
    } [format %o $x]] 0]
}

And some examples:
% dec2bin 9
1001
% dec2bin 255
11111111

Only slightly different from the previous example, by Lars H:
proc dec2bin {dec} {
    if {[string match -* $dec]} {
        set sign -
        set dec [expr {abs($dec)}]
    } else {
        set sign {}
    }
    return $sign[string map {
        +0 0 +1 1 +2 {10} +3 {11} +4 {100} +5 {101} +6 {110} +7 {111}
        0 {000} 1 {001} 2 {010} 3 {011} 4 {100} 5 {101} 6 {110} 7 {111}
    } [format +%o $dec]]
}

The following example, which came from Googol magnitude is similar in spirit, but much slower:
proc dec2bin {dec} {
   while {[regexp {[0-9]} $dec]} {
      set dec\
         [string map {o0 0 o1 1 o2 2 o3 3 o4 4 i0 5 i1 6 i2 7 i3 8 i4 9 0 {}}\
            [string map {0 0o 1 0i 2 1o 3 1i 4 2o 5 2i 6 3o 7 3i 8 4o 9 4i} $dec]]
   }
   string map {i 1 o 0} $dec
}

The idea of dec2bin is to alternate between a normal decimal notation and an a bi-quinary [1] or "abacistic" notation where there are separate positions for fives (i = a five, o = no five). It is easy to generate the abacistic notation for half of a number in decimal notation -- just half every digit in place, e.g. 7 -> 3i -- and also easy to convert from abacistic to decimal notation, e.g. i3 -> 8. (Note that this doesn't combine is and os into the same decimal position as they were split off from.) is and os that don't have a digit to their right to recombine with constitute the bits that have been shifted out from the decimal number. bin2dec does the same thing backwards, with an extra h in the leftmost position to discard unnecessary zeroes.

Another alternative is to use [binary]. The general idea is illustrated in this simple example, followed by a more complete example:
proc dec2bin {int} {
    set binRep [binary format c $int]
    binary scan $binRep B* binStr
    return [string trimleft $binStr 0]
}

In the complete example, the bytes a list of numbers representing the individual bytes of the machine binary representation are built up, in little-ending order. The list is then reversed so that it is in big-endian order, and then scanned:
proc dec2bin {i} {
    #returns the binary representation of $i e.g. dec2bin 10 => 1010

    #this did not work properly where $i > [expr {2**32-1}]
    #binary scan [binary format I1 $i] B* x

    if {$i == 0} {
        return 0 
    } elseif  {$i > 0} {
        set sign {}
    } else {
        set sign -
        set i [expr {abs($i)}]
    }
    while {$i>0} {
        set mod [expr {$i % 256}]
        set i [expr {$i/256}]
        lappend mods $mod
    }
    binary scan [binary format c* [lreverse $mods[set mods {}]]] B* x
    return $sign[string trimleft $x 0]
}

Converting Binary to Decimal  edit

KBK: contributed the precursor to this variant in the Tcl chatroom on 2002-11-18:
proc bin2dec {bin} {
    if {$bin == 0} {
        return 0 
    } elseif  {[string match -* $bin]} {
        set sign -
        set bin [string range $bin 1 end]
    } else {
        set sign {}
    }
    if {[string map [list 1 {} 0 {}] $bin] ne {}} {
        error "argument is not in base 2: $bin"
    }
    set r 0
    foreach d [split $bin {}] {
        incr r $r
        incr r $d
    }
    return $sign$r
}
proc bin2dec {bin} {
    if {$bin == 0} {
        return 0
    } elseif {[string match -* $bin]} {
        set sign -
        set bin [string range $bin[set bin {}] 1 end]
    } else {
        set sign {}
    }
    return $sign[expr 0b$bin]
}
proc bin2dec {bin} {
    #returns integer equivalent of $bin 
    set res 0
    if {$bin == 0} {
        return 0
    } elseif {[string match -* $bin]} {
        set sign -
        set bin [string range $bin[set bin {}] 1 end]
    } else {
        set sign {}
    }
    foreach i [split $bin {}] {
        set res [expr {$res*2+$i}]
    }
    return $sign$res
}
proc bin2dec {bin} {
    if {$bin == 0} {
        return 0
    } elseif {[string match -* $bin]} {
        set sign -
        set bin [string range $bin[set bin {}] 1 end]
    } else {
        set sign {}
    }
    set res 0
    for {set j 0} {$j < [string length $bin]} {incr j} {
        set bit [string index $bin $j]
        set res [expr {$res << 1}]
        set res [expr {$res | $bit}]
    }
    return $sign$res
}
proc bin2dec {num} {
   set num h[string map {1 i 0 o} $num]
   while {[regexp {[io]} $num]} {
      set num\
         [string map {0o 0 0i 1 1o 2 1i 3 2o 4 2i 5 3o 6 3i 7 4o 8 4i 9 ho h hi h1}\
            [string map {0 o0 1 o1 2 o2 3 o3 4 o4 5 i0 6 i1 7 i2 8 i3 9 i4} $num]]
   }
   string range $num 1 end
}

[binary] could also be used:
proc bin2dec {bin} {
    #returns decimal representation of the binary representation $bin
    if {$bin == 0} {
        return 0 
    } elseif  {[string match -* $bin]} {
        set sign -
        set bin [string range $bin 1 end]
    } else {
        set sign {}
    }

    set mod [expr {[string length $bin]%8}]
    if {$mod} {
        set mod [expr {8-$mod}]
    }
    set bin [string repeat 0 $mod]$bin
    set len [string length $bin]
    set bin [binary format B* $bin]
    #the else block ould do it all, but for illustration...
    if {$len<=8} {
        binary scan $bin cu res
    } elseif {$len <=16} {
        binary scan $bin Su res
    } elseif {$len <=32} {
        if {$len <= 24} {
            set bin [binary format B* 0]$bin
        }
        binary scan $bin Iu res
    } else {
        set res 0
        set blen [expr {$len/8}]
        set pos -1
        while {$blen} {
            incr blen -1
            binary scan $bin x[incr pos]cu next
            set res [expr {$res + $next*(2**($blen*8))} ]
        }
    }
    return $sign$res
}

Misc  edit


##
##format Sign(1bit)Integer(8bit).fractional(2bit)  
##               X XXXXXXXX.XX


proc sigbinfrac2dec {bin} {
    #puts "Input String to be converted:  $bin"
    if {$bin == 0} {
        return 0 
    } elseif  {[string match *.* $bin]} {
        #fractional
        #set range [string length $bin]
        set flot [string first . $bin]
 
        set sign [string range $bin 0 0]
        set integer [string range $bin  1 [expr $flot - 1] ]
        #puts "Integer Part to be converted:  $integer"
        set frac [string range $bin [expr $flot + 1] end]
        #puts "Fractional Part to be converted:  $frac"
    } else {
        # No Fractional
        set sign [string range $bin 0 0]
        set integer [string range $bin  1 end]
    }
    if {[string map [list 1 {} 0 {} . {}] $bin] ne {}} {
        error "argument is not in base 2 or base 2 fractional: $bin"
    }
    #Algo for Integer Part to be executed in both Format
    set r 0
    foreach d [split $integer {}] {
        incr r $r
        incr r $d
    } 
    if {$sign == 1} {
     set signd -} else {
     set signd + 
     }     
    if  {[string match *.* $bin]} {
        #Algo for fractional Part to be executed only in case of flotting point being
    set x 0
    set flotd 0
    foreach d [split $frac {} ] {
       set x [incr x -1] 
       set flotd [expr $flotd + [expr {pow(2,$x)} * $d ]]
    }
    set flotdo [string range $flotd 1 end]
    return $signd$r$flotdo } else {
    return $signd$r
    }
    
}

Expected Output:
% source sigbinfrac2dec.tcl
% sigbinfrac2dec 11.11
-1.75

[Franco Cesari]
 # SPEC : Get the 2's complement rappresentation
 proc complement2 {binary width} {
 if  {[string length $binary] != $width } {
    puts stdout "Error in complement2 string passed does not match with length declared with width parameter."
    return -1
 } else {
    set compl  [string map {0 1 1 0}  $binary]
    set compl_dec [ bin2dec $compl]
    set complement_2dec [expr $compl_dec  + 1]
    set complement_2bin [dec2bin_width $complement_2dec $width]
    return $complement_2bin
 }
 }

Expected Output:
 complement2 000001100100        12
 111110011100

contributors: RS, Joseph Collard, Eric Amundsen, PYK , Franco Cesari