def speedDemo(): """calculate indicative speed of floating-point operations""" print('=== speed test ===') for res, count in [ (16, 10000), (32, 10000), (64, 10000), (128, 10000), (256, 10000), (512, 10000) ]: fam = FXfamily(res) x = fam(0.5) lmb = fam(3.6) one = fam(1.0) t0 = time.perf_counter() for i in range(count): # use logistic-map in chaotic region: x = lmb * x * (one - x) t1 = time.perf_counter() ops = count * 3 Dt = t1 - t0 print('{0} {1}-bit arithmetic operations in {2:.2f}s ~ {3:.2g} FLOPS' \ .format(ops, res, Dt, (ops / Dt))) for res, count in [ (4, 10000), (8, 10000), (12, 10000), (24, 10000), (48, 10000), (128, 10000), (512, 10000) ]: fam = FXfamily(res, 4) x = fam(2) t0 = time.perf_counter() for i in range(count): y = x.sqrt() t1 = time.perf_counter() Dt = (t1 - t0) print('{} {}-bit square-roots in {:.3g}s ~ {:.3g}/ms' \ .format(count, res, Dt, count*1e-3/Dt))
def testCrudeSqrt(self): """Check initialization of sqrt() initializing approximation""" fam24 = FXfamily(24) self.assertLess(abs(FXnum(2, fam24)._init_sqrt() - 1.5), 0.55) self.assertEqual(FXnum(4, fam24)._init_sqrt(), 2) self.assertEqual(FXnum(16, fam24)._init_sqrt(), 4) self.assertLess(abs(FXnum(120, fam24)._init_sqrt() - 12.0), 4.05) self.assertLess(abs(FXnum(0.5, fam24)._init_sqrt() - 0.7), 0.31) self.assertEqual(FXnum(0.0625, fam24)._init_sqrt(), 0.25) self.assertLess(abs(FXnum(0.002, fam24)._init_sqrt() - 0.046), 0.017) self.assertLess(abs(FXnum(0.0001, fam24)._init_sqrt() - 0.011), 0.005) fam23 = FXfamily(23) self.assertLess(abs(FXnum(2, fam23)._init_sqrt() - 1.5), 0.55) self.assertEqual(FXnum(4, fam23)._init_sqrt(), 2) self.assertEqual(FXnum(16, fam23)._init_sqrt(), 4) self.assertLess(abs(FXnum(120, fam23)._init_sqrt() - 12.0), 4.05) self.assertLess(abs(FXnum(0.5, fam23)._init_sqrt() - 0.7), 0.31) self.assertEqual(FXnum(0.0625, fam23)._init_sqrt(), 0.25) self.assertLess(abs(FXnum(0.002, fam23)._init_sqrt() - 0.046), 0.017) self.assertLess(abs(FXnum(0.0001, fam23)._init_sqrt() - 0.011), 0.005)
def testNumCreateRounding(self): """Check rounding behaviour on FXnum creation""" fam = FXfamily(3) self.assertEqual(FXnum(0.375, fam).scaledval, 3) self.assertEqual(FXnum(0.437, fam).scaledval, 3) self.assertEqual(FXnum(0.438, fam).scaledval, 4) self.assertEqual(FXnum(0.46, fam).scaledval, 4) fam = FXfamily(4) self.assertEqual(FXnum(-0.75, fam).scaledval, -12) self.assertEqual(FXnum(-0.78124, fam).scaledval, -12) self.assertEqual(FXnum(-0.78126, fam).scaledval, -13) self.assertEqual(FXnum(-0.8125, fam).scaledval, -13)
def testConstPrecision(self): """Check claimed precision of constants such as pi, log2, etc.""" extra_bits = 64 for res in (8, 16, 32, 64, 256, 1024): fam0 = FXfamily(res) fam_extra = FXfamily(res + extra_bits) for field in ('exp1', 'log2', 'pi', 'sqrt2'): x0 = getattr(fam0, field).scaledval x_extra = getattr(fam_extra, field).scaledval self.assertEqual(x0, x_extra >> extra_bits)
def testFamEquality(self): """Check tests on equivalence of FXfamilies""" idxlist = [8, 16, 24, 33, 59] for idx in idxlist: fam0 = FXfamily(idx) for jdx in idxlist: fam1 = FXfamily(jdx) if idx == jdx: self.assertTrue(fam0 == fam1) self.assertFalse(fam0 != fam1) else: self.assertFalse(fam0 == fam1) self.assertTrue(fam0 != fam1)
def testIntRange(self): """Check detection of overflow of integer part.""" for top in [-3, -2, 0, 2, 4, 6]: fam = FXfamily(16, top) a = FXnum(1.0 / 16.01, fam) zero = FXnum(0, fam) limit = 1 << (top + 4 - 1) # Unlikely to be valid FXnum(,fam) cnt, x, y = 0, zero, zero while cnt < (limit + 5): cnt += 1 try: x += a except FXoverflowError: if cnt <= limit: self.fail() else: if cnt > limit: self.fail() try: y -= a except FXoverflowError: if cnt <= limit: self.fail() else: if cnt > limit: self.fail()
def testOctalPi(self): # Value from http://turner.faculty.swau.edu/mathematics/materialslibrary/pi/pibases.html: piOctal = '3.1103755242102643021514230630505600670163211220111602105147630720020273724616611633104505120207461615' for res in range(3, 300, 9): printed = FXfamily(res + 2).pi.toBinaryString(3)[:-1] self.assertEqual(piOctal[:len(printed)], printed, 'Octal printing failed for res={}'.format(res))
def testBinarySeries(self): """Check binary printing of enumerated signed integers.""" for res in range(1, 5): fam = FXfamily(res) for scaled in range(1 << (res + 4)): pos = FXnum(family=fam, scaled_value=scaled) for sign in ((1, -1) if scaled > 0 else (1, )): printed = (sign * pos).toBinaryString() if sign > 0: x = scaled else: if scaled < (1 << res): shift = res + 1 else: shift = math.ceil(math.log2(scaled)) + 1 x = (1 << shift) - scaled binstr = bin(x)[2:] pad = res + 1 - len(binstr) if pad > 0: binstr = '0' * pad + binstr binstr = binstr[:-res] + '.' + binstr[-res:] self.assertEqual(binstr, printed, 'Binary printing failed for {}, res={}' \ .format(float(sign * pos), res))
def testFamilyProtection(self): """Check that arithmetic operators do not transmute resolution families""" famlist = [FXfamily(res) for res in [8, 16, 40, 90]] for fam0 in famlist: for fam1 in famlist: x = FXnum(2, fam0) y = FXnum(3, fam1) try: a = x + y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x - y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x * y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1) try: a = x / y except FXfamilyError: self.assertFalse(fam0 is fam1) else: self.assertTrue(fam0 is fam1)
def testTwosComplement(self): """Check conversion to twos-complement for binary/hex printing""" fam = FXfamily(3) self.assertEqual(fam(0.25)._toTwosComplement(), (0x02, 1, 3)) self.assertEqual(fam(0.125)._toTwosComplement(1), (0x01, 1, 3)) self.assertEqual(fam(0.125)._toTwosComplement(4), (0x02, 1, 1)) fam = FXfamily(4) self.assertEqual(fam(15.0625)._toTwosComplement(4), (0xf1, 1, 1)) self.assertEqual(fam(-7.25)._toTwosComplement(4), (0x8c, 1, 1)) self.assertEqual(fam(-25.0)._toTwosComplement(4), (0x0e70, 2, 1)) self.assertEqual(fam(-128.0)._toTwosComplement(4), (0x0800, 2, 1)) fam = FXfamily(5, 3) self.assertEqual(fam(3.625)._toTwosComplement(), (0x74, 3, 5)) self.assertEqual(fam(-3.5)._toTwosComplement(), (0x90, 3, 5))
def testDivision(self): """Division operators should promote & inverse-commute""" fam62 = FXfamily(62) scale = 0.125 scale2 = scale * scale for a in range(-32, 32): if a == 0: continue fpa = FXnum(a * scale, fam62) for y in range(-16, 16): if y == 0: continue fpy = FXnum(y * scale, fam62) fpx = FXnum((y * a) * scale2, fam62) # compute various forms of a = (x / y): self.assertAlmostEqual(fpa, fpx / fpy) self.assertAlmostEqual(1 / fpa, fpy / fpx) self.assertAlmostEqual((a * scale), float(fpx / fpy)) tmp = fpx tmp /= fpy self.assertAlmostEqual(fpa, tmp) tmp = float(a * y * scale2) / fpy self.assertAlmostEqual(fpa, tmp) tmp = fpx / float(y * scale) self.assertAlmostEqual(fpa, tmp)
def testHexPi(self): # Value from https://sourceforge.net/projects/hexpi/: piHex = '3.243f6a8885a308d313198a2e03707344a4093822299f31d0082efa98ec4e6c89452821e638d01377be5466cf34e90c6cc0ac29b7c97c50dd3f84d5b5b54709179216d5d98979fb1bd1310ba698dfb5ac2ffd72dbd01adfb7b8e1afed6a267e96ba7c9045f12c7f9924a19947b3916cf70801f2e2858efc16636920d871574e69a458fea3f4933d7e0d95748f728eb658718bcd588215' self.maxDiff = 900 for res in range(32, 1200, 64): printed = FXfamily(res + 2).pi.toBinaryString(4)[:-1] self.assertEqual(piHex[:len(printed)], printed, 'Hex printing failed for res={}'.format(res))
def testMathConsts(self): """Check various mathematical constants against math module.""" for ident, target in [('unity', 1.0), ('exp1', math.e), ('log2', math.log(2)), ('pi', math.pi), ('sqrt2', math.sqrt(2))]: self.assertAlmostEqual(getattr(FXfamily(64), ident), target, places=15)
def testMonoInt(self): """Check printing of numbers with single non-fractional bit""" fam = FXfamily(4, 1) self.assertEqual(fam(-0.75).toBinaryString(), '1.0100') self.assertEqual(fam(-0.25).toBinaryString(), '1.1100') self.assertEqual(fam(0.5).toBinaryString(), '0.1000') self.assertEqual(fam(0.825).toBinaryString(), '0.1101')
def testRawBuild(self): """Check equivalence of FXnum._rawbuild() and FXnum().""" fam = FXfamily(10, 5) x = FXnum(val=9.7531, family=fam) xraw = FXnum._rawbuild(fam, x.scaledval) self.assertEqual(x, xraw) self.assertIsInstance(x, FXnum) self.assertIsInstance(xraw, FXnum) self.assertIsNot(x, xraw)
def testExpLog(self): """exp and log methods should be inverses & agree with math.*""" fam62 = FXfamily(62) scale = 0.27 for i in range(-32, 32): x = i * scale exp = FXnum(x, fam62).exp() logexp = exp.log() self.assertAlmostEqual(x, float(logexp))
def testExp(self): """Exponent method should agree with math.exp""" fam62 = FXfamily(62) scale = 0.23 for i in range(-32, 32): x = i * scale exp_true = math.exp(x) exp = FXnum(x, fam62).exp() self.assertAlmostEqual(exp_true, exp)
def draw(cls): losses = [] for bits in range(4, 500, 4): fam_acc = FXfamily(bits + 40) fam = FXfamily(bits) const_true = cls.calcConsts(fam_acc, famOnly=True)[0] losses.append([ bits ] + [ cls.lostbits(apx, const_true) for apx in cls.calcConsts(fam) ]) losses = numpy.array(losses) plt.xlabel('resolution bits') plt.ylabel('error bits') plt.grid(True) for colno, label in enumerate(cls.getLabels(), 1): plt.plot(losses[:,0], losses[:,colno], label=label) plt.legend(loc='best', fontsize='small') plt.show()
def testTwosCompCreate(self): """Check creation of FXnum from two's complement representation""" fam = FXfamily(2, 6) self.assertEqual(fam.from2c(0), fam.zero) self.assertEqual(fam.from2c(4), fam.unity) self.assertEqual(fam.from2c(127), fam(31.75)) self.assertEqual(fam.from2c(128), fam(-32)) self.assertEqual(fam.from2c(255), fam(-0.25)) self.assertRaises(FXdomainError, fam.from2c, -1) self.assertRaises(FXdomainError, fam.from2c, 256) fam = FXfamily(3) self.assertEqual(fam.from2c(1023, 7), fam(-0.125)) self.assertEqual(fam.from2c(511, 7), fam(63.875)) self.assertRaises(FXfamilyError, fam.from2c, 0) self.assertRaises(FXfamilyError, fam.from2c, 0, None)
def piPlot(): """Plot graph of approximations to Pi""" b_min, b_max = 8, 25 pi_true = FXfamily(b_max + 40).pi pipoints = [] for res in range(b_min, b_max+1): val = 4 * FXnum(1, FXfamily(res)).atan() pipoints.append([res, val]) pipoints = numpy.array(pipoints) truepoints = numpy.array([[b_min, pi_true], [b_max, pi_true]]) plt.xlabel('bits') plt.ylabel(r'$4 \tan^{-1} 1$') plt.xlim([b_min, b_max]) plt.ylim([3.13, 3.16]) plt.grid(True) for arr, style in ((truepoints, '--'), (pipoints, '-')): plt.plot(arr[:,0], arr[:,1], ls=style) plt.show()
def testBitShifts(self): """Check effects of left & right shift operators.""" fam = FXfamily(32) self.assertEqual(FXnum(1, fam) << 2, 4) self.assertEqual(FXnum(3, fam) << 4, 48) self.assertEqual(FXnum(-7, fam) << 8, -7 * 256) self.assertEqual(FXnum(1, fam) >> 1, 0.5) self.assertEqual(FXnum(12, fam) >> 2, 3) self.assertEqual(FXnum(-71 * 1024, fam) >> 12, -17.75)
def testArctan(self): """atan method agree with math.sin/cos""" fam62 = FXfamily(62) scale = 0.277 self.assertEqual(FXnum(0, fam62), 0) for i in range(-32, 32): tan = i * scale ang_true = math.atan(tan) ang = FXnum(tan, fam62).atan() self.assertAlmostEqual(ang_true, ang) self.assertAlmostEqual(float(ang.tan()), tan)
def testLog(self): """Logarithm method agree with math.log""" fam62 = FXfamily(62) base = 1.5 for i in range(1, 32): for j in range(0, 2): if j == 0: x = 1.0 / (base**i) else: x = base**i log_true = math.log(x) log = FXnum(x, fam62).log() self.assertAlmostEqual(log_true, log)
def testPow(self): """Check raising FXnums to powers.""" fam62 = FXfamily(62) scale = 0.205 scale2 = 0.382 for i in range(1, 32): x = i * scale for j in range(-16, 16): y = j * scale2 pwr_true = math.pow(x, y) pwr = FXnum(x, fam62)**y self.assertAlmostEqual(pwr_true, pwr)
def basicDemo(): """Basic demonstration of roots & exponents at various accuracies""" for resolution in [8, 32, 80, 274]: family = FXfamily(resolution) val = 2 print('=== {0} bits ==='.format(resolution)) rt = family(val).sqrt() print('sqrt(' + str(val) + ')~ ' + str(rt)) print('sqrt(' + str(val) + ')^2 ~ ' + str(rt * rt)) print('exp(1) ~ ' + str(family.exp1)) print()
def testNegating(self): """Check prefix operators""" fam17 = FXfamily(17) for i in range(-32, 32): x = i * 0.819 fx = fam17(x) zero = fam17(0) try: self.assertEqual(zero, fx + (-fx)) self.assertEqual(zero, -fx + fx) self.assertEqual((-1 * fx), -fx) self.assertEqual(zero, (-1 * fx) + (-fx) + 2 * (+fx)) except FXexception: self.fail()
def testNegatives(self): fam = FXfamily(3) x2 = FXnum(-0.25, fam) self.assertEqual(x2.toBinaryString(), '1.110') self.assertEqual(x2.toBinaryString(twosComp=False), '-0.010') x3 = FXnum(-0.625, fam) self.assertEqual(x3.toBinaryString(logBase=3), '7.3') self.assertEqual(x3.toBinaryString(3, twosComp=False), '-0.5') x4 = FXnum(-0.875, fam) self.assertEqual(x4.toBinaryString(4), 'f.2') self.assertEqual(x4.toBinaryString(4, twosComp=False), '-0.e')
def printBaseDemo(): res = 60 pi = FXfamily(res).pi print('==== Pi at {}-bit resolution ===='.format(res)) print('decimal: {}'.format(pi.toDecimalString())) print('binary: {}'.format(pi.toBinaryString())) print('octal: {}'.format(pi.toBinaryString(3))) print('hex: {}'.format(pi.toBinaryString(4)))
def testDecimalRound(self): third = FXnum(1, FXfamily(200)) / 3 x23 = 2 * third self.assertEqual(third.toDecimalString(precision=5), '0.33333') self.assertEqual(third.toDecimalString(precision=8, round10=True), '0.33333333') self.assertEqual(x23.toDecimalString(precision=4), '0.6666') self.assertEqual(x23.toDecimalString(precision=7, round10=True), '0.6666667') self.assertEqual(x23.toDecimalString()[:42], '0.' + '6' * 40) self.assertEqual(x23.toDecimalString(precision=50), '0.' + '6' * 50) self.assertEqual(x23.toDecimalString(precision=50, round10=True), '0.' + '6' * 49 + '7')
def testSqrt(self): """sqrt method should find square-roots""" fam62 = FXfamily(62) scale = 0.94 for i in range(-40, 40): x = i * scale fx = fam62(x) try: rt = fx.sqrt() except FXdomainError: self.assertFalse(x >= 0) else: rt2 = float(rt * rt) self.assertAlmostEqual(x, rt2) if i == 0: self.assertEqual(FXnum(0, fam62), rt)