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)):
77 fill = R.randrange(midbit)
78 if fill != 0 and fill != midbit - 1: break
81 yield base | (fill << lobits)
84 class ExploreParameters (object):
86 Simple structure for exploration parameters; see `explore' for background.
88 The `explo' and `exphi' attributes are the low and high subfield sizes
89 for exponent fields, and `siglo' and `sighi' are the low and high subfield
90 sizes for significand fields.
92 def __init__(me, explo = 0, exphi = 2, siglo = 1, sighi = 3):
93 me.explo, me.exphi = explo, exphi
94 me.siglo, me.sighi = siglo, sighi
96 FMTMAP = {} # maps format names to classes
98 def with_metaclass(meta, *supers):
100 Return an arbitrary instance of the metaclass META.
102 The class will have SUPERS (default just `object') as its superclasses.
103 This is intended to be used in direct-superclass lists, as a compatibility
104 hack, because the Python 2 and 3 syntaxes are wildly different.
106 return meta("#<anonymous base %s>" % meta.__name__,
107 supers or (object,), dict())
109 class FormatClass (type):
111 Metaclass for format classes.
113 If the class defines a `NAME' attribute then register the class in
116 def __new__(cls, name, supers, dict):
117 c = type.__new__(cls, name, supers, dict)
118 try: FMTMAP[c.NAME] = c
119 except AttributeError: pass
122 class IEEEFormat (with_metaclass(FormatClass)):
124 Floating point format class.
126 Concrete subclasses must define the following class attributes.
128 * `HIDDENP' -- true if the format uses a `hidden bit' convention for
131 * `EXPWD' -- exponent field width, in bits.
133 * `PREC' -- precision, in bits, /including/ the hidden bit if any.
135 Many useful quantities are derived.
137 * `_expbias' is the exponent bias.
139 * `_minexp' and `_maxexp' are the minimum and maximum representable
142 * `_sigwd' is the width of the significand field.
144 * `_paywords' is the number of words required to represent a NaN payload.
146 * `_nbits' is the total number of bits in an encoded value.
148 * `_rawbytes' is the number of bytes required for an encoded value.
153 Initialize an instance.
155 me._expbias = mask(me.EXPWD - 1)
156 me._maxexp = me._expbias
157 me._minexp = 1 - me._expbias
158 if me.HIDDENP: me._sigwd = me.PREC - 1
159 else: me._sigwd = me.PREC
161 me._paywords = (me._sigwd + 29)//32
163 me._nbits = 1 + me.EXPWD + me._sigwd
164 me._rawbytes = (me._nbits + 7)//8
168 Decode the encoded floating-point value X, represented as an integer.
170 Return five quantities (FLAGS, EXP, FW, FRAC, ERR), corresponding mostly
171 to the `struct floatbits' representation, characterizing the value
174 * FLAGS is a list of flag tokens:
176 -- `NEG' if the value is negative;
177 -- `ZERO' if the value is exactly zero;
178 -- `INF' if the value is infinite;
179 -- `SNAN' if the value is a signalling NaN; and/or
180 -- `QNAN' if the value is a quiet NaN.
182 FLAGS will be empty if the value is a strictly positive finite
185 * EXP is the exponent, as a signed integer. This will be `None' if the
186 value is zero, infinite, or a NaN.
188 * FW is the length of the fraction, in 32-bit words. This will be
189 `None' if the value is zero or infinite.
191 * FRAC is the fraction or payload. This will be `None' if the value is
192 zero or infinite; otherwise it will be an integer, 0 <= FRAC <
193 2^{32FW}. If the value is a NaN, then the FRAC represents the
194 payload, /not/ including the quiet bit, left aligned. Otherwise,
195 FRAC is normalized so that 2^{32FW-1} <= FRAC < 2^{32FW}, and the
196 value represented is S FRAC 2^{EXP-32FW}, where S = -1 if `NEG' is in
197 FLAGS, or +1 otherwise. The represented value is unchanged by
198 multiplying or dividing FRAC by an exact power of 2^{32} and
199 (respectively) incrementing or decrementing FW to match, but this
200 will affect the output data in a way that affects the tests.
202 * ERR is a list of error tokens:
204 -- `INVAL' if the encoded value is erroneous (though decoding
207 ERR will be empty if no error occurred.
211 sig = x&mask(me._sigwd)
212 biasedexp = (x >> me._sigwd)&mask(me.EXPWD)
213 signbit = (x >> (me._sigwd + me.EXPWD))&1
214 if not me.HIDDENP: unitbit = sig >> me.PREC - 1
216 ## Initialize flag lists.
220 ## Capture the sign. This is always relevant.
221 if signbit: flags.append("NEG")
223 ## If the exponent field is all-bits-set then we have infinity or NaN.
224 if biasedexp == mask(me.EXPWD):
226 ## If there's no hidden bit then the unit bit should be /set/, but is
227 ## /not/ part of the NaN payload -- or even significant for
228 ## distinguishing a NaN from an infinity. If it's clear, signal an
229 ## error; if it's set, then clear it so that we don't have to think
232 if unitbit: sig &= mask(me._sigwd - 1)
233 else: err.append("INVAL")
235 ## If the significand is (now) zero, we have an infinity and there's
236 ## nothing else to do.
239 frac = fw = exp = None
241 ## Otherwise determine the NaN flavour and extract the payload.
243 if sig&bit(me.PREC - 2): flags.append("QNAN")
244 else: flags.append("SNAN")
245 shift = 32*me._paywords + 2 - me.PREC
246 frac = (sig&mask(me.PREC - 2)) << shift
250 ## Otherwise we have a finite number. We handle all of these together.
253 ## If there's no hidden bit, then check that the unit bit matches the
254 ## exponent: it should be clear if the exponent field is all-bits-zero
255 ## (zero or subnormal numbers), and set otherwise (normal numbers). If
256 ## this isn't the case, signal an error, but continue. We'll normalize
257 ## the number correctly as we go.
259 if (not biasedexp and unitbit) or (biasedexp and not unitbit):
262 ## If the exponent is all-bits-zero then set it to 1; otherwise, if the
263 ## format uses a hidden bit then force the unit bit of our significand
264 ## on. The absolute value is now exactly
266 ## 2^{biasedexp-_expbias-PREC+1} sig
269 if not biasedexp: biasedexp = 1
270 elif me.HIDDENP: sig |= bit(me._sigwd)
272 ## If the significand is now zero then the value must be zero.
275 frac = fw = exp = None
277 ## Otherwise we have a nonzero finite value, which might need
280 sigwd = sig.bit_length()
281 fw = (sigwd + 31)//32
282 exp = biasedexp - me._expbias - me.PREC + sigwd + 1
283 frac = sig << (32*fw - sigwd)
286 return flags, exp, frac, fw, err
288 def _dump_as_bytes(me, var, x, wd):
290 Dump an assignment to VAR of X as a WD-byte binary string.
292 Print, on standard output, an assignment `VAR = ...' giving the value of
293 X, in hexadecimal, split with spaces into groups of 8 digits from the
298 print("%s = #empty" % var)
301 for i in xrange(wd - 1, -1, -1):
302 out.write("%02x" % ((x >> 8*i)&0xff))
303 if i and not i%4: out.write(" ")
304 print("%s = %s" % (var, out.getvalue()))
306 def _dump_flags(me, var, flags, zero = "0"):
308 Dump an assignment to VAR of FLAGS as a list of flags.
310 Print, on standard output, an assignment `VAR = ...' giving the named
311 flags. Print ZERO (default `0') if FLAGS is empty.
314 if flags: print("%s = %s" % (var, " | ".join(flags)))
315 else: print("%s = %s" % (var, zero))
317 def genenc(me, ep = ExploreParameters()):
319 Print, on standard output, tests of encoding floating-point values.
321 The tests will cover positive and negative values, with the exponent and
322 signficand fields explored according to the parameters EP.
325 print("[enc%s]" % me.NAME)
327 for e in explore(me.EXPWD, ep.explo, ep.exphi):
328 for m in explore(me.PREC - 1, ep.siglo, ep.sighi):
329 if not me.HIDDENP and e: m |= bit(me.PREC - 1)
330 x = (s << (me.EXPWD + me._sigwd)) | (e << me._sigwd) | m
331 flags, exp, frac, fw, err = me.decode(x)
333 me._dump_flags("f", flags)
334 if exp is not None: print("e = %d" % exp)
336 while not frac&M32 and fw: frac >>= 32; fw -= 1
337 me._dump_as_bytes("m", frac, 4*fw)
338 me._dump_as_bytes("z", x, me._rawbytes)
339 if err: me._dump_flags("err", err, "OK")
341 def gendec(me, ep = ExploreParameters()):
343 Print, on standard output, tests of decoding floating-point values.
345 The tests will cover positive and negative values, with the exponent and
346 signficand fields explored according to the parameters EP.
349 print("[dec%s]" % me.NAME)
351 for e in explore(me.EXPWD, ep.explo, ep.exphi):
352 for m in explore(me._sigwd, ep.siglo, ep.sighi):
353 x = (s << (me.EXPWD + me._sigwd)) | (e << me._sigwd) | m
354 flags, exp, frac, fw, err = me.decode(x)
356 me._dump_as_bytes("x", x, me._rawbytes)
357 me._dump_flags("f", flags)
358 if exp is not None: print("e = %d" % exp)
359 if frac is not None: me._dump_as_bytes("m", frac, 4*fw)
360 if err: me._dump_flags("err", err, "OK")
362 class MiniFloat (IEEEFormat):
368 class BFloat16 (IEEEFormat):
374 class Binary16 (IEEEFormat):
380 class Binary32 (IEEEFormat):
386 class Binary64 (IEEEFormat):
392 class Binary128 (IEEEFormat):
398 class DoubleExtended80 (IEEEFormat):
404 ###--------------------------------------------------------------------------
407 op = OP.OptionParser \
408 (description = "Generate test data for IEEE format encoding and decoding",
409 usage = "usage: %prog [-E LO/HI] [-M LO/HI] [[enc|dec]FORMAT]")
410 for shortopt, longopt, kw in \
411 [("-E", "--explore-exponent",
412 dict(action = "store", metavar = "LO/HI", dest = "expparam",
413 help = "exponent exploration parameters")),
414 ("-M", "--explore-significand",
415 dict(action = "store", metavar = "LO/HI", dest = "sigparam",
416 help = "significand exploration parameters"))]:
417 op.add_option(shortopt, longopt, **kw)
418 opts, args = op.parse_args()
420 ep = ExploreParameters()
421 for optattr, loattr, hiattr in [("expparam", "explo", "exphi"),
422 ("sigparam", "siglo", "sighi")]:
423 opt = getattr(opts, optattr)
426 try: sl = opt.index("/")
427 except ValueError: pass
429 try: lo, hi = map(int, (opt[:sl], opt[sl + 1:]))
430 except ValueError: pass
432 setattr(ep, loattr, lo)
433 setattr(ep, hiattr, hi)
435 if not ok: op.error("bad exploration parameter `%s'" % opt)
438 for fmt in iterkeys(FMTMAP):
439 args.append("enc" + fmt)
440 args.append("dec" + fmt)
444 if arg.startswith("enc"): tail = arg[3:]; gen = lambda f: f.genenc(ep)
445 elif arg.startswith("dec"): tail = arg[3:]; gen = lambda f: f.gendec(ep)
446 if tail is not None: fmt = FMTMAP.get(tail)
447 if not fmt: op.error("unknown test group `%s'" % arg)
448 if firstp: firstp = False
452 ###----- That's all, folks --------------------------------------------------