1 # Copyright (C) 2013-2020 Roland Lutz
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 ## \namespace xorn.hybridnum
18 ## Hybrid fixed-/floating-point numbers.
20 # This module provides formatting and parsing functions for a hybrid
21 # number format which uses floating-point numbers as the base of a
22 # fixed-point notation. This way, decimal fractions down to an
23 # arbitrary (but fixed) number of digits can be represented exactly
24 # while still allowing the benefits from a floating-point number
27 # As an example, in a format with three fixed digits, both the number
28 # \c 1 (represented as the floating-point value \c 1000.0) and the
29 # number \c 0.001 (represented as the floating-point value \c 1.0) can
30 # be represented exactly, whereas the number \c 0.0001 (represented as
31 # the floating-point number \c 0.1) would be subject to conversion
34 # To avoid the usual errors when converting a floating-point number to
35 # a string and vice versa, hexadecimal notation is used for the
36 # decimals to the floating-point representation.
38 ## Convert a floating-point number to its hybrid string representation.
40 # TODO: For efficiency reasons, this should probably be ported to C.
42 def format(x
, decimal_digits
):
43 if not isinstance(decimal_digits
, int):
44 raise TypeError, 'number of decimals must be an integer'
45 if decimal_digits
< 0:
46 raise ValueError, 'number of decimals must be non-negative'
56 assert s
[0] == '0' and s
[1] == 'x'
60 assert s
[pos
+ 1] == '+' or s
[pos
+ 1] == '-'
61 mant
, exp
= s
[:pos
], int(s
[pos
+ 1:])
65 bits
.append(0) # shouldn't normally happen, though
73 bits
.append((d
>> 3) & 1)
74 bits
.append((d
>> 2) & 1)
75 bits
.append((d
>> 1) & 1)
76 bits
.append((d
>> 0) & 1)
80 bits_after
= [0] * -(exp
+ 1) + bits
82 while len(bits
) < exp
+ 1:
84 bits_before
= bits
[:exp
+ 1]
85 bits_after
= bits
[exp
+ 1:]
87 while bits_after
and bits_after
[-1] == 0:
89 while len(bits_after
) % 4 != 0:
92 before
= str(sum(b
<< i
for i
, b
in enumerate(reversed(bits_before
))))
94 for i
in xrange(0, len(bits_after
), 4):
95 after
+= '0123456789abcdef'[(bits_after
[i
] << 3) +
96 (bits_after
[i
+ 1] << 2) +
97 (bits_after
[i
+ 2] << 1) +
100 if decimal_digits
== 0:
103 return sign
+ ':' + after
104 return sign
+ before
+ ':' + after
106 return '0' # signless zero
109 if len(before
) < decimal_digits
:
110 before
= '0' * (decimal_digits
- len(before
)) + before
111 before0
= before
[:-decimal_digits
]
112 before1
= before
[-decimal_digits
:]
114 if before1
== '0' * decimal_digits
:
117 return sign
+ ':' + after
118 return sign
+ before0
+ '.' + before1
+ ':' + after
121 return '0' # signless zero
122 return sign
+ before0
125 return sign
+ before0
+ '.' + before1
+ ':' + after
127 return sign
+ before0
+ '.' + before1
.rstrip('0')
129 ## Convert a hybrid string representation to a floating-point number.
131 # TODO: For efficiency reasons, this should probably be ported to C.
133 def parse(s
, decimal_digits
):
134 if not isinstance(s
, str) and not isinstance(s
, unicode):
135 raise TypeError, 'invalid argument type (must be str or unicode)'
136 if not isinstance(decimal_digits
, int):
137 raise TypeError, 'number of decimals must be an integer'
138 if decimal_digits
< 0:
139 raise ValueError, 'number of decimals must be non-negative'
141 if s
and s
[0] == '-':
156 if not after
and not s
:
159 if decimal_digits
== 0:
168 if s
and after
is not None:
174 before1
= s
[pos
+ 1:]
175 if not before0
and not before1
:
177 if len(before1
) < decimal_digits
:
178 if after
is not None:
180 before1
= before1
+ (decimal_digits
- len(before1
)) * '0'
182 if len(before1
) > decimal_digits
:
188 for c
in before0
+ before1
:
189 if c
not in '0123456789':
192 if c
not in '0123456789abcdef':
200 x
= float(int(before0
) * 10 ** decimal_digits
+ int(before1
)) + \
201 float(int(after
, 16)) / float(1 << len(after
) * 4)
204 return 0. # signless zero