Algorithms for 6-byte ("real48") Borland Pascal Floating Point numbers

Martin Bless m.bless at gmx.de
Sun Dec 16 18:54:49 EST 2001


Below is a working example of 3 different ways I figured out to
convert those 6-byte Borland Pascal floating points to 'normal'
floats.

Status: experimental. 

What do you think about it?

The algorithms presented seem to hold at least for values from -1000.0
to +1000.0. But where are the pitfalls?

I'd be very pleased if someone with more "real" and "numerical"
understandig has a look at it.

And yes, if the algorithm 'real48_to_double'  seems ok,  it would be
wonderful to have this little bit shifting available via the 'struct'
module. Getting greedy: Yes, both ways, pack and unpack... :-)

Here's my 'real48.py':

'''Unpack Borland Pascal 6-byte "real48" floating point number.

Status: experimental

Martin Bless (mb), m.bless at gmx.de, 2001-12-16, 2001-12-16

In the context of this program the data structure of a
real48 number is assumed to be a six-byte string ('r48'):

r48[0]  r48[1]   r48[2]   r48[3]   r48[4]   r48[5]
a0      a1       a2       a3       a4       a5
sffffff ffffffff ffffffff ffffffff ffffffff eeeeeeee
s=sign, f=mantisse (39 bit), e=exponent (8bit, bias 129)
order: S-F-E
See http://www.merlyn.demon.co.uk/pas-type.htm#FF
'''

import struct

START_RANGE = -10
END_RANGE   = +10 + 1
USE_INLINE_DATA = 1

# sampled real nums from file created by Borland Pascal
pascal_data = (''
    +'\x84\x00\x00\x00\x00\xa0'  # -10.000000
    +'\x84\x00\x00\x00\x00\x90'  # -9.000000
    +'\x84\x00\x00\x00\x00\x80'  # -8.000000
    +'\x83\x00\x00\x00\x00\xe0'  # -7.000000
    +'\x83\x00\x00\x00\x00\xc0'  # -6.000000
    +'\x83\x00\x00\x00\x00\xa0'  # -5.000000
    +'\x83\x00\x00\x00\x00\x80'  # -4.000000
    +'\x82\x00\x00\x00\x00\xc0'  # -3.000000
    +'\x82\x00\x00\x00\x00\x80'  # -2.000000
    +'\x81\x00\x00\x00\x00\x80'  # -1.000000
    +'\x00\x00\x00\x00\x00\x00'  # 0.000000
    +'\x81\x00\x00\x00\x00\x00'  # 1.000000
    +'\x82\x00\x00\x00\x00\x00'  # 2.000000
    +'\x82\x00\x00\x00\x00\x40'  # 3.000000
    +'\x83\x00\x00\x00\x00\x00'  # 4.000000
    +'\x83\x00\x00\x00\x00\x20'  # 5.000000
    +'\x83\x00\x00\x00\x00\x40'  # 6.000000
    +'\x83\x00\x00\x00\x00\x60'  # 7.000000
    +'\x84\x00\x00\x00\x00\x00'  # 8.000000
    +'\x84\x00\x00\x00\x00\x10'  # 9.000000
    +'\x84\x00\x00\x00\x00\x20'  # 10.000000
    +'\x84\x00\x00\x00\x00\xa0'  # -10.0
    +'\x84\x00\x00\x00\x00\x90'  #  -9.0
    +'\x84\x00\x00\x00\x00\x80'  #  -8.0
    +'\x83\x00\x00\x00\x00\xe0'  #  -7.0
    +'\x83\x00\x00\x00\x00\xc0'  #  -6.0
    +'\x83\x00\x00\x00\x00\xa0'  #  -5.0
    +'\x83\x00\x00\x00\x00\x80'  #  -4.0
    +'\x82\x00\x00\x00\x00\xc0'  #  -3.0
    +'\x82\x00\x00\x00\x00\x80'  #  -2.0
    +'\x81\x00\x00\x00\x00\x80'  #  -1.0
    +'\x00\x00\x00\x00\x00\x00'  #   0.0
    +'\x81\x00\x00\x00\x00\x00'  #   1.0
    +'\x82\x00\x00\x00\x00\x00'  #   2.0
    +'\x82\x00\x00\x00\x00\x40'  #   3.0
    +'\x83\x00\x00\x00\x00\x00'  #   4.0
    +'\x83\x00\x00\x00\x00\x20'  #   5.0
    +'\x83\x00\x00\x00\x00\x40'  #   6.0
    +'\x83\x00\x00\x00\x00\x60'  #   7.0
    +'\x84\x00\x00\x00\x00\x00'  #   8.0
    +'\x84\x00\x00\x00\x00\x10'  #   9.0
    +'\x84\x00\x00\x00\x00\x20'  #  10.0
    )

