diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ab91c..c4e90b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,3 +8,8 @@ - Added FNV-1 64bit hash function. - Added FNV-1a 64bit hash function. +## 1.0.2 + +- Added a FNV-1 64-bit hash function that returns a BigInt. +- Added a FNV-1a 64-bit hash function that returns a BigInt +- [BREAKING CHANGE] fnv_constants.dart is no longer exported \ No newline at end of file diff --git a/README.md b/README.md index 8d9adf8..c20990a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,14 @@ A simple usage example: import 'package:fnv/fnv.dart'; void main() { + // Returns int, 32-bit precision, full platform support print(fnv1a_32_s('foo')); + + // Returns int, 64-bit precision, but does not support web platforms + print(fnv1a_64_s('foo')); + + // Returns BigInt, 64-bit precision, full platform support + print(fnv1a_64_s_bigint('foo')); } ``` diff --git a/example/fnv_example.dart b/example/fnv_example.dart index d56961c..b1da46a 100644 --- a/example/fnv_example.dart +++ b/example/fnv_example.dart @@ -1,5 +1,12 @@ import 'package:fnv/fnv.dart'; void main() { + // Returns int, 32-bit precision, full platform support print(fnv1a_32_s('foo')); + + // Returns int, 64-bit precision, but does not support web platforms + print(fnv1a_64_s('foo')); + + // Returns BigInt, 64-bit precision, full platform support + print(fnv1a_64_s_bigint('foo')); } diff --git a/lib/fnv.dart b/lib/fnv.dart index 1a2972f..e343264 100644 --- a/lib/fnv.dart +++ b/lib/fnv.dart @@ -1,4 +1,3 @@ library fnv; export 'src/fnv.dart'; -export 'src/fnv_constants.dart'; diff --git a/lib/src/fnv.dart b/lib/src/fnv.dart index 02cb067..1a7765d 100644 --- a/lib/src/fnv.dart +++ b/lib/src/fnv.dart @@ -1,70 +1,96 @@ import 'dart:convert'; -import 'dart:math'; +import 'fnv_algorithm.dart' as adaptive; +import 'fnv_algorithm_full_precision.dart' as full_precision; import 'fnv_constants.dart'; -/// FNV hash algorithm -int _fnv(List bytes, int init, int fnv_prime, int mask) { - var hash = init; - - var i = 0; - while (i < bytes.length) { - hash *= fnv_prime; - hash ^= bytes[i++]; - } - - return hash & mask; -} - -/// FNVA hash algorithm -int _fnva(List bytes, int init, int fnv_prime, int mask) { - var hash = init; - - var i = 0; - while (i < bytes.length) { - hash ^= bytes[i++]; - hash *= fnv_prime; - } - - return hash & mask; -} +const bool _kIsWeb = bool.fromEnvironment('dart.library.js_util'); /// FNV-1 32bit hash algorithm -int fnv1_32(List bytes, {int init = FNV1_32_INIT}) { - return _fnv(bytes, init, FNV_32_PRIME, UINT32_MASK); +int fnv1_32(List bytes, {int? init}) { + final initUsed = init != null ? BigInt.from(init) : FNV1_32_INIT; + return full_precision + .fnv_bigint(bytes, initUsed, FNV_32_PRIME, UINT32_MASK) + .toInt(); } /// FNV-1 32bit hash algorithm -int fnv1_32_s(String str, {int init = FNV1_32_INIT}) { +int fnv1_32_s(String str, {int? init}) { return fnv1_32(utf8.encode(str), init: init); } /// FNV-1a 32bit hash algorithm -int fnv1a_32(List bytes, {int init = FNV1A_32_INIT}) { - return _fnva(bytes, init, FNV_32_PRIME, UINT32_MASK); +int fnv1a_32(List bytes, {int? init}) { + final initUsed = init != null ? BigInt.from(init) : FNV1A_32_INIT; + return full_precision + .fnva_bigint(bytes, initUsed, FNV_32_PRIME, UINT32_MASK) + .toInt(); } /// FNV-1a 32bit hash algorithm -int fnv1a_32_s(String str, {int init = FNV1_32_INIT}) { +int fnv1a_32_s(String str, {int? init}) { return fnv1a_32(utf8.encode(str), init: init); } +void _checkUnsupportedWeb() { + if (_kIsWeb) { + throw UnsupportedError( + 'If you need to support the web platform, please use the _bigint method.'); + } +} + /// FNV-1 64bit hash algorithm -int fnv1_64(List bytes, {int init = FNV1_64_INIT}) { - return _fnv(bytes, init, FNV_64_PRIME, UINT64_MASK); +int fnv1_64(List bytes, {int? init}) { + _checkUnsupportedWeb(); + final initUsed = init != null ? BigInt.from(init) : FNV1_64_INIT; + return full_precision + .fnv_bigint(bytes, initUsed, FNV_64_PRIME, UINT64_MASK) + .toInt(); } /// FNV-1 64bit hash algorithm -int fnv1_64_s(String str, {int init = FNV1_64_INIT}) { +int fnv1_64_s(String str, {int? init}) { return fnv1_64(utf8.encode(str), init: init); } /// FNV-1a 64bit hash algorithm -int fnv1a_64(List bytes, {int init = FNV1A_64_INIT}) { - return _fnva(bytes, init, FNV_64_PRIME, UINT64_MASK); +int fnv1a_64(List bytes, {int? init}) { + _checkUnsupportedWeb(); + final initUsed = init != null ? BigInt.from(init) : FNV1A_64_INIT; + return full_precision + .fnva_bigint(bytes, initUsed, FNV_64_PRIME, UINT64_MASK) + .toInt(); } /// FNV-1a 64bit hash algorithm -int fnv1a_64_s(String str, {int init = FNV1_64_INIT}) { +int fnv1a_64_s(String str, {int? init}) { return fnv1a_64(utf8.encode(str), init: init); } + +/// FNV-1 64bit hash algorithm +/// Support for web platforms +BigInt fnv1_64_bigint(List bytes, {BigInt? init}) { + final initUsed = init ?? FNV1_64_INIT; + assert(initUsed.bitLength <= 64); + return adaptive.fnv_bigint(bytes, initUsed, FNV_64_PRIME, UINT64_MASK); +} + +/// FNV-1 64bit hash algorithm +/// Support for web platforms +BigInt fnv1_64_s_bigint(String str, {BigInt? init}) { + return fnv1_64_bigint(utf8.encode(str), init: init); +} + +/// FNV-1a 64bit hash algorithm +/// Support for web platforms +BigInt fnv1a_64_bigint(List bytes, {BigInt? init}) { + final initUsed = init ?? FNV1A_64_INIT; + assert(initUsed.bitLength <= 64); + return adaptive.fnva_bigint(bytes, initUsed, FNV_64_PRIME, UINT64_MASK); +} + +/// FNV-1a 64bit hash algorithm +/// Support for web platforms +BigInt fnv1a_64_s_bigint(String str, {BigInt? init}) { + return fnv1a_64_bigint(utf8.encode(str), init: init); +} diff --git a/lib/src/fnv_algorithm.dart b/lib/src/fnv_algorithm.dart new file mode 100644 index 0000000..402ee18 --- /dev/null +++ b/lib/src/fnv_algorithm.dart @@ -0,0 +1,2 @@ +export 'fnv_algorithm_full_precision.dart' + if (dart.library.js) 'fnv_algorithm_web.dart'; diff --git a/lib/src/fnv_algorithm_full_precision.dart b/lib/src/fnv_algorithm_full_precision.dart new file mode 100644 index 0000000..30a1a91 --- /dev/null +++ b/lib/src/fnv_algorithm_full_precision.dart @@ -0,0 +1,33 @@ +/// FNV hash algorithm +int _fnv(List bytes, int init, int fnv_prime, int mask) { + var hash = init; + + var i = 0; + while (i < bytes.length) { + hash *= fnv_prime; + hash ^= bytes[i++]; + } + + return hash & mask; +} + +/// FNVA hash algorithm +int _fnva(List bytes, int init, int fnv_prime, int mask) { + var hash = init; + + var i = 0; + while (i < bytes.length) { + hash ^= bytes[i++]; + hash *= fnv_prime; + } + + return hash & mask; +} + +BigInt fnv_bigint( + List bytes, BigInt init, BigInt fnv_prime, BigInt mask) => + BigInt.from(_fnv(bytes, init.toInt(), fnv_prime.toInt(), mask.toInt())); + +BigInt fnva_bigint( + List bytes, BigInt init, BigInt fnv_prime, BigInt mask) => + BigInt.from(_fnva(bytes, init.toInt(), fnv_prime.toInt(), mask.toInt())); diff --git a/lib/src/fnv_algorithm_web.dart b/lib/src/fnv_algorithm_web.dart new file mode 100644 index 0000000..1d4b90d --- /dev/null +++ b/lib/src/fnv_algorithm_web.dart @@ -0,0 +1,30 @@ +/// FNV hash algorithm, Use BigInt +BigInt fnv_bigint(List bytes, BigInt init, BigInt fnv_prime, BigInt mask) { + var hash = init; + + var i = 0; + while (i < bytes.length) { + hash *= fnv_prime; + hash = hash.toSigned(64); + hash ^= BigInt.from(bytes[i++]); + hash = hash.toSigned(64); + } + + return hash & mask; +} + +/// FNVA hash algorithm, Use BigInt +BigInt fnva_bigint( + List bytes, BigInt init, BigInt fnv_prime, BigInt mask) { + var hash = init; + + var i = 0; + while (i < bytes.length) { + hash ^= BigInt.from(bytes[i++]); + hash = hash.toSigned(64); + hash *= fnv_prime; + hash = hash.toSigned(64); + } + + return hash & mask; +} diff --git a/lib/src/fnv_constants.dart b/lib/src/fnv_constants.dart index a762458..f7ff908 100644 --- a/lib/src/fnv_constants.dart +++ b/lib/src/fnv_constants.dart @@ -1,10 +1,10 @@ -const int FNV1_32_INIT = 0x811c9dc5; -const int FNV1A_32_INIT = FNV1_32_INIT; -const int FNV1_64_INIT = 0xcbf29ce484222325; -const int FNV1A_64_INIT = FNV1_64_INIT; +final BigInt FNV1_32_INIT = BigInt.from(0x811c9dc5); +final BigInt FNV1A_32_INIT = FNV1_32_INIT; +final BigInt FNV1_64_INIT = BigInt.parse('0xcbf29ce484222325').toSigned(64); +final BigInt FNV1A_64_INIT = FNV1_64_INIT; -const int FNV_32_PRIME = 0x01000193; -const int FNV_64_PRIME = 0x100000001b3; +final BigInt FNV_32_PRIME = BigInt.from(0x01000193); +final BigInt FNV_64_PRIME = BigInt.from(0x100000001b3); -const int UINT32_MASK = 0xffffffff; -const int UINT64_MASK = 0xffffffffffffffff; +final BigInt UINT32_MASK = BigInt.from(0xffffffff); +final BigInt UINT64_MASK = BigInt.parse('0xffffffffffffffff').toSigned(64); diff --git a/pubspec.yaml b/pubspec.yaml index b5c3877..4b59261 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -8,5 +8,7 @@ environment: sdk: '>=2.13.4 <3.0.0' dev_dependencies: + flutter_test: + sdk: flutter pedantic: ^1.10.0 test: ^1.16.0 diff --git a/test/fnv_bigint_test.dart b/test/fnv_bigint_test.dart new file mode 100644 index 0000000..8777875 --- /dev/null +++ b/test/fnv_bigint_test.dart @@ -0,0 +1,35 @@ +import 'dart:convert'; + +import 'package:fnv/fnv.dart'; +import 'package:test/test.dart'; + +// use ```flutter test --platform chrome .\test\fnv_bigint_test.dart``` +void main() { + group('A group of tests for bigint', () { + test('normal method unsupported web platform', () { + expect(() => fnv1_64_s(''), throwsA(isA())); + expect(() => fnv1a_64_s(''), throwsA(isA())); + }); + + test('fnv1_64', () { + expect(fnv1_64_bigint(utf8.encode('test')), + BigInt.parse('0x8c093f7e9fccbf69').toSigned(64)); + expect(fnv1_64_s_bigint('test'), + BigInt.parse('0x8c093f7e9fccbf69').toSigned(64)); + }); + + test('fnv1a_64', () { + expect(fnv1a_64_bigint(utf8.encode('test')), + BigInt.parse('0xf9e6e6ef197c2b25').toSigned(64)); + expect(fnv1a_64_s_bigint('test'), + BigInt.parse('0xf9e6e6ef197c2b25').toSigned(64)); + }); + + test('error init hash', () { + expect( + () => + fnv1a_64_s_bigint('', init: BigInt.parse('0x1ffffffffffffffff')), + throwsA(isA())); + }); + }); +}