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