// input range: 0..255 * 0..255 // output range: 16 bits let logBits = 8; let logEntries = 2 ** logBits; let powBits = 16; let powEntries = 2 ** powBits; // Room to hold exponents up to 15.9 for 16-bit output let shift = 4; let base = 2 ** (powBits - shift); // total of 12-bit lookup table let reduction = 0; let powCount = powEntries >> reduction; function toFixed(float) { return Math.round(float * base); } function toFloat(fixed) { return fixed / base; } // 256x2 = 512 bytes let enloggen = new Uint32Array(logEntries); for (let i = 0; i < logEntries; i++) { enloggen[i] = toFixed(Math.log2(i)); if (enloggen[i] > 65535) { throw new Error('enloggen entry out of range') } } // 64k entries // 64kx2 = 128 KiB // can reduce number by reducing precision // or splitting into high & low vals let empower = new Uint16Array(powCount); for (let i = 0; i < powCount; i++) { empower[i] = Math.round(2 ** toFloat(i << reduction)); if (empower[i] > 65535) { throw new Error('empower entry out of range') } } // returns fixed point function log2(val) { return enloggen[val]; } // returns rounded integer function pow2(fixed) { let n = fixed >> reduction; if (empower[n] == undefined) { console.log(`float ${toFloat(fixed)} fixed ${fixed} n ${n} max ${empower.length}`); //throw new Error('whoops'); } return empower[n]; } function mul(a, b) { if (a == 0) return 0; if (b == 0) return 0; let la = log2(a)|0; let lb = log2(b)|0; let sum = la + lb; if (sum >= (2 ** powBits)) { // overflow throw new Error('overflow on mul'); } return pow2(la + lb); } for (let i = 0; i < logEntries; i++) { let l = log2(i); let p = pow2(l); console.log(`${i} ${l} ${p}`) if (i !== p) { console.log(`mismatch ${i} expected, got ${p} via log value ${l} (${toFloat(l)})`); } } console.log('empower'); for (let i = 0; i < powEntries; i++) { let fixed = i << reduction; let float = toFloat(fixed); let val = pow2(fixed); console.log(`${i} ${fixed} ${float} ${val}`) } // now just try multipling numbers let deltas = 0; let count = 0; let deltaCount = 0; let results = 0; function round(n, x) { return Math.round(x * n) / n; } /* while (true) { let a = Math.trunc(Math.random() * logEntries); let b = Math.trunc(Math.random() * logEntries); */ for (let i = 0; i < 65536; i++) { let a = i & 0xff; let b = (i >> 8) & 0xff; let expected = a * b; let result = mul(a, b); console.log(`a ${a} b ${b} expected ${expected} result ${result} loga ${log2(a)} logb ${log2(b)} pow ${pow2(log2(a)+log2(b))}`); let delta = Math.abs(result - expected); let epsilon = 1; if (delta >= epsilon || result === undefined) { let percent = 100 * (delta / expected); console.log(`${a} * ${b} = ${expected}, but got ${result} delta ${delta} ${Math.round(percent * 100) / 100}%`); deltas += delta; deltaCount++; } else { console.log(`${a} * ${b} = ${result}`); } count++; } deltaAvg = deltas / deltaCount; console.log(`${count - deltaCount} of ${count} ok -- ${deltaCount} off by avg ${round(10,deltaAvg)})`); count = 0; deltas = 0; deltaCount = 0; console.log('done'); console.log(`size of enloggen table: ${enloggen.length} entries, ${enloggen.length * 3} bytes`); console.log(`size of empower table: ${empower.length} entries, ${empower.length * 2} bytes`); let m = 0; for (let i = 0; i < enloggen.length; i++) { m = Math.max(m, enloggen[i]); } console.log(`max enloggen entry is ${m}`);