# some helper functions
def s2hex(s,delim=''):
    '''String to hex-string.'''
    l = ['0000', '0001', '0010', '0011', '0100', '0101', '0110',
'0111',
         '1000', '1001', '1010', '1011', '1100', '1101', '1110',
'1111']
    result = map( lambda c,l=l: '%02x' % ord(c),s)
    result = delim.join(map( lambda c,l=l: '%02x' % ord(c),s))
    return result

def s2bin(s, byte_delim='', nibble_delim=''):
    '''String to bin-string.

    Example:
      print s2bin('AB') -> '0100000101000010'
      print s2bin('AB', '...') -> '01000001...01000010'
      print s2bin('AB', '...', '-') -> '0100-0001...0100-0010'
    '''

    l = ['0000', '0001', '0010', '0011', '0100', '0101', '0110',
'0111',
         '1000', '1001', '1010', '1011', '1100', '1101', '1110',
'1111']
    result = byte_delim.join(map( lambda c,l=l,d=nibble_delim:
                                  l[ord(c)/16]+d+l[ord(c)%16],s))
    return result

def bin2s(s, byte_delim='', nibble_delim=''):
    '''Reverse s2bin. Nothing checked.'''
    len1 = len(byte_delim)
    len2 = len(nibble_delim)
    L = []
    while s:
        L.append(s[:4] + s[4+len2:8+len2])
        s = s[8+len1+len2:]
    result = ''.join(map(lambda x:chr(int(x,2)),L))
    return result

def s_reverse(s):
    '''Reverse string. Is there something built in???'''
    L = map(lambda c:c,s)
    L.reverse()
    return ''.join(L)

def bin(i):
    # by Andrew Gaul, c.l.p, 2001-05-10          
    l = ['0000', '0001', '0010', '0011', '0100', '0101', '0110',
'0111',
         '1000', '1001', '1010', '1011', '1100', '1101', '1110',
'1111']
    s = ''.join(map(lambda x, l=l: l[int(x, 16)], hex(i)[2:]))
    if s[0] == '1' and i > 0:
        s = '0000' + s
    return s


# now the working part
def real48(r48):
    '''Return value of r48 - explicit calculation.

    Algorithm 'stolen' and adapted from:
      program sixbytes { jrs at merlyn.demon.co.uk  >= 2001-11-25 } ;
    at http://www.merlyn.demon.co.uk/pas-type.htm#FF
    '''
    a0,a1,a2 = ord(r48[0]), ord(r48[1]), ord(r48[2])
    a3,a4,a5 = ord(r48[3]), ord(r48[4]), ord(r48[5])
    sign = a0 / 128
    exp = a5 - 129
    # Q = 1.0/256  # Q / R2 = alternative
    if a5 == 0:
        R1 = 0.0
        # R2 = 0.0
    else:
        R1 = 1.0 + 2.0 * ((a0 %
128)+(a1+(a2+(a3+a4/256.0)/256.0)/256.0)/256.0)/256.0
        # R2 = 1.0 + 2.0 * ((((a4*Q+a3)*Q+a2)*Q+a1)*Q+(a0 % 128))*Q
    if sign:
        R1 = -R1
        # R2 = -R2
    # info_tuple = sign, exp, 2.0**exp * R1, R1, R2
    result = 2.0**exp * R1
    return result


