diff --git a/silly.js b/silly.js index 72a4c31..4a07fa7 100644 --- a/silly.js +++ b/silly.js @@ -1,21 +1,20 @@ -// input range: 0..255 * 0..255 -// output range: 16 bits +// i/o range: 16 bits +let bits = 16; -let logBits = 8; -let logEntries = 2 ** logBits; - -let powBits = 21; -let powEntries = 2 ** powBits; - -// Room to hold exponents up to 15.9 for 16-bit output +// Room to hold values up to 15.9 for 16-bit mandelbrot let shift = 4; -let base = 2 ** (powBits - shift); +let base = 2 ** (bits - shift); + +let inputRange = 8; // max Mandelbrot zx/zy addition range prior to checking distance -// total of 12-bit lookup table let reduction = 0; +let roundOffset = (2 ** (reduction - 1)) + 1; +let entries = 2 ** (bits - reduction); +let bytes = Math.ceil(bits / 8) * entries; -let powCount = powEntries >> reduction; - +// try to keep all but the last few bits semi-accurate +let epsilonBits = 2; +let epsilon = 2 ** epsilonBits; function toFixed(float) { return Math.round(float * base); @@ -25,46 +24,36 @@ 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] > powEntries) { - throw new Error('enloggen entry out of range') +function toIndex(fixed) { + let n = (fixed + roundOffset) >> reduction; + if (n == entries) { + // round down for the maxo for now + n--; } + return n; } -// 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] > (logEntries * logEntries)) { - throw new Error('empower entry out of range') - } +// x -> log2 x +let enloggen = new Uint32Array(entries); +for (let i = 0; i < entries; i++) { + enloggen[i] = toFixed(Math.log2(toFloat(i << reduction))); +} + +// x -> 2 ^ x +let empower = new Uint32Array(entries * 2); +for (let i = 0; i < entries * 2; i++) { + empower[i] = toFixed(2 ** toFloat(i << reduction)); } // returns fixed point -function log2(val) { - return enloggen[val]; +function log2(fixed) { + return enloggen[toIndex(fixed)]; } // returns rounded integer function pow2(fixed) { - let n = fixed >> reduction; - if (n >= empower.length) { - /* - console.log(`ERROR float ${toFloat(fixed)} fixed ${fixed} n ${n} max ${empower.length}`); - - throw new Error('whoops'); - */ - // Overflow? Round down. - return empower[empower.length - 1]; - } - return empower[n]; + return empower[toIndex(fixed)]; } function mul(a, b) { @@ -73,7 +62,7 @@ function mul(a, b) { let la = log2(a)|0; let lb = log2(b)|0; let sum = la + lb; - if (sum >= (2 ** powBits)) { + if (sum >= 2 * entries) { // overflow throw new Error('overflow on mul'); } @@ -101,43 +90,44 @@ for (let i = 0; i < powEntries; i++) { // now just try multipling numbers let deltas = 0; -let count = 0; +let deltaAvg = 0; let deltaCount = 0; -let results = 0; +let count = 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); -*/ + let a = toFixed(Math.random() * inputRange); + let b = toFixed(Math.random() * inputRange); -for (let i = 0; i < 65536; i++) { - let a = i & 0xff; - let b = (i >> 8) & 0xff; - let expected = a * b; + let expected = toFixed(toFloat(a) * toFloat(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))}`); + + //console.log(`fixed a ${a} b ${b} expected ${expected} result ${result}`); + //console.log(`float a ${toFloat(a)} b ${toFloat(b)} expected ${toFloat(expected)} result ${toFloat(result)} loga ${toFloat(log2(a))} logb ${toFloat(log2(b))} pow ${toFloat(pow2(log2(a)+log2(b)))}`); let delta = Math.abs(result - expected); - let epsilon = 1; - if (delta >= epsilon || result === undefined) { + if (delta > 0) { let percent = 100 * (delta / expected); - console.log(`${a} * ${b} = ${expected}, but got ${result} delta ${delta} ${Math.round(percent * 100) / 100}%`); + if (delta > epsilon) { + console.log(`${toFloat(a)} * ${toFloat(b)} = ${toFloat(expected)}, but got ${toFloat(result)} delta ${toFloat(delta)} ${Math.round(percent * 100) / 100}%`); + } deltas += delta; deltaCount++; } else { - console.log(`${a} * ${b} = ${result}`); + //console.log(`${toFloat(a)} * ${toFloat(b)} = ${toFloat(result)}`); } count++; + if (count > 10000) { + break; + } } -deltaAvg = deltas / deltaCount; -console.log(`${count - deltaCount} of ${count} ok -- ${deltaCount} off by avg ${round(10,deltaAvg)})`); +deltaAvg = deltas / count; +console.log(`${count - deltaCount} of ${count} ok -- ${deltaCount} off by avg ${toFloat(deltaAvg)} -- fixed ${round(10,deltaAvg)}`); count = 0; deltas = 0; deltaCount = 0; @@ -145,8 +135,8 @@ 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`); +console.log(`size of enloggen table: ${entries * 2} entries, ${bytes} bytes`); +console.log(`size of empower table: ${entries * 2} entries, ${bytes * 2} bytes`); let m = 0; for (let i = 0; i < enloggen.length; i++) {