Skip to content

Commit 2f6e114

Browse files
committed
BREAKING CHANGE: BigInt support + Unicode handling - Fixes #2, Fixes #3
1 parent 7a72892 commit 2f6e114

File tree

3 files changed

+105
-31
lines changed

3 files changed

+105
-31
lines changed

index.js

+65-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,74 @@
11
'use strict';
2-
const OFFSET_BASIS_32 = 2166136261;
32

4-
const fnv1a = string => {
5-
let hash = OFFSET_BASIS_32;
3+
// FNV_PRIMES and FNV_OFFSETS from
4+
// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-param
5+
//
6+
// Would define these as BigInt literals, but that causes syntax errors on
7+
// legacy platforms.
68

7-
for (let i = 0; i < string.length; i++) {
8-
hash ^= string.charCodeAt(i);
9+
const FNV_PRIMES = {
10+
32: '16777619',
11+
64: '1099511628211',
12+
128: '309485009821345068724781371',
13+
256: '374144419156711147060143317175368453031918731002211',
14+
512: '35835915874844867368919076489095108449946327955754392558399825615420669938882575126094039892345713852759',
15+
1024: '5016456510113118655434598811035278955030765345404790744303017523831112055108147451509157692220295382716162651878526895249385292291816524375083746691371804094271873160484737966720260389217684476157468082573'
16+
};
17+
18+
const FNV_OFFSETS = {
19+
32: '2166136261',
20+
64: '14695981039346656037',
21+
128: '144066263297769815596495629667062367629',
22+
256: '100029257958052580907070968620625704837092796014241193945225284501741471925557',
23+
512: '9659303129496669498009435400716310466090418745672637896108374329434462657994582932197716438449813051892206539805784495328239340083876191928701583869517785',
24+
1024: '14197795064947621068722070641403218320880622795441933960878474914617582723252296732303717722150864096521202355549365628174669108571814760471015076148029755969804077320157692458563003215304957150157403644460363550505412711285966361610267868082893823963790439336411086884584107735010676915'
25+
};
926

10-
// 32-bit FNV prime: 2**24 + 2**8 + 0x93 = 16777619
11-
// Using bitshift for accuracy and performance. Numbers in JS suck.
12-
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
13-
}
27+
// Legacy implementation for 32-bit + Number types, older systems that don't
28+
// support BigInt
29+
function fnv1a(str) {
30+
// Handle unicode code points > 255
31+
str = unescape(encodeURIComponent(str));
1432

15-
return hash >>> 0;
33+
let hash = Number(FNV_OFFSETS[32]);
34+
for (let i = 0; i < str.length; i++) {
35+
hash ^= str.charCodeAt(i);
36+
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
37+
}
38+
39+
return hash >>> 0;
1640
};
1741

42+
function bigInt(str, {size} = {size: 32}) {
43+
if (typeof(BigInt) == 'undefined') {
44+
throw Error('BigInt is not supported');
45+
}
46+
47+
if (!FNV_PRIMES[size]) {
48+
throw Error('`size` must be one of 32, 64, 128, 256, 512, or 1024')
49+
}
50+
51+
const mod = BigInt(2) ** BigInt(size);
52+
let hash = BigInt(FNV_OFFSETS[size]);
53+
const prime = BigInt(FNV_PRIMES[size]);
54+
55+
// Handle unicode code points > 255
56+
str = unescape(encodeURIComponent(str));
57+
58+
for (let i = 0; i < str.length; i++) {
59+
hash ^= BigInt(str.charCodeAt(i));
60+
hash = hash * prime % mod;
61+
}
62+
63+
return hash;
64+
}
65+
66+
// Default = 32-bit hash, returning Number
67+
function fnv1a(str) {
68+
return Number(bigInt(str, {size: 32}));
69+
}
70+
71+
1872
module.exports = fnv1a;
19-
// TODO: remove this in the next major version, refactor the whole definition to:
73+
module.exports.bigInt = bigInt;
2074
module.exports.default = fnv1a;

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"node": ">=6"
1414
},
1515
"scripts": {
16-
"test": "xo && ava && tsd"
16+
"test": "node test.js && tsd"
1717
},
1818
"files": [
1919
"index.js",
@@ -36,7 +36,6 @@
3636
"vo"
3737
],
3838
"devDependencies": {
39-
"ava": "^1.4.1",
4039
"tsd": "^0.7.1",
4140
"xo": "^0.24.0"
4241
}

test.js

+39-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,42 @@
1-
import test from 'ava';
2-
import fnv1a from '.';
1+
const {strictEqual} = require('assert');
2+
const fnv1a = require('.');
3+
const {bigInt} = require('.');
34

