This commit is contained in:
Brooke Vibber 2023-01-07 19:36:11 -08:00
parent badc730f2b
commit 38005a6f7d

114
silly.js
View file

@ -1,21 +1,20 @@
// input range: 0..255 * 0..255 // i/o range: 16 bits
// output range: 16 bits let bits = 16;
let logBits = 8; // Room to hold values up to 15.9 for 16-bit mandelbrot
let logEntries = 2 ** logBits;
let powBits = 21;
let powEntries = 2 ** powBits;
// Room to hold exponents up to 15.9 for 16-bit output
let shift = 4; 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 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) { function toFixed(float) {
return Math.round(float * base); return Math.round(float * base);
@ -25,46 +24,36 @@ function toFloat(fixed) {
return fixed / base; return fixed / base;
} }
// 256x2 = 512 bytes function toIndex(fixed) {
let enloggen = new Uint32Array(logEntries); let n = (fixed + roundOffset) >> reduction;
for (let i = 0; i < logEntries; i++) { if (n == entries) {
enloggen[i] = toFixed(Math.log2(i)); // round down for the maxo for now
if (enloggen[i] > powEntries) { n--;
throw new Error('enloggen entry out of range')
} }
return n;
} }
// 64k entries // x -> log2 x
// 64kx2 = 128 KiB let enloggen = new Uint32Array(entries);
// can reduce number by reducing precision for (let i = 0; i < entries; i++) {
// or splitting into high & low vals enloggen[i] = toFixed(Math.log2(toFloat(i << reduction)));
let empower = new Uint16Array(powCount); }
for (let i = 0; i < powCount; i++) {
empower[i] = Math.round(2 ** toFloat(i << reduction)); // x -> 2 ^ x
if (empower[i] > (logEntries * logEntries)) { let empower = new Uint32Array(entries * 2);
throw new Error('empower entry out of range') for (let i = 0; i < entries * 2; i++) {
} empower[i] = toFixed(2 ** toFloat(i << reduction));
} }
// returns fixed point // returns fixed point
function log2(val) { function log2(fixed) {
return enloggen[val]; return enloggen[toIndex(fixed)];
} }
// returns rounded integer // returns rounded integer
function pow2(fixed) { function pow2(fixed) {
let n = fixed >> reduction; return empower[toIndex(fixed)];
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];
} }
function mul(a, b) { function mul(a, b) {
@ -73,7 +62,7 @@ function mul(a, b) {
let la = log2(a)|0; let la = log2(a)|0;
let lb = log2(b)|0; let lb = log2(b)|0;
let sum = la + lb; let sum = la + lb;
if (sum >= (2 ** powBits)) { if (sum >= 2 * entries) {
// overflow // overflow
throw new Error('overflow on mul'); throw new Error('overflow on mul');
} }
@ -101,43 +90,44 @@ for (let i = 0; i < powEntries; i++) {
// now just try multipling numbers // now just try multipling numbers
let deltas = 0; let deltas = 0;
let count = 0; let deltaAvg = 0;
let deltaCount = 0; let deltaCount = 0;
let results = 0; let count = 0;
function round(n, x) { function round(n, x) {
return Math.round(x * n) / n; return Math.round(x * n) / n;
} }
/*
while (true) { while (true) {
let a = Math.trunc(Math.random() * logEntries); let a = toFixed(Math.random() * inputRange);
let b = Math.trunc(Math.random() * logEntries); let b = toFixed(Math.random() * inputRange);
*/
for (let i = 0; i < 65536; i++) { let expected = toFixed(toFloat(a) * toFloat(b));
let a = i & 0xff;
let b = (i >> 8) & 0xff;
let expected = a * b;
let result = mul(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))}`);
//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 delta = Math.abs(result - expected);
let epsilon = 1;
if (delta >= epsilon || result === undefined) { if (delta > 0) {
let percent = 100 * (delta / expected); 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; deltas += delta;
deltaCount++; deltaCount++;
} else { } else {
console.log(`${a} * ${b} = ${result}`); //console.log(`${toFloat(a)} * ${toFloat(b)} = ${toFloat(result)}`);
} }
count++; count++;
if (count > 10000) {
break;
}
} }
deltaAvg = deltas / deltaCount; deltaAvg = deltas / count;
console.log(`${count - deltaCount} of ${count} ok -- ${deltaCount} off by avg ${round(10,deltaAvg)})`); console.log(`${count - deltaCount} of ${count} ok -- ${deltaCount} off by avg ${toFloat(deltaAvg)} -- fixed ${round(10,deltaAvg)}`);
count = 0; count = 0;
deltas = 0; deltas = 0;
deltaCount = 0; deltaCount = 0;
@ -145,8 +135,8 @@ deltaCount = 0;
console.log('done'); console.log('done');
console.log(`size of enloggen table: ${enloggen.length} entries, ${enloggen.length * 3} bytes`); console.log(`size of enloggen table: ${entries * 2} entries, ${bytes} bytes`);
console.log(`size of empower table: ${empower.length} entries, ${empower.length * 2} bytes`); console.log(`size of empower table: ${entries * 2} entries, ${bytes * 2} bytes`);
let m = 0; let m = 0;
for (let i = 0; i < enloggen.length; i++) { for (let i = 0; i < enloggen.length; i++) {