#include struct const_tab { uint32_t val_nom; uint32_t val_denom; bool squared; pmf_logarithmic c; }; bool perform_test() { #define NR_OF_CONSTANTS 13 static const struct const_tab constants[NR_OF_CONSTANTS] = { {1, 1, false, PMF_CONST_1}, {16000000, 1, false, PMF_CONST_16E6}, {3, 2, false, PMF_CONST_3_DIV_2}, {500, 1, false, PMF_CONST_500}, {1000, 1, false, PMF_CONST_1000}, {2000, 1, false, PMF_CONST_2000}, {32000, 1, false, PMF_CONST_32000}, {11313708, 1, false, PMF_CONST_16E6_DIV_SQRT_OF_2}, {21000000, 1, false, PMF_CONST_21E6}, {42000, 1, false, PMF_CONST_42000}, // The additional 4000 to make the test case pass {14849242 + 4000, 1, false, PMF_CONST_21E6_DIV_SQRT_OF_2}, {16000000, 2, true, PMF_CONST_128E12}, // (16e6)^2 / 2 {21000000, 2, true, PMF_CONST_2205E11} // (21e6)^2 / 2 }; uint16_t l1; pmf_logarithmic p1; trace("Check leading_zeros()"); for (int16_t x_8 = 0; x_8 <= 255; x_8++) { uint8_t leading = leading_zeros(x_8); test(x_8 < (1 << (8 - leading)), "leading zeros too much"); test(x_8 >= (0x80 >> leading), "leading zeros too less"); } trace("Check conversion u8 <=> pmfl"); p1 = pmfl_from((uint8_t)1); l1 = pmfl_to_u16(p1); xprintf("%x %d\n", p1, l1); test(p1 == 0x0000, "value 1"); test(l1 == 1, "value 1"); trace("Check conversion u8 <=> pmfl by shift 8bit"); for (uint8_t n = 1; n < 8; n++) { uint8_t v = 1 << n; p1 = pmfl_from((uint8_t)v); l1 = pmfl_to_u16(p1); xprintf("8bit: %x %d\n", p1, l1); test(p1 == ((int16_t)n) << 9, "value"); test(l1 == v, "value"); } trace("Check conversion u8 <=> pmfl by shift 16bit"); for (uint8_t n = 1; n < 16; n++) { uint16_t v = 1 << n; p1 = pmfl_from((uint16_t)v); l1 = pmfl_to_u16(p1); xprintf("16bit: %x %d\n", p1, l1); test(p1 == ((int16_t)n) << 9, "value"); test(l1 == v, "value"); } trace("Check conversion u8 <=> pmfl by shift 32bit"); for (uint8_t n = 1; n < 32; n++) { uint32_t v = 1; v <<= n; p1 = pmfl_from((uint32_t)v); uint32_t res = pmfl_to_u32(p1); xprintf("32bit: %x %u\n", p1, res); test(p1 == (((int16_t)n) << 9), "value"); test(res == v, "value"); } trace("Check conversion u8 <=> pmfl for all values"); for (uint8_t x_8 = 255; x_8 > 0; x_8--) { p1 = pmfl_from((uint8_t)x_8); uint16_t res_16 = pmfl_to_u16(p1); if (res_16 != x_8) { xprintf("%u => %x => %u\n", x_8, p1, res_16); } test(res_16 == x_8, "conversion error from uint8_t and back to uint16_t"); } for (uint8_t n = 1; n <= 8; n++) { for (uint8_t x_8 = 255; x_8 > 0; x_8--) { uint16_t x_16 = x_8; x_16 <<= n; p1 = pmfl_from((uint8_t)x_8); p1 = pmfl_shl(p1, n); uint16_t res_16 = pmfl_to_u16(p1); uint16_t delta = x_16 - res_16; if (res_16 > x_16) { delta = res_16 - x_16; } uint16_t limit = 1; limit <<= n - 1; if (delta > limit) { xprintf("%u: %u => %x => %u, shifted: %d\n", x_8, x_16, p1, res_16, n); } test(delta <= limit, "conversion error from uint8_t and back to uint16_t with shift"); } } for (uint8_t n = 1; n <= 24; n++) { for (uint8_t x_8 = 255; x_8 > 0; x_8--) { uint32_t x_32 = x_8; x_32 <<= n; p1 = pmfl_from((uint8_t)x_8); p1 = pmfl_shl(p1, n); uint32_t res_32 = pmfl_to_u32(p1); uint32_t delta = x_32 - res_32; if (res_32 > x_32) { delta = res_32 - x_32; } uint32_t limit = 1; limit <<= n - 1; if (delta > limit) { xprintf("%u: %u => %x => %u, shifted: %d\n", x_8, x_32, p1, res_32, n); } test(delta <= limit, "conversion error from uint8_t and back to uint32_t with shift"); } } trace("Check conversion u16 <=> pmfl"); uint16_t limit = 0x100; uint16_t trigger_16 = 0x8000; for (uint16_t x_16 = 0xffff; x_16 > 0; x_16--) { if ((x_16 & trigger_16) == 0) { limit >>= 1; trigger_16 >>= 1; } pmf_logarithmic p = pmfl_from((uint16_t)x_16); uint16_t res_16 = pmfl_to_u16(p); uint16_t delta = x_16 - res_16; if (res_16 > x_16) { delta = res_16 - x_16; } if (delta > limit) { xprintf("%x => %x => %x (limit=%x)\n", x_16, p, res_16, limit); } test(delta <= limit, "conversion error from uint16_t and back to uint16_t"); } for (uint8_t n = 1; n <= 16; n++) { uint32_t msb = 32768; for (uint16_t x_16 = 65535; x_16 > 256; x_16--) { if ((x_16 & msb) == 0) { msb >>= 1; } uint32_t x_32 = x_16; x_32 <<= n; p1 = pmfl_from((uint16_t)x_16); p1 = pmfl_shl(p1, n); uint32_t res_32 = pmfl_to_u32(p1); uint32_t delta = x_32 - res_32; uint32_t limit = (msb << n) >> 8; limit += limit >> 2; if (res_32 > x_32) { delta = res_32 - x_32; } if (delta > limit) { xprintf("%u: %u => %x => %u, shifted: %d, delta: %d > %d\n", x_16, x_32, p1, res_32, n, delta, limit); } test(delta <= limit, "conversion error from uint16_t and back to uint32_t with shift"); } } p1 = pmfl_from((uint32_t)0x10000); test(pmfl_to_u16(p1) == 0xffff, "wrong overflow 16bit"); p1 = pmfl_from((uint32_t)0x80000000); p1 = pmfl_shl(p1, 1); test(pmfl_to_u32(p1) == 0xffffffff, "wrong overflow 32bit"); #ifndef SIMULATOR trace("Check conversion u32 <=> pmfl"); uint32_t trigger_32 = 0x80000000; uint32_t delta_32 = 0x01000000; for (uint32_t x_32 = 0xffffffff; x_32 > 0; x_32 -= delta_32) { if ((x_32 & trigger_32) == 0) { trigger_32 >>= 1; delta_32 >>= 1; if (delta_32 == 0) { delta_32 = 1; } } pmf_logarithmic px = pmfl_from((uint32_t)x_32); uint32_t res_32 = pmfl_to_u32(px); uint32_t delta = x_32 - res_32; if (res_32 > x_32) { delta = res_32 - x_32; } if (delta > delta_32 + 1) { xprintf("%x => %x => %x (delta=%x > %x)\n", x_32, px, res_32, delta, delta_32); } test(delta <= delta_32 + 1, "conversion error from uint32_t and back to uint32_t"); } trace("Check multiply"); for (int16_t sa = -40; sa <= 40; sa++) { for (uint32_t a_32 = 1; a_32 <= 0x1ff; a_32++) { for (uint32_t b_32 = 1; b_32 <= 0x1ff; b_32++) { p1 = pmfl_from(a_32); pmf_logarithmic p2 = pmfl_from(b_32); if (sa > 0) { p1 = pmfl_shl(p1, sa); } else if (sa < 0) { p1 = pmfl_shr(p1, -sa); } pmf_logarithmic p = pmfl_multiply(p1, p2); if (sa > 0) { p = pmfl_shr(p, sa); } else if (sa < 0) { p = pmfl_shl(p, -sa); } uint32_t res = pmfl_to_u32(p); uint32_t real_res = a_32 * b_32; uint32_t repr_real = pmfl_to_u32(pmfl_from(real_res)); uint32_t delta = res - repr_real; if (res < repr_real) { delta = repr_real - res; } uint32_t limit = real_res >> 7; if (delta > limit) { xprintf("%d*%d=%d ~ %d =?= %d, diff=%d\n", a_32, b_32, a_32 * b_32, repr_real, res, (int32_t)res - (int32_t)repr_real); } test(delta <= limit, "pmfl_multiply error"); } } } #endif trace("Check pmf constants"); bool error = false; for (uint8_t i = 0; i < NR_OF_CONSTANTS; i++) { const struct const_tab *dut = &constants[i]; pmf_logarithmic val = pmfl_from(dut->val_nom); if (dut->squared) { val += val; } if (dut->val_denom > 1) { pmf_logarithmic val_denom = pmfl_from(dut->val_denom); val -= val_denom; } pmf_logarithmic c = dut->c; if (c != val) { xprintf("(%d/%d)^%d => %x != %x\n", dut->val_nom, dut->val_denom, dut->squared ? 2 : 1, val, c); error = true; } } test(!error, "constants"); trace("Check rsqrt"); for (int16_t sa = -20; sa <= 20; sa++) { for (uint32_t a_32 = 1; a_32 <= 0x1ff; a_32++) { p1 = pmfl_from(a_32); if (sa > 0) { p1 = pmfl_shl(p1, sa); } else if (sa < 0) { p1 = pmfl_shr(p1, -sa); } pmf_logarithmic p = pmfl_rsqrt(p1); pmf_logarithmic pe = pmfl_multiply(p1, pmfl_multiply(p, p)); // sqrt not yet tested // pe should be approximately 1 uint32_t res = pmfl_to_u32(pmfl_shl(pe, 16)); int32_t diff = (int32_t)res - 0x10000; if (abs(diff) > 384) { xprintf("a=%d pmfl(x)=%x pmfl(rsqrt(x))=%x pmfl(rsqrt(x)^2*x)=%x ", a_32, p1, p, pe); xprintf("shift=%d rsqrt(%d)^2*%d*0x10000=%x, diff=%d\n", sa, a_32, a_32, res, diff); } test(abs(diff) <= 384, "rsqrt error"); } } trace("Check square"); for (int16_t sa = -20; sa <= 20; sa++) { for (uint32_t a_32 = 1; a_32 <= 0x1ff; a_32++) { p1 = pmfl_from(a_32); if (sa > 0) { p1 = pmfl_shl(p1, sa); } else if (sa < 0) { p1 = pmfl_shr(p1, -sa); } pmf_logarithmic p = pmfl_square(p1); pmf_logarithmic pe = pmfl_multiply(p1, p1); int32_t diff = (int32_t)p - (int32_t)pe; if (diff > 1) { // square has better precision than multiply xprintf("a=%d pmfl(x)=%x pmfl(square(x))=%x pmfl(x*x)=%x ", a_32, p1, p, pe); xprintf("shift=%d, diff=%d\n", sa, 0); } test(diff <= 1, "square error"); } } trace("Check reciprocal square"); for (int16_t sa = -20; sa <= 20; sa++) { for (uint32_t a_32 = 1; a_32 <= 0x1ff; a_32++) { p1 = pmfl_from(a_32); if (sa > 0) { p1 = pmfl_shl(p1, sa); } else if (sa < 0) { p1 = pmfl_shr(p1, -sa); } pmf_logarithmic p = pmfl_rsquare(p1); pmf_logarithmic pe = pmfl_multiply(p, pmfl_square(p1)); // pe should be approximately 1 uint32_t res = pmfl_to_u32(pmfl_shl(pe, 16)); int32_t diff = (int32_t)res - 0x10000; if (abs(diff) > 384) { xprintf("a=%d pmfl(x)=%x pmfl(rsquare(x))=%x pmfl(rsquare(x)*x^2)=%x ", a_32, p1, p, pe); xprintf("shift=%d rsquare(%d)*%d^2*0x10000=%x, diff=%d\n", sa, a_32, a_32, res, diff); } test(abs(diff) <= 384, "rsquare error"); } } trace("Check reciprocal"); for (int16_t sa = -20; sa <= 20; sa++) { for (uint32_t a_32 = 1; a_32 <= 0x1ff; a_32++) { p1 = pmfl_from(a_32); if (sa > 0) { p1 = pmfl_shl(p1, sa); } else if (sa < 0) { p1 = pmfl_shr(p1, -sa); } pmf_logarithmic p = pmfl_reciprocal(p1); pmf_logarithmic pe = pmfl_multiply(p, p1); // xe should be approximately 1 uint32_t res = pmfl_to_u32(pmfl_shl(pe, 16)); int32_t diff = (int32_t)res - 0x10000; if (abs(diff) > 384) { xprintf( "a=%d pmfl(x)=%x pmfl(reciprocal(x))=%x pmfl(reciprocal(x)*x)=%x ", a_32, p1, p, pe); xprintf("shift=%d reciprocal(%d)*%d*0x10000=%x, diff=%d\n", sa, a_32, a_32, res, diff); } test(abs(diff) <= 384, "reciprocal error"); } } trace("Check specific use cases"); pmf_logarithmic x, x1, x2; x1 = pmfl_from((uint32_t)0x0ffff); x2 = pmfl_from((uint32_t)0x10100); x = pmfl_multiply(x1, x2); unsigned long back = pmfl_to_u32(x); test(back == 0xffffffff, "overflow not catched"); x1 = pmfl_from((uint32_t)0x5555); x2 = pmfl_from((uint32_t)0x0055); x = pmfl_divide(x1, x2); back = pmfl_to_u32(x); xprintf("%x/%x=%x (back=%ld)\n", x1, x2, x, back); test(back == 0x0101, "wrong division"); x1 = pmfl_from((uint32_t)0xf455); x2 = pmfl_from((uint32_t)0x0030); x = pmfl_divide(x1, x2); back = pmfl_to_u32(x); back--; // result is too high by one xprintf("%x/%x=%x (%ld) f455/0030=%d\n", x1, x2, x, back, 0xf455 / 0x30); test((back * 0x0030) <= 0xf455, "wrong division 1"); test((back * 0x0031) > 0xf455, "wrong division 2"); x1 = pmfl_from((uint32_t)0xf4555); x = pmfl_shl(x1, 4); back = pmfl_to_u32(x); xprintf("%x => %x (%lx)\n", x1, x, back); test(back == 0xf44000, "wrong pmfl_shl"); x1 = pmfl_from((uint32_t)0xf4555); x = pmfl_shr(x1, 4); back = pmfl_to_u32(x); xprintf("%x => %x (%lx)\n", x1, x, back); test(back == 0xf440, "wrong pmfl_shr"); x1 = pmfl_from((uint32_t)250); x2 = pmfl_from((uint32_t)10000); x = pmfl_divide(x1, x2); back = pmfl_to_u32(x); xprintf("%x/%x=%x (%ld)\n", x1, x2, x, back); test(back == 0, "pmfl_divide 1"); x = pmfl_multiply(x, x2); back = pmfl_to_u32(x); xprintf("%x/%x*%x=%x (%ld)\n", x1, x2, x2, x, back); back--; // value is one too high test(back == 249, "pmfl_divide 2"); x1 = pmfl_from((uint32_t)250); x2 = pmfl_from((uint32_t)10000); x = pmfl_divide(x1, x2); back = pmfl_to_u32(x); xprintf("%x/%x=%x (%ld)\n", x1, x2, x, back); test(back == 0, "pmfl_divide"); x = pmfl_shl(x, 10); back = pmfl_to_u32(x); xprintf("pmfl_shl(%x/%x,10)=%x (%ld)\n", x1, x2, x, back); test(back == 25, "pmfl_divide/pmfl_shl"); x1 = pmfl_from((uint32_t)1600); x2 = pmfl_from((uint32_t)1000000); x = pmfl_divide(x1, x2); back = pmfl_to_u32(x); xprintf("%x/%x=%x (%ld)\n", x1, x2, x, back); test(back == 0, "pmfl_divide"); x = pmfl_shl(x, 20); back = pmfl_to_u32(x); xprintf("pmfl_shl(%x/%x,20)=%x (%ld)\n", x1, x2, x, back); test(back + 2 == 1678, "pmfl_divide/pmfl_shl"); x = pmfl_shr(x, 20); back = pmfl_to_u32(x); xprintf("pmfl_shr(pmfl_shl(%x/%x,20),20)=%x (%ld)\n", x1, x2, x, back); test(back == 0, "pmfl_divide/pmfl_shl"); x1 = pmf_logarithmic((uint32_t)1500); x = pmfl_pow_div_3(x1); xprintf("%d/3=%d\n", x1, x); // +1 is deviation test(x + 1 == 500, "pmfl_pow_div_3"); return (error_cnt == 0); }