4-
test('main', t => {
5-
t.is(fnv1a(''), 2166136261);
6-
t.is(fnv1a('🦄🌈'), 582881315);
5+
// NOTE: Note: Using native `assert` for testing here because ava and mocha both
6+
// break when BigIntsPI are involved :-(
77

8-
t.is(fnv1a('h'), 3977000791);
9-
t.is(fnv1a('he'), 1547363254);
10-
t.is(fnv1a('hel'), 179613742);
11-
t.is(fnv1a('hell'), 477198310);
12-
t.is(fnv1a('hello'), 1335831723);
13-
t.is(fnv1a('hello '), 3801292497);
14-
t.is(fnv1a('hello w'), 1402552146);
15-
t.is(fnv1a('hello wo'), 3611200775);
16-
t.is(fnv1a('hello wor'), 1282977583);
17-
t.is(fnv1a('hello worl'), 2767971961);
18-
t.is(fnv1a('hello world'), 3582672807);
8+
// Test 32-bit for various strings
9+
strictEqual(fnv1a(''), 2166136261);
10+
strictEqual(fnv1a('h'), 3977000791);
11+
strictEqual(fnv1a('he'), 1547363254);
12+
strictEqual(fnv1a('hel'), 179613742);
13+
strictEqual(fnv1a('hell'), 477198310);
14+
strictEqual(fnv1a('hello'), 1335831723);
15+
strictEqual(fnv1a('hello '), 3801292497);
16+
strictEqual(fnv1a('hello w'), 1402552146);
17+
strictEqual(fnv1a('hello wo'), 3611200775);
18+
strictEqual(fnv1a('hello wor'), 1282977583);
19+
strictEqual(fnv1a('hello worl'), 2767971961);
20+
strictEqual(fnv1a('hello world'), 3582672807);
1921

20-
t.is(fnv1a('Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.'), 2964896417);
21-
});
22+
// Bigger test
23+
strictEqual(fnv1a('Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium.'), 2964896417);
24+
25+
// Verify unicode handling against values from https://www.tools4noobs.com/online_tools/hash/
26+
strictEqual(fnv1a('🦄🌈'), 0xaaf5fee7);
27+
strictEqual(fnv1a('\u{0000}\u{0080}\u{0100}\u{0180}\u{0250}\u{02b0}\u{0300}\u{0370}\u{0400}\u{0500}\u{0530}\u{0590}\u{0600}\u{0700}\u{0780}\u{0900}\u{0980}\u{0a00}\u{0a80}\u{0b00}\u{0b80}\u{0c00}\u{0c80}\u{0d00}\u{0d80}\u{0e00}\u{0e80}\u{0f00}\u{1000}\u{10a0}\u{1100}\u{1200}\u{13a0}\u{1400}\u{1680}\u{16a0}\u{1700}\u{1720}\u{1740}\u{1760}\u{1780}\u{1800}\u{1900}\u{1950}\u{19e0}\u{1d00}\u{1e00}\u{1f00}\u{2000}\u{2070}\u{20a0}\u{20d0}\u{2100}\u{2150}\u{2190}\u{2200}\u{2300}\u{2400}\u{2440}\u{2460}\u{2500}\u{2580}\u{25a0}\u{2600}\u{2700}\u{27c0}\u{27f0}\u{2800}\u{2900}\u{2980}\u{2a00}\u{2b00}\u{2e80}\u{2f00}\u{2ff0}\u{3000}\u{3040}\u{30a0}\u{3100}\u{3130}\u{3190}\u{31a0}\u{31f0}\u{3200}\u{3300}\u{3400}\u{4dc0}\u{4e00}\u{a000}\u{a490}\u{ac00}\u{d800}\u{dc00}\u{e000}\u{f900}\u{fb00}\u{fb50}\u{fe00}\u{fe20}\u{fe30}\u{fe50}\u{fe70}\u{ff00}\u{fff0}\u{10000}\u{10080}\u{10100}\u{10300}\u{10330}\u{10380}\u{10400}\u{10450}\u{10480}\u{10800}\u{1d000}\u{1d100}\u{1d300}\u{1d400}\u{20000}\u{2f800}\u{e0000}\u{e0100}'), 0x983fdf05);
28+
29+
// If BigInt support available ...
30+
if (typeof(BigInt) != 'undefined') {
31+
// Sanity check larger hashes against values from
32+
// https://fnvhash.github.io/fnv-calculator-online/
33+
34+
strictEqual(bigInt('hello world', {size: 32}), 0xd58b3fa7n);
35+
strictEqual(bigInt('hello world', {size: 64}), 0x779a65e7023cd2e7n);
36+
strictEqual(bigInt('hello world', {size: 128}), 0x6c155799fdc8eec4b91523808e7726b7n);
37+
strictEqual(bigInt('hello world', {size: 256}), 0xecc3cf2e0edfccd3d87f21ec0883aad4db43eead66ce09eb4a97e04e1a184527n);
38+
strictEqual(bigInt('hello world', {size: 512}), 0x2b9c19ec56ccf98da0f227cc82bfaacbd8350928bd2ceacae7bc8aa13e747f5c43ca4e2e98fc25e94e4e805675545ee95a3b968c0acfaecb90aea2fdbcd4de0fn);
39+
strictEqual(bigInt('hello world', {size: 1024}), 0x3fa9d253e52ae80105b382c80a01e27a53d7bc1d201efb47b38f4d6e465489829d7d272127d20e1076129c00000000000000000000000000000000000000000000000000000000000000000000000000000253eb20f42a7228af9022d9f35ece5bb71e40fcd8717b80d164ab921709996e5c43aae801418e878cddf968d4616fn);
40+
}
41+
42+
console.log('All tests pass');

0 commit comments

Comments
 (0)