Version 8 of IEEE binary float to string conversion

Updated 2004-08-02 19:58:27

From a post in news:comp.lang.tcl :

FPX: Floating point values are usually transferred in IEEE format. IEEE 754-1985, "IEEE Standard for Binary Floating-Point Arithmetic" [L1 ] defines 32-bit and 64-bit encoding formats for floating-point numbers.

Normally, such values can be interpreted using Tcl's binary command, using the "f" and "d" formats. However, there is a catch: Tcl ultimately depends on the encoding of the "float" and "double" data types in the C language, and according to ISO C, the encoding of these data types is implementation dependent.

Consequently, the manual for binary warns that input and output of the "f" and "d" formats is not portable.

I have written the following code to read an IEEE float value, in case that your machine doesn't use IEEE natively. It also supports a "byteorder" flag that allows to read the value whether in big-endian or little-endian byteorder.

 proc IEEE2float {data byteorder} {
    if {$byteorder == 0} {
        set code [binary scan $data cccc se1 e2f1 f2 f3]
    } else {
        set code [binary scan $data cccc f3 f2 e2f1 se1]
    }

    set se1  [expr {($se1 + 0x100) % 0x100}]
    set e2f1 [expr {($e2f1 + 0x100) % 0x100}]
    set f2   [expr {($f2 + 0x100) % 0x100}]
    set f3   [expr {($f3 + 0x100) % 0x100}]

    set sign [expr {$se1 >> 7}]
    set exponent [expr {(($se1 & 0x7f) << 1 | ($e2f1 >> 7))}]
    set f1 [expr {$e2f1 & 0x7f}]

    set fraction [expr {double($f1)*0.0078125 + \
            double($f2)*3.0517578125e-05 + \
            double($f3)*1.19209289550781e-07}]

    set res [expr {($sign ? -1. : 1.) * \
            pow(2.,double($exponent-127)) * \
            (1. + $fraction)}]
    return $res
 }

It expects a binary buffer containing an IEEE number and the byte order the number is in (0 for big-endian and 1 for little-endian).

3fa22435 yields 1.2667299509 (big-endian) or 6.1330860035e-07 (little).

Here's code for the reverse transformation, from a floating-point value to IEEE format:

 proc float2IEEE {val byteorder} {
    if {$val > 0} {
        set sign 0
    } else {
        set sign 1
        set val [expr {-1. * $val}]
    }

    #
    # If the following math fails, then it's because of the
    # logarithm. That means that val is indistinguishable from
    # zero
    #

    if {[catch {
        set exponent [expr {int(floor(log($val)/0.69314718055994529))+127}]
        set fraction [expr {($val/pow(2.,double($exponent-127)))-1.}]
    }]} {
        set exponent 0
        set fraction 0.0
    } else {
        #
        # round off too-small values to zero, throw error for
        # too-large values
        #

        if {$exponent < 0} {
            set exponent 0
            set fraction 0.0
        } elseif {$exponent > 255} {
            error "value $val outside legal range for a float"
        }
    }

    set fraction [expr {$fraction * 128.}]
    set f1f      [expr {floor($fraction)}]
    set fraction [expr {($fraction - $f1f) * 256.}]
    set f2f      [expr {floor($fraction)}]
    set fraction [expr {($fraction - $f2f) * 256.}]
    set f3f      [expr {floor($fraction)}]

    set f1       [expr {int($f1f)}]
    set f2       [expr {int($f2f)}]
    set f3       [expr {int($f3f)}]

    set se1      [expr {($sign ? 128 : 0) | ($exponent >> 1)}]
    set e2f1     [expr {(($exponent & 0x1) << 7) | $f1}]

    if {$byteorder == 0} {
        set bytes [binary format cccc $se1 $e2f1 $f2 $f3]
    } else {
        set bytes [binary format cccc $f3 $f2 $e2f1 $se1]
    }

    return $bytes
 }

Also see 1750A to Float Conversion for converting to and from a MIL-STD-1750A 32-bit floating number.

Michael Jacobson ~ [email protected]


CL intends to make time to demonstrate how the constants above introduce small imprecisions around the twelfth decimal place.


Category Binary Data