diff --git a/fixed.js b/fixed.js new file mode 100644 index 0000000..397de0f --- /dev/null +++ b/fixed.js @@ -0,0 +1,155 @@ +// i/o range: 16 bits +let bits = 16; + +// max Mandelbrot zx/zy addition range prior to checking distance +let inputRange = 4; + +// Room to hold power up to -12/+4 for 16-bit mandelbrot +let shift = 4; +let base = 2 ** (bits - shift); + +let reduction = 4; +let roundOffset = (2 ** (reduction - 1)) + 1; +let entries = 2 ** (bits - reduction); +let bytes = Math.ceil(bits / 8) * entries; + +// try to keep all but the last few bits semi-accurate +let epsilonBits = 1 ; +let epsilon = 2 ** epsilonBits; + +function toFixed(float) { + return Math.round(float * base); +} + +function toFloat(fixed) { + return fixed / base; +} + +function toIndex(fixed) { + let n = (fixed + roundOffset) >> reduction; + if (n == entries) { + // round down for the maxo for now + n--; + } + return n; +} + +// x -> log2 x +let enloggen = new Int32Array(entries); +for (let i = 0; i < entries; i++) { + enloggen[i] = toFixed(Math.log2(toFloat(i << reduction))); +} + +// x -> 2 ^ x +let empower = new Int32Array(entries * 2); +for (let i = 0; i < entries * 2; i++) { + empower[i] = toFixed(2 ** toFloat(i - entries << reduction)); +} + + +// returns fixed point +function log2(fixed) { + return enloggen[toIndex(fixed)]; +} + +// returns rounded integer +function pow2(fixed) { + let n = toIndex(fixed); + if (n > empower.length) { + n = empower.length - 1; + } + return empower[entries + 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 * entries) { + // 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 deltaAvg = 0; +let deltaCount = 0; +let count = 0; + +function round(n, x) { + return Math.round(x * n) / n; +} + +while (true) { + let a = toFixed(Math.random() * inputRange); + let b = toFixed(Math.random() * inputRange); + + let expected = toFixed(toFloat(a) * toFloat(b)); + let result = mul(a, b); + if (result === NaN || result === undefined) { + console.log(a, b, result); + console.log(log2(a), log2(b)); + throw new Error('invalid'); + } + + console.log(`fixed a ${a} b ${b} expected ${expected} result ${result} loga ${log2(a)} logb ${log2(b)} sum ${log2(a)+log2(b)} pow ${pow2(log2(a)+log2(b))}`); + console.log(`float a ${toFloat(a)} b ${toFloat(b)} expected ${toFloat(expected)} result ${toFloat(result)} loga ${toFloat(log2(a))} logb ${toFloat(log2(b))} sum ${toFloat(log2(a)+log2(b))} pow ${toFloat(pow2(log2(a)+log2(b)))}`); + + let delta = Math.abs(result - expected); + + if (delta >= epsilon) { + let percent = 100 * (delta / expected); + 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(`${toFloat(a)} * ${toFloat(b)} = ${toFloat(result)}`); + } + count++; + if (count > 10000) { + break; + } +} + +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; + +console.log('done'); + + +console.log(`size of enloggen table: ${entries} 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++) { + m = Math.max(m, enloggen[i]); +} +console.log(`max enloggen entry is ${m}`);