From 26e512394eff10dda8297f18b30d1310076b9fb4 Mon Sep 17 00:00:00 2001 From: Cian Hughes Date: Wed, 19 Jul 2023 16:38:52 +0100 Subject: [PATCH] First dev commit --- .gitignore | 4 + Cargo.lock | 709 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 30 ++ benches/bench.rs | 111 ++++++ build.rs | 49 +++ src/fastmath.rs | 208 +++++++++++ src/lib.rs | 10 + src/lookup/const_tables.rs | 30 ++ src/lookup/data/cos_f32.bin | Bin 0 -> 8036 bytes src/lookup/data/cos_f64.bin | Bin 0 -> 16056 bytes src/lookup/data/sin_f32.bin | Bin 0 -> 8036 bytes src/lookup/data/sin_f64.bin | Bin 0 -> 16056 bytes src/lookup/lookup_table.rs | 149 ++++++++ src/lookup/mod.rs | 4 + src/tests.rs | 156 ++++++++ 15 files changed, 1460 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 benches/bench.rs create mode 100644 build.rs create mode 100644 src/fastmath.rs create mode 100644 src/lib.rs create mode 100644 src/lookup/const_tables.rs create mode 100644 src/lookup/data/cos_f32.bin create mode 100644 src/lookup/data/cos_f64.bin create mode 100644 src/lookup/data/sin_f32.bin create mode 100644 src/lookup/data/sin_f64.bin create mode 100644 src/lookup/lookup_table.rs create mode 100644 src/lookup/mod.rs create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b169044 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.vscode +/tmp +*.ipynb \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f2e7446 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,709 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f644d0dac522c8b05ddc39aaaccc5b136d5dc4ff216610c5641e3be5becf56c" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af410122b9778e024f9e0fb35682cc09cc3f85cad5e8d3ba8f47a9702df6e73d" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fastmath" +version = "0.1.0" +dependencies = [ + "bincode", + "criterion", + "num-traits", + "once_cell", + "serde", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "linux-raw-sys" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "rustix" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..53b4b0f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "fastmath" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1.3.3" +num-traits = "0.2.15" +once_cell = "1.18.0" +serde = {version = "1.0.171", features = ["derive"] } + +[build-dependencies] +bincode = "1.3.3" +num-traits = "0.2.15" +serde = {version = "1.0.171", features = ["derive"] } + +[lib] +name = "fastmath" +path = "src/lib.rs" +test = true +bench = true + +[[bench]] +name = "bench" +harness = false + +[dev-dependencies] +criterion = "0.5.1" diff --git a/benches/bench.rs b/benches/bench.rs new file mode 100644 index 0000000..838ab4d --- /dev/null +++ b/benches/bench.rs @@ -0,0 +1,111 @@ +#![allow(unused_imports)] +extern crate fastmath; + +use fastmath::*; +use criterion::{Criterion, BenchmarkGroup, measurement::WallTime}; +use criterion::{black_box, criterion_group, criterion_main}; + +fn pow2_benchmarks(group: &mut BenchmarkGroup, x_f64: &[f64], x_f32: &[f32]) { + group.bench_function("f64_fast", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).fast_pow2()).collect::>()) + }); + group.bench_function("f64_builtin_fn", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).powi(2)).collect::>()) + }); + group.bench_function("f64_builtin_mul", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x) * x).collect::>()) + }); + group.bench_function("f32_fast", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).fast_pow2()).collect::>()) + }); + group.bench_function("f32_builtin_fn", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).powi(2)).collect::>()) + }); + group.bench_function("f32_builtin_mul", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x) * x).collect::>()) + }); +} + +fn exp_benchmarks(group: &mut BenchmarkGroup, x_f64: &[f64], x_f32: &[f32]) { + group.bench_function("f64_fast", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).fast_exp()).collect::>()) + }); + group.bench_function("f64_builtin", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).exp()).collect::>()) + }); + group.bench_function("f32_fast", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).fast_exp()).collect::>()) + }); + group.bench_function("f32_builtin", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).exp()).collect::>()) + }); +} + +fn cos_benchmarks(group: &mut BenchmarkGroup, x_f64: &[f64], x_f32: &[f32]) { + group.bench_function("f64_fast", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).fast_cos()).collect::>()) + }); + group.bench_function("f64_lookup", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).lookup_cos()).collect::>()) + }); + group.bench_function("f64_builtin", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).cos()).collect::>()) + }); + group.bench_function("f32_fast", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).fast_cos()).collect::>()) + }); + group.bench_function("f32_lookup", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).lookup_cos()).collect::>()) + }); + group.bench_function("f32_builtin", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).cos()).collect::>()) + }); +} + +fn sigmoid_benchmarks(group: &mut BenchmarkGroup, x_f64: &[f64], x_f32: &[f32]) { + group.bench_function("f64_fast", |b| { + b.iter(|| x_f64.iter().map(|&x| black_box(x).fast_sigmoid()).collect::>()) + }); + group.bench_function("f64_builtin", |b| { + b.iter(|| x_f64.iter().map(|&x| sigmoid_builtin_f64(black_box(x))).collect::>()) + }); + group.bench_function("f32_fast", |b| { + b.iter(|| x_f32.iter().map(|&x| black_box(x).fast_sigmoid()).collect::>()) + }); + group.bench_function("f32_builtin", |b| { + b.iter(|| x_f32.iter().map(|&x| sigmoid_builtin_f32(black_box(x))).collect::>()) + }); +} + +fn criterion_benchmark(c: &mut Criterion) { + // Prepare x values for testing functions + let x_f64 = (-10000..10000) + .map(|a| (a as f64) / 1000.) + .collect::>(); + let x_f32 = (-10000..10000) + .map(|a| (a as f32) / 1000.) + .collect::>(); + // to ensure tests are fair, we need to instantiate the lookup tables + 1.0f64.lookup_cos(); + 1.0f32.lookup_cos(); + // Then, tests can begin + let mut group = c.benchmark_group("pow2"); + pow2_benchmarks(&mut group, &x_f64, &x_f32); + group.finish(); + + let mut group = c.benchmark_group("exp"); + exp_benchmarks(&mut group, &x_f64, &x_f32); + group.finish(); + + let mut group = c.benchmark_group("cos"); + cos_benchmarks(&mut group, &x_f64, &x_f32); + group.finish(); + + let mut group = c.benchmark_group("sigmoid"); + sigmoid_benchmarks(&mut group, &x_f64, &x_f32); + group.finish(); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); + diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..f76240b --- /dev/null +++ b/build.rs @@ -0,0 +1,49 @@ +// build.rs + +mod precalculate_lookup_tables { + use std::fs::{create_dir_all, File}; + use std::io::Write; + include!("src/lookup/lookup_table.rs"); + use bincode::serialize; + + const PRECISION: usize = 1000; + + fn precalculate_sin_tables() -> Result<(), Box> { + let data = serialize(&EndoSinLookupTable::::new(PRECISION))?; + let mut file = File::create("src/lookup/data/sin_f32.bin")?; + file.write_all(&data)?; + + let data = serialize(&EndoSinLookupTable::::new(PRECISION))?; + let mut file = File::create("src/lookup/data/sin_f64.bin")?; + file.write_all(&data)?; + + Ok(()) + } + + fn precalculate_cos_tables() -> Result<(), Box> { + let data = serialize(&EndoCosLookupTable::::new(PRECISION))?; + let mut file = File::create("src/lookup/data/cos_f32.bin")?; + file.write_all(&data)?; + + let data = serialize(&EndoCosLookupTable::::new(PRECISION))?; + let mut file = File::create("src/lookup/data/cos_f64.bin")?; + file.write_all(&data)?; + + Ok(()) + } + + pub fn generate() -> Result<(), Box> { + create_dir_all("src/lookup/data")?; + + precalculate_sin_tables()?; + precalculate_cos_tables()?; + + Ok(()) + } +} + +fn main() -> Result<(), Box> { + precalculate_lookup_tables::generate()?; + + Ok(()) +} \ No newline at end of file diff --git a/src/fastmath.rs b/src/fastmath.rs new file mode 100644 index 0000000..64b79ac --- /dev/null +++ b/src/fastmath.rs @@ -0,0 +1,208 @@ +//! A collection of fast (often approximate) mathematical functions for accelerating mathematical functions + +// Optimisation note: lookup tables become faster when calculation takes > ~1ms + +use std::f32::consts as f32_consts; +use std::f64::consts as f64_consts; +use crate::lookup::*; + +pub trait FastMath: FastCos + FastPow2 + FastExp + FastSigmoid {} +impl FastMath for f32 {} +impl FastMath for f64 {} + + +pub trait LookupCos { + fn lookup_cos(self: Self) -> Self; +} +impl LookupCos for f64 { + #[inline] + fn lookup_cos(self: Self) -> f64 { + // Look up the value in the table + COS_LOOKUP_F64.lookup(self) + } +} +impl LookupCos for f32 { + #[inline] + fn lookup_cos(self: Self) -> f32 { + // Look up the value in the table + COS_LOOKUP_F32.lookup(self) + } +} + +pub trait FastCos { + fn fast_cos(self: Self) -> Self; +} +impl FastCos for f32 { + #[inline] + fn fast_cos(self: Self) -> f32 { + const BITAND: u32 = u32::MAX / 2; + const ONE: f32 = 1.0; + let mod_x = (((self + f32_consts::PI).abs()) % f32_consts::TAU) - f32_consts::PI; + let v = mod_x.to_bits() & BITAND; + let qpprox = ONE - f32_consts::FRAC_2_PI * f32::from_bits(v); + qpprox + f32_consts::FRAC_PI_6 * qpprox * (ONE - qpprox * qpprox) + } +} +impl FastCos for f64 { + #[inline] + fn fast_cos(self: Self) -> f64 { + const BITAND: u64 = u64::MAX / 2; + const ONE: f64 = 1.0; + let mod_x = (((self + f64_consts::PI).abs()) % f64_consts::TAU) - f64_consts::PI; + let v = mod_x.to_bits() & BITAND; + let qpprox = ONE - f64_consts::FRAC_2_PI * f64::from_bits(v); + qpprox + f64_consts::FRAC_PI_6 * qpprox * (ONE - qpprox * qpprox) + } +} + +pub trait FastPow2 { + fn fast_pow2(self: Self) -> Self; +} +impl FastPow2 for f32 { + #[inline] + fn fast_pow2(self: Self) -> f32 { + // Khinchins constant over 3. IDK why it gives the best fit, but it does + const KHINCHIN_3: f32 = 2.68545200106530644530971483548179569382038229399446295305115234555721885953715200280114117493184769799515 / 3.0; + const CLIPP_THRESH: f32 = 0.12847338; + const V_SCALE: f32 = 8388608.0; // (1_i32 << 23) as f32 + const CLIPP_SHIFT: f32 = 126.67740855; + let abs_p = self.abs(); + let clipp = abs_p.max(CLIPP_THRESH); // if abs_p < CLIPP_THRESH { CLIPP_THRESH } else { abs_p }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u32; + f32::from_bits(v) - KHINCHIN_3 + } +} +impl FastPow2 for f64 { + #[inline] + fn fast_pow2(self: Self) -> f64 { + const KHINCHIN_3: f64 = 2.68545200106530644530971483548179569382038229399446295305115234555721885953715200280114117493184769799515 / 3.0; + const CLIPP_THRESH: f64 = -45774.9247660416; + const V_SCALE: f64 = 4503599627370496.0; // (1i64 << 52) as f64 + const CLIPP_SHIFT: f64 = 1022.6769200000002; + const ZERO: f64 = 0.; + let abs_p = self.abs(); + let clipp = abs_p.max(CLIPP_THRESH); // if abs_p < CLIPP_THRESH { CLIPP_THRESH } else { abs_p }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u64; + let y = f64::from_bits(v) - KHINCHIN_3; + if y.is_sign_positive() { + y + } else { + ZERO + } + } +} + +pub trait FastExp { + fn fast_exp(self: Self) -> Self; +} +impl FastExp for f32 { + #[inline] + fn fast_exp(self: Self) -> f32 { + const CLIPP_THRESH: f32 = -126.0; // 0.12847338; + const V_SCALE: f32 = 8388608.0; // (1_i32 << 23) as f32 + const CLIPP_SHIFT: f32 = 126.94269504; // 126.67740855; + + let scaled_p = f32_consts::LOG2_E * self; + let clipp = scaled_p.max(CLIPP_THRESH); // if scaled_p < CLIPP_THRESH { CLIPP_THRESH } else { scaled_p }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u32; + f32::from_bits(v) + } +} +impl FastExp for f64 { + #[inline] + fn fast_exp(self: Self) -> f64 { + const CLIPP_THRESH: f64 = -180335.51911105003; + const V_SCALE: f64 = 4524653012949098.0; + const CLIPP_SHIFT: f64 = 1018.1563534409383; + + let scaled_p = f64_consts::LOG2_E * self; + let clipp = scaled_p.max(CLIPP_THRESH); // let clipp = if scaled_p < CLIPP_THRESH { CLIPP_THRESH } else { scaled_p }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u64; + f64::from_bits(v) + } +} + +pub trait FastSigmoid { + fn fast_sigmoid(self: Self) -> Self; +} +impl FastSigmoid for f32 { + #[inline] + fn fast_sigmoid(self: Self) -> f32 { + const ONE: f32 = 1.0; + (ONE + (-self).fast_exp()).recip() + } +} +impl FastSigmoid for f64 { + #[inline] + fn fast_sigmoid(self: Self) -> f64 { + const ONE: f64 = 1.0; + (ONE + (-self).fast_exp()).recip() + } +} + +// A trait for testing and improving implementations of fast functions +pub trait Test { + fn test(self: Self) -> Self; +} +impl Test for f32 { + #[inline] + fn test(self: Self) -> f32 { + // Khinchins constant over 3. IDK why it gives the best fit, but it does + // const KHINCHIN_3: f32 = 2.68545200106530644530971483548179569382038229399446295305115234555721885953715200280114117493184769799515 / 3.0; + const CLIPP_THRESH: f32 = -126.0; // 0.12847338; + const V_SCALE: f32 = 8388608.0; // (1_i32 << 23) as f32 + const CLIPP_SHIFT: f32 = 126.94269504; // 126.67740855; + + let scaled_p = f32_consts::LOG2_E * self; + let clipp = if scaled_p < CLIPP_THRESH { + CLIPP_THRESH + } else { + scaled_p + }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u32; + f32::from_bits(v) // - KHINCHIN_3 + } +} +impl Test for f64 { + #[inline] + fn test(self: Self) -> f64 { + const CLIPP_THRESH: f64 = -180335.51911105003; + const V_SCALE: f64 = 4524653012949098.0; + const CLIPP_SHIFT: f64 = 1018.1563534409383; + + let scaled_p = f64_consts::LOG2_E * self; + let clipp = if scaled_p < CLIPP_THRESH { + CLIPP_THRESH + } else { + scaled_p + }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u64; + f64::from_bits(v) + } +} + +#[allow(non_snake_case, dead_code)] +pub fn optimizing(p: f64, CLIPP_THRESH: f64, V_SCALE: f64, CLIPP_SHIFT: f64) -> f64 { + // const CLIPP_THRESH: f64 = -45774.9247660416; + // const V_SCALE: f64 = 4503599627370496.0; + // const CLIPP_SHIFT: f64 = 1022.6769200000002; + + let scaled_p = f64_consts::LOG2_E * p; + let clipp = if scaled_p < CLIPP_THRESH { + CLIPP_THRESH + } else { + scaled_p + }; + let v = (V_SCALE * (clipp + CLIPP_SHIFT)) as u64; + f64::from_bits(v) +} + +#[inline] +pub fn sigmoid_builtin_f32(p: f32) -> f32 { + (1. + (-p).exp()).recip() +} + +#[inline] +pub fn sigmoid_builtin_f64(p: f64) -> f64 { + (1. + (-p).exp()).recip() +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..7427728 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,10 @@ +//lib.rs +#![allow(unused_imports)] + +pub mod lookup; +mod fastmath; + +pub use fastmath::*; + +#[cfg(test)] +mod tests; \ No newline at end of file diff --git a/src/lookup/const_tables.rs b/src/lookup/const_tables.rs new file mode 100644 index 0000000..31a064e --- /dev/null +++ b/src/lookup/const_tables.rs @@ -0,0 +1,30 @@ +// lookup/const_tables.rs + +use once_cell::sync::Lazy; +use std::fs::read; +use bincode::deserialize; +use super::lookup_table::*; + +pub const SIN_LOOKUP_F32: Lazy> = Lazy::new(|| { + deserialize( + &read("src/lookup/data/sin_f32.bin").expect("Failed to read sin_f64.bin") + ).expect("Failed to load SIN_LOOKUP_F32") +}); + +pub const SIN_LOOKUP_F64: Lazy> = Lazy::new(|| { + deserialize( + &read("src/lookup/data/sin_f64.bin").expect("Failed to read sin_f32.bin") + ).expect("Failed to load SIN_LOOKUP_F64") +}); + +pub const COS_LOOKUP_F32: Lazy> = Lazy::new(|| { + deserialize( + &read("src/lookup/data/cos_f32.bin").expect("Failed to read cos_f64.bin") + ).expect("Failed to load COS_LOOKUP_F32") +}); + +pub const COS_LOOKUP_F64: Lazy> = Lazy::new(|| { + deserialize( + &read("src/lookup/data/cos_f64.bin").expect("Failed to read cos_f32.bin") + ).expect("Failed to load COS_LOOKUP_F64") +}); diff --git a/src/lookup/data/cos_f32.bin b/src/lookup/data/cos_f32.bin new file mode 100644 index 0000000000000000000000000000000000000000..78252d94ad7eabc99e71997a5efe04b43fff7996 GIT binary patch literal 8036 zcmYkAc~p*F)Q6Ldkuee~Lqti&QqR4Ap%hY)G$>StlID4Qie{P=4N6fe5lSh|gM?S6 zSFb6A$UJ2pzy1C5t*^DtTCL}PI_I>1`@XJq&WpYh|M#Q*q+MJ6$toTD?U_3KyUyA5M8;KU3-K1ZUIz-it`Pfl7ta|jy-=U8!CCtYQeR=j@NAxVnpY7?Ka`iu}i#uyHF#4 zo%e84xEXtk_ijihbIbOvC#T|L=6aY9QsU9ZXK|rHSi6U#1VE+Lqz8YvV}bn$WN`S^978SjxI0 zM_eD8(SHKHJ2a8b2#UnDq9s0)Y0!wNboID0aoxzKTZJZv&7|k!)rf0IK4!B?^iG2$ zch4oRCnYv7AoC5HH2#Mcac!w+j)+{_1zO+);yP1P?h=X->5=hmed3zayZl`bXyyG`^}chQ#TT4M2j!m(*VcK z)ZTY1ab9Gt<3vigwo{Mi4&v;{yV#AimwAxHV^887DSn(UnN|3a{OSPWOsOCym|UiY z(!547alX_Q6wWo=Nd;qfaUFM&%jpQNWhBiriQ;-j)AyIrT+l;s_tP{A#iFD_4BG);Ia+8y})_ZB|tQ4+y3aMR8<(luK-p%{D?rC&ZBAsiW zPU5T#u74&i(>lNzWYLhDS)4;Q-Ecd|S>#ZLbS~$SOB)LhaVCdJd1)T!l25N60i|%Gs6C zQ_ItwUpXE7RL&VzkY~~v&aslT)T%hkD*D@Ymh-HpR)-qSw3c@Nt>s+nXw89o&bEQ% zG#feJCVFtAi8F4dBd#r+^Eq-J+{#(E(wu_xoOc_2TYP~tzex4>FLLgeC?en=&i*p3 zlx^qy+i67U74Ctnbld0}_rY~K`20He!VTIS@h|toO;T0r;GXE94>kXBU)-Wfi`(2A zcPO;?4)@1h(o4L@J<>^oRqu13+^6g3A8@Zcq%`|3?w3bo`{xn&%ww9G`GotXn_ez> z%DwZ9O0PfT{^=nAvB9dP_;2Z@H)5k%j+z?yC

