-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
v1.7.4 mark sideEffects=false, fix f16round() overflow
- Loading branch information
Showing
14 changed files
with
539 additions
and
384 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"name": "tinybuf", | ||
"version": "1.7.3", | ||
"version": "1.7.4", | ||
"author": "Reece Como <[email protected]>", | ||
"authors": [ | ||
"Reece Como <[email protected]>", | ||
|
@@ -12,8 +12,9 @@ | |
"module": "dist/index.mjs", | ||
"types": "./dist/index.d.ts", | ||
"files": ["./dist"], | ||
"sideEffects": false, | ||
"scripts": { | ||
"build": "rimraf dist && rollup -c --bundleConfigAsCjs && rimraf dist/core && ./node_modules/.bin/dts-bundle-generator -o dist/index.d.ts src/index.ts --sort --no-banner --export-referenced-types", | ||
"build": "rimraf dist && rollup -c --bundleConfigAsCjs && rimraf dist/core && ./node_modules/.bin/dts-bundle-generator -o dist/index.d.ts src/index.ts --no-banner --export-referenced-types", | ||
"coverage": "jest --collectCoverage", | ||
"lint": "eslint src --ext .ts", | ||
"test": "jest" | ||
|
@@ -23,14 +24,16 @@ | |
"url": "git+https://github.com/reececomo/tinybuf.git" | ||
}, | ||
"keywords": [ | ||
"buffer", | ||
"arraybuffer", | ||
"binary", | ||
"parse", | ||
"encode", | ||
"buffer", | ||
"decode", | ||
"json", | ||
"encode", | ||
"js-binary", | ||
"json", | ||
"parse", | ||
"ts-binary", | ||
"typedarray", | ||
"typescript-binary" | ||
], | ||
"devDependencies": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,141 @@ | ||
import { fround16 } from "../core/lib/float16"; | ||
import { f16round, $f16mask, $f16unmask } from "../core/lib/float16"; | ||
|
||
describe('fround16', () => { | ||
it('should quantize numbers to the nearest float16 representation', () => { | ||
expect(fround16(3.1419)).toBe(3.142578125); | ||
describe('f16round', () => { | ||
it('rounds to the nearest float16 for a given native float', () => { | ||
expect(f16round(3.1419)).toBe(3.142578125); | ||
expect(typeof f16round(3.1419)).toBe('number'); | ||
}); | ||
|
||
it('gives a close approximation (roughly ±0.01) for numbers below 1.0', () => { | ||
expect(f16round(0.0123456789123456)).toBe(0.0123443603515625); | ||
expect(f16round(0.0026789123456732)).toBe(0.0026798248291015625); | ||
expect(f16round(0.4512312351232131)).toBe(0.451171875); | ||
}); | ||
|
||
it('gives a close approximation (roughly ±0.25) for numbers between 1.0 and ~1024', () => { | ||
expect(f16round(-99.123456789)).toBe(-99.125); | ||
expect(f16round(512.333901298)).toBe(512.5); | ||
expect(f16round(938.712391412)).toBe(938.5); | ||
expect(f16round(-1_023.248888)).toBe(-1_023); | ||
expect(f16round(-1_023.249999)).toBe(-1_023.5); | ||
}); | ||
|
||
it('gives a reasonable approximatation (roughly ±1.0) for between ~1024 and ~4096', () => { | ||
expect(f16round(2_234.523412)).toBe(2_234); | ||
expect(f16round(-2_234.52341)).toBe(-2_234); | ||
expect(f16round(3_431.646731)).toBe(3_432); | ||
expect(f16round(-3_431.64672)).toBe(-3_432); | ||
expect(f16round(4_096.599433)).toBe(4_096); | ||
}); | ||
|
||
it('gives a lossy representation (roughly ±10.0) for numbers between ~4096 and 65504', () => { | ||
expect(f16round(15_123.12304)).toBe(15_120); | ||
expect(f16round(-15_123.1230)).toBe(-15_120); | ||
expect(f16round(-64_065.25)).toBe(-64_064); | ||
expect(f16round(64_123.25)).toBe(64_128); | ||
}); | ||
|
||
it('overflows to Infinity, -Infinity for numbers beyond ±65,504', () => { | ||
expect(f16round(65_503.999999999898)).toBe(65_504); | ||
expect(f16round(-65_503.999999999898)).toBe(-65_504); | ||
|
||
|
||
// Overflow to infinity | ||
expect(f16round(65_505)).toBe(Infinity); | ||
expect(f16round(65_505)).toBe(Infinity); | ||
expect(f16round(65_518.12121212)).toBe(Infinity); | ||
expect(f16round(65_520.89898989)).toBe(Infinity); | ||
expect(f16round(-65_520.89898989)).toBe(-Infinity); | ||
expect(f16round(65_555.123)).toBe(Infinity); | ||
expect(f16round(-65_555.123)).toBe(-Infinity); | ||
}); | ||
}); | ||
|
||
describe('$f16mask', () => { | ||
it('should return a 16-bit bitmask', () => { | ||
// 65,504 upper bound | ||
expect($f16mask(-65_504)).toBe(0b00000000000000001111101111111111); | ||
expect($f16mask(-65_504)).toBe(0b1111101111111111); | ||
|
||
expect(asUint16Str($f16mask(-65_504))).toBe('1111101111111111'); | ||
expect(asInt32Str($f16mask(-65_504))).toBe('00000000000000001111101111111111'); | ||
expect(asUint32Str($f16mask(-65_504))).toBe('00000000000000001111101111111111'); | ||
expect(asInt64Str(BigInt($f16mask(-65_504)))).toBe('0000000000000000000000000000000000000000000000001111101111111111'); | ||
}); | ||
|
||
it('should NOT equal its own raw float64 representation', () => { | ||
expect($f16mask(65_504)).not.toBe(65_504); | ||
}); | ||
}); | ||
|
||
describe('$f16unmask', () => { | ||
it('should read a 16-bit mask', () => { | ||
// 65,504 upper bound | ||
expect($f16unmask(0b1111_1011_1111_1111)).toBe(-65_504); | ||
expect($f16unmask(0b0000_0000_0000_0000_1111_1011_1111_1111)).toBe(-65_504); | ||
}); | ||
|
||
it('should survive a 16-bit mask', () => { | ||
// 65,504 upper bound | ||
expect($f16unmask(0b1111_1011_1111_1111)).toBe(-65_504); | ||
expect($f16unmask(0b1111_1011_1111_1111_1111_1011_1111_1111)).toBe(-65_504); | ||
}); | ||
}); | ||
|
||
test('mask / unmask basic compatibility', () => { | ||
// this is covered pretty comprehensively in other tests | ||
// including the coders - but just a quick sanity check here: | ||
expect($f16mask(1023.5)).not.toBe(1023.5); | ||
expect($f16unmask(1023.5)).not.toBe(1023.5); | ||
expect($f16unmask($f16mask(1023.5))).toBe(1023.5); | ||
}); | ||
|
||
test('performance benchmark: f16round() faster than native Math.fround()', () => { | ||
const iterations = 1_000_000; | ||
const inputs = Array.from({ length: iterations }, () => Math.random() > 0.5 | ||
? getRandomFloat(-1, 1) // mix small floats (e.g. vector normals) | ||
: getRandomFloat(-70_000, 70_000) // and larger floats | ||
); | ||
|
||
// native Math.fround() - float32s: | ||
const f32start = performance.now(); | ||
for (let i = 0; i < iterations; i++) { | ||
Math.fround(inputs[i]); | ||
} | ||
const f32end = performance.now(); | ||
const f32duration = f32end - f32start; | ||
|
||
// f16round() - float16s: | ||
const f16start = performance.now(); | ||
for (let i = 0; i < iterations; i++) { | ||
f16round(inputs[i]); | ||
} | ||
const f16end = performance.now(); | ||
const f16duration = f16end - f16start; | ||
|
||
// generally 5-10x faster | ||
// show soft warning if unexpectedly slower | ||
try { | ||
expect(f16duration).toBeLessThan(f32duration); | ||
// console.debug(`result: f16round() (${f16duration.toFixed(3)}ms) vs Math.fround() (${f32duration.toFixed(3)}ms) for ${iterations} iterations`); | ||
} | ||
catch (error) { | ||
console.warn(`f16round() (${f16duration.toFixed(3)}ms) vs Math.fround() (${f32duration.toFixed(3)}ms) for ${iterations} iterations`); | ||
} | ||
}); | ||
|
||
function asUint16Str(val: number): string { | ||
return new Uint16Array([val])[0].toString(2).padStart(16, '0'); | ||
} | ||
function asUint32Str(val: number): string { | ||
return new Uint32Array([val])[0].toString(2).padStart(32, '0'); | ||
} | ||
function asInt32Str(val: number): string { | ||
return new Int32Array([val])[0].toString(2).padStart(32, '0'); | ||
} | ||
function asInt64Str(val: bigint): string { | ||
return new BigInt64Array([val])[0].toString(2).padStart(64, '0'); | ||
} | ||
function getRandomFloat(min: number, max: number): number { | ||
return Math.random() * (max - min) + min; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.