def real48_to_single(r48):
    '''Convert r48 via struct as 'single float'. By mb.

    Experimental. What happens to precision? Does it
    hold for - at least - all possibilities of single floats?
    '''
    exp = ord(r48[5])
    if not exp:
        return 0.0
    # adjust for exponent bias 127 of singles
    # instead of 129 of real48s
    exp = exp - 129 + 127
    s = s2bin(r48[:6])
    rearranged = s[0]+s2bin(chr(exp))+s[1:40]
    packed_single = bin2s(rearranged)
    result, = struct.unpack('>f', packed_single[0:4])
    return result

def real48_to_double(r48):
    '''Convert r48 via struct as 'double float'. By mb.

    Experimental. What happens to precision? Does it
    hold for possibilities of a real48?

    order S-E-F, s=sign, e=exponent, f=fraction part,
    1-11-52 bits, exponent bias 1023
    '''
    exp = ord(r48[5])
    if not exp:
        return 0.0

    # adjust for exponent bias 1023 of doubles
    # instead of 129 of real48s.
    exp = exp - 129 + 1023
    # we only want 11 bits
    bin_exp = s2bin(chr(exp/256)+chr(exp%256))[5:16]
    
    s = s2bin(r48[:6])
    rearranged = s[0] + bin_exp + s[1:40] + '0'*(52-39)
    packed_double = bin2s(rearranged)
    result, = struct.unpack('>d', packed_double[0:8])
    return result


if __name__== "__main__":
    if USE_INLINE_DATA:
        position_adjust = 10
    else:
        #file with 6-byte floats  -1000.0 to +1000.0
        pascal_data = open('real.txt','rb').read()
        position_adjust = 1000

    for x in range(START_RANGE,END_RANGE):
        offset = (x + position_adjust) * 6
        s, = unpack('6s',pascal_data[offset:offset+6])
        # create some source data
        if 0:
            print "    +'\\x%02x\\x%02x\\x%02x\\x%02x\\x%02x\\x%02x'
# %5.1f" % \
                  (ord(s[0]),ord(s[1]),ord(s[2]),ord(s[3]),
                   ord(s[4]),ord(s[5]), x*1.0)
        r48 = s_reverse(s)
        v = '%6d: ' % x
        if 0:
            print v, s2bin(r48,' ')
        if 1:
            fmt = '%5.1f   '
            print v,
            print fmt % real48(r48),
            print fmt % real48_to_single(r48),
            print fmt % real48_to_double(r48)

++++++++++++++
History:


On 14 Dec 2001 22:18:22 +1100, nigel at pc714.maths.usyd.edu.au wrote:

>Ron Reidy <rereidy at indra.com> writes:
>
>> Martin Bless wrote:
>> > 
>> > I have to convert those "good (?) old" 6-byte, "real48" floating point
>> > numbers used in Borland's Pascal to nowadays "normal" floating points.
>> > 
>> > Has is already been done?
>> > 
>> > Martin
>> You will have to do this using pack().
>> -- 
>> Ron Reidy
>> Oracle DBA
>> Reidy Consulting, L.L.C.
>
>The little-known xdrlib in the standard python distro should do
>part of the job.
>
>http://www.python.org/doc/current/lib/module-xdrlib.html
>
>From what I can see in RFC1014 (http://www.faqs.org/rfcs/rfc1014.html)
>the XDR and IEEE formats coincide for floating point. The real48 format
>(http://www.borland.com/techpubs/delphi/delphi5/oplg/memory.html)
>looks similar enough to IEEE that a bit of byte-shifting should do
>the conversion, and xdrlib.pack_double(data) gives you your number.
>
>nigel
>( at maths. usyd. edu. au )




More information about the Python-list mailing list