Yxp>7jkW?=^yqq~LN|3LE76 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 C6=v@M literal 0 HcmV?d00001 diff --git a/src/lookup/data/cos_f64.bin b/src/lookup/data/cos_f64.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a7b3acd0a0189fa8d955aa28cc3584880678852 GIT binary patch literal 16056 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 literal 0 HcmV?d00001 diff --git a/src/lookup/data/sin_f32.bin b/src/lookup/data/sin_f32.bin new file mode 100644 index 0000000000000000000000000000000000000000..78252d94ad7eabc99e71997a5efe04b43fff7996 GIT binary patch literal 8036 zcmYkAc~p*F)Q6Ldkuee~Lqti&QqR4Ap%hY)G$>StlID4Qie{P=4N6fe5lSh|gM?S6 zSFb6A$UJ2pzy1C5t*^DtTCL}PI_I>1`@XJq&WpYh|M#Q*q+MJ6$toTD?U_3KyUyA5M8;KU3-K1ZUIz-it`Pfl7ta|jy-=U8!CCtYQeR=j@NAxVnpY7?Ka`iu}i#uyHF#4 zo%e84xEXtk_ijihbIbOvC#T|L=6aY9QsU9ZXK|rHSi6U#1VE+Lqz8YvV}bn$WN`S^978SjxI0 zM_eD8(SHKHJ2a8b2#UnDq9s0)Y0!wNboID0aoxzKTZJZv&7|k!)rf0IK4!B?^iG2$ zch4oRCnYv7AoC5HH2#Mcac!w+j)+{_1zO+);yP1P?h=X->5=hmed3zayZl`bXyyG`^}chQ#TT4M2j!m(*VcK z)ZTY1ab9Gt<3vigwo{Mi4&v;{yV#AimwAxHV^887DSn(UnN|3a{OSPWOsOCym|UiY z(!547alX_Q6wWo=Nd;qfaUFM&%jpQNWhBiriQ;-j)AyIrT+l;s_tP{A#iFD_4BG);Ia+8y})_ZB|tQ4+y3aMR8<(luK-p%{D?rC&ZBAsiW zPU5T#u74&i(>lNzWYLhDS)4;Q-Ecd|S>#ZLbS~$SOB)LhaVCdJd1)T!l25N60i|%Gs6C zQ_ItwUpXE7RL&VzkY~~v&aslT)T%hkD*D@Ymh-HpR)-qSw3c@Nt>s+nXw89o&bEQ% zG#feJCVFtAi8F4dBd#r+^Eq-J+{#(E(wu_xoOc_2TYP~tzex4>FLLgeC?en=&i*p3 zlx^qy+i67U74Ctnbld0}_rY~K`20He!VTIS@h|toO;T0r;GXE94>kXBU)-Wfi`(2A zcPO;?4)@1h(o4L@J<>^oRqu13+^6g3A8@Zcq%`|3?w3bo`{xn&%ww9G`GotXn_ez> z%DwZ9O0PfT{^=nAvB9dP_;2Z@H)5k%j+z?yC

Yxp>7jkW?=^yqq~LN|3LE76 zf!}c~e%=rBO!`ClOn>nEj)4&a@crcgyjndF{O;q5;vh6e4MORgL0}FbwPrA)CQHLV zRvOF%thX2fqxvBr<)L6cU{dc;jIx%2RFe#t6?mR740k>c!#_5|!Q8-!nIn*!JOatz zMt~Ut@AD(Eb=D|Yq>cjf1UmMkp>}aJ6f|VPY(bwtvgqAB2HpRR0dodz8Dnu)LJr5a z$$^=JgazXu&Kd`gzVcxHVC{8zEYlhf&7ARI7D3)+0*2h2fIr$3!Cb-Fddx#2{by&4>22eHwbH>dCa}T3?W}=_5D!!Jh zf*FXrJJoRcg&OKiXMuT$Tm^OPjZ{a(YjrRiv8{SGEGKDTMT`cRlbE$`4ixL=VEELz zU}mEC!(2SIoQGQt^T7N>b^LrB`!pZf>lc7oim(|A;hD4$4qq37xe9|;O=ziUVP=XJ zn6VgYw+MgR7U8|RHkh~g_m?(W9dvN!k`9=?$V?Yu_g@i0whCYlW7B-XngfKTk^nOq z)2;z>nz|T#P#4T+ym4NP2R9bu+M*?3R^#-5r8wfc6#F}tg1L==JUuuM)`zW!KA7Rq z#WKt(ScWM>4Zu7{|9b}bw%8CaiVVSQN4xKG)Zbr@Q+g}FoJY#Ym52yf3E!@jVCKW> zgb`NCu7W6d6`21}T49V4rN-zhX99Kso;@|eZ6i}$JZ%d00*;5T#=+;Sk!Z38>%p$UxN|lbtZIv2$+lqc;Niv%xPE>EnrCeUI|v0o zHzLh`6JjrJ0{aL%((GXS#||c2?7?or+<6X|n&|-9KAXXw!uKni@p8cybY^V#*nQYC!W|a=?l64l4)!2aOFS@fv?qoId4ipYkA_}& za?%SOW4ytBL{+ypidXvJK$#ENl@N#d!o9~A_Qrl-Z$f{9KQwpwL*=DE*rAZF3czoL zK)j6%1p5>>%!1HT9fXQW!Co7Zg|SVm9u= zh_YSG$6b&d7r~5-z$bA8b20)?pG7b$BXMh0B=a&77t14=nUSa-AI02^!ttF^%+4re zzldUfMkCQQni(36@XBcBXf!+(Vwj~d*c=(dJdJ_*>lkKgEDX$InX9o_R2|D~jm6AK zam?2^Oo)kN#>QdjyEx|TZu~Xh&8*#x_qDs3x4ZFp${uF!9^8!E!`$72)(?A_z455D zjA#DFqo^UC861zyX$j2X1nh}VU=}AJVAy)0p$=m^(k6S)Yz62hy4M>5!GoVCH9FfKvu@KLg*cW-$9R@p54% z^FI^!vNPEMnP`_fz#ce&dglY|f&(~p;{f|03x^kFu@kb8lAFa|$U@|RY<5F7d|k8I z57}_+$Yw_zgth1(d*UEg<{e~L9E319hkcO)b&ni&Mh=v2=dd?&F%r4#j$HID$Yp=z z;`5M0?2tov=5>fYatOEY9%7dq#-+uF*(ZllQ*@Y}au_AU^4KeRIOvI+o9#$;XzFN7yw-urA;T`{oD?yNd{9aW4y^2gXy#}FERj9qmM?$3|0ua3jcevI?oQ&ayMlA}aMPd-E*(ex7A_R>R4@n*CV~ z>xC=sZ9wgU2KH?OPGmK(a~qJ;w~@Wuh~({!?A}J~yxz$EZGyK} z6FayGTXUM&!%eX0-^?y<#&VZt_Hi?`Z#J`&TcD=X!d`B{#KSG@<`xVabdLRe4t?Cu zv7^u7%7`aC=PJkENaXK$ZJ@tyPR?lxrUwz0q45MS8F z4sU~4<^p^C0z7;!u*)yNzVib6{32fbzq7gupVj5~tS<0bz2kAaP640QFFVPL?kdk0 z{qty*C^LJT=3h7cvZd^G+*=z|H^+A42*sVsUg3G^%fFz`&}PGOkdio)Q_%K^`|eU18KmEK_nS2 zP0!?pQu!$vaxxrFgStjglHVx$DK5iOiD5+4*nHkh%q)H`^)hO6soo35uP;1^C!lHS6?wn8e92U~=Z<-{T zu!wpl>+l&-L{>&bPr5)RK8q=9&{DdPtw;aOSw_vR29#*EoOIr-pz~oyq%qo<0t-#( zkoIaSXkSBdo7U2bUUPaGX+c}$tf;QontDYxBz4u6K5W`ZH6J(8rU-lLlHE)~;TH1J z+C~u<9m(B#J1uzSOdUZkWIV);aM^L}`NUAl9qK)#=^r|bG>8a(4BS#{r} z=$sDPYyBSu$=)K}Yq#ie+-l4Oiu(SK5}$-m|`^?JM^LzTDW|KKf!rM@G(74K=h#0NTA@qxxWe(dv%t@^^GtS@#C zmO`1m6wXykp>bqC6mIVazh*w~%lF3}kN$AH*dLFT24I>00PMRu09U3B#JiAz_;qt2 zKB^AFjh%y#b!QMvGzQ~M)L?ksAB-pSq#?vfBdkjrB@2h3c@K}rLr|PVHqi{BQ6t3JGh0C)?qjcA3h;NO?0##XD50!<*4Oz4(kAYmk7?@uk1AoP_i0~W> zzt*u>BPWMZ&T^=$lY{Z_acH$4hp83guzi3$(yZih{Fponf6F7*WIWa!8V||d@yO7d zfHC_gAh2fwTD2zPOYB6*+?$9YY6|!etbp2f1-L3GqMw^0b~PyC`7kBS*`x&fQ%dlc zm;`UrNwCPB1cmpLa2b=aDRDCHJeZ6{>QfLBG6jW~r{K)^sW`rUDx%I##j=6Qc)d;; zzDJbttXCP@OQyj)X&O==OhcO5bc6>^hw+8!=)-pi84fCtEmeWv9~IQ{Jwi|V419Sy z125*zM3ZS zqYkwqb=dc+BZy}6xq|N&ZqCL8MGdq#X~4K#19g8iAhUc9^it-)=KdU5OrHx)kGc3( zJr~(h^Pp@z4@qhB@UUwhhRvLhDPHp-UppVYQVVcu)dCprUw~^57GVDLg$Qz6h`h>$ zIQe%WvJEuhlBmgNP)$@QXhChO7NSbD(DX?Q4|Nvd!Olf!ytoJv!?iKPLK~&o+8EcZ zjV&{EknFC5oHIH|`lSQAB_fQD5#eaN2#TWw_*n`#l`Y`XV*#zx33)pRcBKS~FN82{ z;87?rwFNL7pbMjwx|o}&i?26ykuzp7rdcmWV)kO(?ph4VsY@_q+Y)>|x&$q+m%wfI zQvCE-3di!LsQ9uJPqg&#Ay5wwYxPj_M-S_D_3<)XAC@ipD3n@;|MZukJ8Btjv@JvK zKm!;rH$Z2s0ajcxK-yqKoLgatYjK9CyJU!%!OJmk#d6feE{Dp+>m$OmW1}6s{4b7}IEqte>Wk&|HnBo~z+d zvKsb$CxR(!@ZEL|_NK4Fz<<}kcBmPW49t)lZia*!Ggy5#L*LnJ5$Cv;&q`}C_ug8p z9&HYjRpyu(WsWEH<_P#|j@Pr-L2z6L=iGJhyS)w@hg(3|&;l347BH-|K*3uJbSYcn zn~f!2r&w}LEU}}H6~1X&VUw#B^7E~5{;m~Thg;*&GHX}{TjNcsH8%8Eqh!K*+*-XJ z52Dtisb)QPzF!XoWgC9KZSXqY1`5qKQ2SzoF*9v(-_{mlzIXX^&K6dD_mZT#0eKrX zAUSyhY|m|g)YlD&p0N=<>o;Oj!bWH{Zp6%w8}WV0CghoKf=cuz#8quV+lx);lD9+0 zN;~8S+rhNN4!7>xVUdhI{Ky_juJ(vLXbeR zn{muwGcQa- zY26Clk6V$hunqsL*oND_+t6@$8^W(_gTjw(*r)7>9#cn*40go0LPz|#?ud$Cj#x3> z3Dv8eAQkKcl>#SBzvhI$-i> zc%pEwCtjO*Vvvs~{_gX{l{233yXA?mUp=u}&I>!Wyby2c1rL8O%u4q{bEOy5Z+XG{ zix*O4y%9Ix8&+oC=;P^)@FZ{CJ>d=Mc5leO^2XPJJ}8~$13f(-oV4@7$50>0Wcfhy ztPies_`vI<58li8LSNMvo(8_~ckqR|*cXEj_#*v`FaBKjh3Jhh%m(^FcZwhSlOK*) z`eD46ADrU-kXY!4_$EI%-1EbT&wfag@yDm>{+PMgA6nM_nCRt?ySx42mhX?wT7M{Y z`15@J(C8O{UlRh5Gd}?1jRN565PmO57pPQng-#jLlD&c zg5VGr1o!M9n4b#5h?XGacLYJ^c@Rv01i@)YFsu}Up`;OvhQ+~{Zx)R3&3vcl6O2Pq z!SG58#>Apv9IOh)mkYsAz7q`97r_|#Js5QZLtrQug5v2R=w29t&-x+gTpNM|4k4K1 z8G?ghA$XJ!f?ru7cv~ET%Bm2oJ0F7fe?u_-Q3&uh1Uf%MFmzxjDo2Mxb5bZ0W`&}5 zQ7D@9Ly^6j?+|T6amO(f+TNk?5Qjn>7mCgMLZOr!iqplRm{1W4+lEkhUkruazoD3R zKNQW+LNWh+C_=vT9Fk(BNQ+@NT8w@Z#RyXtoVl4YBMhTBsJR~H-ct}X_9lY-Uj{gBy C6=v@M literal 0 HcmV?d00001 diff --git a/src/lookup/data/sin_f64.bin b/src/lookup/data/sin_f64.bin new file mode 100644 index 0000000000000000000000000000000000000000..0a7b3acd0a0189fa8d955aa28cc3584880678852 GIT binary patch literal 16056 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 literal 0 HcmV?d00001 diff --git a/src/lookup/lookup_table.rs b/src/lookup/lookup_table.rs new file mode 100644 index 0000000..0431979 --- /dev/null +++ b/src/lookup/lookup_table.rs @@ -0,0 +1,149 @@ +use num_traits::sign::Signed; +use num_traits::float::{Float, FloatConst}; +use num_traits::NumCast; +use std::ops::{Sub, Rem}; +use serde::{Serialize, Deserialize}; +// use packed_simd::f64x4; + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct FloatLookupTable +where T1: Float, + T2: Float, +{ + keys: Vec, + values: Vec, +} +impl FloatLookupTable +where T1: Float, + T2: Float, +{ + pub fn new(mut keys: Vec, mut values: Vec) -> Self { + let mut indices: Vec<_> = (0..keys.len()).collect(); + indices.sort_by(|&i, &j| keys[i].partial_cmp(&keys[j]).unwrap()); + for i in 0..keys.len() { + while i != indices[i] { + let swap_index = indices[i]; + keys.swap(i, swap_index); + values.swap(i, swap_index); + indices.swap(i, swap_index); + } + } + FloatLookupTable { keys, values } + } + + #[allow(dead_code)] + pub fn lookup(&self, key: T1) -> T2 { + match self.keys.binary_search_by(|probe| probe.partial_cmp(&key).unwrap()) { + Ok(index) => self.values[index], + Err(index) => { + let upper_key = &self.keys[index]; + let upper_val = &self.values[index]; + let low_index = index - 1; + let lower_key = &self.keys[low_index]; + let lower_val = &self.values[low_index]; + // select nearest neighbour + let diff_upper = (key - *upper_key).abs(); + let diff_lower = (key - *lower_key).abs(); + let mask = diff_lower <= diff_upper; + (*lower_val * T2::from(mask as u8).expect("Failed to unwrap mask")) + + (*upper_val * T2::from(!mask as u8).expect("Failed to unwrap !mask")) + } + } + } +} + + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct CyclingFloatLookupTable +where T1: Float, + T2: Float, +{ + lookup_table: FloatLookupTable, + lower_bound: T1, + upper_bound: T1, + bound_range: T1, +} +impl CyclingFloatLookupTable +where T1: Float, + T2: Float, +{ + pub fn new(keys: Vec, values: Vec, lower_bound: T1, upper_bound: T1) -> Self { + CyclingFloatLookupTable { + lookup_table: FloatLookupTable::new(keys, values), + lower_bound: lower_bound, + upper_bound: upper_bound, + bound_range: upper_bound - lower_bound, + } + } + + pub fn lookup(&self, key: T1) -> T2 { + let key = (key % self.bound_range) + self.lower_bound; + self.lookup_table.lookup(key) + } +} + + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct EndoSinLookupTable +where + T: Float + FloatConst, +{ + lookup_table: CyclingFloatLookupTable, +} +impl EndoSinLookupTable +where + T: Float + FloatConst, +{ + pub fn new(precision: usize) -> Self { + let mut keys = Vec::with_capacity(precision); + let mut values = Vec::with_capacity(precision); + let upper_bound = T::PI(); + let step = T::FRAC_PI_2() / ::from(precision).unwrap(); + for i in 0..precision+1 { + let key = step * ::from(i).unwrap(); + let value = key.sin(); + keys.push(key); + values.push(value); + } + EndoSinLookupTable { + lookup_table: CyclingFloatLookupTable::new(keys, values, T::zero(), upper_bound), + } + } + + #[allow(dead_code)] + pub fn lookup(&self, key: T) -> T { + if key < T::zero() { + -self.lookup(-key) + } else if key < T::FRAC_PI_2() { + self.lookup_table.lookup(key) + } else if key < T::PI() { + self.lookup_table.lookup(T::PI() - key) + } else { + -self.lookup(key - T::PI()) + } + } +} + + +#[derive(Default, Debug, Serialize, Deserialize, Clone)] +pub struct EndoCosLookupTable +where + T: Float + FloatConst + Signed + Sub + Rem + NumCast + From, +{ + lookup_table: EndoSinLookupTable, +} +impl EndoCosLookupTable +where + T: Float + FloatConst + Signed + Sub + Rem + NumCast + From, +{ + pub fn new(precision: usize) -> Self { + EndoCosLookupTable { + lookup_table: EndoSinLookupTable::new(precision), + } + } + + #[allow(dead_code)] + pub fn lookup(&self, key: T) -> T { + self.lookup_table.lookup(key + T::FRAC_PI_2()) + } +} \ No newline at end of file diff --git a/src/lookup/mod.rs b/src/lookup/mod.rs new file mode 100644 index 0000000..ce14091 --- /dev/null +++ b/src/lookup/mod.rs @@ -0,0 +1,4 @@ +mod const_tables; +pub mod lookup_table; + +pub use const_tables::*; \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..f7e78a1 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,156 @@ +//tests.rs + +use num_traits::Float; + +fn calculate_percentage_error(vector1: &[T], vector2: &[T]) -> T + where T: Float + std::ops::AddAssign, +{ + let n = vector1.len(); + assert_eq!(n, vector2.len(), "Vectors must have equal lengths."); + + let mut total_error = T::zero(); + for i in 0..n { + let diff = (vector1[i] - vector2[i]).abs(); + let error = diff / if vector1[i] == T::zero() { T::min_positive_value() } else { vector1[i] }; + total_error += error; + } + + let average_error = total_error / T::from(n).unwrap(); + let percentage_error = average_error * T::from(100).expect("Cannot convert 100 to type T"); + percentage_error +} + +#[cfg(test)] +mod tests { + mod f64_error { + use crate::*; + use super::super::calculate_percentage_error; + use once_cell::sync::Lazy; + + const TOLERANCE: f64 = 2.5; + + static X: Lazy> = Lazy::new(|| { + (-10000..10000) + .map(|a| (a as f64) / 1000.) + .collect::>() + }); + + #[test] + fn pow2() -> Result<(), Box> { + let percentage_error = calculate_percentage_error( + &X.iter().map(|&x| x.fast_pow2()).collect::>(), + &X.iter().map(|&x| x.powi(2)).collect::>() + ); + assert!(!percentage_error.is_nan(), "fast_pow2 percentage error is NaN"); + assert!( + percentage_error < TOLERANCE, + "fast_pow2 percentage error: {0}", + percentage_error + ); + Ok(()) + } + + #[test] + fn exp() -> Result<(), Box> { + let percentage_error = calculate_percentage_error( + &X.iter().map(|&x| x.fast_exp()).collect::>(), + &X.iter().map(|&x| x.exp()).collect::>() + ); + assert!(!percentage_error.is_nan(), "fast_exp percentage error is NaN"); + assert!( + percentage_error < TOLERANCE, + "fast_exp percentage error: {0}", + percentage_error + ); + Ok(()) + } + + #[test] + fn cos() -> Result<(), Box> { + let percentage_error = calculate_percentage_error( + &X.iter().map(|&x| x.fast_cos()).collect::>(), + &X.iter().map(|&x| x.cos()).collect::>() + ); + assert!(!percentage_error.is_nan(), "fast_cos percentage error is NaN"); + assert!( + percentage_error < TOLERANCE, + "fast_cos percentage error: {0}", + percentage_error + ); + Ok(()) + } + + #[test] + fn sigmoid() -> Result<(), Box> { + let percentage_error = calculate_percentage_error( + &X.iter().map(|&x| x.fast_sigmoid()).collect::>(), + &X.iter().map(|&x| sigmoid_builtin_f64(x)).collect::>() + ); + assert!(!percentage_error.is_nan(), "fast_sigmoid percentage error is NaN"); + assert!( + percentage_error < TOLERANCE, + "fast_sigmoid percentage error: {0}", + percentage_error + ); + Ok(()) + } + } + + mod f32_error { + use crate::*; + use super::super::calculate_percentage_error; + use once_cell::sync::Lazy; + + const TOLERANCE: f32 = 2.5; + + static X: Lazy> = Lazy::new(|| { + (-10000..10000) + .map(|a| (a as f32) / 1000.) + .collect::>() + }); + + #[test] + fn pow2() -> Result<(), Box> { + assert!( + calculate_percentage_error( + &X.iter().map(|&x| x.fast_pow2()).collect::>(), + &X.iter().map(|&x| x.powi(2)).collect::>() + ) < TOLERANCE + ); + Ok(()) + } + + #[test] + fn exp() -> Result<(), Box> { + assert!( + calculate_percentage_error( + &X.iter().map(|&x| x.fast_exp()).collect::>(), + &X.iter().map(|&x| x.exp()).collect::>() + ) < TOLERANCE + ); + Ok(()) + } + + #[test] + fn cos() -> Result<(), Box> { + assert!( + calculate_percentage_error( + &X.iter().map(|&x| x.fast_cos()).collect::>(), + &X.iter().map(|&x| x.cos()).collect::>() + ) < TOLERANCE + ); + Ok(()) + } + + #[test] + fn sigmoid() -> Result<(), Box> { + assert!( + calculate_percentage_error( + &X.iter().map(|&x| x.fast_sigmoid()).collect::>(), + &X.iter().map(|&x| sigmoid_builtin_f32(x)).collect::>() + ) < TOLERANCE + ); + Ok(()) + } + } +} \ No newline at end of file