3 ### Generate exhaustive tests for floating-point conversions.
5 ### (c) 2024 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the mLib utilities library.
12 ### mLib is free software: you can redistribute it and/or modify it under
13 ### the terms of the GNU Library General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or (at
15 ### your option) any later version.
17 ### mLib is distributed in the hope that it will be useful, but WITHOUT
18 ### ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 ### FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
20 ### License for more details.
22 ### You should have received a copy of the GNU Library General Public
23 ### License along with mLib. If not, write to the Free Software
24 ### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
27 ###--------------------------------------------------------------------------
33 if SYS
.version_info
>= (3,):
34 from io
import StringIO
36 def iterkeys(d
): return d
.keys()
38 from cStringIO
import StringIO
39 def iterkeys(d
): return d
.iterkeys()
41 ###--------------------------------------------------------------------------
44 def bit(k
): "Return an integer with just bit K set."; return 1 << k
45 def mask(k
): "Return an integer with bits 0 to K - 1 set."; return bit(k
) - 1
48 def explore(wd
, lobits
, hibits
):
50 Return an iterator over various WD-bit values.
52 Suppose that a test wants to explore various WD-bit fields, but WD might be
53 too large to do this exhaustively. We assume (reasonably, in the case at
54 hand of floating-point formats) that the really interesting bits are those
55 at the low and high ends of the field, and test small subfields at the ends
56 exhaustively, filling in the bits in the middle with zeros, ones, or random
59 So, the generator behaves as follows. If WD <= LOBITS + HIBITS + 1 then
60 the iterator will yield all WD-bit values exhaustively. Otherwise, it
61 yields a sequence which includes all combinations of: every LOBITS-bit
62 pattern in the least significant bits; every HIBITS-bit pattern in the most
63 significant bits; and all-bits-clear, all-bits-set, and a random pattern in
67 if wd
<= hibits
+ lobits
+ 1:
68 for i
in xrange(bit(wd
)): yield i
70 midbit
= bit(wd
- hibits
- lobits
)
72 m
= (midbit
- 1) << lobits
73 for hi
in xrange(bit(hibits
)):
75 for lo
in xrange(bit(lobits
)):
78 yield base |
(R
.randrange(midbit
) << lobits
)
81 class ExploreParameters (object):
83 Simple structure for exploration parameters; see `explore' for background.
85 The `explo' and `exphi' attributes are the low and high subfield sizes
86 for exponent fields, and `siglo' and `sighi' are the low and high subfield
87 sizes for significand fields.
89 def __init__(me
, explo
= 0, exphi
= 2, siglo
= 1, sighi
= 3):
90 me
.explo
, me
.exphi
= explo
, exphi
91 me
.siglo
, me
.sighi
= siglo
, sighi
93 FMTMAP
= {} # maps format names to classes
95 def with_metaclass(meta
, *supers
):
97 Return an arbitrary instance of the metaclass META.
99 The class will have SUPERS (default just `object') as its superclasses.
100 This is intended to be used in direct-superclass lists, as a compatibility
101 hack, because the Python 2 and 3 syntaxes are wildly different.
103 return meta("#<anonymous base %s>" % meta
.__name__
,
104 supers
or (object,), dict())
106 class FormatClass (type):
108 Metaclass for format classes.
110 If the class defines a `NAME' attribute then register the class in
113 def __new__(cls
, name
, supers
, dict):
114 c
= type.__new__(cls
, name
, supers
, dict)
115 try: FMTMAP
[c
.NAME
] = c
116 except AttributeError: pass
119 class IEEEFormat (with_metaclass(FormatClass
)):
121 Floating point format class.
123 Concrete subclasses must define the following class attributes.
125 * `HIDDENP' -- true if the format uses a `hidden bit' convention for
128 * `EXPWD' -- exponent field width, in bits.
130 * `PREC' -- precision, in bits, /including/ the hidden bit if any.
132 Many useful quantities are derived.
134 * `_expbias' is the exponent bias.
136 * `_minexp' and `_maxexp' are the minimum and maximum representable
139 * `_sigwd' is the width of the significand field.
141 * `_paywords' is the number of words required to represent a NaN payload.
143 * `_nbits' is the total number of bits in an encoded value.
145 * `_rawbytes' is the number of bytes required for an encoded value.
150 Initialize an instance.
152 me
._expbias
= mask(me
.EXPWD
- 1)
153 me
._maxexp
= me
._expbias
154 me
._minexp
= 1 - me
._expbias
155 if me
.HIDDENP
: me
._sigwd
= me
.PREC
- 1
156 else: me
._sigwd
= me
.PREC
158 me
._paywords
= (me
._sigwd
+ 29)//32
160 me
._nbits
= 1 + me
.EXPWD
+ me
._sigwd
161 me
._rawbytes
= (me
._nbits
+ 7)//8
165 Decode the encoded floating-point value X, represented as an integer.
167 Return five quantities (FLAGS, EXP, FW, FRAC, ERR), corresponding mostly
168 to the `struct floatbits' representation, characterizing the value
171 * FLAGS is a list of flag tokens:
173 -- `NEG' if the value is negative;
174 -- `ZERO' if the value is exactly zero;
175 -- `INF' if the value is infinite;
176 -- `SNAN' if the value is a signalling NaN; and/or
177 -- `QNAN' if the value is a quiet NaN.
179 FLAGS will be empty if the value is a strictly positive finite
182 * EXP is the exponent, as a signed integer. This will be `None' if the
183 value is zero, infinite, or a NaN.
185 * FW is the length of the fraction, in 32-bit words. This will be
186 `None' if the value is zero or infinite.
188 * FRAC is the fraction or payload. This will be `None' if the value is
189 zero or infinite; otherwise it will be an integer, 0 <= FRAC <
190 2^{32FW}. If the value is a NaN, then the FRAC represents the
191 payload, /not/ including the quiet bit, left aligned. Otherwise,
192 FRAC is normalized so that 2^{32FW-1} <= FRAC < 2^{32FW}, and the
193 value represented is S FRAC 2^{EXP-32FW}, where S = -1 if `NEG' is in
194 FLAGS, or +1 otherwise. The represented value is unchanged by
195 multiplying or dividing FRAC by an exact power of 2^{32} and
196 (respectively) incrementing or decrementing FW to match, but this
197 will affect the output data in a way that affects the tests.
199 * ERR is a list of error tokens:
201 -- `INVAL' if the encoded value is erroneous (though decoding
204 ERR will be empty if no error occurred.
208 sig
= x
&mask(me
._sigwd
)
209 biasedexp
= (x
>> me
._sigwd
)&mask(me
.EXPWD
)
210 signbit
= (x
>> (me
._sigwd
+ me
.EXPWD
))&1
211 if not me
.HIDDENP
: unitbit
= sig
>> me
.PREC
- 1
213 ## Initialize flag lists.
217 ## Capture the sign. This is always relevant.
218 if signbit
: flags
.append("NEG")
220 ## If the exponent field is all-bits-set then we have infinity or NaN.
221 if biasedexp
== mask(me
.EXPWD
):
223 ## If there's no hidden bit then the unit bit should be /set/, but is
224 ## /not/ part of the NaN payload -- or even significant for
225 ## distinguishing a NaN from an infinity. If it's clear, signal an
226 ## error; if it's set, then clear it so that we don't have to think
229 if unitbit
: sig
&= mask(me
._sigwd
- 1)
230 else: err
.append("INVAL")
232 ## If the significand is (now) zero, we have an infinity and there's
233 ## nothing else to do.
236 frac
= fw
= exp
= None
238 ## Otherwise determine the NaN flavour and extract the payload.
240 if sig
&bit(me
.PREC
- 2): flags
.append("QNAN")
241 else: flags
.append("SNAN")
242 shift
= 32*me
._paywords
+ 2 - me
.PREC
243 frac
= (sig
&mask(me
.PREC
- 2)) << shift
247 ## Otherwise we have a finite number. We handle all of these together.
250 ## If there's no hidden bit, then check that the unit bit matches the
251 ## exponent: it should be clear if the exponent field is all-bits-zero
252 ## (zero or subnormal numbers), and set otherwise (normal numbers). If
253 ## this isn't the case, signal an error, but continue. We'll normalize
254 ## the number correctly as we go.
256 if (not biasedexp
and unitbit
) or (biasedexp
and not unitbit
):
259 ## If the exponent is all-bits-zero then set it to 1; otherwise, if the
260 ## format uses a hidden bit then force the unit bit of our significand
261 ## on. The absolute value is now exactly
263 ## 2^{biasedexp-_expbias-PREC+1} sig
266 if not biasedexp
: biasedexp
= 1
267 elif me
.HIDDENP
: sig |
= bit(me
._sigwd
)
269 ## If the significand is now zero then the value must be zero.
272 frac
= fw
= exp
= None
274 ## Otherwise we have a nonzero finite value, which might need
277 sigwd
= sig
.bit_length()
278 fw
= (sigwd
+ 31)//32
279 exp
= biasedexp
- me
._expbias
- me
.PREC
+ sigwd
+ 1
280 frac
= sig
<< (32*fw
- sigwd
)
283 return flags
, exp
, frac
, fw
, err
285 def _dump_as_bytes(me
, var
, x
, wd
):
287 Dump an assignment to VAR of X as a WD-byte binary string.
289 Print, on standard output, an assignment `VAR = ...' giving the value of
290 X, in hexadecimal, split with spaces into groups of 8 digits from the
295 print("%s = #empty" % var
)
298 for i
in xrange(wd
- 1, -1, -1):
299 out
.write("%02x" %
((x
>> 8*i
)&0xff))
300 if i
and not i
%4: out
.write(" ")
301 print("%s = %s" %
(var
, out
.getvalue()))
303 def _dump_flags(me
, var
, flags
, zero
= "0"):
305 Dump an assignment to VAR of FLAGS as a list of flags.
307 Print, on standard output, an assignment `VAR = ...' giving the named
308 flags. Print ZERO (default `0') if FLAGS is empty.
311 if flags
: print("%s = %s" %
(var
, " | ".join(flags
)))
312 else: print("%s = %s" %
(var
, zero
))
314 def genenc(me
, ep
= ExploreParameters()):
316 Print, on standard output, tests of encoding floating-point values.
318 The tests will cover positive and negative values, with the exponent and
319 signficand fields explored according to the parameters EP.
322 print("[enc%s]" % me
.NAME
)
324 for e
in explore(me
.EXPWD
, ep
.explo
, ep
.exphi
):
325 for m
in explore(me
.PREC
- 1, ep
.siglo
, ep
.sighi
):
326 if not me
.HIDDENP
and e
: m |
= bit(me
.PREC
- 1)
327 x
= (s
<< (me
.EXPWD
+ me
._sigwd
)) |
(e
<< me
._sigwd
) | m
328 flags
, exp
, frac
, fw
, err
= me
.decode(x
)
330 me
._dump_flags("f", flags
)
331 if exp
is not None: print("e = %d" % exp
)
333 while not frac
&M32
and fw
: frac
>>= 32; fw
-= 1
334 me
._dump_as_bytes("m", frac
, 4*fw
)
335 me
._dump_as_bytes("z", x
, me
._rawbytes
)
336 if err
: me
._dump_flags("err", err
, "OK")
338 def gendec(me
, ep
= ExploreParameters()):
340 Print, on standard output, tests of decoding floating-point values.
342 The tests will cover positive and negative values, with the exponent and
343 signficand fields explored according to the parameters EP.
346 print("[dec%s]" % me
.NAME
)
348 for e
in explore(me
.EXPWD
, ep
.explo
, ep
.exphi
):
349 for m
in explore(me
._sigwd
, ep
.siglo
, ep
.sighi
):
350 x
= (s
<< (me
.EXPWD
+ me
._sigwd
)) |
(e
<< me
._sigwd
) | m
351 flags
, exp
, frac
, fw
, err
= me
.decode(x
)
353 me
._dump_as_bytes("x", x
, me
._rawbytes
)
354 me
._dump_flags("f", flags
)
355 if exp
is not None: print("e = %d" % exp
)
356 if frac
is not None: me
._dump_as_bytes("m", frac
, 4*fw
)
357 if err
: me
._dump_flags("err", err
, "OK")
359 class MiniFloat (IEEEFormat
):
365 class BFloat16 (IEEEFormat
):
371 class Binary16 (IEEEFormat
):
377 class Binary32 (IEEEFormat
):
383 class Binary64 (IEEEFormat
):
389 class Binary128 (IEEEFormat
):
395 class DoubleExtended80 (IEEEFormat
):
401 ###--------------------------------------------------------------------------
404 op
= OP
.OptionParser \
405 (description
= "Generate test data for IEEE format encoding and decoding",
406 usage
= "usage: %prog [-E LO/HI] [-M LO/HI] [[enc|dec]FORMAT]")
407 for shortopt
, longopt
, kw
in \
408 [("-E", "--explore-exponent",
409 dict(action
= "store", metavar
= "LO/HI", dest
= "expparam",
410 help = "exponent exploration parameters")),
411 ("-M", "--explore-significand",
412 dict(action
= "store", metavar
= "LO/HI", dest
= "sigparam",
413 help = "significand exploration parameters"))]:
414 op
.add_option(shortopt
, longopt
, **kw
)
415 opts
, args
= op
.parse_args()
417 ep
= ExploreParameters()
418 for optattr
, loattr
, hiattr
in [("expparam", "explo", "exphi"),
419 ("sigparam", "siglo", "sighi")]:
420 opt
= getattr(opts
, optattr
)
423 try: sl
= opt
.index("/")
424 except ValueError: pass
426 try: lo
, hi
= map(int, (opt
[:sl
], opt
[sl
+ 1:]))
427 except ValueError: pass
429 setattr(ep
, loattr
, lo
)
430 setattr(ep
, hiattr
, hi
)
432 if not ok
: op
.error("bad exploration parameter `%s'" % opt
)
435 for fmt
in iterkeys(FMTMAP
):
436 args
.append("enc" + fmt
)
437 args
.append("dec" + fmt
)
441 if arg
.startswith("enc"): tail
= arg
[3:]; gen
= lambda f
: f
.genenc(ep
)
442 elif arg
.startswith("dec"): tail
= arg
[3:]; gen
= lambda f
: f
.gendec(ep
)
443 if tail
is not None: fmt
= FMTMAP
.get(tail
)
444 if not fmt
: op
.error("unknown test group `%s'" % arg
)
445 if firstp
: firstp
= False
449 ###----- That's all, folks --------------------------------------------------