├── .github └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── codecov.yml ├── img ├── micromath-sq.png └── micromath.png └── src ├── f32ext.rs ├── float.rs ├── float ├── abs.rs ├── acos.rs ├── asin.rs ├── atan.rs ├── atan2.rs ├── ceil.rs ├── copysign.rs ├── cos.rs ├── cosh.rs ├── div_euclid.rs ├── exp.rs ├── floor.rs ├── fract.rs ├── hypot.rs ├── inv.rs ├── invsqrt.rs ├── ln.rs ├── log.rs ├── log10.rs ├── log2.rs ├── mul_add.rs ├── powf.rs ├── powi.rs ├── recip.rs ├── rem_euclid.rs ├── round.rs ├── signum.rs ├── sin.rs ├── sin_cos.rs ├── sqrt.rs ├── tan.rs └── trunc.rs ├── lib.rs ├── quaternion.rs ├── statistics.rs ├── statistics ├── mean.rs ├── stddev.rs ├── trim.rs └── variance.rs ├── vector.rs └── vector ├── commutative.rs ├── component.rs ├── iter.rs ├── vector2d.rs └── vector3d.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: { } 5 | push: 6 | branches: main 7 | 8 | env: 9 | CARGO_INCREMENTAL: 0 10 | RUSTFLAGS: "-Dwarnings" 11 | 12 | jobs: 13 | check: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | override: true 21 | profile: minimal 22 | - run: cargo check 23 | 24 | build: 25 | runs-on: ubuntu-latest 26 | strategy: 27 | matrix: 28 | rust: 29 | - 1.62.0 # MSRV 30 | - stable 31 | target: 32 | # - armv7a-none-eabi 33 | - thumbv7em-none-eabi 34 | - wasm32-unknown-unknown 35 | steps: 36 | - uses: actions/checkout@v2 37 | - uses: actions-rs/toolchain@v1 38 | with: 39 | toolchain: ${{ matrix.rust }} 40 | target: ${{ matrix.target }} 41 | override: true 42 | profile: minimal 43 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features 44 | - run: cargo build --target ${{ matrix.target }} --release --no-default-features --features num-traits 45 | 46 | test: 47 | runs-on: ubuntu-latest 48 | strategy: 49 | matrix: 50 | include: 51 | # 32-bit Linux 52 | - target: i686-unknown-linux-gnu 53 | rust: 1.62.0 # MSRV 54 | deps: sudo apt update && sudo apt install gcc-multilib 55 | - target: i686-unknown-linux-gnu 56 | rust: stable 57 | deps: sudo apt update && sudo apt install gcc-multilib 58 | 59 | # 64-bit Linux 60 | - target: x86_64-unknown-linux-gnu 61 | rust: 1.62.0 # MSRV 62 | - target: x86_64-unknown-linux-gnu 63 | rust: stable 64 | 65 | steps: 66 | - uses: actions/checkout@v2 67 | - uses: actions-rs/toolchain@v1 68 | with: 69 | toolchain: ${{ matrix.rust }} 70 | target: ${{ matrix.target }} 71 | override: true 72 | profile: minimal 73 | - run: ${{ matrix.deps }} 74 | - run: cargo test --target ${{ matrix.target }} --release --all-features 75 | 76 | rustfmt: 77 | runs-on: ubuntu-latest 78 | steps: 79 | - uses: actions/checkout@v2 80 | - uses: actions-rs/toolchain@v1 81 | with: 82 | toolchain: stable 83 | components: rustfmt 84 | override: true 85 | profile: minimal 86 | - uses: actions-rs/cargo@v1 87 | with: 88 | command: fmt 89 | args: --all -- --check 90 | clippy: 91 | runs-on: ubuntu-latest 92 | steps: 93 | - uses: actions/checkout@v2 94 | - uses: actions-rs/toolchain@v1 95 | with: 96 | toolchain: 1.62.0 # MSRV 97 | components: clippy 98 | override: true 99 | profile: minimal 100 | - run: cargo clippy -- -D warnings 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## 2.1.0 (2023-10-31) 8 | ### Added 9 | - Accessors to quaternion elements ([#89]) 10 | - Quaternion -> Euler conversion ([#90]) 11 | - Method for normalizing a quaternion ([#91]) 12 | - Method to get the quaternion which is the smallest rotation between two vectors ([#94]) 13 | - `recip`, `mul_add` and `signum` ([#100]) 14 | 15 | [#89]: https://github.com/tarcieri/micromath/pull/89 16 | [#90]: https://github.com/tarcieri/micromath/pull/90 17 | [#91]: https://github.com/tarcieri/micromath/pull/91 18 | [#94]: https://github.com/tarcieri/micromath/pull/94 19 | [#100]: https://github.com/tarcieri/micromath/pull/100 20 | 21 | ## 2.0.0 (2021-05-15) 22 | ### Added 23 | - `Vector` subtraction support ([#71]) 24 | - `F32` newtype ([#72], [#75]) 25 | - `num-traits` support ([#80]) 26 | - `Quaternion::dot` and `::inv` ([#81]) 27 | - `Vector3d` ops for `Quaternion` ([#82]) 28 | - `Quaternion::magnitude`, `::scale`, `::to_array`, and `::IDENTITY` ([#83]) 29 | - `Quaternion::axis_angle` ([#84]) 30 | - `Quaternion::new` ([#85]) 31 | 32 | ### Changed 33 | - Refactor `Vector` types ([#69]) 34 | - MSRV 1.47+ ([#75]) 35 | - Make `Quaternion` opaque and module private ([#70], [#85]) 36 | 37 | ### Fixed 38 | - `acos()` behavior for zero/negative ([#79]) 39 | 40 | [#69]: https://github.com/tarcieri/micromath/pull/69 41 | [#70]: https://github.com/tarcieri/micromath/pull/70 42 | [#71]: https://github.com/tarcieri/micromath/pull/71 43 | [#72]: https://github.com/tarcieri/micromath/pull/72 44 | [#75]: https://github.com/tarcieri/micromath/pull/75 45 | [#79]: https://github.com/tarcieri/micromath/pull/79 46 | [#80]: https://github.com/tarcieri/micromath/pull/80 47 | [#81]: https://github.com/tarcieri/micromath/pull/81 48 | [#82]: https://github.com/tarcieri/micromath/pull/82 49 | [#83]: https://github.com/tarcieri/micromath/pull/83 50 | [#84]: https://github.com/tarcieri/micromath/pull/84 51 | [#85]: https://github.com/tarcieri/micromath/pull/85 52 | 53 | ## 1.1.1 (2021-03-27) 54 | ### Added 55 | - `doc_cfg` ([#64]) 56 | 57 | [#64]: https://github.com/tarcieri/micromath/pull/64 58 | 59 | ## 1.1.0 (2020-09-30) 60 | ### Added 61 | - `powi` support ([#53]) 62 | 63 | ### Changed 64 | - Bump `generic-array` dependency to v0.14; MSRV 1.36+ ([#54]) 65 | 66 | [#54]: https://github.com/tarcieri/micromath/pull/54 67 | [#53]: https://github.com/tarcieri/micromath/pull/53 68 | 69 | ## 1.0.1 (2020-06-12) 70 | ### Added 71 | - Support for `powf` with negative bases ([#51]) 72 | 73 | [#51]: https://github.com/tarcieri/micromath/pull/51 74 | 75 | ## 1.0.0 (2019-12-02) 76 | - Initial 1.0 release! 🎉 (otherwise unchanged) 77 | 78 | ## 0.5.1 (2019-11-27) 79 | - Cargo.toml: Add mathematics category ([#45]) 80 | 81 | [#45]: https://github.com/tarcieri/micromath/pull/45 82 | 83 | ## 0.5.0 (2019-11-13) 84 | - Remove default cargo features ([#42]) 85 | - Add `asin`, `acos`, and `hypot` ([#39]) 86 | 87 | [#42]: https://github.com/tarcieri/micromath/pull/42 88 | [#39]: https://github.com/tarcieri/micromath/pull/39 89 | 90 | ## 0.4.1 (2019-10-08) 91 | - Implement `F32Ext::round` ([#37]) 92 | 93 | [#37]: https://github.com/tarcieri/micromath/pull/37 94 | 95 | ## 0.4.0 (2019-10-02) 96 | - Add `powf`, `exp`, `log10`, `log2`, `log`, `ln`, `trunc`, `fract`, `copysign` ([#35]) 97 | 98 | [#35]: https://github.com/tarcieri/micromath/pull/35 99 | 100 | ## 0.3.1 (2019-05-11) 101 | - Rust 1.31.0 support ([#33]) 102 | 103 | [#33]: https://github.com/tarcieri/micromath/pull/33 104 | 105 | ## 0.3.0 (2019-05-04) 106 | - statistics: Add Trim trait for statistical outlier culling iterators ([#29]) 107 | - Quaternions ([#28]) 108 | - f32ext: fast `inv()` approximation ([#27]) 109 | - Improve documentation throughout the library ([#25], [#26]) 110 | 111 | [#29]: https://github.com/tarcieri/micromath/pull/29 112 | [#28]: https://github.com/tarcieri/micromath/pull/28 113 | [#27]: https://github.com/tarcieri/micromath/pull/27 114 | [#26]: https://github.com/tarcieri/micromath/pull/26 115 | [#25]: https://github.com/tarcieri/micromath/pull/25 116 | 117 | ## 0.2.2 (2019-05-04) 118 | - Add `i32` and `u32` vectors ([#23]) 119 | 120 | [#23]: https://github.com/tarcieri/micromath/pull/23 121 | 122 | ## 0.2.1 (2019-05-03) 123 | - Add `html_logo_url` and square icon ([#20]) 124 | - `README.md`: Update links to use 'develop' branch ([#19]) 125 | 126 | [#20]: https://github.com/tarcieri/micromath/pull/20 127 | [#19]: https://github.com/tarcieri/micromath/pull/19 128 | 129 | ## 0.2.0 (2019-05-03) 130 | - `tan(x)` ([#17]) 131 | - `invsqrt(x)` ([#16]) 132 | - `cos(x)` and `sin(x)` ([#15]) 133 | - `ceil(x)` and `floor(x)` ([#14]) 134 | - Update to `generic-array` v0.13 ([#12]) 135 | 136 | [#17]: https://github.com/tarcieri/micromath/pull/17 137 | [#16]: https://github.com/tarcieri/micromath/pull/16 138 | [#15]: https://github.com/tarcieri/micromath/pull/15 139 | [#14]: https://github.com/tarcieri/micromath/pull/14 140 | [#12]: https://github.com/tarcieri/micromath/pull/12 141 | 142 | ## 0.1.0 (2019-05-03) 143 | - Initial release 144 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [bascule@gmail.com]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | [bascule@gmail.com]: mailto:bascule@gmail.com 69 | 70 | ## Attribution 71 | 72 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 73 | available at [http://contributor-covenant.org/version/1/4][version] 74 | 75 | [homepage]: http://contributor-covenant.org 76 | [version]: http://contributor-covenant.org/version/1/4/ 77 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "defmt" 19 | version = "0.3.8" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" 22 | dependencies = [ 23 | "bitflags", 24 | "defmt-macros", 25 | ] 26 | 27 | [[package]] 28 | name = "defmt-macros" 29 | version = "0.3.9" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" 32 | dependencies = [ 33 | "defmt-parser", 34 | "proc-macro-error", 35 | "proc-macro2", 36 | "quote", 37 | "syn 2.0.68", 38 | ] 39 | 40 | [[package]] 41 | name = "defmt-parser" 42 | version = "0.3.4" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" 45 | dependencies = [ 46 | "thiserror", 47 | ] 48 | 49 | [[package]] 50 | name = "micromath" 51 | version = "2.1.0" 52 | dependencies = [ 53 | "defmt", 54 | "num-traits", 55 | ] 56 | 57 | [[package]] 58 | name = "num-traits" 59 | version = "0.2.17" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" 62 | dependencies = [ 63 | "autocfg", 64 | ] 65 | 66 | [[package]] 67 | name = "proc-macro-error" 68 | version = "1.0.4" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 71 | dependencies = [ 72 | "proc-macro-error-attr", 73 | "proc-macro2", 74 | "quote", 75 | "syn 1.0.109", 76 | "version_check", 77 | ] 78 | 79 | [[package]] 80 | name = "proc-macro-error-attr" 81 | version = "1.0.4" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 84 | dependencies = [ 85 | "proc-macro2", 86 | "quote", 87 | "version_check", 88 | ] 89 | 90 | [[package]] 91 | name = "proc-macro2" 92 | version = "1.0.86" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 95 | dependencies = [ 96 | "unicode-ident", 97 | ] 98 | 99 | [[package]] 100 | name = "quote" 101 | version = "1.0.36" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 104 | dependencies = [ 105 | "proc-macro2", 106 | ] 107 | 108 | [[package]] 109 | name = "syn" 110 | version = "1.0.109" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 113 | dependencies = [ 114 | "proc-macro2", 115 | "unicode-ident", 116 | ] 117 | 118 | [[package]] 119 | name = "syn" 120 | version = "2.0.68" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "unicode-ident", 127 | ] 128 | 129 | [[package]] 130 | name = "thiserror" 131 | version = "1.0.61" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 134 | dependencies = [ 135 | "thiserror-impl", 136 | ] 137 | 138 | [[package]] 139 | name = "thiserror-impl" 140 | version = "1.0.61" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 143 | dependencies = [ 144 | "proc-macro2", 145 | "quote", 146 | "syn 2.0.68", 147 | ] 148 | 149 | [[package]] 150 | name = "unicode-ident" 151 | version = "1.0.12" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 154 | 155 | [[package]] 156 | name = "version_check" 157 | version = "0.9.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 160 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "micromath" 3 | version = "2.1.0" # Also update html_root_url in lib.rs when bumping this 4 | description = """ 5 | Embedded-friendly math library featuring fast floating point approximations 6 | (with small code size) for common arithmetic operations, trigonometry, 7 | 2D/3D vector types, statistical analysis, and quaternions. 8 | Optimizes for performance and small code size at the cost of precision. 9 | """ 10 | authors = ["Tony Arcieri "] 11 | license = "Apache-2.0 OR MIT" 12 | repository = "https://github.com/tarcieri/micromath" 13 | readme = "README.md" 14 | edition = "2018" 15 | categories = ["embedded", "mathematics", "science", "no-std"] 16 | keywords = ["math", "quaternions", "statistics", "trigonometry", "vector"] 17 | 18 | [dependencies] 19 | defmt = { version = "0.3.8", optional = true, default-features = false } 20 | num-traits = { version = "0.2", optional = true, default-features = false } 21 | 22 | [features] 23 | quaternion = [] 24 | statistics = [] 25 | vector = [] 26 | defmt = ["dep:defmt"] 27 | 28 | [package.metadata.docs.rs] 29 | all-features = true 30 | rustdoc-args = ["--cfg", "docsrs"] 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2021 Tony Arcieri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | [![Crate][crate-img]][crate-link] 4 | [![Docs][docs-img]][docs-link] 5 | [![Build Status][build-image]][build-link] 6 | [![Safety Dance][safety-image]][safety-link] 7 | ![MSRV][msrv-image] 8 | [![Apache 2.0/MIT Licensed][license-image]][license-link] 9 | 10 | Embedded-friendly (i.e. `no_std`) Rust math library featuring fast, safe 11 | floating point approximations for common arithmetic operations, trigonometry, 12 | 2D/3D vector types, statistical analysis, and quaternions. 13 | 14 | Optimizes for performance and small code size at the cost of precision. 15 | 16 | [Documentation][docs-link] 17 | 18 | ## Minimum Supported Rust Version 19 | 20 | Requires Rust **1.62** or newer. 21 | 22 | ## SemVer Policy 23 | 24 | We reserve the right to change the following in future releases with a minor 25 | version bump *only*: 26 | 27 | - MSRV 28 | - `num-traits` version (optional dependency) 29 | 30 | ## Features 31 | 32 | - [`f32` extension]: 33 | - Fast approximations: 34 | - [asin] 35 | - [acos] 36 | - [atan] 37 | - [atan2] 38 | - [cos] 39 | - [hypot] 40 | - [inv] 41 | - [invsqrt] 42 | - [ln] 43 | - [log] 44 | - [log2] 45 | - [log10] 46 | - [powf] 47 | - [exp] 48 | - [sin] 49 | - [sqrt] 50 | - [tan] 51 | - `std` polyfills: 52 | - [abs] 53 | - [ceil] 54 | - [floor] 55 | - [round] 56 | - [trunc] 57 | - [fract] 58 | - [copysign] 59 | - [signum] 60 | - [sin_cos] 61 | - [mul_add] 62 | - [recip] 63 | 64 | - [Algebraic vector types]: 65 | - 2D: 66 | - [I8x2] 67 | - [I16x2] 68 | - [I32x2] 69 | - [U8x2] 70 | - [U16x2] 71 | - [U32x2] 72 | - [F32x2] 73 | - 3D: 74 | - [I8x3] 75 | - [I16x3] 76 | - [I32x3] 77 | - [U8x3] 78 | - [U16x3] 79 | - [U32x3] 80 | - [F32x3] 81 | - [Statistical analysis]: 82 | - [mean] 83 | - [stddev] 84 | - [trim] 85 | - [variance] 86 | - [Quaternions] 87 | 88 | ## Code of Conduct 89 | 90 | We abide by the [Contributor Covenant][cc] and ask that you do as well. 91 | 92 | For more information, please see [CODE_OF_CONDUCT.md]. 93 | 94 | ## License 95 | 96 | Copyright © 2019-2024 Tony Arcieri 97 | 98 | Dual licensed under your choice of either of: 99 | 100 | - Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 101 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 102 | 103 | Incorporates portions of some tests from the [libm crate]. 104 | Copyright © 2018 Jorge Aparicio and also dual licensed under the 105 | Apache 2.0 and MIT licenses. 106 | 107 | [//]: # (badges) 108 | 109 | [crate-img]: https://img.shields.io/crates/v/micromath.svg?logo=rust 110 | 111 | [crate-link]: https://crates.io/crates/micromath 112 | 113 | [docs-img]: https://docs.rs/micromath/badge.svg 114 | 115 | [docs-link]: https://docs.rs/micromath/ 116 | 117 | [build-image]: https://github.com/tarcieri/micromath/workflows/CI/badge.svg 118 | 119 | [build-link]: https://github.com/tarcieri/micromath/actions 120 | 121 | [safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg 122 | 123 | [safety-link]: https://github.com/rust-secure-code/safety-dance/ 124 | 125 | [msrv-image]: https://img.shields.io/badge/rustc-1.62+-blue.svg 126 | 127 | [license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg 128 | 129 | [license-link]: https://github.com/tarcieri/micromath/blob/main/LICENSE-APACHE 130 | 131 | [//]: # (general links) 132 | 133 | [`f32` extension]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html 134 | 135 | [asin]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.asin 136 | 137 | [acos]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.acos 138 | 139 | [atan]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.atan 140 | 141 | [atan2]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.atan2 142 | 143 | [cos]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.cos 144 | 145 | [hypot]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.hypot 146 | 147 | [inv]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.inv 148 | 149 | [invsqrt]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.invsqrt 150 | 151 | [ln]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.ln 152 | 153 | [log]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log 154 | 155 | [log2]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log2 156 | 157 | [log10]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log10 158 | 159 | [powf]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.powf 160 | 161 | [powi]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.powi 162 | 163 | [exp]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.exp 164 | 165 | [sin]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.sin 166 | 167 | [sqrt]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.sqrt 168 | 169 | [tan]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.tan 170 | 171 | [abs]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.abs 172 | 173 | [ceil]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.ceil 174 | 175 | [floor]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.floor 176 | 177 | [round]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.round 178 | 179 | [trunc]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.trunc 180 | 181 | [fract]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.fract 182 | 183 | [copysign]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.copysign 184 | 185 | [signum]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.signum 186 | 187 | [sin_cos]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.sin_cos 188 | 189 | [mul_add]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.mul_add 190 | 191 | [recip]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.recip 192 | 193 | [Algebraic vector types]: https://docs.rs/micromath/latest/micromath/vector/index.html 194 | 195 | [I8x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I8x2.html 196 | 197 | [I16x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I16x2.html 198 | 199 | [I32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I32x2.html 200 | 201 | [U8x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U8x2.html 202 | 203 | [U16x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U16x2.html 204 | 205 | [U32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U32x2.html 206 | 207 | [F32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.F32x2.html 208 | 209 | [I8x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I8x3.html 210 | 211 | [I16x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I16x3.html 212 | 213 | [I32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I32x3.html 214 | 215 | [U8x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U8x3.html 216 | 217 | [U16x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U16x3.html 218 | 219 | [U32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U32x3.html 220 | 221 | [F32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.F32x3.html 222 | 223 | [Statistical analysis]: https://docs.rs/micromath/latest/micromath/statistics/index.html 224 | 225 | [mean]: https://docs.rs/micromath/latest/micromath/statistics/trait.Mean.html 226 | 227 | [stddev]: https://docs.rs/micromath/latest/micromath/statistics/trait.StdDev.html 228 | 229 | [trim]: https://docs.rs/micromath/latest/micromath/statistics/trim/trait.Trim.html 230 | 231 | [variance]: https://docs.rs/micromath/latest/micromath/statistics/trait.Variance.html 232 | 233 | [Quaternions]: https://docs.rs/micromath/latest/micromath/quaternion/struct.Quaternion.html 234 | 235 | [libm crate]: https://github.com/rust-lang-nursery/libm 236 | 237 | [vek crate]: https://github.com/yoanlcq/vek 238 | 239 | [approx crate]: https://crates.io/crates/approx 240 | 241 | [cc]: https://contributor-covenant.org 242 | 243 | [CODE_OF_CONDUCT.md]: https://github.com/tarcieri/micromath/blob/main/CODE_OF_CONDUCT.md 244 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: off 4 | patch: off 5 | -------------------------------------------------------------------------------- /img/micromath-sq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarcieri/micromath/7139d270fa77aa5c66468cf9f154ed8ee1a207b4/img/micromath-sq.png -------------------------------------------------------------------------------- /img/micromath.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarcieri/micromath/7139d270fa77aa5c66468cf9f154ed8ee1a207b4/img/micromath.png -------------------------------------------------------------------------------- /src/f32ext.rs: -------------------------------------------------------------------------------- 1 | //! `f32` extension 2 | 3 | use crate::float::F32; 4 | 5 | /// `f32` extension providing various arithmetic approximations and polyfills 6 | /// for `std` functionality. 7 | pub trait F32Ext: Sized { 8 | /// Compute absolute value with a constant-time, data-independent 9 | /// implementation. 10 | fn abs(self) -> f32; 11 | 12 | /// Approximates `acos(x)` in radians in the range `[0, pi]` 13 | fn acos(self) -> f32; 14 | 15 | /// Approximates `asin(x)` in radians in the range `[-pi/2, pi/2]`. 16 | fn asin(self) -> f32; 17 | 18 | /// Approximates `atan(x)` in radians with a maximum error of `0.002`. 19 | fn atan(self) -> f32; 20 | 21 | /// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum 22 | /// error of `0.1620` degrees. 23 | fn atan_norm(self) -> f32; 24 | 25 | /// Approximates the four quadrant arctangent `atan2(x)` in radians, with 26 | /// a maximum error of `0.002`. 27 | fn atan2(self, other: f32) -> f32; 28 | 29 | /// Approximates the four quadrant arctangent. 30 | /// Normalized to the `[0,4)` range with a maximum error of `0.1620` degrees. 31 | fn atan2_norm(self, other: f32) -> f32; 32 | 33 | /// Approximates floating point ceiling. 34 | fn ceil(self) -> f32; 35 | 36 | /// Copies the sign from one number to another and returns it. 37 | fn copysign(self, sign: f32) -> f32; 38 | 39 | /// Approximates cosine in radians with a maximum error of `0.002`. 40 | fn cos(self) -> f32; 41 | 42 | /// Calculates Euclidean division, the matching method for `rem_euclid`. 43 | fn div_euclid(self, other: f32) -> f32; 44 | 45 | /// Approximates `e^x`. 46 | fn exp(self) -> f32; 47 | 48 | /// Approximates floating point floor. 49 | fn floor(self) -> f32; 50 | 51 | /// Retrieve the fractional part of floating point with sign. 52 | fn fract(self) -> f32; 53 | 54 | /// Approximates the length of the hypotenuse of a right-angle triangle given 55 | /// legs of length `x` and `y`. 56 | fn hypot(self, other: f32) -> f32; 57 | 58 | /// Approximates `1/x` with an average deviation of ~8%. 59 | fn inv(self) -> f32; 60 | 61 | /// Approximates inverse square root with an average deviation of ~5%. 62 | fn invsqrt(self) -> f32; 63 | 64 | /// Approximates `ln(x)`. 65 | fn ln(self) -> f32; 66 | 67 | /// Approximates `log` with an arbitrary base. 68 | fn log(self, base: f32) -> f32; 69 | 70 | /// Approximates `log2`. 71 | fn log2(self) -> f32; 72 | 73 | /// Approximates `log10`. 74 | fn log10(self) -> f32; 75 | 76 | /// Computes `(self * a) + b`. 77 | fn mul_add(self, a: f32, b: f32) -> f32; 78 | 79 | /// Approximates `self^n`. 80 | fn powf(self, n: f32) -> f32; 81 | 82 | /// Approximates `self^n` where n is an `i32` 83 | fn powi(self, n: i32) -> f32; 84 | 85 | /// Returns the reciprocal (inverse) of a number, `1/x`. 86 | fn recip(self) -> f32; 87 | 88 | /// Calculates the least nonnegative remainder of `self (mod other)`. 89 | fn rem_euclid(self, other: f32) -> f32; 90 | 91 | /// Round the number part of floating point with sign. 92 | fn round(self) -> f32; 93 | 94 | /// Returns a number that represents the sign of `self`. 95 | fn signum(self) -> f32; 96 | 97 | /// Approximates sine in radians with a maximum error of `0.002`. 98 | fn sin(self) -> f32; 99 | 100 | /// Simultaneously computes the sine and cosine of the number, `x`. 101 | /// Returns `(sin(x), cos(x))`. 102 | fn sin_cos(self) -> (f32, f32); 103 | 104 | /// Approximates square root with an average deviation of ~5%. 105 | fn sqrt(self) -> f32; 106 | 107 | /// Approximates `tan(x)` in radians with a maximum error of `0.6`. 108 | fn tan(self) -> f32; 109 | 110 | /// Retrieve whole number part of floating point with sign. 111 | fn trunc(self) -> f32; 112 | } 113 | 114 | impl F32Ext for f32 { 115 | #[inline] 116 | fn abs(self) -> f32 { 117 | F32(self).abs().0 118 | } 119 | 120 | #[inline] 121 | fn acos(self) -> f32 { 122 | F32(self).acos().0 123 | } 124 | 125 | #[inline] 126 | fn asin(self) -> f32 { 127 | F32(self).asin().0 128 | } 129 | 130 | #[inline] 131 | fn atan(self) -> f32 { 132 | F32(self).atan().0 133 | } 134 | 135 | #[inline] 136 | fn atan_norm(self) -> f32 { 137 | F32(self).atan_norm().0 138 | } 139 | 140 | #[inline] 141 | fn atan2(self, other: f32) -> f32 { 142 | F32(self).atan2(F32(other)).0 143 | } 144 | 145 | #[inline] 146 | fn atan2_norm(self, other: f32) -> f32 { 147 | F32(self).atan2_norm(F32(other)).0 148 | } 149 | 150 | #[inline] 151 | fn ceil(self) -> f32 { 152 | F32(self).ceil().0 153 | } 154 | 155 | #[inline] 156 | fn copysign(self, sign: f32) -> f32 { 157 | F32(self).copysign(F32(sign)).0 158 | } 159 | 160 | #[inline] 161 | fn cos(self) -> f32 { 162 | F32(self).cos().0 163 | } 164 | 165 | #[inline] 166 | fn div_euclid(self, other: f32) -> f32 { 167 | F32(self).div_euclid(F32(other)).0 168 | } 169 | 170 | #[inline] 171 | fn exp(self) -> f32 { 172 | F32(self).exp().0 173 | } 174 | 175 | #[inline] 176 | fn floor(self) -> f32 { 177 | F32(self).floor().0 178 | } 179 | 180 | #[inline] 181 | fn fract(self) -> f32 { 182 | F32(self).fract().0 183 | } 184 | 185 | #[inline] 186 | fn hypot(self, other: f32) -> f32 { 187 | F32(self).hypot(other.into()).0 188 | } 189 | 190 | #[inline] 191 | fn inv(self) -> f32 { 192 | F32(self).inv().0 193 | } 194 | 195 | #[inline] 196 | fn invsqrt(self) -> f32 { 197 | F32(self).invsqrt().0 198 | } 199 | 200 | #[inline] 201 | fn ln(self) -> f32 { 202 | F32(self).ln().0 203 | } 204 | 205 | #[inline] 206 | fn log(self, base: f32) -> f32 { 207 | F32(self).log(F32(base)).0 208 | } 209 | 210 | #[inline] 211 | fn log2(self) -> f32 { 212 | F32(self).log2().0 213 | } 214 | 215 | #[inline] 216 | fn log10(self) -> f32 { 217 | F32(self).log10().0 218 | } 219 | 220 | #[inline] 221 | fn mul_add(self, a: f32, b: f32) -> f32 { 222 | F32(self).mul_add(F32(a), F32(b)).0 223 | } 224 | 225 | #[inline] 226 | fn powf(self, n: f32) -> f32 { 227 | F32(self).powf(F32(n)).0 228 | } 229 | 230 | #[inline] 231 | fn powi(self, n: i32) -> f32 { 232 | F32(self).powi(n).0 233 | } 234 | 235 | #[inline] 236 | fn recip(self) -> f32 { 237 | F32(self).recip().0 238 | } 239 | 240 | #[inline] 241 | fn rem_euclid(self, other: f32) -> f32 { 242 | F32(self).rem_euclid(F32(other)).0 243 | } 244 | 245 | #[inline] 246 | fn round(self) -> f32 { 247 | F32(self).round().0 248 | } 249 | 250 | #[inline] 251 | fn signum(self) -> f32 { 252 | F32(self).signum().0 253 | } 254 | 255 | #[inline] 256 | fn sin(self) -> f32 { 257 | F32(self).sin().0 258 | } 259 | 260 | #[inline] 261 | fn sin_cos(self) -> (f32, f32) { 262 | (F32(self).sin().0, F32(self).cos().0) 263 | } 264 | 265 | #[inline] 266 | fn sqrt(self) -> f32 { 267 | F32(self).sqrt().0 268 | } 269 | 270 | #[inline] 271 | fn tan(self) -> f32 { 272 | F32(self).tan().0 273 | } 274 | 275 | #[inline] 276 | fn trunc(self) -> f32 { 277 | F32(self).trunc().0 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/float.rs: -------------------------------------------------------------------------------- 1 | //! Floating point operations 2 | 3 | pub(crate) mod abs; 4 | pub(crate) mod acos; 5 | pub(crate) mod asin; 6 | pub(crate) mod atan; 7 | pub(crate) mod atan2; 8 | pub(crate) mod ceil; 9 | pub(crate) mod copysign; 10 | pub(crate) mod cos; 11 | pub(crate) mod cosh; 12 | pub(crate) mod div_euclid; 13 | pub(crate) mod exp; 14 | pub(crate) mod floor; 15 | pub(crate) mod fract; 16 | pub(crate) mod hypot; 17 | pub(crate) mod inv; 18 | pub(crate) mod invsqrt; 19 | pub(crate) mod ln; 20 | pub(crate) mod log; 21 | pub(crate) mod log10; 22 | pub(crate) mod log2; 23 | pub(crate) mod mul_add; 24 | pub(crate) mod powf; 25 | pub(crate) mod powi; 26 | pub(crate) mod recip; 27 | pub(crate) mod rem_euclid; 28 | pub(crate) mod round; 29 | pub(crate) mod signum; 30 | pub(crate) mod sin; 31 | pub(crate) mod sin_cos; 32 | pub(crate) mod sqrt; 33 | pub(crate) mod tan; 34 | pub(crate) mod trunc; 35 | 36 | use core::{ 37 | cmp::Ordering, 38 | fmt::{self, Display, LowerExp, UpperExp}, 39 | iter::{Product, Sum}, 40 | num::ParseFloatError, 41 | ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign}, 42 | str::FromStr, 43 | }; 44 | 45 | #[cfg(feature = "num-traits")] 46 | use num_traits::{Inv, Num, One, Zero}; 47 | 48 | /// Sign mask. 49 | pub(crate) const SIGN_MASK: u32 = 0b1000_0000_0000_0000_0000_0000_0000_0000; 50 | 51 | /// Exponent mask. 52 | pub(crate) const EXPONENT_MASK: u32 = 0b0111_1111_1000_0000_0000_0000_0000_0000; 53 | 54 | /// Mantissa mask. 55 | pub(crate) const MANTISSA_MASK: u32 = 0b0000_0000_0111_1111_1111_1111_1111_1111; 56 | 57 | /// Exponent mask. 58 | pub(crate) const EXPONENT_BIAS: u32 = 127; 59 | 60 | /// Mantissa bits. 61 | /// 62 | /// Note: `MANTISSA_DIGITS` is available in `core::f32`, but the actual bits taken up are 24 - 1. 63 | pub(crate) const MANTISSA_BITS: u32 = 23; 64 | 65 | /// 32-bit floating point wrapper which implements fast approximation-based 66 | /// operations. 67 | #[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)] 68 | pub struct F32(pub f32); 69 | 70 | impl F32 { 71 | /// The value `0.0`. 72 | pub const ZERO: Self = Self(0.0); 73 | 74 | /// The value `1.0`. 75 | pub const ONE: Self = Self(1.0); 76 | 77 | /// The radix or base of the internal representation of `f32`. 78 | pub const RADIX: u32 = f32::RADIX; 79 | 80 | /// Number of significant digits in base 2. 81 | pub const MANTISSA_DIGITS: u32 = f32::MANTISSA_DIGITS; 82 | 83 | /// Approximate number of significant digits in base 10. 84 | pub const DIGITS: u32 = f32::DIGITS; 85 | 86 | /// [Machine epsilon] value for `f32`. 87 | /// 88 | /// This is the difference between `1.0` and the next larger representable number. 89 | /// 90 | /// [Machine epsilon]: https://en.wikipedia.org/wiki/Machine_epsilon 91 | pub const EPSILON: Self = Self(f32::EPSILON); 92 | 93 | /// Smallest finite `f32` value. 94 | pub const MIN: Self = Self(f32::MIN); 95 | 96 | /// Smallest positive normal `f32` value. 97 | pub const MIN_POSITIVE: Self = Self(f32::MIN_POSITIVE); 98 | 99 | /// Largest finite `f32` value. 100 | pub const MAX: Self = Self(f32::MAX); 101 | 102 | /// One greater than the minimum possible normal power of 2 exponent. 103 | pub const MIN_EXP: i32 = f32::MIN_EXP; 104 | 105 | /// Maximum possible power of 2 exponent. 106 | pub const MAX_EXP: i32 = f32::MAX_EXP; 107 | 108 | /// Minimum possible normal power of 10 exponent. 109 | pub const MIN_10_EXP: i32 = f32::MIN_10_EXP; 110 | 111 | /// Maximum possible power of 10 exponent. 112 | pub const MAX_10_EXP: i32 = f32::MAX_10_EXP; 113 | 114 | /// Not a Number (NaN). 115 | pub const NAN: Self = Self(f32::NAN); 116 | 117 | /// Infinity (∞). 118 | pub const INFINITY: Self = Self(f32::INFINITY); 119 | 120 | /// Negative infinity (−∞). 121 | pub const NEG_INFINITY: Self = Self(f32::NEG_INFINITY); 122 | 123 | /// Returns `true` if this value is `NaN`. 124 | #[inline] 125 | pub fn is_nan(self) -> bool { 126 | self.0.is_nan() 127 | } 128 | 129 | /// Returns `true` if this value is positive infinity or negative infinity, and 130 | /// `false` otherwise. 131 | #[inline] 132 | pub fn is_infinite(self) -> bool { 133 | self.0.is_infinite() 134 | } 135 | 136 | /// Returns `true` if this number is neither infinite nor `NaN`. 137 | #[inline] 138 | pub fn is_finite(self) -> bool { 139 | self.0.is_finite() 140 | } 141 | 142 | /// Returns `true` if `self` has a positive sign, including `+0.0`, `NaN`s with 143 | /// positive sign bit and positive infinity. 144 | #[inline] 145 | pub fn is_sign_positive(self) -> bool { 146 | self.0.is_sign_positive() 147 | } 148 | 149 | /// Returns `true` if `self` has a negative sign, including `-0.0`, `NaN`s with 150 | /// negative sign bit and negative infinity. 151 | #[inline] 152 | pub fn is_sign_negative(self) -> bool { 153 | self.0.is_sign_negative() 154 | } 155 | 156 | /// Raw transmutation to `u32`. 157 | /// 158 | /// This is currently identical to `transmute::(self)` on all platforms. 159 | /// 160 | /// See [`F32::from_bits`] for some discussion of the portability of this operation 161 | /// (there are almost no issues). 162 | #[inline] 163 | pub fn to_bits(self) -> u32 { 164 | self.0.to_bits() 165 | } 166 | 167 | /// Raw transmutation from `u32`. 168 | /// 169 | /// This is currently identical to `transmute::(v)` on all platforms. 170 | /// It turns out this is incredibly portable, for two reasons: 171 | /// 172 | /// - Floats and Ints have the same endianness on all supported platforms. 173 | /// - IEEE-754 very precisely specifies the bit layout of floats. 174 | /// 175 | /// See [`f32::from_bits`] for more information. 176 | #[inline] 177 | pub fn from_bits(v: u32) -> Self { 178 | Self(f32::from_bits(v)) 179 | } 180 | 181 | /// Extract exponent bits. 182 | pub(crate) fn extract_exponent_bits(self) -> u32 { 183 | (self.to_bits() & EXPONENT_MASK) 184 | .overflowing_shr(MANTISSA_BITS) 185 | .0 186 | } 187 | 188 | /// Extract the exponent of a float's value. 189 | pub(crate) fn extract_exponent_value(self) -> i32 { 190 | (self.extract_exponent_bits() as i32) - EXPONENT_BIAS as i32 191 | } 192 | 193 | /// Remove sign. 194 | pub(crate) fn without_sign(self) -> Self { 195 | Self::from_bits(self.to_bits() & !SIGN_MASK) 196 | } 197 | 198 | /// Set the exponent to the given value. 199 | pub(crate) fn set_exponent(self, exponent: i32) -> Self { 200 | debug_assert!(exponent <= 127 && exponent >= -128); 201 | let without_exponent: u32 = self.to_bits() & !EXPONENT_MASK; 202 | let only_exponent: u32 = ((exponent + EXPONENT_BIAS as i32) as u32) 203 | .overflowing_shl(MANTISSA_BITS) 204 | .0; 205 | 206 | Self::from_bits(without_exponent | only_exponent) 207 | } 208 | 209 | /// Is this floating point value equivalent to an integer? 210 | pub(crate) fn is_integer(&self) -> bool { 211 | let exponent = self.extract_exponent_value(); 212 | let self_bits = self.to_bits(); 213 | 214 | // if exponent is negative we shouldn't remove anything, this stops an opposite shift. 215 | let exponent_clamped = i32::max(exponent, 0) as u32; 216 | 217 | // find the part of the fraction that would be left over 218 | let fractional_part = (self_bits).overflowing_shl(exponent_clamped).0 & MANTISSA_MASK; 219 | 220 | // if fractional part contains anything, we know it *isn't* an integer. 221 | // if zero there will be nothing in the fractional part 222 | // if it is whole, there will be nothing in the fractional part 223 | fractional_part == 0 224 | } 225 | 226 | /// Is this floating point value even? 227 | fn is_even(&self) -> bool { 228 | // any floating point value that doesn't fit in an i32 range is even, 229 | // and will lose 1's digit precision at exp values of 23+ 230 | if self.extract_exponent_value() >= 31 { 231 | true 232 | } else { 233 | (self.0 as i32) % 2 == 0 234 | } 235 | } 236 | } 237 | 238 | impl Add for F32 { 239 | type Output = F32; 240 | 241 | #[inline] 242 | fn add(self, rhs: F32) -> F32 { 243 | F32(self.0 + rhs.0) 244 | } 245 | } 246 | 247 | impl Add for F32 { 248 | type Output = F32; 249 | 250 | #[inline] 251 | fn add(self, rhs: f32) -> F32 { 252 | F32(self.0 + rhs) 253 | } 254 | } 255 | 256 | impl Add for f32 { 257 | type Output = F32; 258 | 259 | #[inline] 260 | fn add(self, rhs: F32) -> F32 { 261 | F32(self + rhs.0) 262 | } 263 | } 264 | 265 | impl AddAssign for F32 { 266 | #[inline] 267 | fn add_assign(&mut self, rhs: F32) { 268 | self.0 += rhs.0; 269 | } 270 | } 271 | 272 | impl AddAssign for F32 { 273 | #[inline] 274 | fn add_assign(&mut self, rhs: f32) { 275 | self.0 += rhs; 276 | } 277 | } 278 | 279 | impl AddAssign for f32 { 280 | #[inline] 281 | fn add_assign(&mut self, rhs: F32) { 282 | *self += rhs.0; 283 | } 284 | } 285 | 286 | impl Display for F32 { 287 | #[inline] 288 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { 289 | write!(fmt, "{}", self.0) 290 | } 291 | } 292 | 293 | impl Div for F32 { 294 | type Output = F32; 295 | 296 | #[inline] 297 | fn div(self, rhs: F32) -> F32 { 298 | F32(self.0 / rhs.0) 299 | } 300 | } 301 | 302 | impl Div for F32 { 303 | type Output = F32; 304 | 305 | #[inline] 306 | fn div(self, rhs: f32) -> F32 { 307 | F32(self.0 / rhs) 308 | } 309 | } 310 | 311 | impl Div for f32 { 312 | type Output = F32; 313 | 314 | #[inline] 315 | fn div(self, rhs: F32) -> F32 { 316 | F32(self / rhs.0) 317 | } 318 | } 319 | 320 | impl DivAssign for F32 { 321 | #[inline] 322 | fn div_assign(&mut self, rhs: F32) { 323 | self.0 /= rhs.0; 324 | } 325 | } 326 | 327 | impl DivAssign for F32 { 328 | #[inline] 329 | fn div_assign(&mut self, rhs: f32) { 330 | self.0 /= rhs; 331 | } 332 | } 333 | 334 | impl DivAssign for f32 { 335 | #[inline] 336 | fn div_assign(&mut self, rhs: F32) { 337 | *self /= rhs.0; 338 | } 339 | } 340 | 341 | impl From for F32 { 342 | #[inline] 343 | fn from(n: f32) -> F32 { 344 | F32(n) 345 | } 346 | } 347 | 348 | impl From for f32 { 349 | #[inline] 350 | fn from(n: F32) -> f32 { 351 | n.0 352 | } 353 | } 354 | 355 | impl From for F32 { 356 | #[inline] 357 | fn from(n: i8) -> F32 { 358 | F32(n.into()) 359 | } 360 | } 361 | 362 | impl From for F32 { 363 | #[inline] 364 | fn from(n: i16) -> F32 { 365 | F32(n.into()) 366 | } 367 | } 368 | 369 | impl From for F32 { 370 | #[inline] 371 | fn from(n: u8) -> F32 { 372 | F32(n.into()) 373 | } 374 | } 375 | 376 | impl From for F32 { 377 | #[inline] 378 | fn from(n: u16) -> F32 { 379 | F32(n.into()) 380 | } 381 | } 382 | 383 | impl FromStr for F32 { 384 | type Err = ParseFloatError; 385 | 386 | #[inline] 387 | fn from_str(src: &str) -> Result { 388 | f32::from_str(src).map(F32) 389 | } 390 | } 391 | 392 | impl LowerExp for F32 { 393 | #[inline] 394 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 395 | write!(f, "{:e}", self.0) 396 | } 397 | } 398 | 399 | impl Mul for F32 { 400 | type Output = F32; 401 | 402 | #[inline] 403 | fn mul(self, rhs: F32) -> F32 { 404 | F32(self.0 * rhs.0) 405 | } 406 | } 407 | 408 | impl Mul for F32 { 409 | type Output = F32; 410 | 411 | #[inline] 412 | fn mul(self, rhs: f32) -> F32 { 413 | F32(self.0 * rhs) 414 | } 415 | } 416 | 417 | impl Mul for f32 { 418 | type Output = F32; 419 | 420 | #[inline] 421 | fn mul(self, rhs: F32) -> F32 { 422 | F32(self * rhs.0) 423 | } 424 | } 425 | 426 | impl MulAssign for F32 { 427 | #[inline] 428 | fn mul_assign(&mut self, rhs: F32) { 429 | self.0 *= rhs.0; 430 | } 431 | } 432 | 433 | impl MulAssign for F32 { 434 | #[inline] 435 | fn mul_assign(&mut self, rhs: f32) { 436 | self.0 *= rhs; 437 | } 438 | } 439 | 440 | impl MulAssign for f32 { 441 | #[inline] 442 | fn mul_assign(&mut self, rhs: F32) { 443 | *self *= rhs.0; 444 | } 445 | } 446 | 447 | impl Neg for F32 { 448 | type Output = F32; 449 | 450 | #[inline] 451 | fn neg(self) -> F32 { 452 | F32(-self.0) 453 | } 454 | } 455 | 456 | impl PartialEq for F32 { 457 | fn eq(&self, other: &f32) -> bool { 458 | self.0.eq(other) 459 | } 460 | } 461 | 462 | impl PartialEq for f32 { 463 | fn eq(&self, other: &F32) -> bool { 464 | self.eq(&other.0) 465 | } 466 | } 467 | 468 | impl PartialOrd for F32 { 469 | fn partial_cmp(&self, other: &f32) -> Option { 470 | self.0.partial_cmp(other) 471 | } 472 | } 473 | 474 | impl PartialOrd for f32 { 475 | fn partial_cmp(&self, other: &F32) -> Option { 476 | self.partial_cmp(&other.0) 477 | } 478 | } 479 | 480 | impl Product for F32 { 481 | #[inline] 482 | fn product(iter: I) -> Self 483 | where 484 | I: Iterator, 485 | { 486 | F32(f32::product(iter.map(f32::from))) 487 | } 488 | } 489 | 490 | impl Rem for F32 { 491 | type Output = F32; 492 | 493 | #[inline] 494 | fn rem(self, rhs: F32) -> F32 { 495 | F32(self.0 % rhs.0) 496 | } 497 | } 498 | 499 | impl Rem for F32 { 500 | type Output = F32; 501 | 502 | #[inline] 503 | fn rem(self, rhs: f32) -> F32 { 504 | F32(self.0 % rhs) 505 | } 506 | } 507 | 508 | impl Rem for f32 { 509 | type Output = F32; 510 | 511 | #[inline] 512 | fn rem(self, rhs: F32) -> F32 { 513 | F32(self % rhs.0) 514 | } 515 | } 516 | 517 | impl RemAssign for F32 { 518 | #[inline] 519 | fn rem_assign(&mut self, rhs: F32) { 520 | self.0 %= rhs.0; 521 | } 522 | } 523 | 524 | impl RemAssign for F32 { 525 | #[inline] 526 | fn rem_assign(&mut self, rhs: f32) { 527 | self.0 %= rhs; 528 | } 529 | } 530 | 531 | impl Sub for F32 { 532 | type Output = F32; 533 | 534 | #[inline] 535 | fn sub(self, rhs: F32) -> F32 { 536 | F32(self.0 - rhs.0) 537 | } 538 | } 539 | 540 | impl Sub for F32 { 541 | type Output = F32; 542 | 543 | #[inline] 544 | fn sub(self, rhs: f32) -> F32 { 545 | F32(self.0 - rhs) 546 | } 547 | } 548 | 549 | impl Sub for f32 { 550 | type Output = F32; 551 | 552 | #[inline] 553 | fn sub(self, rhs: F32) -> F32 { 554 | F32(self - rhs.0) 555 | } 556 | } 557 | 558 | impl SubAssign for F32 { 559 | #[inline] 560 | fn sub_assign(&mut self, rhs: F32) { 561 | self.0 -= rhs.0; 562 | } 563 | } 564 | 565 | impl SubAssign for F32 { 566 | #[inline] 567 | fn sub_assign(&mut self, rhs: f32) { 568 | self.0 -= rhs; 569 | } 570 | } 571 | 572 | impl SubAssign for f32 { 573 | #[inline] 574 | fn sub_assign(&mut self, rhs: F32) { 575 | *self -= rhs.0; 576 | } 577 | } 578 | 579 | impl Sum for F32 { 580 | #[inline] 581 | fn sum(iter: I) -> Self 582 | where 583 | I: Iterator, 584 | { 585 | F32(f32::sum(iter.map(f32::from))) 586 | } 587 | } 588 | 589 | impl UpperExp for F32 { 590 | #[inline] 591 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 592 | write!(f, "{:E}", self.0) 593 | } 594 | } 595 | 596 | #[cfg(feature = "num-traits")] 597 | #[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] 598 | impl Zero for F32 { 599 | fn zero() -> Self { 600 | Self::ZERO 601 | } 602 | 603 | fn is_zero(&self) -> bool { 604 | Self::ZERO == *self 605 | } 606 | } 607 | 608 | #[cfg(feature = "num-traits")] 609 | #[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] 610 | impl One for F32 { 611 | fn one() -> Self { 612 | Self::ONE 613 | } 614 | 615 | fn is_one(&self) -> bool { 616 | Self::ONE == *self 617 | } 618 | } 619 | 620 | #[cfg(feature = "num-traits")] 621 | #[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] 622 | impl Num for F32 { 623 | type FromStrRadixErr = num_traits::ParseFloatError; 624 | 625 | fn from_str_radix(str: &str, radix: u32) -> Result { 626 | f32::from_str_radix(str, radix).map(Self) 627 | } 628 | } 629 | 630 | #[cfg(feature = "num-traits")] 631 | #[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] 632 | impl Inv for F32 { 633 | type Output = Self; 634 | 635 | fn inv(self) -> Self { 636 | self.inv() 637 | } 638 | } 639 | 640 | #[cfg(feature = "defmt")] 641 | #[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] 642 | impl defmt::Format for F32 { 643 | fn format(&self, fmt: defmt::Formatter<'_>) { 644 | defmt::write!(fmt, "{}", self.0) 645 | } 646 | } 647 | 648 | #[cfg(test)] 649 | mod tests { 650 | #[allow(unused_imports)] // remove when we have more tests 651 | use super::F32; 652 | 653 | #[cfg(feature = "num-traits")] 654 | #[test] 655 | fn inv_trait() { 656 | assert_eq!(num_traits::Inv::inv(F32(2.0)), F32(0.5)); 657 | } 658 | } 659 | -------------------------------------------------------------------------------- /src/float/abs.rs: -------------------------------------------------------------------------------- 1 | //! Compute the absolute value of a single-precision float. 2 | //! 3 | //! Method described at: 4 | 5 | use super::{F32, SIGN_MASK}; 6 | 7 | impl F32 { 8 | /// Computes the absolute value of `self`. 9 | /// 10 | /// Returns [`Self::NAN`] if the number is [`Self::NAN`]. 11 | pub fn abs(self) -> Self { 12 | Self::from_bits(self.to_bits() & !SIGN_MASK) 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::F32; 19 | 20 | #[test] 21 | fn sanity_check() { 22 | assert_eq!(F32::ONE.abs(), 1.0); 23 | assert_eq!(F32::ZERO.abs(), 0.0); 24 | assert_eq!(F32(-1.0).abs(), 1.0); 25 | } 26 | 27 | #[test] 28 | fn nan() { 29 | assert!(F32::NAN.abs().is_nan()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/float/acos.rs: -------------------------------------------------------------------------------- 1 | //! arccos approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | //! 5 | 6 | use super::F32; 7 | use core::f32::consts::PI; 8 | 9 | impl F32 { 10 | /// Computes `acos(x)` approximation in radians in the range `[0, pi]`. 11 | pub(crate) fn acos(self) -> Self { 12 | if self > 0.0 { 13 | ((Self::ONE - self * self).sqrt() / self).atan() 14 | } else if self == 0.0 { 15 | Self(PI / 2.0) 16 | } else { 17 | ((Self::ONE - self * self).sqrt() / self).atan() + PI 18 | } 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod tests { 24 | use super::F32; 25 | use core::f32::consts; 26 | 27 | const MAX_ERROR: f32 = 0.03; 28 | 29 | #[test] 30 | fn sanity_check() { 31 | // Arccosine test vectors - `(input, output)` 32 | let test_vectors: &[(f32, f32)] = &[ 33 | (2.000, f32::NAN), 34 | (1.000, 0.0), 35 | (0.866, consts::FRAC_PI_6), 36 | (0.707, consts::FRAC_PI_4), 37 | (0.500, consts::FRAC_PI_3), 38 | (f32::EPSILON, consts::FRAC_PI_2), 39 | (0.000, consts::FRAC_PI_2), 40 | (-f32::EPSILON, consts::FRAC_PI_2), 41 | (-0.500, 2.0 * consts::FRAC_PI_3), 42 | (-0.707, 3.0 * consts::FRAC_PI_4), 43 | (-0.866, 5.0 * consts::FRAC_PI_6), 44 | (-1.000, consts::PI), 45 | (-2.000, f32::NAN), 46 | ]; 47 | 48 | for &(x, expected) in test_vectors { 49 | let actual = F32(x).acos(); 50 | if expected.is_nan() { 51 | assert!( 52 | actual.is_nan(), 53 | "acos({}) returned {}, should be NAN", 54 | x, 55 | actual 56 | ); 57 | } else { 58 | let delta = (actual - expected).abs(); 59 | 60 | assert!( 61 | delta <= MAX_ERROR, 62 | "delta {} too large: {} vs {}", 63 | delta, 64 | actual, 65 | expected 66 | ); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/float/asin.rs: -------------------------------------------------------------------------------- 1 | //! arcsin approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | //! 5 | 6 | use super::F32; 7 | 8 | impl F32 { 9 | /// Computes `asin(x)` approximation in radians in the range `[-pi/2, pi/2]`. 10 | pub fn asin(self) -> Self { 11 | (self * (Self::ONE - self * self).invsqrt()).atan() 12 | } 13 | } 14 | 15 | #[cfg(test)] 16 | mod tests { 17 | use super::F32; 18 | use core::f32::consts::FRAC_PI_2; 19 | 20 | #[test] 21 | fn sanity_check() { 22 | let difference = F32(FRAC_PI_2).sin().asin() - FRAC_PI_2; 23 | assert!(difference.abs() <= F32::EPSILON); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/float/atan.rs: -------------------------------------------------------------------------------- 1 | //! arctangent approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | //! 5 | 6 | use super::F32; 7 | use core::f32::consts::FRAC_PI_2; 8 | 9 | impl F32 { 10 | /// Approximates `atan(x)` approximation in radians with a maximum error of 11 | /// `0.002`. 12 | /// 13 | /// Returns [`Self::NAN`] if the number is [`Self::NAN`]. 14 | pub fn atan(self) -> Self { 15 | FRAC_PI_2 * self.atan_norm() 16 | } 17 | 18 | /// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum 19 | /// error of `0.1620` degrees. 20 | pub fn atan_norm(self) -> Self { 21 | const SIGN_MASK: u32 = 0x8000_0000; 22 | const B: f32 = 0.596_227; 23 | 24 | // Extract the sign bit 25 | let ux_s = SIGN_MASK & self.to_bits(); 26 | 27 | // Calculate the arctangent in the first quadrant 28 | let bx_a = (B * self).abs(); 29 | let n = bx_a + self * self; 30 | let atan_1q = n / (1.0 + bx_a + n); 31 | 32 | // Restore the sign bit and convert to float 33 | Self::from_bits(ux_s | atan_1q.to_bits()) 34 | } 35 | } 36 | 37 | #[cfg(test)] 38 | mod tests { 39 | use super::F32; 40 | use core::f32::consts; 41 | 42 | /// 0.1620 degrees in radians 43 | const MAX_ERROR: f32 = 0.003; 44 | 45 | #[test] 46 | fn sanity_check() { 47 | // Arctangent test vectors - `(input, output)` 48 | let test_vectors: &[(f32, f32)] = &[ 49 | (3.0_f32.sqrt() / 3.0, consts::FRAC_PI_6), 50 | (1.0, consts::FRAC_PI_4), 51 | (3.0_f32.sqrt(), consts::FRAC_PI_3), 52 | (-(3.0_f32.sqrt()) / 3.0, -consts::FRAC_PI_6), 53 | (-1.0, -consts::FRAC_PI_4), 54 | (-(3.0_f32.sqrt()), -consts::FRAC_PI_3), 55 | ]; 56 | 57 | for &(x, expected) in test_vectors { 58 | let actual = F32(x).atan().0; 59 | let delta = actual - expected; 60 | 61 | assert!( 62 | delta <= MAX_ERROR, 63 | "delta {} too large: {} vs {}", 64 | delta, 65 | actual, 66 | expected 67 | ); 68 | } 69 | } 70 | 71 | #[test] 72 | fn zero() { 73 | assert_eq!(F32::ZERO.atan(), F32::ZERO); 74 | } 75 | 76 | #[test] 77 | fn nan() { 78 | assert!(F32::NAN.atan().is_nan()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/float/atan2.rs: -------------------------------------------------------------------------------- 1 | //! Four quadrant arctangent approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | 5 | use super::F32; 6 | use core::f32::consts::PI; 7 | 8 | impl F32 { 9 | /// Approximates the four quadrant arctangent of `self` (`y`) and 10 | /// `rhs` (`x`) in radians with a maximum error of `0.002`. 11 | /// 12 | /// - `x = 0`, `y = 0`: `0` 13 | /// - `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]` 14 | /// - `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]` 15 | /// - `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)` 16 | pub fn atan2(self, rhs: Self) -> Self { 17 | let n = self.atan2_norm(rhs); 18 | PI / 2.0 * if n > 2.0 { n - 4.0 } else { n } 19 | } 20 | 21 | /// Approximates `atan2(y,x)` normalized to the `[0, 4)` range with a maximum 22 | /// error of `0.1620` degrees. 23 | pub(crate) fn atan2_norm(self, rhs: Self) -> Self { 24 | const SIGN_MASK: u32 = 0x8000_0000; 25 | const B: f32 = 0.596_227; 26 | 27 | let y = self; 28 | let x = rhs; 29 | 30 | // Extract sign bits from floating point values 31 | let ux_s = SIGN_MASK & x.to_bits(); 32 | let uy_s = SIGN_MASK & y.to_bits(); 33 | 34 | // Determine quadrant offset 35 | let q = ((!ux_s & uy_s) >> 29 | ux_s >> 30) as f32; 36 | 37 | // Calculate arctangent in the first quadrant 38 | let bxy_a = (B * x * y).abs(); 39 | let n = bxy_a + y * y; 40 | let atan_1q = n / (x * x + bxy_a + n); 41 | 42 | // Translate it to the proper quadrant 43 | let uatan_2q = (ux_s ^ uy_s) | atan_1q.to_bits(); 44 | Self(q) + Self::from_bits(uatan_2q) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::F32; 51 | use core::f32::consts::PI; 52 | 53 | /// 0.1620 degrees in radians 54 | const MAX_ERROR: f32 = 0.003; 55 | 56 | #[test] 57 | fn sanity_check() { 58 | let test_vectors: &[(f32, f32, f32)] = &[ 59 | (0.0, 1.0, 0.0), 60 | (0.0, -1.0, PI), 61 | (3.0, 2.0, (3.0f32 / 2.0).atan()), 62 | (2.0, -1.0, (2.0f32 / -1.0).atan() + PI), 63 | (-2.0, -1.0, (-2.0f32 / -1.0).atan() - PI), 64 | ]; 65 | 66 | for &(y, x, expected) in test_vectors { 67 | let actual = F32(y).atan2(F32(x)).0; 68 | let delta = actual - expected; 69 | 70 | assert!( 71 | delta <= MAX_ERROR, 72 | "delta {} too large: {} vs {}", 73 | delta, 74 | actual, 75 | expected 76 | ); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/float/ceil.rs: -------------------------------------------------------------------------------- 1 | //! Floating point ceiling approximation for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Returns the smallest integer greater than or equal to a number. 7 | pub fn ceil(self) -> Self { 8 | -(-self).floor() 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | #[test] 17 | fn sanity_check() { 18 | assert_eq!(F32(-1.1).ceil().0, -1.0); 19 | assert_eq!(F32(-0.1).ceil().0, 0.0); 20 | assert_eq!(F32(0.0).ceil().0, 0.0); 21 | assert_eq!(F32(1.0).ceil().0, 1.0); 22 | assert_eq!(F32(1.1).ceil().0, 2.0); 23 | assert_eq!(F32(2.9).ceil().0, 3.0); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/float/copysign.rs: -------------------------------------------------------------------------------- 1 | //! Copy the sign over from another number. 2 | 3 | use super::{F32, SIGN_MASK}; 4 | 5 | impl F32 { 6 | /// Returns a number composed of the magnitude of `self` and the sign of 7 | /// `sign`. 8 | pub fn copysign(self, sign: Self) -> Self { 9 | let source_bits = sign.to_bits(); 10 | let source_sign = source_bits & SIGN_MASK; 11 | let signless_destination_bits = self.to_bits() & !SIGN_MASK; 12 | Self::from_bits(signless_destination_bits | source_sign) 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use super::F32; 19 | 20 | #[test] 21 | fn sanity_check() { 22 | assert_eq!(F32(1.0).copysign(F32(-1.0)).0, -1.0); 23 | assert_eq!(F32(-1.0).copysign(F32(1.0)).0, 1.0); 24 | assert_eq!(F32(1.0).copysign(F32(1.0)).0, 1.0); 25 | assert_eq!(F32(-1.0).copysign(F32(-1.0)).0, -1.0); 26 | 27 | let large_float = F32(100_000_000.13425345345); 28 | assert_eq!(large_float.copysign(-large_float), -large_float); 29 | assert_eq!((-large_float).copysign(large_float), large_float); 30 | assert_eq!(large_float.copysign(large_float), large_float); 31 | assert_eq!((-large_float).copysign(-large_float), -large_float); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/float/cos.rs: -------------------------------------------------------------------------------- 1 | //! Cosine function implemented using a parabolic approximation. 2 | //! 3 | //! Note that while this method is inspired by a [Taylor series] approximation, 4 | //! it provides both better performance and accuracy. 5 | //! 6 | //! Most of the other trigonometric functions in this crate are implemented in 7 | //! terms of this approximation. 8 | //! 9 | //! Method described: 10 | //! 11 | //! Originally based on this approximation by Nick Capen: 12 | //! 13 | //! 14 | //! 15 | //! Post quoted below for posterity as the original site is down: 16 | //! 17 | //! > In some situations you need a good approximation of sine and cosine that 18 | //! > runs at very high performance. One example is to implement dynamic 19 | //! > subdivision of round surfaces, comparable to those in Quake 3. Or to 20 | //! > implement wave motion, in case there are no vertex shaders 2.0 available. 21 | //! > 22 | //! > The standard C `sinf()` and `cosf()` functions are terribly slow and offer 23 | //! > much more precision than what we actually need. What we really want is an 24 | //! > approximation that offers the best compromise between precision and 25 | //! > performance. The most well known approximation method is to use a 26 | //! > [Taylor series] about 0 (also known as a Maclaurin series), which for 27 | //! > sine becomes: 28 | //! > 29 | //! > ```text 30 | //! > x - 1/6 x^3 + 1/120 x^5 - 1/5040 x^7 + ... 31 | //! > ``` 32 | //! > 33 | //! > When we plot this we get: taylor.gif. The green line is the real sine, 34 | //! > the red line is the first four terms of the Taylor series. This seems 35 | //! > like an acceptable approximation, but lets have a closer look: 36 | //! > taylor_zoom.gif. It behaves very nicely up till `pi/2`, but after that it 37 | //! > quickly deviates. At pi, it evaluates to -0.075 instead of 0. Using this 38 | //! > for wave simulation will result in jerky motion which is unacceptable. 39 | //! > 40 | //! > We could add another term, which in fact reduces the error significantly, 41 | //! > but this makes the formula pretty lengthy. We already need 7 42 | //! > multiplications and 3 additions for the 4-term version. The Taylor series 43 | //! > just can't give us the precision and performance we're looking for. 44 | //! > 45 | //! > We did however note that we need `sin(pi) = 0`. And there's another thing 46 | //! > we can see from taylor_zoom.gif: this looks very much like a parabola! 47 | //! > So let's try to find the formula of a parabola that matches it as closely 48 | //! > as possible. The generic formula for a parabola is `A + B x + C x²`. So 49 | //! > this gives us three degrees of freedom. Obvious choices are that we want 50 | //! > `sin(0) = 0`, `sin(pi/2) = 1` and `sin(pi) = 0`. This gives us the following 51 | //! > three equations: 52 | //! > 53 | //! > ```text 54 | //! > A + B 0 + C 0² = 0 55 | //! > A + B pi/2 + C (pi/2)² = 1 56 | //! > A + B pi + C pi² = 0 57 | //! > ``` 58 | //! > 59 | //! > Which has as solution `A = 0, B = 4/pi, C = -4/pi²`. So our parabola 60 | //! > approximation becomes `4/pi x - 4/pi² x²`. Plotting this we get: 61 | //! > parabola.gif. This looks worse than the 4-term Taylor series, right? 62 | //! > Wrong! The maximum absolute error is 0.056. Furthermore, this 63 | //! > approximation will give us smooth wave motion, and can be calculated 64 | //! > in only 3 multiplications and 1 addition! 65 | //! > 66 | //! > Unfortunately it's not very practical yet. This is what we get in the 67 | //! > `[-pi, pi]` range: negative.gif. It's quite obvious we want at least a 68 | //! > full period. But it's also clear that it's just another parabola, 69 | //! > mirrored around the origin. The formula for it is `4/pi x + 4/pi² x²`. 70 | //! > So the straightforward (pseudo-C) solution is: 71 | //! > 72 | //! > ```text 73 | //! > if(x > 0) 74 | //! > { 75 | //! > y = 4/pi x - 4/pi² x²; 76 | //! > } 77 | //! > else 78 | //! > { 79 | //! > y = 4/pi x + 4/pi² x²; 80 | //! > } 81 | //! > ``` 82 | //! > 83 | //! > Adding a branch is not a good idea though. It makes the code 84 | //! > significantly slower. But look at how similar the two parts really are. 85 | //! > A subtraction becomes an addition depending on the sign of x. In a naive 86 | //! > first attempt to eliminate the branch we can 'extract' the sign of x 87 | //! > using `x / abs(x)`. The division is very expensive but look at the resulting 88 | //! > formula: `4/pi x - x / abs(x) 4/pi² x²`. By inverting the division we can 89 | //! > simplify this to a very nice and clean `4/pi x - 4/pi² x abs(x)`. So for 90 | //! > just one extra operation we get both halves of our sine approximation! 91 | //! > Here's the graph of this formula confirming the result: abs.gif. 92 | //! > 93 | //! > Now let's look at cosine. Basic trigonometry tells us that 94 | //! > `cos(x) = sin(pi/2 + x)`. Is that all, add pi/2 to x? No, we actually get 95 | //! > the unwanted part of the parabola again: shift_sine.gif. What we need to 96 | //! > do is 'wrap around' when x > pi/2. This can be done by subtracting 2 pi. 97 | //! > So the code becomes: 98 | //! > 99 | //! > ```text 100 | //! > x += pi/2; 101 | //! > 102 | //! > if(x > pi) // Original x > pi/2 103 | //! > { 104 | //! > x -= 2 * pi; // Wrap: cos(x) = cos(x - 2 pi) 105 | //! > } 106 | //! > 107 | //! > y = sin(x); 108 | //! > ``` 109 | //! > 110 | //! > Yet another branch. To eliminate it, we can use binary logic, like this: 111 | //! > 112 | //! > ```text 113 | //! > x -= (x > pi) & (2 * pi); 114 | //! > ``` 115 | //! > 116 | //! > Note that this isn't valid C code at all. But it should clarify how this 117 | //! > can work. When x > pi is false the & operation zeroes the right hand part 118 | //! > so the subtraction does nothing, which is perfectly equivalent. I'll 119 | //! > leave it as an excercise to the reader to create working C code for this 120 | //! > (or just read on). Clearly the cosine requires a few more operations than 121 | //! > the sine but there doesn't seem to be any other way and it's still 122 | //! > extremely fast. 123 | //! > 124 | //! > Now, a maximum error of 0.056 is nice but clearly the 4-term Taylor series 125 | //! > still has a smaller error on average. Recall what our sine looked like: 126 | //! > abs.gif. So isn't there anything we can do to further increase precision 127 | //! > at minimal cost? Of course the current version is already applicable for 128 | //! > many situations where something that looks like a sine is just as good as 129 | //! > the real sine. But for other situations that's just not good enough. 130 | //! > 131 | //! > Looking at the graphs you'll notice that our approximation always 132 | //! > overestimates the real sine, except at 0, pi/2 and pi. So what we need is 133 | //! > to 'scale it down' without touching these important points. The solution 134 | //! > is to use the squared parabola, which looks like this: squared.gif. Note 135 | //! > how it preserves those important points but it's always lower than the 136 | //! > real sine. So we can use a weighted average of the two to get a better 137 | //! > approximation: 138 | //! > 139 | //! > ```text 140 | //! > Q (4/pi x - 4/pi² x²) + P (4/pi x - 4/pi² x²)² 141 | //! > ``` 142 | //! > 143 | //! > With `Q + P = 1`. You can use exact minimization of either the absolute 144 | //! > or relative error but I'll save you the math. The optimal weights are 145 | //! > `Q = 0.775`, `P = 0.225` for the absolute error and `Q = 0.782`, 146 | //! > `P = 0.218` for the relative error. I will use the former. The resulting 147 | //! > graph is: average.gif. Where did the red line go? It's almost entirely 148 | //! > covered by the green line, which instantly shows how good this 149 | //! > approximation really is. The maximum error is about 0.001, a 50x 150 | //! > improvement! The formula looks lengthy but the part between parenthesis 151 | //! > is the same value from the parabola, which needs to be computed only 152 | //! > once. In fact only 2 extra multiplications and 2 additions are required 153 | //! > to achieve this precision boost. 154 | //! > 155 | //! > It shouldn't come as a big surprise that to make it work for negative x 156 | //! > as well we need a second abs() operation. The final C code for the sine 157 | //! > becomes: 158 | //! > 159 | //! > ```text 160 | //! > float sin(float x) 161 | //! > { 162 | //! > const float B = 4/pi; 163 | //! > const float C = -4/(pi*pi); 164 | //! > 165 | //! > float y = B * x + C * x * abs(x); 166 | //! > 167 | //! > #ifdef EXTRA_PRECISION 168 | //! > // const float Q = 0.775; 169 | //! > const float P = 0.225; 170 | //! > 171 | //! > y = P * (y * abs(y) - y) + y; // Q * y + P * y * abs(y) 172 | //! > #endif 173 | //! > } 174 | //! > ``` 175 | //! > 176 | //! > So we need merely 5 multiplications and 3 additions; still faster than 177 | //! > the 4-term Taylor if we neglect the abs(), and much more precise! The 178 | //! > cosine version just needs the extra shift and wrap operations on x. 179 | //! > 180 | //! > Last but not least, I wouldn't be Nick if I didn't include the SIMD 181 | //! > optimized assembly version. It allows to perform the wrap operation very 182 | //! > efficiently so I'll give you the cosine: 183 | //! > 184 | //! > ```asm 185 | //! > // cos(x) = sin(x + pi/2) 186 | //! > addps xmm0, PI_2 187 | //! > movaps xmm1, xmm0 188 | //! > cmpnltps xmm1, PI 189 | //! > andps xmm1, PIx2 190 | //! > subps xmm0, xmm1 191 | //! > 192 | //! > // Parabola 193 | //! > movaps xmm1, xmm0 194 | //! > andps xmm1, abs 195 | //! > mulps xmm1, xmm0 196 | //! > mulps xmm0, B 197 | //! > mulps xmm1, C 198 | //! > addps xmm0, xmm1 199 | //! > 200 | //! > // Extra precision 201 | //! > movaps xmm1, xmm0 202 | //! > andps xmm1, abs 203 | //! > mulps xmm1, xmm0 204 | //! > subps xmm1, xmm0 205 | //! > mulps xmm1, P 206 | //! > addps xmm0, xmm1 207 | //! > ``` 208 | //! > 209 | //! > This code computes four cosines in parallel, resulting in a peak 210 | //! > performance of about 9 clock cycles per cosine for most CPU architectures. 211 | //! > Sines take only 6 clock cycles ideally. The lower precision version can 212 | //! > even run at 3 clock cycles per sine... And lets not forget that all input 213 | //! > between -pi and pi is valid and the formula is exact at -pi, -pi/2, 0, 214 | //! > pi/2 and pi. 215 | //! > 216 | //! > So, the conclusion is don't ever again use a Taylor series to approximate 217 | //! > a sine or cosine! To add a useful discussion to this article, I'd love to 218 | //! > hear if anyone knows good approximations for other transcendental functions 219 | //! > like the exponential, logarithm and power functions. 220 | 221 | use super::F32; 222 | use core::f32::consts::FRAC_1_PI; 223 | 224 | impl F32 { 225 | /// Approximates `cos(x)` in radians with a maximum error of `0.002`. 226 | pub fn cos(self) -> Self { 227 | let mut x = self; 228 | x *= FRAC_1_PI / 2.0; 229 | x -= 0.25 + (x + 0.25).floor().0; 230 | x *= 16.0 * (x.abs() - 0.5); 231 | x += 0.225 * x * (x.abs() - 1.0); 232 | x 233 | } 234 | } 235 | 236 | #[cfg(test)] 237 | pub(crate) mod tests { 238 | use super::F32; 239 | 240 | /// Maximum error in radians 241 | pub(crate) const MAX_ERROR: f32 = 0.002; 242 | 243 | /// Cosine test vectors - `(input, output)` 244 | const TEST_VECTORS: &[(f32, f32)] = &[ 245 | (0.000, 1.000), 246 | (0.140, 0.990), 247 | (0.279, 0.961), 248 | (0.419, 0.914), 249 | (0.559, 0.848), 250 | (0.698, 0.766), 251 | (0.838, 0.669), 252 | (0.977, 0.559), 253 | (1.117, 0.438), 254 | (1.257, 0.309), 255 | (1.396, 0.174), 256 | (1.536, 0.035), 257 | (1.676, -0.105), 258 | (1.815, -0.242), 259 | (1.955, -0.375), 260 | (2.094, -0.500), 261 | (2.234, -0.616), 262 | (2.374, -0.719), 263 | (2.513, -0.809), 264 | (2.653, -0.883), 265 | (2.793, -0.940), 266 | (2.932, -0.978), 267 | (3.072, -0.998), 268 | (3.211, -0.998), 269 | (3.351, -0.978), 270 | (3.491, -0.940), 271 | (3.630, -0.883), 272 | (3.770, -0.809), 273 | (3.910, -0.719), 274 | (4.049, -0.616), 275 | (4.189, -0.500), 276 | (4.328, -0.375), 277 | (4.468, -0.242), 278 | (4.608, -0.105), 279 | (4.747, 0.035), 280 | (4.887, 0.174), 281 | (5.027, 0.309), 282 | (5.166, 0.438), 283 | (5.306, 0.559), 284 | (5.445, 0.669), 285 | (5.585, 0.766), 286 | (5.725, 0.848), 287 | (5.864, 0.914), 288 | (6.004, 0.961), 289 | (6.144, 0.990), 290 | (6.283, 1.000), 291 | ]; 292 | 293 | #[test] 294 | fn sanity_check() { 295 | for &(x, expected) in TEST_VECTORS { 296 | let cos_x = F32(x).cos(); 297 | let delta = (cos_x - expected).abs(); 298 | 299 | assert!( 300 | delta <= MAX_ERROR, 301 | "delta {} too large: {} vs {}", 302 | delta, 303 | cos_x, 304 | expected 305 | ); 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/float/cosh.rs: -------------------------------------------------------------------------------- 1 | //! Hyperbolic cosine function 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Approximates `cosh(x)` with a maximum error of `0.003` in the range 0...π, 7 | /// and a maximum error of `0.1` in the range `π...τ`. 8 | /// 9 | /// ## Arguments 10 | /// * `self` - The angle in radians. 11 | pub fn cosh(self) -> Self { 12 | let exp_x = self.exp(); 13 | let exp_neg_x = (-self).exp(); 14 | (exp_x + exp_neg_x) / 2.0 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | pub(crate) mod tests { 20 | use super::F32; 21 | 22 | /// Maximum error in radians 23 | pub(crate) const MAX_ERROR_1: f32 = 0.001; 24 | pub(crate) const MAX_ERROR_2: f32 = 0.002; 25 | pub(crate) const MAX_ERROR_3: f32 = 0.003; 26 | pub(crate) const MAX_ERROR_4: f32 = 0.005; 27 | pub(crate) const MAX_ERROR_5: f32 = 0.02; 28 | pub(crate) const MAX_ERROR: f32 = 0.092; 29 | 30 | /// Cosine test vectors - `(input, output)` 31 | const TEST_VECTORS: &[(f32, f32, f32)] = &[ 32 | (0.000, 1.000, MAX_ERROR_1), 33 | (0.140, 1.010, MAX_ERROR_1), 34 | (0.279, 1.039, MAX_ERROR_1), 35 | (0.419, 1.089, MAX_ERROR_1), 36 | (0.559, 1.160, MAX_ERROR_1), 37 | (0.698, 1.254, MAX_ERROR_1), 38 | (0.838, 1.372, MAX_ERROR_1), 39 | (0.977, 1.516, MAX_ERROR_1), 40 | (1.117, 1.691, MAX_ERROR_1), 41 | (1.257, 1.900, MAX_ERROR_1), 42 | (1.396, 2.143, MAX_ERROR_1), 43 | (1.536, 2.431, MAX_ERROR_1), 44 | (1.676, 2.766, MAX_ERROR_1), 45 | (1.815, 3.152, MAX_ERROR_1), 46 | (1.955, 3.603, MAX_ERROR_2), 47 | (2.094, 4.120, MAX_ERROR_2), 48 | (2.234, 4.722, MAX_ERROR_2), 49 | (2.374, 5.417, MAX_ERROR_2), 50 | (2.513, 6.211, MAX_ERROR_2), 51 | (2.653, 7.134, MAX_ERROR_3), 52 | (2.793, 8.196, MAX_ERROR_3), 53 | (2.932, 9.409, MAX_ERROR_3), 54 | (3.072, 10.816, MAX_ERROR_3), 55 | (3.211, 12.422, MAX_ERROR_3), 56 | (3.351, 14.283, MAX_ERROR_4), 57 | (3.491, 16.425, MAX_ERROR_4), 58 | (3.630, 18.870, MAX_ERROR_4), 59 | (3.770, 21.702, MAX_ERROR_4), 60 | (3.910, 24.959, MAX_ERROR_4), 61 | (4.049, 28.679, MAX_ERROR_5), 62 | (4.189, 32.986, MAX_ERROR_5), 63 | (4.328, 37.903, MAX_ERROR_5), 64 | (4.468, 43.597, MAX_ERROR_5), 65 | (4.608, 50.147, MAX_ERROR_5), 66 | (4.747, 57.623, MAX_ERROR), 67 | (4.887, 66.281, MAX_ERROR), 68 | (5.027, 76.241, MAX_ERROR), 69 | (5.166, 87.609, MAX_ERROR), 70 | (5.306, 100.774, MAX_ERROR), 71 | (5.445, 115.801, MAX_ERROR), 72 | (5.585, 133.202, MAX_ERROR), 73 | (5.725, 153.218, MAX_ERROR), 74 | (5.864, 176.066, MAX_ERROR), 75 | (6.004, 202.524, MAX_ERROR), 76 | (6.144, 232.958, MAX_ERROR), 77 | (6.283, 267.697, MAX_ERROR), 78 | ]; 79 | 80 | #[test] 81 | fn sanity_check() { 82 | for &(x, expected, max_error) in TEST_VECTORS { 83 | let cosh_x = F32(x).cosh(); 84 | let delta = (cosh_x - expected).abs(); 85 | 86 | assert!( 87 | delta <= max_error, 88 | "delta {} too large: {} vs {}", 89 | delta, 90 | cosh_x, 91 | expected 92 | ); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/float/div_euclid.rs: -------------------------------------------------------------------------------- 1 | //! Calculates Euclidian division for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Calculates Euclidean division, the matching method for `rem_euclid`. 7 | pub fn div_euclid(self, rhs: Self) -> Self { 8 | let q = (self / rhs).trunc(); 9 | 10 | if self % rhs >= Self::ZERO { 11 | q 12 | } else if rhs > Self::ZERO { 13 | q - Self::ONE 14 | } else { 15 | q + Self::ONE 16 | } 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::F32; 23 | 24 | #[test] 25 | fn sanity_check() { 26 | let a = F32(7.0); 27 | let b = F32(4.0); 28 | 29 | assert_eq!(a.div_euclid(b), F32(1.0)); 30 | assert_eq!((-a).div_euclid(b), F32(-2.0)); 31 | assert_eq!(a.div_euclid(-b), F32(-1.0)); 32 | assert_eq!((-a).div_euclid(-b), F32(2.0)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/float/exp.rs: -------------------------------------------------------------------------------- 1 | //! Exp approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | 5 | use super::{EXPONENT_BIAS, F32}; 6 | use core::f32::consts; 7 | 8 | impl F32 { 9 | /// Returns `e^(self)`, (the exponential function). 10 | #[inline] 11 | pub fn exp(self) -> Self { 12 | self.exp_ln2_approx(4) 13 | } 14 | 15 | /// Exp approximation for `f32`. 16 | pub(crate) fn exp_ln2_approx(self, partial_iter: u32) -> Self { 17 | if self == Self::ZERO { 18 | return Self::ONE; 19 | } 20 | 21 | if (self - Self::ONE).abs() < f32::EPSILON { 22 | return consts::E.into(); 23 | } 24 | 25 | if (self - (-Self::ONE)).abs() < f32::EPSILON { 26 | return Self::ONE / consts::E; 27 | } 28 | 29 | // log base 2(E) == 1/ln(2) 30 | // x_fract + x_whole = x/ln2_recip 31 | // ln2*(x_fract + x_whole) = x 32 | let x_ln2recip = self * consts::LOG2_E; 33 | let x_fract = x_ln2recip.fract(); 34 | let x_trunc = x_ln2recip.trunc(); 35 | 36 | //guaranteed to be 0 < x < 1.0 37 | let x_fract = x_fract * consts::LN_2; 38 | let fract_exp = x_fract.exp_smallx(partial_iter); 39 | 40 | //need the 2^n portion, we can just extract that from the whole number exp portion 41 | let fract_exponent: i32 = fract_exp 42 | .extract_exponent_value() 43 | .saturating_add(x_trunc.0 as i32); 44 | 45 | if fract_exponent < -(EXPONENT_BIAS as i32) { 46 | return Self::ZERO; 47 | } 48 | 49 | if fract_exponent > ((EXPONENT_BIAS + 1) as i32) { 50 | return Self::INFINITY; 51 | } 52 | 53 | fract_exp.set_exponent(fract_exponent) 54 | } 55 | 56 | /// if x is between 0.0 and 1.0, we can approximate it with the a series 57 | /// 58 | /// Series from here: 59 | /// 60 | /// 61 | /// e^x ~= 1 + x(1 + x/2(1 + (x? 62 | #[inline] 63 | pub(crate) fn exp_smallx(self, iter: u32) -> Self { 64 | let mut total = 1.0; 65 | 66 | for i in (1..=iter).rev() { 67 | total = 1.0 + ((self.0 / (i as f32)) * total); 68 | } 69 | 70 | Self(total) 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::F32; 77 | 78 | pub(crate) const MAX_ERROR: f32 = 0.001; 79 | 80 | /// exp test vectors - `(input, output)` 81 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 82 | (1e-07, 1.0000001), 83 | (1e-06, 1.000001), 84 | (1e-05, 1.00001), 85 | (1e-04, 1.0001), 86 | (0.001, 1.0010005), 87 | (0.01, 1.0100502), 88 | (0.1, 1.105171), 89 | (1.0, 2.7182817), 90 | (10.0, 22026.465), 91 | (-1e-08, 1.0), 92 | (-1e-07, 0.9999999), 93 | (-1e-06, 0.999999), 94 | (-1e-05, 0.99999), 95 | (-1e-04, 0.9999), 96 | (-0.001, 0.9990005), 97 | (-0.01, 0.99004984), 98 | (-0.1, 0.9048374), 99 | (-1.0, 0.36787945), 100 | (-10.0, 4.539_993e-5), 101 | ]; 102 | 103 | #[test] 104 | fn sanity_check() { 105 | assert_eq!(F32(-1000000.0).exp(), F32::ZERO); 106 | 107 | for &(x, expected) in TEST_VECTORS { 108 | let exp_x = F32(x).exp(); 109 | let relative_error = (exp_x - expected).abs() / expected; 110 | 111 | assert!( 112 | relative_error <= MAX_ERROR, 113 | "relative_error {} too large for input {} : {} vs {}", 114 | relative_error, 115 | x, 116 | exp_x, 117 | expected 118 | ); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/float/floor.rs: -------------------------------------------------------------------------------- 1 | //! Floating point floor approximation for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Returns the largest integer less than or equal to a number. 7 | pub fn floor(self) -> Self { 8 | let mut res = (self.0 as i32) as f32; 9 | 10 | if self.0 < res { 11 | res -= 1.0; 12 | } 13 | 14 | Self(res) 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::F32; 21 | 22 | #[test] 23 | fn sanity_check() { 24 | assert_eq!(F32(-1.1).floor().0, -2.0); 25 | assert_eq!(F32(-0.1).floor().0, -1.0); 26 | assert_eq!(F32(0.0).floor().0, 0.0); 27 | assert_eq!(F32(1.0).floor().0, 1.0); 28 | assert_eq!(F32(1.1).floor().0, 1.0); 29 | assert_eq!(F32(2.9).floor().0, 2.0); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/float/fract.rs: -------------------------------------------------------------------------------- 1 | //! Floating point fractional number for a single-precision float. 2 | 3 | use super::F32; 4 | use super::{EXPONENT_BIAS, MANTISSA_BITS, MANTISSA_MASK}; 5 | 6 | impl F32 { 7 | /// Returns the fractional part of a number with sign. 8 | pub fn fract(self) -> Self { 9 | let x_bits = self.to_bits(); 10 | let exponent = self.extract_exponent_value(); 11 | 12 | // we know it is *only* fraction 13 | if exponent < 0 { 14 | return self; 15 | } 16 | 17 | // find the part of the fraction that would be left over 18 | let fractional_part = x_bits.overflowing_shl(exponent as u32).0 & MANTISSA_MASK; 19 | 20 | // if there isn't a fraction we can just return 0 21 | if fractional_part == 0 { 22 | // TODO: most people don't actually care about -0.0, 23 | // so would it be better to just not copysign? 24 | return Self(0.0).copysign(self); 25 | } 26 | 27 | // Note: alternatively this could use -1.0, but it's assumed subtraction would be more costly 28 | // example: 'let new_exponent_bits = 127_u32.overflowing_shl(23_u32).0)) - 1.0' 29 | let exponent_shift: u32 = (fractional_part.leading_zeros() - (32 - MANTISSA_BITS)) + 1; 30 | 31 | let fractional_normalized: u32 = 32 | fractional_part.overflowing_shl(exponent_shift).0 & MANTISSA_MASK; 33 | 34 | let new_exponent_bits = (EXPONENT_BIAS - (exponent_shift)) 35 | .overflowing_shl(MANTISSA_BITS) 36 | .0; 37 | 38 | Self::from_bits(fractional_normalized | new_exponent_bits).copysign(self) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::F32; 45 | 46 | #[test] 47 | fn sanity_check() { 48 | // fraction check actually won't be the same, though technically exactly accurate 49 | // so we test by adding back the number removed. 50 | assert_eq!(F32(2.9).fract().0 + 2.0, 2.9); 51 | assert_eq!(F32(-1.1).fract().0 - 1.0, -1.1); 52 | assert_eq!(F32(-0.1).fract().0, -0.1); 53 | assert_eq!(F32(0.0).fract().0, 0.0); 54 | assert_eq!(F32(1.0).fract().0 + 1.0, 1.0); 55 | assert_eq!(F32(1.1).fract().0 + 1.0, 1.1); 56 | 57 | assert_eq!(F32(-100_000_000.13425345345).fract().0, -0.0); 58 | assert_eq!(F32(100_000_000.13425345345).fract().0, 0.0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/float/hypot.rs: -------------------------------------------------------------------------------- 1 | //! Calculate length of the hypotenuse of a right triangle. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Calculate the length of the hypotenuse of a right-angle triangle. 7 | pub fn hypot(self, rhs: Self) -> Self { 8 | (self * self + rhs * rhs).sqrt() 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | #[test] 17 | fn sanity_check() { 18 | let x = F32(3.0); 19 | let y = F32(4.0); 20 | let difference = x.hypot(y) - F32(25.0).sqrt(); 21 | assert!(difference.abs() <= F32::EPSILON); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/float/inv.rs: -------------------------------------------------------------------------------- 1 | //! Fast approximation of `1/x`. 2 | //! 3 | //! Method described at: 4 | 5 | use super::F32; 6 | 7 | impl F32 { 8 | /// Fast approximation of the reciprocal (inverse) of a number, `1/x`. 9 | /// 10 | /// Assumes that the underlying data is in IEEE 754 format. 11 | #[doc(alias = "recip")] 12 | pub fn inv(self) -> Self { 13 | // Perform the bit manipulation for the approximation 14 | // The constant 0x7f00_0000 corresponds to the bit pattern for 1.0 in IEEE 754 format. 15 | // Subtracting the bits of the original number from this constant effectively inverts the exponent, 16 | // resulting in an approximation of the reciprocal. 17 | match 0x7f00_0000_u32.checked_sub(self.0.to_bits()) { 18 | Some(result) => Self(f32::from_bits(result)), 19 | // Check if the value is too large for the approximation, NaN (e.g. 0x7fC0_0000) or infinity. 20 | None => { 21 | if self.0.is_infinite() { 22 | // 1/∞ = 0; by definition. 23 | if self.0.is_sign_positive() { 24 | Self(0.0) 25 | } else { 26 | Self(-0.0) 27 | } 28 | } else if self.0.is_nan() { 29 | Self(f32::NAN) 30 | } else { 31 | // Values larger than the threshold result in zero for 1/x 32 | Self(0.0) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | pub(crate) mod tests { 41 | use super::F32; 42 | 43 | /// Deviation from the actual value (8%) 44 | pub(crate) const MAX_ERROR: f32 = 0.08; 45 | 46 | #[test] 47 | fn sanity_check() { 48 | for x in 0..100 { 49 | let x = F32(x as f32); 50 | let inv_x = x.inv().0; 51 | let expected = 1.0 / x; 52 | let allowed_delta = x * MAX_ERROR; 53 | let actual_delta = inv_x - expected; 54 | 55 | assert!( 56 | actual_delta <= allowed_delta, 57 | "delta {} too large: {} vs {}", 58 | actual_delta, 59 | inv_x, 60 | expected 61 | ); 62 | } 63 | } 64 | 65 | #[test] 66 | fn special_floats() { 67 | assert!(f32::NAN.to_bits() > 0x7f00_0000); 68 | 69 | assert_eq!(F32(f32::from_bits(0x7f00_0001)).inv(), F32(0.0)); 70 | assert!(F32(f32::NAN).inv().is_nan()); 71 | assert_eq!(F32(f32::INFINITY).inv(), F32(0.0)); 72 | assert_eq!(F32(f32::NEG_INFINITY).inv(), F32(-0.0)); 73 | assert!(F32(f32::INFINITY).inv().is_sign_positive()); 74 | assert!(F32(f32::NEG_INFINITY).inv().is_sign_negative()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/float/invsqrt.rs: -------------------------------------------------------------------------------- 1 | //! Inverse square root approximation function for a single-precision float. 2 | //! 3 | //! Method described at: 4 | 5 | use super::F32; 6 | 7 | impl F32 { 8 | /// Approximate inverse square root with an average deviation of ~5%. 9 | pub fn invsqrt(self) -> Self { 10 | Self::from_bits(0x5f37_5a86 - (self.to_bits() >> 1)) 11 | } 12 | } 13 | 14 | #[cfg(test)] 15 | mod tests { 16 | use super::F32; 17 | use crate::float::sqrt::tests::TEST_VECTORS; 18 | 19 | /// Deviation from the actual value (5%) 20 | const MAX_ERROR: f32 = 0.05; 21 | 22 | #[test] 23 | fn sanity_check() { 24 | for (x, expected) in TEST_VECTORS { 25 | // The tests vectors are for sqrt(x), so invert the expected value 26 | let expected = 1.0 / expected; 27 | 28 | let invsqrt_x = F32(*x).invsqrt().0; 29 | let allowed_delta = x * MAX_ERROR; 30 | let actual_delta = invsqrt_x - expected; 31 | 32 | assert!( 33 | actual_delta <= allowed_delta, 34 | "delta {} too large: {} vs {}", 35 | actual_delta, 36 | invsqrt_x, 37 | expected 38 | ); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/float/ln.rs: -------------------------------------------------------------------------------- 1 | //! Natural log (ln) approximation for a single-precision float. 2 | //! 3 | //! Method described at: 4 | //! 5 | //! Modified to not be restricted to int range and only values of x above 1.0. 6 | //! Also got rid of most of the slow conversions. Should work for all positive values of x. 7 | 8 | use super::{EXPONENT_MASK, F32}; 9 | use core::f32::consts::LN_2; 10 | 11 | impl F32 { 12 | /// Approximates the natural logarithm of the number. 13 | // Note: excessive precision ignored because it hides the origin of the numbers used for the 14 | // ln(1.0->2.0) polynomial 15 | #[allow(clippy::excessive_precision)] 16 | pub fn ln(self) -> Self { 17 | // x may essentially be 1.0 but, as clippy notes, these kinds of 18 | // floating point comparisons can fail when the bit pattern is not the sames 19 | if (self - Self::ONE).abs() < f32::EPSILON { 20 | return Self::ZERO; 21 | } 22 | 23 | let x_less_than_1 = self < 1.0; 24 | 25 | // Note: we could use the fast inverse approximation here found in super::inv::inv_approx, but 26 | // the precision of such an approximation is assumed not good enough. 27 | let x_working = if x_less_than_1 { self.inv() } else { self }; 28 | 29 | // according to the SO post ln(x) = ln((2^n)*y)= ln(2^n) + ln(y) = ln(2) * n + ln(y) 30 | // get exponent value 31 | let base2_exponent = x_working.extract_exponent_value() as u32; 32 | let divisor = f32::from_bits(x_working.to_bits() & EXPONENT_MASK); 33 | 34 | // supposedly normalizing between 1.0 and 2.0 35 | let x_working = x_working / divisor; 36 | 37 | // approximate polynomial generated from maple in the post using Remez Algorithm: 38 | // https://en.wikipedia.org/wiki/Remez_algorithm 39 | let ln_1to2_polynomial = -1.741_793_9 40 | + (2.821_202_6 41 | + (-1.469_956_8 + (0.447_179_55 - 0.056_570_851 * x_working) * x_working) 42 | * x_working) 43 | * x_working; 44 | 45 | // ln(2) * n + ln(y) 46 | let result = (base2_exponent as f32) * LN_2 + ln_1to2_polynomial; 47 | 48 | if x_less_than_1 { 49 | -result 50 | } else { 51 | result 52 | } 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::F32; 59 | 60 | pub(crate) const MAX_ERROR: f32 = 0.001; 61 | 62 | /// ln(x) test vectors - `(input, output)` 63 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 64 | (1e-20, -46.0517), 65 | (1e-19, -43.749115), 66 | (1e-18, -41.446533), 67 | (1e-17, -39.143948), 68 | (1e-16, -36.841362), 69 | (1e-15, -34.538776), 70 | (1e-14, -32.23619), 71 | (1e-13, -29.933607), 72 | (1e-12, -27.631021), 73 | (1e-11, -25.328436), 74 | (1e-10, -23.02585), 75 | (1e-09, -20.723267), 76 | (1e-08, -18.420681), 77 | (1e-07, -16.118095), 78 | (1e-06, -13.815511), 79 | (1e-05, -11.512925), 80 | (1e-04, -9.2103405), 81 | (0.001, -6.9077554), 82 | (0.01, -4.6051702), 83 | (0.1, -2.3025851), 84 | (10.0, 2.3025851), 85 | (100.0, 4.6051702), 86 | (1000.0, 6.9077554), 87 | (10000.0, 9.2103405), 88 | (100000.0, 11.512925), 89 | (1000000.0, 13.815511), 90 | (10000000.0, 16.118095), 91 | (100000000.0, 18.420681), 92 | (1000000000.0, 20.723267), 93 | (10000000000.0, 23.02585), 94 | (100000000000.0, 25.328436), 95 | (1000000000000.0, 27.631021), 96 | (10000000000000.0, 29.933607), 97 | (100000000000000.0, 32.23619), 98 | (1000000000000000.0, 34.538776), 99 | (1e+16, 36.841362), 100 | (1e+17, 39.143948), 101 | (1e+18, 41.446533), 102 | (1e+19, 43.749115), 103 | ]; 104 | 105 | #[test] 106 | fn sanity_check() { 107 | assert_eq!(F32::ONE.ln(), F32::ZERO); 108 | for &(x, expected) in TEST_VECTORS { 109 | let ln_x = F32(x).ln(); 110 | let relative_error = (ln_x - expected).abs() / expected; 111 | 112 | assert!( 113 | relative_error <= MAX_ERROR, 114 | "relative_error {} too large: {} vs {}", 115 | relative_error, 116 | ln_x, 117 | expected 118 | ); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/float/log.rs: -------------------------------------------------------------------------------- 1 | //! log_b(a) approximation for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Approximates the logarithm of the number with respect to an arbitrary base. 7 | pub fn log(self, base: Self) -> Self { 8 | (Self::ONE / base.ln()) * self.ln() 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | pub(crate) const MAX_ERROR: f32 = 0.001; 17 | 18 | /// log3(x) test vectors - `(input, output)` 19 | pub(crate) const TEST_VECTORS_BASE3: &[(f32, f32)] = &[ 20 | (1e-20, -41.918_064), 21 | (1e-19, -39.822_16), 22 | (1e-18, -37.726_26), 23 | (1e-17, -35.630_356), 24 | (1e-16, -33.534_454), 25 | (1e-15, -31.438_549), 26 | (1e-14, -29.342_646), 27 | (1e-13, -27.246_744), 28 | (1e-12, -25.150_839), 29 | (1e-11, -23.054_935), 30 | (1e-10, -20.959_032), 31 | (1e-09, -18.863_13), 32 | (1e-08, -16.767_227), 33 | (1e-07, -14.671_323), 34 | (1e-06, -12.575_419), 35 | (1e-05, -10.479_516), 36 | (1e-04, -8.383_614), 37 | (0.001, -6.287_709_7), 38 | (0.01, -4.191_807), 39 | (0.1, -2.095_903_4), 40 | (10.0, 2.095_903_4), 41 | (100.0, 4.191_807), 42 | (1000.0, 6.287_709_7), 43 | (10000.0, 8.383_614), 44 | (100000.0, 10.479_516), 45 | (1000000.0, 12.575_419), 46 | (10000000.0, 14.671_323), 47 | (100000000.0, 16.767_227), 48 | (1000000000.0, 18.863_13), 49 | (10000000000.0, 20.959_032), 50 | (100000000000.0, 23.054_935), 51 | (1000000000000.0, 25.150_839), 52 | (10000000000000.0, 27.246_744), 53 | (100000000000000.0, 29.342_646), 54 | (1000000000000000.0, 31.438_549), 55 | (1e+16, 33.534_454), 56 | (1e+17, 35.630_356), 57 | (1e+18, 37.726_26), 58 | (1e+19, 39.822_16), 59 | ]; 60 | 61 | /// log5.5(x) test vectors - `(input, output)` 62 | pub(crate) const TEST_VECTORS_BASE5_5: &[(f32, f32)] = &[ 63 | (1e-20, -27.013_786), 64 | (1e-19, -25.663_097), 65 | (1e-18, -24.312_408), 66 | (1e-17, -22.961_72), 67 | (1e-16, -21.611_03), 68 | (1e-15, -20.260_34), 69 | (1e-14, -18.909_65), 70 | (1e-13, -17.558_962), 71 | (1e-12, -16.208_273), 72 | (1e-11, -14.857_583), 73 | (1e-10, -13.506_893), 74 | (1e-09, -12.156_204), 75 | (1e-08, -10.805_515), 76 | (1e-07, -9.454_825), 77 | (1e-06, -8.104_136), 78 | (1e-05, -6.753_446_6), 79 | (1e-04, -5.402_757_6), 80 | (0.001, -4.052_068), 81 | (0.01, -2.701_378_8), 82 | (0.1, -1.350_689_4), 83 | (10.0, 1.350_689_4), 84 | (100.0, 2.701_378_8), 85 | (1000.0, 4.052_068), 86 | (10000.0, 5.402_757_6), 87 | (100000.0, 6.753_446_6), 88 | (1000000.0, 8.104_136), 89 | (10000000.0, 9.454_825), 90 | (100000000.0, 10.805_515), 91 | (1000000000.0, 12.156_204), 92 | (10000000000.0, 13.506_893), 93 | (100000000000.0, 14.857_583), 94 | (1000000000000.0, 16.208_273), 95 | (10000000000000.0, 17.558_962), 96 | (100000000000000.0, 18.909_65), 97 | (1000000000000000.0, 20.260_34), 98 | (1e+16, 21.611_03), 99 | (1e+17, 22.961_72), 100 | (1e+18, 24.312_408), 101 | (1e+19, 25.663_097), 102 | ]; 103 | 104 | /// log12.7(x) test vectors - `(input, output)` 105 | pub(crate) const TEST_VECTORS_BASE12_7: &[(f32, f32)] = &[ 106 | (1e-20, -18.119_164), 107 | (1e-19, -17.213_205), 108 | (1e-18, -16.307_247), 109 | (1e-17, -15.401_289), 110 | (1e-16, -14.495_331), 111 | (1e-15, -13.589_373), 112 | (1e-14, -12.683_414), 113 | (1e-13, -11.777_456), 114 | (1e-12, -10.871_498), 115 | (1e-11, -9.965_54), 116 | (1e-10, -9.059_582), 117 | (1e-09, -8.153_624), 118 | (1e-08, -7.247_665_4), 119 | (1e-07, -6.341_707), 120 | (1e-06, -5.435_749), 121 | (1e-05, -4.529_791), 122 | (1e-04, -3.623_832_7), 123 | (0.001, -2.717_874_5), 124 | (0.01, -1.811_916_4), 125 | (0.1, -0.905_958_2), 126 | (10.0, 0.905_958_2), 127 | (100.0, 1.811_916_4), 128 | (1000.0, 2.717_874_5), 129 | (10000.0, 3.623_832_7), 130 | (100000.0, 4.529_791), 131 | (1000000.0, 5.435_749), 132 | (10000000.0, 6.341_707), 133 | (100000000.0, 7.247_665_4), 134 | (1000000000.0, 8.153_624), 135 | (10000000000.0, 9.059_582), 136 | (100000000000.0, 9.965_54), 137 | (1000000000000.0, 10.871_498), 138 | (10000000000000.0, 11.777_456), 139 | (100000000000000.0, 12.683_414), 140 | (1000000000000000.0, 13.589_373), 141 | (1e+16, 14.495_331), 142 | (1e+17, 15.401_289), 143 | (1e+18, 16.307_247), 144 | (1e+19, 17.213_205), 145 | ]; 146 | 147 | #[test] 148 | fn sanity_check() { 149 | assert_eq!(F32::ONE.log(F32(3.0)), F32::ZERO); 150 | assert_eq!(F32::ONE.log(F32(5.5)), F32::ZERO); 151 | assert_eq!(F32::ONE.log(F32(12.7)), F32::ZERO); 152 | 153 | for &(x, expected) in TEST_VECTORS_BASE3 { 154 | let log_x = F32(x).log(F32(3.0)); 155 | let relative_error = (log_x - expected).abs() / expected; 156 | 157 | assert!( 158 | relative_error <= MAX_ERROR, 159 | "relative_error {} too large: {} vs {}", 160 | relative_error, 161 | log_x, 162 | expected 163 | ); 164 | } 165 | 166 | for &(x, expected) in TEST_VECTORS_BASE5_5 { 167 | let log_x = F32(x).log(F32(5.5)); 168 | let relative_error = (log_x - expected).abs() / expected; 169 | 170 | assert!( 171 | relative_error <= MAX_ERROR, 172 | "relative_error {} too large: {} vs {}", 173 | relative_error, 174 | log_x, 175 | expected 176 | ); 177 | } 178 | 179 | for &(x, expected) in TEST_VECTORS_BASE12_7 { 180 | let log_x = F32(x).log(F32(12.7)); 181 | let relative_error = (log_x - expected).abs() / expected; 182 | 183 | assert!( 184 | relative_error <= MAX_ERROR, 185 | "relative_error {} too large: {} vs {}", 186 | relative_error, 187 | log_x, 188 | expected 189 | ); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/float/log10.rs: -------------------------------------------------------------------------------- 1 | //! log base 2 approximation for a single-precision float. 2 | 3 | use super::F32; 4 | use core::f32::consts::LOG10_E; 5 | 6 | impl F32 { 7 | /// Approximates the base 10 logarithm of the number. 8 | pub fn log10(self) -> Self { 9 | self.ln() * LOG10_E 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::F32; 16 | pub(crate) const MAX_ERROR: f32 = 0.001; 17 | 18 | /// log10(x) test vectors - `(input, output)` 19 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 20 | (1e-20, -20.0), 21 | (1e-19, -19.0), 22 | (1e-18, -18.0), 23 | (1e-17, -17.0), 24 | (1e-16, -16.0), 25 | (1e-15, -15.0), 26 | (1e-14, -14.0), 27 | (1e-13, -13.0), 28 | (1e-12, -12.0), 29 | (1e-11, -11.0), 30 | (1e-10, -10.0), 31 | (1e-09, -9.0), 32 | (1e-08, -8.0), 33 | (1e-07, -7.0), 34 | (1e-06, -6.0), 35 | (1e-05, -5.0), 36 | (1e-04, -4.0), 37 | (0.001, -3.0), 38 | (0.01, -2.0), 39 | (0.1, -1.0), 40 | (10.0, 1.0), 41 | (100.0, 2.0), 42 | (1000.0, 3.0), 43 | (10000.0, 4.0), 44 | (100000.0, 5.0), 45 | (1000000.0, 6.0), 46 | (10000000.0, 7.0), 47 | (100000000.0, 8.0), 48 | (1000000000.0, 9.0), 49 | (10000000000.0, 10.0), 50 | (100000000000.0, 11.0), 51 | (1000000000000.0, 12.0), 52 | (10000000000000.0, 13.0), 53 | (100000000000000.0, 14.0), 54 | (1000000000000000.0, 15.0), 55 | (1e+16, 16.0), 56 | (1e+17, 17.0), 57 | (1e+18, 18.0), 58 | (1e+19, 19.0), 59 | ]; 60 | 61 | #[test] 62 | fn sanity_check() { 63 | assert_eq!(F32::ONE.log10(), F32::ZERO); 64 | 65 | for &(x, expected) in TEST_VECTORS { 66 | let ln_x = F32(x).log10(); 67 | let relative_error = (ln_x - expected).abs() / expected; 68 | 69 | assert!( 70 | relative_error <= MAX_ERROR, 71 | "relative_error {} too large: {} vs {}", 72 | relative_error, 73 | ln_x, 74 | expected 75 | ); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/float/log2.rs: -------------------------------------------------------------------------------- 1 | //! log base 2 approximation for a single-precision float. 2 | 3 | use super::F32; 4 | use core::f32::consts::LOG2_E; 5 | 6 | impl F32 { 7 | /// Approximates the base 2 logarithm of the number. 8 | pub fn log2(self) -> Self { 9 | self.ln() * LOG2_E 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::F32; 16 | 17 | pub(crate) const MAX_ERROR: f32 = 0.001; 18 | 19 | /// log2(x) test vectors - `(input, output)` 20 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 21 | (1e-20, -66.43856), 22 | (1e-19, -63.116634), 23 | (1e-18, -59.794704), 24 | (1e-17, -56.47278), 25 | (1e-16, -53.15085), 26 | (1e-15, -49.828922), 27 | (1e-14, -46.506992), 28 | (1e-13, -43.185066), 29 | (1e-12, -39.863136), 30 | (1e-11, -36.54121), 31 | (1e-10, -33.21928), 32 | (1e-09, -29.897352), 33 | (1e-08, -26.575424), 34 | (1e-07, -23.253496), 35 | (1e-06, -19.931568), 36 | (1e-05, -16.60964), 37 | (1e-04, -13.287712), 38 | (0.001, -9.965784), 39 | (0.01, -6.643856), 40 | (0.1, -3.321928), 41 | (10.0, 3.321928), 42 | (100.0, 6.643856), 43 | (1000.0, 9.965784), 44 | (10000.0, 13.287712), 45 | (100000.0, 16.60964), 46 | (1000000.0, 19.931568), 47 | (10000000.0, 23.253496), 48 | (100000000.0, 26.575424), 49 | (1000000000.0, 29.897352), 50 | (10000000000.0, 33.21928), 51 | (100000000000.0, 36.54121), 52 | (1000000000000.0, 39.863136), 53 | (10000000000000.0, 43.185066), 54 | (100000000000000.0, 46.506992), 55 | (1000000000000000.0, 49.828922), 56 | (1e+16, 53.15085), 57 | (1e+17, 56.47278), 58 | (1e+18, 59.794704), 59 | (1e+19, 63.116634), 60 | ]; 61 | 62 | #[test] 63 | fn sanity_check() { 64 | assert_eq!(F32::ONE.log2(), F32::ZERO); 65 | 66 | for &(x, expected) in TEST_VECTORS { 67 | let ln_x = F32(x).log2().0; 68 | let relative_error = (ln_x - expected).abs() / expected; 69 | 70 | assert!( 71 | relative_error <= MAX_ERROR, 72 | "relative_error {} too large: {} vs {}", 73 | relative_error, 74 | ln_x, 75 | expected 76 | ); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/float/mul_add.rs: -------------------------------------------------------------------------------- 1 | //! Fused multiply-add. Computes `(self * a) + b` 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Computes `(self * a) + b`. 7 | pub fn mul_add(self, a: Self, b: Self) -> Self { 8 | self * a + b 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | #[test] 17 | fn sanity_check() { 18 | assert_eq!(F32(0.0).mul_add(F32(0.0), F32(1.0)), F32(1.0)); 19 | assert_eq!(F32(1.0).mul_add(F32(2.0), F32(3.5)), F32(5.5)); 20 | assert_eq!(F32(1.0).mul_add(F32(-1.0), F32(0.0)), F32(-1.0)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/float/powf.rs: -------------------------------------------------------------------------------- 1 | //! `x^n` with fractional `n` approximation for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Approximates a number raised to a floating point power. 7 | pub fn powf(self, n: Self) -> Self { 8 | // using x^n = exp(ln(x^n)) = exp(n*ln(x)) 9 | if self >= Self::ZERO { 10 | (n * self.ln()).exp() 11 | } else if !n.is_integer() { 12 | Self::NAN 13 | } else if n.is_even() { 14 | // if n is even, then we know that the result will have no sign, so we can remove it 15 | (n * self.without_sign().ln()).exp() 16 | } else { 17 | // if n isn't even, we need to multiply by -1.0 at the end. 18 | -(n * self.without_sign().ln()).exp() 19 | } 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use super::F32; 26 | 27 | /// error builds up from both exp and ln approximation, so we double the error allowed. 28 | pub(crate) const MAX_ERROR: f32 = 0.002; 29 | 30 | /// powf(3,x) test vectors - `(input, output)` 31 | pub(crate) const TEST_VECTORS_POW3: &[(f32, f32)] = &[ 32 | (-1e-20, 1.0), 33 | (-1e-19, 1.0), 34 | (-1e-18, 1.0), 35 | (-1e-17, 1.0), 36 | (-1e-16, 0.9999999999999999), 37 | (-1e-15, 0.9999999999999989), 38 | (-1e-14, 0.999999999999989), 39 | (-1e-13, 0.9999999999998901), 40 | (-1e-12, 0.9999999999989014), 41 | (-1e-11, 0.9999999999890139), 42 | (-1e-10, 0.9999999998901388), 43 | (-1e-09, 0.9999999989013877), 44 | (-1e-08, 0.9999999890138772), 45 | (-1e-07, 0.999_999_9), 46 | (-1e-06, 0.999_998_9), 47 | (-1e-05, 0.999_989_03), 48 | (-1e-04, 0.999_890_15), 49 | (-0.001, 0.998_901_96), 50 | (-0.01, 0.989_074), 51 | (-0.1, 0.895_958_5), 52 | (-1.0, 0.333_333_34), 53 | (-10.0, 1.693_508_8e-5), 54 | (-100.0, 0e0), 55 | (-1000.0, 0.0), 56 | (1e-20, 1.0), 57 | (1e-19, 1.0), 58 | (1e-18, 1.0), 59 | (1e-17, 1.0), 60 | (1e-16, 1.0), 61 | (1e-15, 1.000000000000001), 62 | (1e-14, 1.0000000000000109), 63 | (1e-13, 1.00000000000011), 64 | (1e-12, 1.0000000000010987), 65 | (1e-11, 1.000000000010986), 66 | (1e-10, 1.0000000001098612), 67 | (1e-09, 1.0000000010986123), 68 | (1e-08, 1.000000010986123), 69 | (1e-07, 1.000_000_1), 70 | (1e-06, 1.000_001_1), 71 | (1e-05, 1.000_011), 72 | (1e-04, 1.000_109_9), 73 | (0.001, 1.001_099_2), 74 | (0.01, 1.011_046_6), 75 | (0.1, 1.116_123_2), 76 | (1.0, 3.0), 77 | (10.0, 59049.0), 78 | ]; 79 | 80 | /// powf(150,x) test vectors - `(input, output)` 81 | pub(crate) const TEST_VECTORS_POW150: &[(f32, f32)] = &[ 82 | (-1e-20, 1.0), 83 | (-1e-19, 1.0), 84 | (-1e-18, 1.0), 85 | (-1e-17, 1.0), 86 | (-1e-16, 0.9999999999999994), 87 | (-1e-15, 0.999999999999995), 88 | (-1e-14, 0.9999999999999499), 89 | (-1e-13, 0.999999999999499), 90 | (-1e-12, 0.9999999999949893), 91 | (-1e-11, 0.9999999999498936), 92 | (-1e-10, 0.9999999994989365), 93 | (-1e-09, 0.9999999949893649), 94 | (-1e-08, 0.999_999_94), 95 | (-1e-07, 0.999_999_5), 96 | (-1e-06, 0.999_995), 97 | (-1e-05, 0.999_949_9), 98 | (-1e-04, 0.999_499_1), 99 | (-0.001, 0.995_001_9), 100 | (-0.01, 0.951_128_24), 101 | (-0.1, 0.605_885_9), 102 | (-1.0, 0.006_666_667), 103 | (-10.0, 1.734_153e-22), 104 | (-100.0, 0e0), 105 | (-1000.0, 0.0), 106 | (-10000.0, 0.0), 107 | (-100000.0, 0.0), 108 | (-1000000.0, 0.0), 109 | (-10000000.0, 0.0), 110 | (-100000000.0, 0.0), 111 | (-1000000000.0, 0.0), 112 | (-10000000000.0, 0.0), 113 | (-100000000000.0, 0.0), 114 | (-1000000000000.0, 0.0), 115 | (-10000000000000.0, 0.0), 116 | (-100000000000000.0, 0.0), 117 | (-1000000000000000.0, 0.0), 118 | (-1e+16, 0.0), 119 | (-1e+17, 0.0), 120 | (-1e+18, 0.0), 121 | (-1e+19, 0.0), 122 | (1e-20, 1.0), 123 | (1e-19, 1.0), 124 | (1e-18, 1.0), 125 | (1e-17, 1.0), 126 | (1e-16, 1.0000000000000004), 127 | (1e-15, 1.000000000000005), 128 | (1e-14, 1.0000000000000502), 129 | (1e-13, 1.0000000000005012), 130 | (1e-12, 1.0000000000050107), 131 | (1e-11, 1.0000000000501064), 132 | (1e-10, 1.0000000005010636), 133 | (1e-09, 1.0000000050106352), 134 | (1e-08, 1.000000050106354), 135 | (1e-07, 1.000_000_5), 136 | (1e-06, 1.000_005), 137 | (1e-05, 1.000_050_1), 138 | (1e-04, 1.000_501_2), 139 | (0.001, 1.005_023_2), 140 | (0.01, 1.051_382_9), 141 | (0.1, 1.650_475_6), 142 | (1.0, 150.0), 143 | (10.0, 5.766_504e21), 144 | ]; 145 | 146 | /// misc powf(x,n) test vectors - `(base_input, power_input, output)` 147 | pub(crate) const TEST_VECTORS_MISC: &[(f32, f32, f32)] = &[ 148 | (-0.5881598, 2.0, 0.345_931_95), 149 | (-0.5881598, 3.2, f32::NAN), 150 | (-0.5881598, 3.0, -0.203_463_27), 151 | (-1000000.0, 4.0, 1e+24), 152 | ]; 153 | 154 | fn calc_relative_error(experimental: F32, expected: f32) -> F32 { 155 | if experimental.is_nan() && expected.is_nan() { 156 | F32::ZERO 157 | } else if expected != 0.0 { 158 | (experimental - expected) / expected 159 | } else { 160 | (experimental - expected) / (expected + 1.0e-20) 161 | } 162 | } 163 | 164 | #[test] 165 | fn sanity_check() { 166 | for &(x, expected) in TEST_VECTORS_POW3 { 167 | let exp_x = F32(3.0).powf(F32(x)); 168 | let relative_error = calc_relative_error(exp_x, expected); 169 | 170 | assert!( 171 | relative_error <= MAX_ERROR, 172 | "relative_error {} too large for input {} : {} vs {}", 173 | relative_error, 174 | x, 175 | exp_x, 176 | expected 177 | ); 178 | } 179 | 180 | for &(x, expected) in TEST_VECTORS_POW150 { 181 | let exp_x = F32(150.0).powf(F32(x)); 182 | let relative_error = calc_relative_error(exp_x, expected); 183 | 184 | assert!( 185 | relative_error <= MAX_ERROR, 186 | "relative_error {} too large for input {} : {} vs {}", 187 | relative_error, 188 | x, 189 | exp_x, 190 | expected 191 | ); 192 | } 193 | 194 | for &(base_input, power_input, expected) in TEST_VECTORS_MISC { 195 | let exp_x = F32(base_input).powf(F32(power_input)); 196 | let relative_error = calc_relative_error(exp_x, expected); 197 | 198 | assert!( 199 | relative_error <= MAX_ERROR, 200 | "relative_error {} too large for input {}.powf({}) : {} vs {}", 201 | relative_error, 202 | base_input, 203 | power_input, 204 | exp_x, 205 | expected 206 | ); 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/float/recip.rs: -------------------------------------------------------------------------------- 1 | //! Takes the reciprocal (inverse) of a number, `1/x`. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Returns the reciprocal (inverse) of a number, `1/x`. 7 | pub fn recip(self) -> Self { 8 | let mut x = self; 9 | 10 | let sx = if x < 0.0 { F32(-1.0) } else { F32(1.0) }; 11 | x *= sx; 12 | 13 | let mut v = F32(f32::from_bits(0x7EF1_27EAu32.wrapping_sub(x.to_bits()))); 14 | let w = x * v; 15 | 16 | // v.0 *= 2.0 - w; 17 | // v.0 *= 4.0 + w * (-6.0 + w * (4.0 - w)); 18 | v.0 *= 19 | 8.0 + w * (-28.0 + w * (56.0 + w * (-70.0 + w * (56.0 + w * (-28.0 + w * (8.0 - w)))))); 20 | 21 | v.0 * sx 22 | } 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use super::F32; 28 | 29 | pub(crate) const MAX_ERROR: f32 = 1e-5; 30 | 31 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 32 | (0.00001, 100000.0), 33 | (1.0, 1.0), 34 | (2.0, 0.5), 35 | (0.25, 4.0), 36 | (-0.5, -2.0), 37 | (core::f32::consts::PI, 1.0 / core::f32::consts::PI), 38 | ]; 39 | 40 | #[test] 41 | fn sanity_check() { 42 | assert_eq!(F32(0.0).recip(), F32(core::f32::INFINITY)); 43 | assert_eq!(F32(-0.0).recip(), F32(core::f32::NEG_INFINITY)); 44 | 45 | for &(x, expected) in TEST_VECTORS { 46 | let recip_x = F32(x).recip(); 47 | let relative_error = (recip_x - expected).abs() / expected; 48 | 49 | assert!( 50 | relative_error <= MAX_ERROR, 51 | "relative_error {} too large for input {} : {} vs {}", 52 | relative_error, 53 | x, 54 | recip_x, 55 | expected 56 | ); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/float/rem_euclid.rs: -------------------------------------------------------------------------------- 1 | //! Calculate Euclidian remainder for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Calculates the least non-negative remainder of `self (mod rhs)`. 7 | pub fn rem_euclid(self, rhs: Self) -> Self { 8 | let r = self % rhs; 9 | 10 | if r >= Self::ZERO { 11 | r 12 | } else { 13 | r + rhs.abs() 14 | } 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use super::F32; 21 | 22 | #[test] 23 | fn sanity_check() { 24 | let a = F32(7.0); 25 | let b = F32(4.0); 26 | 27 | assert_eq!(a.rem_euclid(b), F32(3.0)); 28 | assert_eq!((-a).rem_euclid(b), F32(1.0)); 29 | assert_eq!(a.rem_euclid(-b), F32(3.0)); 30 | assert_eq!((-a).rem_euclid(-b), F32(1.0)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/float/round.rs: -------------------------------------------------------------------------------- 1 | //! Round a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Returns the nearest integer to a number. 7 | pub fn round(self) -> Self { 8 | Self(((self.0 + Self(0.5).copysign(self).0) as i32) as f32) 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | #[test] 17 | fn sanity_check() { 18 | assert_eq!(F32(0.0).round(), F32(0.0)); 19 | assert_eq!(F32(-0.0).round(), F32(-0.0)); 20 | 21 | assert_eq!(F32(0.49999).round(), F32(0.0)); 22 | assert_eq!(F32(-0.49999).round(), F32(-0.0)); 23 | 24 | assert_eq!(F32(0.5).round(), F32(1.0)); 25 | assert_eq!(F32(-0.5).round(), F32(-1.0)); 26 | 27 | assert_eq!(F32(9999.499).round(), F32(9999.0)); 28 | assert_eq!(F32(-9999.499).round(), F32(-9999.0)); 29 | 30 | assert_eq!(F32(9999.5).round(), F32(10000.0)); 31 | assert_eq!(F32(-9999.5).round(), F32(-10000.0)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/float/signum.rs: -------------------------------------------------------------------------------- 1 | //! Returns a number that represents the sign of `self`. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Returns a number that represents the sign of `self`. 7 | /// 8 | /// * `1.0` if the number is positive, `+0.0` or `INFINITY` 9 | /// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY` 10 | /// * `NAN` if the number is `NAN` 11 | pub fn signum(self) -> Self { 12 | if self.is_nan() { 13 | Self::NAN 14 | } else { 15 | F32(1.0).copysign(self) 16 | } 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | mod tests { 22 | use super::F32; 23 | 24 | #[test] 25 | fn sanity_check() { 26 | assert_eq!(F32::INFINITY.signum(), F32(1.0)); 27 | assert_eq!(F32(0.0).signum(), F32(1.0)); 28 | assert_eq!(F32(1.0).signum(), F32(1.0)); 29 | assert_eq!(F32::NEG_INFINITY.signum(), F32(-1.0)); 30 | assert_eq!(F32(-0.0).signum(), F32(-1.0)); 31 | assert_eq!(F32(-1.0).signum(), F32(-1.0)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/float/sin.rs: -------------------------------------------------------------------------------- 1 | //! Sine approximation, implemented in terms of `cos(x)`. 2 | 3 | use super::F32; 4 | use core::f32::consts::PI; 5 | 6 | impl F32 { 7 | /// Approximates `sin(x)` in radians with a maximum error of `0.002`. 8 | pub fn sin(self) -> Self { 9 | (self - PI / 2.0).cos() 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::F32; 16 | use crate::float::cos::tests::MAX_ERROR; 17 | 18 | /// Sine test vectors - `(input, output)` 19 | const TEST_VECTORS: &[(f32, f32)] = &[ 20 | (0.000, 0.000), 21 | (0.140, 0.139), 22 | (0.279, 0.276), 23 | (0.419, 0.407), 24 | (0.559, 0.530), 25 | (0.698, 0.643), 26 | (0.838, 0.743), 27 | (0.977, 0.829), 28 | (1.117, 0.899), 29 | (1.257, 0.951), 30 | (1.396, 0.985), 31 | (1.536, 0.999), 32 | (1.676, 0.995), 33 | (1.815, 0.970), 34 | (1.955, 0.927), 35 | (2.094, 0.866), 36 | (2.234, 0.788), 37 | (2.374, 0.695), 38 | (2.513, 0.588), 39 | (2.653, 0.469), 40 | (2.793, 0.342), 41 | (2.932, 0.208), 42 | (3.072, 0.070), 43 | (3.211, -0.070), 44 | (3.351, -0.208), 45 | (3.491, -0.342), 46 | (3.630, -0.469), 47 | (3.770, -0.588), 48 | (3.910, -0.695), 49 | (4.049, -0.788), 50 | (4.189, -0.866), 51 | (4.328, -0.927), 52 | (4.468, -0.970), 53 | (4.608, -0.995), 54 | (4.747, -0.999), 55 | (4.887, -0.985), 56 | (5.027, -0.951), 57 | (5.166, -0.899), 58 | (5.306, -0.829), 59 | (5.445, -0.743), 60 | (5.585, -0.643), 61 | (5.725, -0.530), 62 | (5.864, -0.407), 63 | (6.004, -0.276), 64 | (6.144, -0.139), 65 | (6.283, 0.000), 66 | ]; 67 | 68 | #[test] 69 | fn sanity_check() { 70 | for &(x, expected) in TEST_VECTORS { 71 | let sin_x = F32(x).sin(); 72 | let delta = (sin_x - expected).abs(); 73 | 74 | assert!( 75 | delta <= MAX_ERROR, 76 | "delta {} too large: {} vs {}", 77 | delta, 78 | sin_x, 79 | expected 80 | ); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/float/sin_cos.rs: -------------------------------------------------------------------------------- 1 | //! Simultaneously computes the sine and cosine of the number, `x`. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Simultaneously computes the sine and cosine of the number, `x`. 7 | /// Returns `(sin(x), cos(x))`. 8 | pub fn sin_cos(self) -> (Self, Self) { 9 | (self.sin(), self.cos()) 10 | } 11 | } 12 | 13 | #[cfg(test)] 14 | mod tests { 15 | use super::F32; 16 | 17 | const TEST_VECTORS: &[f32] = &[ 18 | 0.000, 0.140, 0.279, 0.419, 0.559, 0.698, 0.838, 0.977, 1.117, 1.257, 1.396, 1.536, 1.676, 19 | 1.815, 1.955, 2.094, 2.234, 2.374, 2.513, 2.653, 2.793, 2.932, 3.072, 3.211, 3.351, 3.491, 20 | 3.630, 3.770, 3.910, 4.049, 4.189, 4.328, 4.468, 4.608, 4.747, 4.887, 5.027, 5.166, 5.306, 21 | 5.445, 5.585, 5.725, 5.864, 6.004, 6.144, 6.283, 22 | ]; 23 | 24 | #[test] 25 | fn sanity_check() { 26 | for &x in TEST_VECTORS { 27 | let sin_x = F32(x).sin(); 28 | let cos_x = F32(x).cos(); 29 | 30 | assert_eq!(F32(x).sin_cos(), (sin_x, cos_x)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/float/sqrt.rs: -------------------------------------------------------------------------------- 1 | //! Square root approximation function for a single-precision float. 2 | //! 3 | //! Method described at: 4 | 5 | use super::F32; 6 | 7 | impl F32 { 8 | /// Approximates the square root of a number with an average deviation of ~5%. 9 | /// 10 | /// Returns [`Self::NAN`] if `self` is a negative number. 11 | pub fn sqrt(self) -> Self { 12 | if self >= Self::ZERO { 13 | Self::from_bits((self.to_bits() + 0x3f80_0000) >> 1) 14 | } else { 15 | Self::NAN 16 | } 17 | } 18 | } 19 | 20 | #[cfg(test)] 21 | pub(crate) mod tests { 22 | use super::F32; 23 | 24 | /// Deviation from the actual value (5%) 25 | pub(crate) const MAX_ERROR: f32 = 0.05; 26 | 27 | /// Square root test vectors - `(input, output)` 28 | pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[ 29 | (1.0, 1.0), 30 | (2.0, 1.414), 31 | (3.0, 1.732), 32 | (4.0, 2.0), 33 | (5.0, 2.236), 34 | (10.0, 3.162), 35 | (100.0, 10.0), 36 | (250.0, 15.811), 37 | (500.0, 22.36), 38 | (1000.0, 31.622), 39 | (2500.0, 50.0), 40 | (5000.0, 70.710), 41 | (1000000.0, 1000.0), 42 | (2500000.0, 1581.138), 43 | (5000000.0, 2236.067), 44 | (10000000.0, 3162.277), 45 | (25000000.0, 5000.0), 46 | (50000000.0, 7071.067), 47 | (100000000.0, 10000.0), 48 | ]; 49 | 50 | #[test] 51 | fn sanity_check() { 52 | for &(x, expected) in TEST_VECTORS { 53 | let sqrt_x = F32(x).sqrt(); 54 | let allowed_delta = x * MAX_ERROR; 55 | let actual_delta = sqrt_x - expected; 56 | 57 | assert!( 58 | actual_delta <= allowed_delta, 59 | "delta {} too large: {} vs {}", 60 | actual_delta, 61 | sqrt_x, 62 | expected 63 | ); 64 | } 65 | } 66 | 67 | #[test] 68 | fn negative_is_nan() { 69 | assert!(F32(-1.0).sqrt().is_nan()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/float/tan.rs: -------------------------------------------------------------------------------- 1 | //! Tangent approximation for a single-precision float. 2 | 3 | use super::F32; 4 | 5 | impl F32 { 6 | /// Approximates `tan(x)` in radians with a maximum error of `0.6`. 7 | pub fn tan(self) -> Self { 8 | self.sin() / self.cos() 9 | } 10 | } 11 | 12 | #[cfg(test)] 13 | mod tests { 14 | use super::F32; 15 | 16 | /// Maximum error in radians 17 | // TODO(tarcieri): this is kinda bad, find a better approximation? 18 | const MAX_ERROR: f32 = 0.6; 19 | 20 | /// Tangent test vectors - `(input, output)` 21 | const TEST_VECTORS: &[(f32, f32)] = &[ 22 | (0.000, 0.000), 23 | (0.140, 0.141), 24 | (0.279, 0.287), 25 | (0.419, 0.445), 26 | (0.559, 0.625), 27 | (0.698, 0.839), 28 | (0.838, 1.111), 29 | (0.977, 1.483), 30 | (1.117, 2.050), 31 | (1.257, 3.078), 32 | (1.396, 5.671), 33 | (1.536, 28.636), 34 | (1.676, -9.514), 35 | (1.815, -4.011), 36 | (1.955, -2.475), 37 | (2.094, -1.732), 38 | (2.234, -1.280), 39 | (2.374, -0.966), 40 | (2.513, -0.727), 41 | (2.653, -0.532), 42 | (2.793, -0.364), 43 | (2.932, -0.213), 44 | (3.072, -0.070), 45 | (3.211, 0.070), 46 | (3.351, 0.213), 47 | (3.491, 0.364), 48 | (3.630, 0.532), 49 | (3.770, 0.727), 50 | (3.910, 0.966), 51 | (4.049, 1.280), 52 | (4.189, 1.732), 53 | (4.328, 2.475), 54 | (4.468, 4.011), 55 | (4.608, 9.514), 56 | (4.747, -28.636), 57 | (4.887, -5.671), 58 | (5.027, -3.078), 59 | (5.166, -2.050), 60 | (5.306, -1.483), 61 | (5.445, -1.111), 62 | (5.585, -0.839), 63 | (5.725, -0.625), 64 | (5.864, -0.445), 65 | (6.004, -0.287), 66 | (6.144, -0.141), 67 | (6.283, 0.000), 68 | ]; 69 | 70 | #[test] 71 | fn sanity_check() { 72 | for &(x, expected) in TEST_VECTORS { 73 | let tan_x = F32(x).tan(); 74 | let delta = (tan_x - expected).abs(); 75 | 76 | assert!( 77 | delta <= MAX_ERROR, 78 | "delta {} too large: {} vs {}", 79 | delta, 80 | tan_x, 81 | expected 82 | ); 83 | } 84 | } 85 | 86 | #[test] 87 | fn zero() { 88 | assert_eq!(F32::ZERO.tan(), F32::ZERO); 89 | } 90 | 91 | #[test] 92 | fn nan() { 93 | assert!(F32::NAN.tan().is_nan()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/float/trunc.rs: -------------------------------------------------------------------------------- 1 | //! Floating point whole number for a single-precision float. 2 | 3 | use super::{F32, MANTISSA_MASK}; 4 | 5 | impl F32 { 6 | /// Returns the integer part of a number. 7 | pub fn trunc(self) -> Self { 8 | let x_bits = self.to_bits(); 9 | let exponent = self.extract_exponent_value(); 10 | 11 | // exponent is negative, there is no whole number, just return zero 12 | if exponent < 0 { 13 | return F32::ZERO.copysign(self); 14 | } 15 | 16 | let exponent_clamped = i32::max(exponent, 0) as u32; 17 | 18 | // find the part of the fraction that would be left over 19 | let fractional_part = x_bits.overflowing_shl(exponent_clamped).0 & MANTISSA_MASK; 20 | 21 | // if there isn't a fraction we can just return the whole thing. 22 | if fractional_part == 0_u32 { 23 | return self; 24 | } 25 | 26 | let fractional_mask = fractional_part.overflowing_shr(exponent_clamped).0; 27 | 28 | Self::from_bits(x_bits & !fractional_mask) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::F32; 35 | 36 | #[test] 37 | fn sanity_check() { 38 | assert_eq!(F32(-1.1).trunc(), F32(-1.0)); 39 | assert_eq!(F32(-0.1).trunc(), F32(-0.0)); 40 | assert_eq!(F32(0.0).trunc(), F32(0.0)); 41 | assert_eq!(F32(1.0).trunc(), F32(1.0)); 42 | assert_eq!(F32(1.1).trunc(), F32(1.0)); 43 | assert_eq!(F32(2.9).trunc(), F32(2.0)); 44 | 45 | assert_eq!(F32(-100_000_000.13425345345).trunc(), F32(-100_000_000.0)); 46 | assert_eq!(F32(100_000_000.13425345345).trunc(), F32(100_000_000.0)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Embedded-friendly (i.e. `#![no_std]`) math library featuring fast, safe 2 | //! floating point approximations for common arithmetic operations, as well as 3 | //! 2D and 3D vector types, statistical analysis functions, and quaternions. 4 | //! 5 | //! ## Floating point approximations: `F32` and `F32Ext` 6 | //! 7 | //! `micromath` supports approximating many arithmetic operations on `f32` 8 | //! using bitwise operations, providing great performance and small code size 9 | //! at the cost of precision. For use cases like graphics and signal 10 | //! processing, these approximations are often sufficient and the performance 11 | //! gains worth the lost precision. 12 | //! 13 | //! These approximations are defined on the [`F32`] newtype wrapper. 14 | //! 15 | //! ### `F32Ext` extension trait 16 | //! 17 | //! Floating point approximations can used via the the [`F32Ext`] trait which 18 | //! is impl'd for `f32`, providing a drop-in `std`-compatible API. 19 | //! 20 | //! ``` 21 | //! use micromath::F32Ext; 22 | //! 23 | //! let n = 2.0.sqrt(); 24 | //! assert_eq!(n, 1.5); // close enough 25 | //! ``` 26 | //! 27 | //! Since the `F32Ext` trait provides methods which are already defined in 28 | //! `std`, in cases where your crate links `std` the `F32Ext` versions of 29 | //! the same methods will not be used, in which case you will get an unused 30 | //! import warning for `F32Ext`. 31 | //! 32 | //! If you encounter this, add an `#[allow(unused_imports)]` above the import. 33 | //! 34 | //! ``` 35 | //! #[allow(unused_imports)] 36 | //! use micromath::F32Ext; 37 | //! ``` 38 | //! 39 | //! ## Vector types 40 | //! 41 | //! See the [`vector`] module for more information on vector types. 42 | //! 43 | //! The following vector types are available, all of which have `pub x` and 44 | //! `pub y` (and on 3D vectors, `pub z`) members: 45 | //! 46 | //! | Rust | 2D | 3D | 47 | //! |-------|---------|---------| 48 | //! | `i8` | `I8x2` | `I8x3` | 49 | //! | `i16` | `I16x2` | `I16x3` | 50 | //! | `i32` | `I32x2` | `I32x3` | 51 | //! | `u8` | `U8x2` | `U8x3` | 52 | //! | `u16` | `U16x2` | `U16x3` | 53 | //! | `u32` | `U32x2` | `U32x3` | 54 | //! | `f32` | `F32x2` | `F32x3` | 55 | //! 56 | //! ## Statistical analysis 57 | //! 58 | //! See the [`statistics`] module for more information on statistical analysis 59 | //! traits and functionality. 60 | //! 61 | //! The following traits are available and impl'd for slices and iterators of 62 | //! `f32` (and can be impl'd for other types): 63 | //! 64 | //! - [`Mean`][`statistics::Mean`] - compute arithmetic mean with the `mean()` method. 65 | //! - [`StdDev`][`statistics::StdDev`] - compute standard deviation with the `stddev()` method. 66 | //! - [`Trim`][`statistics::Trim`] - cull outliers from a sample slice with the `trim()` method. 67 | //! - [`Variance`][`statistics::Variance`] - compute variance with the `variance()` method. 68 | 69 | #![no_std] 70 | #![cfg_attr(docsrs, feature(doc_cfg))] 71 | #![doc( 72 | html_logo_url = "https://raw.githubusercontent.com/tarcieri/micromath/main/img/micromath-sq.png", 73 | html_root_url = "https://docs.rs/micromath/2.0.0" 74 | )] 75 | #![forbid(unsafe_code)] 76 | #![warn( 77 | missing_docs, 78 | rust_2018_idioms, 79 | trivial_casts, 80 | trivial_numeric_casts, 81 | unused_qualifications 82 | )] 83 | 84 | #[cfg(feature = "statistics")] 85 | #[cfg_attr(docsrs, doc(cfg(feature = "statistics")))] 86 | pub mod statistics; 87 | 88 | #[cfg(feature = "vector")] 89 | #[cfg_attr(docsrs, doc(cfg(feature = "vector")))] 90 | pub mod vector; 91 | 92 | mod f32ext; 93 | mod float; 94 | #[cfg(feature = "quaternion")] 95 | mod quaternion; 96 | 97 | pub use crate::{f32ext::F32Ext, float::F32}; 98 | 99 | #[cfg(feature = "quaternion")] 100 | pub use crate::quaternion::Quaternion; 101 | 102 | #[cfg(feature = "num-traits")] 103 | #[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))] 104 | pub use num_traits; 105 | -------------------------------------------------------------------------------- /src/quaternion.rs: -------------------------------------------------------------------------------- 1 | //! Adapted from the `madgwick` crate: 2 | //! Copyright (c) 2018 Jorge Aparicio 3 | //! 4 | //! Original sources dual licensed under your choice of the Apache 2.0 5 | //! and/or MIT licenses, which matches this crate's licensing terms. 6 | //! 7 | //! See toplevel LICENSE-MIT for more information on the MIT license. 8 | //! Apache 2.0 license follows: 9 | //! 10 | //! Licensed under the Apache License, Version 2.0 (the "License"); 11 | //! you may not use this file except in compliance with the License. 12 | //! You may obtain a copy of the License at: 13 | //! 14 | //! 15 | //! 16 | //! Unless required by applicable law or agreed to in writing, software 17 | //! distributed under the License is distributed on an "AS IS" BASIS, 18 | //! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | //! See the License for the specific language governing permissions and 20 | //! limitations under the License. 21 | 22 | use crate::F32; 23 | use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; 24 | 25 | #[cfg(feature = "vector")] 26 | use crate::vector::{Component, F32x3, Vector3d}; 27 | 28 | /// Quaternions are a number system that extends the complex numbers which can 29 | /// be used for efficiently computing spatial rotations. 30 | /// 31 | /// They're computed as the quotient of two directed lines in a 32 | /// three-dimensional space, or equivalently as the quotient of two vectors. 33 | /// 34 | /// For given real numbers `a`, `b`, `c`, and `d`, they take the form: 35 | /// 36 | /// `a + bi + cj + dk` 37 | /// 38 | /// where `i`, `j`, and `k` are the fundamental quaternion units: 39 | /// 40 | /// `i² = j² = k² = i*j*k = -1` 41 | /// 42 | /// Quaternion multiplication is non-commutative: 43 | /// 44 | /// | x | 1 | i | j | k | 45 | /// |---|----|----|----|----| 46 | /// | 1 | 1 | i | j | k | 47 | /// | i | i | -1 | k | -j | 48 | /// | j | j | -k | -1 | i | 49 | /// | k | k | j | -i | -1 | 50 | #[cfg_attr(docsrs, doc(cfg(feature = "quaternion")))] 51 | #[derive(Clone, Copy, Debug, PartialEq)] 52 | pub struct Quaternion(f32, f32, f32, f32); 53 | 54 | impl Quaternion { 55 | /// Identity quaternion. 56 | pub const IDENTITY: Self = Self(1.0, 0.0, 0.0, 0.0); 57 | 58 | /// Create a new quaternion. 59 | pub const fn new(a: f32, b: f32, c: f32, d: f32) -> Self { 60 | Self(a, b, c, d) 61 | } 62 | 63 | /// Get the quaternion that represents the smallest rotation between two vectors. 64 | #[cfg(feature = "vector")] 65 | pub fn from_two_vectors(u: Vector3d, v: Vector3d) -> Self 66 | where 67 | C: Component + Into, 68 | { 69 | // Implementation from http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final 70 | let n_uv = F32(u.dot(u).into() * v.dot(v).into()).sqrt(); 71 | let mut realpart = n_uv + u.dot(v).into(); 72 | 73 | let w = if realpart < 1e-6 * n_uv { 74 | realpart = F32(0.); 75 | 76 | if F32(u.x.into()).abs() > F32(u.z.into()).abs() { 77 | Vector3d { 78 | x: -u.y.into(), 79 | y: u.x.into(), 80 | z: 0., 81 | } 82 | } else { 83 | Vector3d { 84 | x: 0., 85 | y: -u.z.into(), 86 | z: u.y.into(), 87 | } 88 | } 89 | } else { 90 | F32x3 { 91 | x: u.x.into(), 92 | y: u.y.into(), 93 | z: u.z.into(), 94 | } * F32x3 { 95 | x: v.x.into(), 96 | y: v.y.into(), 97 | z: v.z.into(), 98 | } 99 | }; 100 | 101 | let q = Quaternion(realpart.0, w.x, w.y, w.z); 102 | let n = F32(q.norm()).invsqrt(); 103 | q * n.0 104 | } 105 | 106 | /// Returns the conjugate of this quaternion. 107 | pub fn conj(self) -> Self { 108 | Quaternion(self.0, -self.1, -self.2, -self.3) 109 | } 110 | 111 | /// Returns the dot product of this quaternion. 112 | pub fn dot(self, rhs: Self) -> f32 { 113 | self.0 * rhs.0 + self.1 * rhs.1 + self.2 * rhs.2 + self.3 * rhs.3 114 | } 115 | 116 | /// Compute the inverse of this quaternion. 117 | /// 118 | /// Panics if [`Quaternion::norm`] is zero. 119 | pub fn inv(self) -> Self { 120 | let norm = self.norm(); 121 | assert_ne!(norm, 0.0, "quaternion norm is zero"); 122 | self.conj() * F32(norm).inv().0 123 | } 124 | 125 | /// Compute the magnitude (a.k.a length) of this quaternion. 126 | pub fn magnitude(self) -> f32 { 127 | F32(self.norm()).sqrt().0 128 | } 129 | 130 | /// Returns the norm of this quaternion, i.e. `a²+b²+c²+d²`. 131 | /// 132 | /// 133 | pub fn norm(self) -> f32 { 134 | self.0 * self.0 + self.1 * self.1 + self.2 * self.2 + self.3 * self.3 135 | } 136 | 137 | /// Compute a quaternion for the given axis vector and angle. 138 | #[cfg_attr(docsrs, doc(cfg(feature = "vector")))] 139 | pub fn axis_angle(v: Vector3d, theta: C) -> Self 140 | where 141 | C: Component + Into, 142 | { 143 | let half_theta = F32(theta.into() * 0.5); 144 | 145 | // TODO(tarcieri): refactor `Quaternion` to be (f32 + F32x3) 146 | let v = F32x3 { 147 | x: v.x.into(), 148 | y: v.y.into(), 149 | z: v.z.into(), 150 | } * half_theta.sin().0; 151 | 152 | Self(half_theta.cos().0, v.x, v.y, v.z) 153 | } 154 | 155 | /// Rotate a 3D vector using this quaternion, assumes the quaternion is of unit length. 156 | #[cfg_attr(docsrs, doc(cfg(feature = "vector")))] 157 | pub fn rotate(self, v: Vector3d) -> F32x3 158 | where 159 | C: Component + Into, 160 | { 161 | let Quaternion(qw, qx, qy, qz) = self; 162 | 163 | let qwsq = qw * qw; 164 | let qxsq = qx * qx; 165 | let qysq = qy * qy; 166 | let qzsq = qz * qz; 167 | 168 | let x = (qwsq + qxsq - qysq - qzsq) * v.x.into() 169 | + (2. * (qx * qy - qw * qz)) * v.y.into() 170 | + (2. * (qx * qz + qw * qy)) * v.z.into(); 171 | 172 | let y = (2. * (qx * qy + qw * qz)) * v.x.into() 173 | + (qwsq - qxsq + qysq - qzsq) * v.y.into() 174 | + (2. * (qy * qz - qw * qx)) * v.z.into(); 175 | 176 | let z = (2. * (qx * qz - qw * qy)) * v.x.into() 177 | + (2. * (qy * qz + qw * qx)) * v.y.into() 178 | + (qwsq - qxsq - qysq + qzsq) * v.z.into(); 179 | 180 | F32x3 { x, y, z } 181 | } 182 | 183 | /// Scale by a scalar. 184 | pub fn scale(self, scalar: S) -> Self 185 | where 186 | S: Into, 187 | { 188 | let k = scalar.into(); 189 | Self(self.0 * k, self.1 * k, self.2 * k, self.3 * k) 190 | } 191 | 192 | /// Normalize the quaternion. 193 | pub fn normalize(self) -> Self { 194 | let norm = self.norm(); 195 | assert_ne!(norm, 0.0, "quaternion norm is zero"); 196 | let n = F32(norm).invsqrt(); 197 | 198 | self.scale(n) 199 | } 200 | 201 | /// Get the (roll, pitch, yaw) Euler angles, assumes the quaternion is normalized. 202 | pub fn to_euler(&self) -> (f32, f32, f32) { 203 | let r = F32(2. * (self.0 * self.1 + self.2 * self.3)) 204 | .atan2(F32(1. - 2. * (self.1 * self.1 + self.2 * self.2))); 205 | let p = F32(2. * (self.0 * self.2 - self.1 * self.3)).asin(); 206 | let y = F32(2. * (self.0 * self.3 + self.1 * self.2)) 207 | .atan2(F32(1. - 2. * (self.2 * self.2 + self.3 * self.3))); 208 | 209 | (r.0, p.0, y.0) 210 | } 211 | 212 | /// Convert this quaternion into an array. 213 | pub fn to_array(&self) -> [f32; 4] { 214 | [self.0, self.1, self.2, self.3] 215 | } 216 | 217 | /// Access the `w` / `a` real component 218 | pub fn w(&self) -> f32 { 219 | self.0 220 | } 221 | 222 | /// Access the `x` / `b` / `i` imaginary component 223 | pub fn x(&self) -> f32 { 224 | self.1 225 | } 226 | 227 | /// Access the `y` / `c` / `j` imaginary component 228 | pub fn y(&self) -> f32 { 229 | self.2 230 | } 231 | 232 | /// Access the `z` / `d` / `k` imaginary component 233 | pub fn z(&self) -> f32 { 234 | self.3 235 | } 236 | } 237 | 238 | impl Add for Quaternion { 239 | type Output = Self; 240 | 241 | fn add(self, rhs: Self) -> Self { 242 | Self( 243 | self.0 + rhs.0, 244 | self.1 + rhs.1, 245 | self.2 + rhs.2, 246 | self.3 + rhs.3, 247 | ) 248 | } 249 | } 250 | 251 | impl AddAssign for Quaternion { 252 | fn add_assign(&mut self, rhs: Self) { 253 | *self = *self + rhs; 254 | } 255 | } 256 | 257 | impl Default for Quaternion { 258 | fn default() -> Self { 259 | Self::IDENTITY 260 | } 261 | } 262 | 263 | impl From<(f32, f32, f32, f32)> for Quaternion { 264 | fn from(q: (f32, f32, f32, f32)) -> Quaternion { 265 | Self::new(q.0, q.1, q.2, q.3) 266 | } 267 | } 268 | 269 | impl From for (f32, f32, f32, f32) { 270 | fn from(q: Quaternion) -> (f32, f32, f32, f32) { 271 | (q.0, q.1, q.2, q.3) 272 | } 273 | } 274 | 275 | impl From<[f32; 4]> for Quaternion { 276 | fn from(q: [f32; 4]) -> Quaternion { 277 | Self::new(q[0], q[1], q[2], q[3]) 278 | } 279 | } 280 | 281 | impl From for [f32; 4] { 282 | fn from(q: Quaternion) -> [f32; 4] { 283 | q.to_array() 284 | } 285 | } 286 | 287 | impl Mul for Quaternion { 288 | type Output = Self; 289 | 290 | fn mul(self, other: Self) -> Self { 291 | Self( 292 | self.0 * other.0 - self.1 * other.1 - self.2 * other.2 - self.3 * other.3, 293 | self.0 * other.1 + self.1 * other.0 + self.2 * other.3 - self.3 * other.2, 294 | self.0 * other.2 - self.1 * other.3 + self.2 * other.0 + self.3 * other.1, 295 | self.0 * other.3 + self.1 * other.2 - self.2 * other.1 + self.3 * other.0, 296 | ) 297 | } 298 | } 299 | 300 | impl Mul for Quaternion { 301 | type Output = Self; 302 | 303 | fn mul(self, k: f32) -> Self { 304 | self.scale(k) 305 | } 306 | } 307 | 308 | impl Mul for f32 { 309 | type Output = Quaternion; 310 | 311 | fn mul(self, q: Quaternion) -> Quaternion { 312 | q.scale(self) 313 | } 314 | } 315 | 316 | impl MulAssign for Quaternion { 317 | fn mul_assign(&mut self, k: f32) { 318 | *self = *self * k; 319 | } 320 | } 321 | 322 | impl Sub for Quaternion { 323 | type Output = Self; 324 | 325 | fn sub(self, rhs: Self) -> Self { 326 | Self( 327 | self.0 - rhs.0, 328 | self.1 - rhs.1, 329 | self.2 - rhs.2, 330 | self.3 - rhs.3, 331 | ) 332 | } 333 | } 334 | 335 | impl SubAssign for Quaternion { 336 | fn sub_assign(&mut self, rhs: Self) { 337 | *self = *self - rhs; 338 | } 339 | } 340 | 341 | #[cfg(feature = "vector")] 342 | #[cfg_attr(docsrs, doc(cfg(feature = "vector")))] 343 | impl From> for Quaternion 344 | where 345 | C: Component + Into, 346 | { 347 | fn from(v: Vector3d) -> Quaternion { 348 | Self(0.0, v.x.into(), v.y.into(), v.z.into()) 349 | } 350 | } 351 | 352 | #[cfg(feature = "vector")] 353 | #[cfg_attr(docsrs, doc(cfg(feature = "vector")))] 354 | impl Mul> for Quaternion 355 | where 356 | C: Component + Into, 357 | { 358 | type Output = F32x3; 359 | 360 | fn mul(self, v: Vector3d) -> F32x3 { 361 | self.rotate(v) 362 | } 363 | } 364 | 365 | #[cfg(feature = "defmt")] 366 | #[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] 367 | impl defmt::Format for Quaternion { 368 | fn format(&self, fmt: defmt::Formatter<'_>) { 369 | defmt::write!(fmt, "({}, {}, {}, {})", self.0, self.1, self.2, self.3) 370 | } 371 | } 372 | 373 | #[cfg(test)] 374 | mod tests { 375 | use super::Quaternion; 376 | use crate::vector::Vector3d; 377 | use crate::F32Ext; 378 | 379 | const MAX_ERROR: f32 = 0.05; 380 | 381 | #[test] 382 | fn conj_test() { 383 | let q = Quaternion(1.0, 2.0, 3.0, 4.0); 384 | assert_eq!(q.conj(), Quaternion(1.0, -2.0, -3.0, -4.0)); 385 | } 386 | 387 | #[test] 388 | fn norm_test() { 389 | let q = Quaternion(1.0, 2.0, 3.0, 4.0); 390 | assert_eq!(q.norm(), 30.0); 391 | 392 | let n = q.norm().invsqrt(); 393 | let r = q * n; 394 | 395 | // The magnitude of the norm should be 1.0 396 | let allowed_delta = 1.0 * MAX_ERROR; 397 | let actual_delta = (r.norm() - 1.0).abs(); 398 | 399 | assert!( 400 | actual_delta <= allowed_delta, 401 | "delta {} too large: {} vs {}", 402 | actual_delta, 403 | r.norm(), 404 | 1.0 405 | ); 406 | } 407 | 408 | #[test] 409 | fn add_assign() { 410 | let mut q = Quaternion(1.0, 2.0, 3.0, 4.0); 411 | q += Quaternion(1.0, 2.0, 3.0, 4.0); 412 | assert_eq!(q, Quaternion(2.0, 4.0, 6.0, 8.0)); 413 | } 414 | 415 | #[test] 416 | fn mul_assign() { 417 | let mut q = Quaternion(1.0, 2.0, 3.0, 4.0); 418 | q *= 2.0; 419 | assert_eq!(q, Quaternion(2.0, 4.0, 6.0, 8.0)); 420 | } 421 | 422 | #[test] 423 | fn sub_assign() { 424 | let mut q = Quaternion(2.0, 4.0, 6.0, 8.0); 425 | q -= Quaternion(1.0, 2.0, 3.0, 4.0); 426 | assert_eq!(q, Quaternion(1.0, 2.0, 3.0, 4.0)); 427 | } 428 | 429 | #[test] 430 | fn mul_quaternion() { 431 | let q = Quaternion(1.0, 2.0, 3.0, 4.0); 432 | let r = Quaternion(4.0, 3.0, 2.0, 1.0); 433 | assert_eq!(q * r, Quaternion(-12.0, 6.0, 24.0, 12.0)); 434 | } 435 | 436 | #[test] 437 | fn mul_f32() { 438 | let q = Quaternion(1.0, 2.0, 3.0, 4.0); 439 | let r = 2.0 * q; 440 | assert_eq!(r, Quaternion(2.0, 4.0, 6.0, 8.0)); 441 | 442 | let s = r * 0.5; 443 | assert_eq!(s, Quaternion(1.0, 2.0, 3.0, 4.0)); 444 | } 445 | 446 | #[test] 447 | fn smallest_rot() { 448 | use crate::vector::Vector; 449 | 450 | let v1 = Vector3d { 451 | x: 0., 452 | y: 0., 453 | z: 1., 454 | }; 455 | let v2 = Vector3d { 456 | x: 0., 457 | y: 1., 458 | z: 0., 459 | }; 460 | 461 | let q = Quaternion::from_two_vectors(v1, v2); 462 | let v1_r = q.rotate(v1); 463 | 464 | assert!((v1_r - v2).magnitude() < 1e-1); 465 | } 466 | 467 | #[test] 468 | fn from_tuple() { 469 | let quat: Quaternion = (1.0, 2.0, 3.0, 4.0).into(); 470 | let (a, b, c, d) = quat.into(); 471 | assert_eq!(a, 1.0); 472 | assert_eq!(b, 2.0); 473 | assert_eq!(c, 3.0); 474 | assert_eq!(d, 4.0); 475 | } 476 | 477 | #[test] 478 | fn from_array() { 479 | let quat: Quaternion = [1.0, 2.0, 3.0, 4.0].into(); 480 | let quat: [f32; 4] = quat.into(); 481 | assert_eq!(quat[0], 1.0); 482 | assert_eq!(quat[1], 2.0); 483 | assert_eq!(quat[2], 3.0); 484 | assert_eq!(quat[3], 4.0); 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /src/statistics.rs: -------------------------------------------------------------------------------- 1 | //! Statistical analysis support. 2 | //! 3 | //! The `statistics` Cargo feature must be enabled to use this functionality. 4 | //! 5 | //! The following traits are available and impl'd for slices and iterators of 6 | //! `f32` (and can be impl'd for other types): 7 | //! 8 | //! - [Mean] - compute arithmetic mean with the `mean()` method. 9 | //! - [StdDev] - compute standard deviation with the `stddev()` method 10 | //! - [Trim] - cull outliers from a sample slice with the `trim()` method. 11 | //! - [Variance] - compute variance with the `variance() method. 12 | //! 13 | //! [Mean]: https://docs.rs/micromath/latest/micromath/statistics/trait.Mean.html 14 | //! [StdDev]: https://docs.rs/micromath/latest/micromath/statistics/trait.StdDev.html 15 | //! [Trim]: https://docs.rs/micromath/latest/micromath/statistics/trim/trait.Trim.html 16 | //! [Variance]: https://docs.rs/micromath/latest/micromath/statistics/trait.Variance.html 17 | 18 | mod mean; 19 | mod stddev; 20 | pub mod trim; 21 | mod variance; 22 | 23 | pub use self::{mean::Mean, stddev::StdDev, trim::Trim, variance::Variance}; 24 | -------------------------------------------------------------------------------- /src/statistics/mean.rs: -------------------------------------------------------------------------------- 1 | /// Arithmetic mean 2 | pub trait Mean { 3 | /// Result type 4 | type Result; 5 | 6 | /// Compute the arithmetic mean 7 | fn mean(self) -> Self::Result; 8 | } 9 | 10 | impl Mean for I 11 | where 12 | I: Iterator, 13 | { 14 | type Result = f32; 15 | 16 | fn mean(self) -> f32 { 17 | let mut num_items = 0; 18 | let mut sum = 0.0; 19 | 20 | for item in self { 21 | num_items += 1; 22 | sum += item; 23 | } 24 | 25 | sum / (num_items as f32) 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | use super::Mean; 32 | 33 | #[test] 34 | fn mean_test() { 35 | assert_eq!([1.0, 3.0, 5.0].iter().cloned().mean(), 3.0); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/statistics/stddev.rs: -------------------------------------------------------------------------------- 1 | use super::variance::Variance; 2 | #[allow(unused_imports)] 3 | use crate::F32Ext; 4 | 5 | /// Compute standard deviation 6 | pub trait StdDev { 7 | /// Result type 8 | type Result; 9 | 10 | /// Compute standard deviation 11 | fn stddev(self) -> Self::Result; 12 | } 13 | 14 | impl StdDev for &[T] 15 | where 16 | T: Copy + Into, 17 | { 18 | /// Result type 19 | type Result = f32; 20 | 21 | fn stddev(self) -> f32 { 22 | self.variance().sqrt() 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::StdDev; 29 | 30 | #[test] 31 | fn stddev_test() { 32 | assert_eq!([1.0, 3.0, 5.0].as_ref().stddev(), 2.0); 33 | assert_eq!([1.0, 3.0, 5.0].as_ref().stddev(), 2.0); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/statistics/trim.rs: -------------------------------------------------------------------------------- 1 | //! Iterate over input slices after culling statistical outliers. 2 | 3 | use super::mean::Mean; 4 | #[allow(unused_imports)] 5 | use crate::F32Ext; 6 | use core::{iter, slice}; 7 | 8 | /// Default number of standard deviations at which a value should be 9 | /// considered an outlier. 10 | pub const DEFAULT_THRESHOLD: f32 = 2.0; 11 | 12 | /// Iterate over the given input after culling outliers. 13 | pub trait Trim { 14 | /// Result type 15 | type Result: Copy + Into; 16 | 17 | /// Trim this collection (cull outliers) at the default threshold of 2 standard 18 | /// deviations. 19 | fn trim(&self) -> Iter<'_, Self::Result> { 20 | self.trim_at(DEFAULT_THRESHOLD) 21 | } 22 | 23 | /// Trim this collection (cull outliers) at the specified number of standard deviations. 24 | fn trim_at(&self, threshold: f32) -> Iter<'_, Self::Result>; 25 | } 26 | 27 | impl Trim for &[N] 28 | where 29 | N: Copy, 30 | f32: From, 31 | { 32 | type Result = N; 33 | 34 | fn trim_at(&self, threshold: f32) -> Iter<'_, Self::Result> { 35 | Iter::new(self, threshold) 36 | } 37 | } 38 | 39 | /// A "trimmed" iterator which culls outliers at a given number of standard 40 | /// deviations from the mean. 41 | pub struct Iter<'a, N: Copy> { 42 | /// Iterator over the input values 43 | input: iter::Cloned>, 44 | 45 | /// Arithmetic mean of the input values as a float 46 | mean: f32, 47 | 48 | /// Standard deviation of the input values 49 | stddev: f32, 50 | 51 | /// Number of standard deviations at which values are considered outliers 52 | threshold: f32, 53 | } 54 | 55 | impl<'a, N> Iter<'a, N> 56 | where 57 | N: Copy, 58 | f32: From, 59 | { 60 | /// Create a new trimmed iterator over an input slice. 61 | /// 62 | /// Inputs will be considered outliers at the given `threshold` number 63 | /// of standard deviations (e.g. `2.0`). 64 | pub fn new(input: &'a [N], threshold: f32) -> Self { 65 | let len = input.len() as f32; 66 | let input = input.iter().cloned(); 67 | let input_f32 = input.clone().map(f32::from); 68 | 69 | // TODO(tarcieri): eliminate duplication with mean/variance/stddev in super 70 | let mean = input_f32.clone().mean(); 71 | let sum = input_f32.fold(0.0, |sum, n| { 72 | let n = n - mean; 73 | sum + n * n 74 | }); 75 | let variance = sum / (len - 1.0); 76 | let stddev = variance.sqrt(); 77 | 78 | Self { 79 | input, 80 | mean, 81 | stddev, 82 | threshold, 83 | } 84 | } 85 | } 86 | 87 | impl<'a, N> Iterator for Iter<'a, N> 88 | where 89 | N: Copy, 90 | f32: From, 91 | { 92 | type Item = N; 93 | 94 | fn next(&mut self) -> Option { 95 | while let Some(n) = self.input.next() { 96 | let distance = (f32::from(n) - self.mean).abs(); 97 | 98 | // TODO(tarcieri): better method for finding outliers? (e.g. MAD, IQD) 99 | if (distance / self.stddev) < self.threshold { 100 | return Some(n); 101 | } 102 | } 103 | 104 | None 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::Trim; 111 | 112 | #[test] 113 | fn stddev_test() { 114 | let input: &[f32] = &[1.0, 2.0, 3.0, 999.0, 5.0]; 115 | let mut trimmed = input.trim_at(1.0); 116 | 117 | assert_eq!(trimmed.next().unwrap(), 1.0); 118 | assert_eq!(trimmed.next().unwrap(), 2.0); 119 | assert_eq!(trimmed.next().unwrap(), 3.0); 120 | assert_eq!(trimmed.next().unwrap(), 5.0); 121 | assert_eq!(trimmed.next(), None); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/statistics/variance.rs: -------------------------------------------------------------------------------- 1 | use super::mean::Mean; 2 | 3 | /// Statistical variance 4 | pub trait Variance { 5 | /// Result type 6 | type Result; 7 | 8 | /// Compute the statistical variance 9 | fn variance(self) -> Self::Result; 10 | } 11 | 12 | impl Variance for &[T] 13 | where 14 | T: Copy + Into, 15 | { 16 | /// Result type 17 | type Result = f32; 18 | 19 | fn variance(self) -> f32 { 20 | let mean = self.iter().map(|n| (*n).into()).mean(); 21 | let mut sum = 0.0; 22 | 23 | for item in self { 24 | let n = (*item).into() - mean; 25 | sum += n * n; 26 | } 27 | 28 | sum / (self.len() as f32 - 1.0) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::Variance; 35 | 36 | #[test] 37 | fn variance_test() { 38 | assert_eq!([1.0, 3.0, 5.0].as_ref().variance(), 4.0); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/vector.rs: -------------------------------------------------------------------------------- 1 | //! Algebraic vector types generic over a component type. 2 | //! 3 | //! All vectors types impl the [Vector] trait, and all vector components 4 | //! impl the [Component] trait. 5 | 6 | mod commutative; 7 | mod component; 8 | mod iter; 9 | mod vector2d; 10 | mod vector3d; 11 | 12 | pub use self::{ 13 | component::Component, 14 | iter::Iter, 15 | vector2d::{F32x2, I16x2, I32x2, I8x2, U16x2, U32x2, U8x2, Vector2d}, 16 | vector3d::{F32x3, I16x3, I32x3, I8x3, U16x3, U32x3, U8x3, Vector3d}, 17 | }; 18 | 19 | use core::{fmt::Debug, iter::FromIterator}; 20 | 21 | #[allow(unused_imports)] 22 | use crate::F32Ext; 23 | 24 | /// Algebraic vector generic over a given [`Component`] type. 25 | pub trait Vector: Copy + Debug + Default + FromIterator + Send + Sync 26 | where 27 | C: Component, 28 | { 29 | /// Number of axes 30 | const AXES: usize; 31 | 32 | /// Get the component value for a particular index 33 | fn get(self, index: usize) -> Option; 34 | 35 | /// Compute the dot product of two vectors 36 | fn dot(self, rhs: Self) -> C; 37 | 38 | /// Instantiate a vector from a slice of components. 39 | /// 40 | /// Panics if the slice is not the right size. 41 | fn from_slice(slice: &[C]) -> Self { 42 | Self::from_iter(slice.iter().cloned()) 43 | } 44 | 45 | /// Iterate over the components of this vector 46 | fn iter(&self) -> Iter<'_, Self, C> { 47 | Iter::new(self) 48 | } 49 | 50 | /// Compute the distance between two vectors 51 | fn distance(self, rhs: Self) -> f32 52 | where 53 | C: Into, 54 | { 55 | let differences = self 56 | .iter() 57 | .zip(rhs.iter()) 58 | .map(|(a, b)| a.into() - b.into()); 59 | 60 | differences.map(|n| n * n).sum::().sqrt() 61 | } 62 | 63 | /// Compute the squared magnitude of a vector 64 | fn magnitude_sq(self) -> C 65 | where 66 | C: core::iter::Sum, 67 | { 68 | self.iter().map(|n| n * n).sum() 69 | } 70 | 71 | /// Compute the magnitude of a vector 72 | fn magnitude(self) -> f32 73 | where 74 | C: Into, 75 | { 76 | self.iter() 77 | .map(|n| { 78 | let n = n.into(); 79 | n * n 80 | }) 81 | .sum::() 82 | .sqrt() 83 | } 84 | 85 | /// Returns a normalized version of the vector. 86 | fn normalized(mut self) -> Self 87 | where 88 | Self: FromIterator, 89 | C: Into + From, 90 | { 91 | let norm = self.magnitude(); 92 | self.map(|n| C::from(n.into() / norm)) 93 | } 94 | 95 | /// Applies a function to each element of the vector 96 | /// and returns a new vector of the transformed elements. 97 | fn map(&mut self, map: F) -> Self 98 | where 99 | F: FnMut(C) -> C, 100 | { 101 | Self::from_iter(self.iter().map(map)) 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod tests { 107 | use super::*; 108 | 109 | #[test] 110 | fn magnitude_sq() { 111 | let vec = Vector3d { 112 | x: 3.0, 113 | y: 4.0, 114 | z: 5.0, 115 | }; 116 | let mag = vec.magnitude_sq(); 117 | assert_eq!(mag, 3.0 * 3.0 + 4.0 * 4.0 + 5.0 * 5.0); 118 | } 119 | 120 | #[test] 121 | fn normalized() { 122 | const ERROR: f32 = 1e-6; 123 | let vec = Vector3d { 124 | x: 3.0, 125 | y: 4.0, 126 | z: 5.0, 127 | }; 128 | let norm = vec.magnitude(); 129 | assert!((norm - 7.071068).abs() <= ERROR); 130 | 131 | let normalized = vec.normalized(); 132 | assert!((normalized.x - 0.42426407).abs() <= ERROR); 133 | assert!((normalized.y - 0.56568545).abs() <= ERROR); 134 | assert!((normalized.z - 0.70710677).abs() <= ERROR); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/vector/commutative.rs: -------------------------------------------------------------------------------- 1 | /// Implements standard commutative operations such as Add and Mul on primitive types. 2 | macro_rules! impl_commutative { 3 | ($vector:ident, $component:ident) => { 4 | impl Mul<$vector<$component>> for $component { 5 | type Output = $vector<$component>; 6 | 7 | fn mul(self, rhs: $vector<$component>) -> Self::Output { 8 | rhs.mul(self) 9 | } 10 | } 11 | }; 12 | } 13 | 14 | pub(crate) use impl_commutative; 15 | -------------------------------------------------------------------------------- /src/vector/component.rs: -------------------------------------------------------------------------------- 1 | //! Components of numeric vectors. 2 | 3 | use crate::F32; 4 | use core::{ 5 | fmt::Debug, 6 | ops::{Add, Div, Mul, Sub}, 7 | }; 8 | 9 | /// Components of numeric vectors. 10 | /// 11 | /// All components must be [`Copy`] + [`Sized`] types which support a minimal 12 | /// set of arithmetic operations ([`Add`], [`Sub`], [`Mul`], [`Div`]), as well as 13 | /// [`Default`], [`PartialEq`] and [`PartialOrd`]. 14 | /// 15 | /// This trait is impl'd for the following primitive types: 16 | /// 17 | /// - [`i8`], [`i16`], [`i32`] 18 | /// - [`u8`], [`u16`], [`u32`] 19 | /// - [`f32`] 20 | pub trait Component: 21 | Copy 22 | + Debug 23 | + Default 24 | + PartialEq 25 | + PartialOrd 26 | + Send 27 | + Sized 28 | + Sync 29 | + Add 30 | + Sub 31 | + Mul 32 | + Div 33 | { 34 | } 35 | 36 | impl Component for i8 {} 37 | impl Component for i16 {} 38 | impl Component for i32 {} 39 | impl Component for u8 {} 40 | impl Component for u16 {} 41 | impl Component for u32 {} 42 | impl Component for f32 {} 43 | impl Component for F32 {} 44 | -------------------------------------------------------------------------------- /src/vector/iter.rs: -------------------------------------------------------------------------------- 1 | //! Iterator over the components of an algebraic vector 2 | 3 | use super::{Component, Vector}; 4 | use core::marker::PhantomData; 5 | 6 | /// Iterator over the components of an algebraic vector 7 | #[derive(Clone, Debug)] 8 | pub struct Iter<'a, V, C> 9 | where 10 | V: Vector, 11 | C: Component, 12 | { 13 | /// Reference to the original vector 14 | vector: &'a V, 15 | 16 | /// Iteration position within the vector 17 | position: usize, 18 | 19 | /// Component type 20 | component: PhantomData, 21 | } 22 | 23 | impl<'a, V, C> Iter<'a, V, C> 24 | where 25 | V: Vector, 26 | C: Component, 27 | { 28 | /// Create a new iterator over the vector's components 29 | pub(super) fn new(vector: &'a V) -> Self { 30 | Self { 31 | vector, 32 | position: 0, 33 | component: PhantomData, 34 | } 35 | } 36 | } 37 | 38 | impl<'a, V, C> Iterator for Iter<'a, V, C> 39 | where 40 | V: Vector, 41 | C: Component, 42 | { 43 | type Item = C; 44 | 45 | fn next(&mut self) -> Option { 46 | let item = self.vector.get(self.position); 47 | 48 | if item.is_some() { 49 | self.position += 1; 50 | } 51 | 52 | item 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/vector/vector2d.rs: -------------------------------------------------------------------------------- 1 | //! 2-dimensional vector 2 | 3 | use super::{Component, Vector, Vector3d}; 4 | use crate::vector::commutative::impl_commutative; 5 | use crate::F32; 6 | use core::ops::{Div, DivAssign}; 7 | use core::{ 8 | iter::{FromIterator, Sum}, 9 | ops::{Add, AddAssign, Index, Mul, MulAssign, Sub, SubAssign}, 10 | }; 11 | 12 | /// 2-dimensional XY vector of `i8` values 13 | pub type I8x2 = Vector2d; 14 | 15 | /// 2-dimensional XY vector of `i16` values 16 | pub type I16x2 = Vector2d; 17 | 18 | /// 2-dimensional XY vector of `i32` values 19 | pub type I32x2 = Vector2d; 20 | 21 | /// 2-dimensional XY vector of `u8` values 22 | pub type U8x2 = Vector2d; 23 | 24 | /// 2-dimensional XY vector of `u16` values 25 | pub type U16x2 = Vector2d; 26 | 27 | /// 2-dimensional XY vector of `u32` values 28 | pub type U32x2 = Vector2d; 29 | 30 | /// 2-dimensional XY vector of `f32` values 31 | pub type F32x2 = Vector2d; 32 | 33 | impl_commutative!(Vector2d, i8); 34 | impl_commutative!(Vector2d, i16); 35 | impl_commutative!(Vector2d, i32); 36 | impl_commutative!(Vector2d, u8); 37 | impl_commutative!(Vector2d, u16); 38 | impl_commutative!(Vector2d, u32); 39 | impl_commutative!(Vector2d, f32); 40 | impl_commutative!(Vector2d, F32); 41 | 42 | /// 2-dimensional vector 43 | #[derive(Copy, Clone, Debug, Default, PartialEq)] 44 | pub struct Vector2d { 45 | /// X component 46 | pub x: C, 47 | 48 | /// Y component 49 | pub y: C, 50 | } 51 | 52 | impl Vector2d 53 | where 54 | C: Component, 55 | { 56 | /// Return a 2-element array containing the coordinates 57 | // TODO(tarcieri): move this to the `Vector` trait leveraging const generics? 58 | pub fn to_array(&self) -> [C; 2] { 59 | [self.x, self.y] 60 | } 61 | 62 | /// Calculates the inner product. 63 | pub fn dot(self, rhs: Self) -> C { 64 | (self.x * rhs.x) + (self.y * rhs.y) 65 | } 66 | 67 | /// Calculates the perpendicular dot product. 68 | /// 69 | /// This value can be understood as the `z` component of the [`cross`](Self::cross) product 70 | /// between two `Vector2d` instances in 3D space, or the signed area of the parallelogram 71 | /// formed by the two vectors. 72 | /// 73 | /// This value can be used to perform side tests without having to promote the vectors 74 | /// into [`Vector3d`] instances. 75 | pub fn perpendicular_dot(self, rhs: Self) -> C { 76 | (self.x * rhs.y) - (self.y * rhs.x) 77 | } 78 | 79 | /// Calculates the outer product. 80 | /// 81 | /// Note that due to tye type of operation, the result is a [`Vector3d`], not a `Vector2d`. 82 | /// See also [`perpendicular_dot`](Self::perpendicular_dot) for a simplified version. 83 | pub fn cross(&self, rhs: Self) -> Vector3d { 84 | Vector3d::from(*self) * Vector3d::from(rhs) 85 | } 86 | } 87 | 88 | impl FromIterator for Vector2d 89 | where 90 | C: Component, 91 | { 92 | fn from_iter(into_iter: T) -> Self 93 | where 94 | T: IntoIterator, 95 | { 96 | let mut iter = into_iter.into_iter(); 97 | 98 | let x = iter.next().expect("no x-axis component in slice"); 99 | let y = iter.next().expect("no y-axis component in slice"); 100 | 101 | assert!( 102 | iter.next().is_none(), 103 | "too many items for 2-dimensional vector" 104 | ); 105 | 106 | Self { x, y } 107 | } 108 | } 109 | 110 | impl Vector for Vector2d 111 | where 112 | C: Component, 113 | { 114 | const AXES: usize = 2; 115 | 116 | fn get(self, index: usize) -> Option { 117 | match index { 118 | 0 => Some(self.x), 119 | 1 => Some(self.y), 120 | _ => None, 121 | } 122 | } 123 | 124 | fn dot(self, rhs: Self) -> C { 125 | self.dot(rhs) 126 | } 127 | } 128 | 129 | impl From<(C, C)> for Vector2d 130 | where 131 | C: Component, 132 | { 133 | fn from(vector: (C, C)) -> Self { 134 | Self { 135 | x: vector.0, 136 | y: vector.1, 137 | } 138 | } 139 | } 140 | 141 | impl From> for (C, C) 142 | where 143 | C: Component, 144 | { 145 | fn from(vector: Vector2d) -> (C, C) { 146 | (vector.x, vector.y) 147 | } 148 | } 149 | 150 | impl From<[C; 2]> for Vector2d 151 | where 152 | C: Component, 153 | { 154 | fn from(vector: [C; 2]) -> Self { 155 | Self { 156 | x: vector[0], 157 | y: vector[1], 158 | } 159 | } 160 | } 161 | 162 | impl From> for [C; 2] 163 | where 164 | C: Component, 165 | { 166 | fn from(vector: Vector2d) -> [C; 2] { 167 | vector.to_array() 168 | } 169 | } 170 | 171 | impl Index for Vector2d 172 | where 173 | C: Component, 174 | { 175 | type Output = C; 176 | 177 | fn index(&self, i: usize) -> &C { 178 | match i { 179 | 0 => &self.x, 180 | 1 => &self.y, 181 | _ => panic!("index out of range"), 182 | } 183 | } 184 | } 185 | 186 | impl Add for Vector2d 187 | where 188 | C: Component, 189 | { 190 | type Output = Self; 191 | 192 | fn add(self, rhs: Self) -> Self { 193 | Self { 194 | x: self.x + rhs.x, 195 | y: self.y + rhs.y, 196 | } 197 | } 198 | } 199 | 200 | impl AddAssign for Vector2d 201 | where 202 | C: Component, 203 | { 204 | fn add_assign(&mut self, other: Self) { 205 | *self = *self + other; 206 | } 207 | } 208 | 209 | impl Sub for Vector2d 210 | where 211 | C: Component, 212 | { 213 | type Output = Self; 214 | 215 | fn sub(self, rhs: Self) -> Self { 216 | Self { 217 | x: self.x - rhs.x, 218 | y: self.y - rhs.y, 219 | } 220 | } 221 | } 222 | 223 | impl SubAssign for Vector2d 224 | where 225 | C: Component, 226 | { 227 | fn sub_assign(&mut self, other: Self) { 228 | *self = *self - other; 229 | } 230 | } 231 | 232 | impl Mul for Vector2d 233 | where 234 | C: Component, 235 | { 236 | type Output = Self; 237 | 238 | fn mul(self, rhs: C) -> Self { 239 | Self { 240 | x: self.x * rhs, 241 | y: self.y * rhs, 242 | } 243 | } 244 | } 245 | 246 | impl Mul> for Vector2d 247 | where 248 | C: Component, 249 | { 250 | type Output = Vector3d; 251 | 252 | fn mul(self, rhs: Vector2d) -> Vector3d { 253 | self.cross(rhs) 254 | } 255 | } 256 | 257 | impl MulAssign for Vector2d 258 | where 259 | C: Component, 260 | { 261 | fn mul_assign(&mut self, rhs: C) { 262 | *self = *self * rhs; 263 | } 264 | } 265 | 266 | impl Div for Vector2d 267 | where 268 | C: Component, 269 | { 270 | type Output = Self; 271 | 272 | fn div(self, rhs: C) -> Self { 273 | Self { 274 | x: self.x / rhs, 275 | y: self.y / rhs, 276 | } 277 | } 278 | } 279 | 280 | impl DivAssign for Vector2d 281 | where 282 | C: Component, 283 | { 284 | fn div_assign(&mut self, rhs: C) { 285 | self.x = self.x / rhs; 286 | self.y = self.y / rhs; 287 | } 288 | } 289 | 290 | impl Sum> for Vector2d 291 | where 292 | C: Component, 293 | { 294 | /// Method which takes an iterator and generates `Self` from the elements by 295 | /// "summing up" the items. 296 | /// 297 | /// ## Example 298 | /// ``` 299 | /// use micromath::vector::Vector2d; 300 | /// let vectors = [ 301 | /// Vector2d { x: 1.0, y: 0.0 }, 302 | /// Vector2d { x: 0.0, y: 2.0 }, 303 | /// ]; 304 | /// let sum: Vector2d = vectors.iter().copied().sum(); 305 | /// assert_eq!(sum.x, 1.0); 306 | /// assert_eq!(sum.y, 2.0); 307 | /// ``` 308 | fn sum>>(iter: I) -> Self { 309 | iter.fold(Vector2d::default(), |prev, current| prev + current) 310 | } 311 | } 312 | 313 | impl<'a, C> Sum<&'a Vector2d> for Vector2d 314 | where 315 | C: Component + 'a, 316 | { 317 | /// Method which takes an iterator and generates `Self` from the elements by 318 | /// "summing up" the items. 319 | /// 320 | /// ## Example 321 | /// ``` 322 | /// use micromath::vector::Vector2d; 323 | /// let vectors = [ 324 | /// Vector2d { x: 1.0, y: 0.0 }, 325 | /// Vector2d { x: 0.0, y: 2.0 }, 326 | /// ]; 327 | /// let sum: Vector2d = vectors.iter().sum(); 328 | /// assert_eq!(sum.x, 1.0); 329 | /// assert_eq!(sum.y, 2.0); 330 | /// ``` 331 | fn sum>>(iter: I) -> Self { 332 | iter.copied().sum() 333 | } 334 | } 335 | 336 | impl From for F32x2 { 337 | fn from(vector: I8x2) -> F32x2 { 338 | Self { 339 | x: vector.x.into(), 340 | y: vector.y.into(), 341 | } 342 | } 343 | } 344 | 345 | impl From for F32x2 { 346 | fn from(vector: I16x2) -> F32x2 { 347 | Self { 348 | x: vector.x.into(), 349 | y: vector.y.into(), 350 | } 351 | } 352 | } 353 | 354 | impl From for F32x2 { 355 | fn from(vector: U8x2) -> F32x2 { 356 | Self { 357 | x: vector.x.into(), 358 | y: vector.y.into(), 359 | } 360 | } 361 | } 362 | 363 | impl From for F32x2 { 364 | fn from(vector: U16x2) -> F32x2 { 365 | Self { 366 | x: vector.x.into(), 367 | y: vector.y.into(), 368 | } 369 | } 370 | } 371 | 372 | #[cfg(feature = "defmt")] 373 | #[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] 374 | impl defmt::Format for Vector2d 375 | where 376 | C: Component + defmt::Format, 377 | { 378 | fn format(&self, fmt: defmt::Formatter<'_>) { 379 | defmt::write!(fmt, "({}, {})", self.x, self.y) 380 | } 381 | } 382 | 383 | #[cfg(test)] 384 | mod tests { 385 | use super::*; 386 | 387 | #[test] 388 | fn from_tuple() { 389 | let vec: Vector2d<_> = (1, 2).into(); 390 | assert_eq!(vec[0], 1); 391 | assert_eq!(vec[1], 2); 392 | 393 | let (x, y) = vec.into(); 394 | assert_eq!(x, 1); 395 | assert_eq!(y, 2); 396 | } 397 | 398 | #[test] 399 | fn from_array() { 400 | let vec: Vector2d<_> = [1, 2].into(); 401 | assert_eq!(vec[0], 1); 402 | assert_eq!(vec[1], 2); 403 | 404 | let arr: [_; 2] = vec.into(); 405 | assert_eq!(arr[0], 1); 406 | assert_eq!(arr[1], 2); 407 | } 408 | 409 | #[test] 410 | fn cross() { 411 | let lhs = Vector2d { x: 1, y: 2 }; 412 | let rhs = Vector2d { x: 3, y: 4 }; 413 | let cross = lhs.cross(rhs); 414 | assert_eq!(cross.x, 0); 415 | assert_eq!(cross.y, 0); 416 | assert_eq!(cross.z, -2); 417 | 418 | let mul = lhs * rhs; 419 | assert_eq!(mul, cross); 420 | 421 | let perp_dot = lhs.perpendicular_dot(rhs); 422 | assert_eq!(perp_dot, cross.z); 423 | } 424 | 425 | #[test] 426 | fn dot() { 427 | let lhs = Vector2d { x: 1, y: 2 }; 428 | let rhs = Vector2d { x: 3, y: 4 }; 429 | let dot = lhs.dot(rhs); 430 | assert_eq!(dot, 11); 431 | } 432 | 433 | #[test] 434 | fn div() { 435 | let vec = Vector2d { x: 10, y: 20 }; 436 | let result = vec / 2; 437 | assert_eq!(result.x, 5); 438 | assert_eq!(result.y, 10); 439 | } 440 | 441 | #[test] 442 | fn div_assign() { 443 | let mut vec = Vector2d { x: 10, y: 20 }; 444 | vec /= 2; 445 | assert_eq!(vec.x, 5); 446 | assert_eq!(vec.y, 10); 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /src/vector/vector3d.rs: -------------------------------------------------------------------------------- 1 | //! 3-dimensional vector 2 | 3 | use super::{commutative::impl_commutative, Component, Vector, Vector2d}; 4 | use crate::F32; 5 | use core::ops::{Div, DivAssign}; 6 | use core::{ 7 | iter::{FromIterator, Sum}, 8 | ops::{Add, AddAssign, Index, Mul, MulAssign, Sub, SubAssign}, 9 | }; 10 | 11 | /// 3-dimensional XYZ vector of `i8` values 12 | pub type I8x3 = Vector3d; 13 | 14 | /// 3-dimensional XYZ vector of `i16` values 15 | pub type I16x3 = Vector3d; 16 | 17 | /// 3-dimensional XYZ vector of `i32` values 18 | pub type I32x3 = Vector3d; 19 | 20 | /// 3-dimensional XYZ vector of `u8` values 21 | pub type U8x3 = Vector3d; 22 | 23 | /// 3-dimensional XYZ vector of `u16` values 24 | pub type U16x3 = Vector3d; 25 | 26 | /// 3-dimensional XYZ vector of `u32` values 27 | pub type U32x3 = Vector3d; 28 | 29 | /// 3-dimensional XYZ vector of `f32` values 30 | pub type F32x3 = Vector3d; 31 | 32 | impl_commutative!(Vector3d, i8); 33 | impl_commutative!(Vector3d, i16); 34 | impl_commutative!(Vector3d, i32); 35 | impl_commutative!(Vector3d, u8); 36 | impl_commutative!(Vector3d, u16); 37 | impl_commutative!(Vector3d, u32); 38 | impl_commutative!(Vector3d, f32); 39 | impl_commutative!(Vector3d, F32); 40 | 41 | /// 3-dimensional vector 42 | #[derive(Copy, Clone, Debug, Default, PartialEq)] 43 | pub struct Vector3d { 44 | /// X component 45 | pub x: C, 46 | 47 | /// Y component 48 | pub y: C, 49 | 50 | /// Z component 51 | pub z: C, 52 | } 53 | 54 | impl Vector3d 55 | where 56 | C: Component, 57 | { 58 | /// Return a 3-element array containing the coordinates 59 | // TODO(tarcieri): move this to the `Vector` trait leveraging const generics? 60 | pub fn to_array(&self) -> [C; 3] { 61 | [self.x, self.y, self.z] 62 | } 63 | 64 | /// Calculates the inner product. 65 | pub fn dot(self, rhs: Self) -> C { 66 | (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) 67 | } 68 | 69 | /// Calculates the outer product. 70 | pub fn cross(&self, rhs: Self) -> Vector3d { 71 | Self { 72 | x: (self.y * rhs.z) - (self.z * rhs.y), 73 | y: (self.z * rhs.x) - (self.x * rhs.z), 74 | z: (self.x * rhs.y) - (self.y * rhs.x), 75 | } 76 | } 77 | } 78 | 79 | impl FromIterator for Vector3d 80 | where 81 | C: Component, 82 | { 83 | fn from_iter(into_iter: T) -> Self 84 | where 85 | T: IntoIterator, 86 | { 87 | let mut iter = into_iter.into_iter(); 88 | 89 | let x = iter.next().expect("no x-axis component in slice"); 90 | let y = iter.next().expect("no y-axis component in slice"); 91 | let z = iter.next().expect("no z-axis component in slice"); 92 | 93 | assert!( 94 | iter.next().is_none(), 95 | "too many items for 3-dimensional vector" 96 | ); 97 | 98 | Self { x, y, z } 99 | } 100 | } 101 | 102 | impl Vector for Vector3d 103 | where 104 | C: Component, 105 | { 106 | const AXES: usize = 3; 107 | 108 | fn get(self, index: usize) -> Option { 109 | match index { 110 | 0 => Some(self.x), 111 | 1 => Some(self.y), 112 | 2 => Some(self.z), 113 | _ => None, 114 | } 115 | } 116 | 117 | fn dot(self, rhs: Self) -> C { 118 | (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) 119 | } 120 | } 121 | 122 | impl From> for Vector3d 123 | where 124 | C: Component, 125 | { 126 | fn from(vector: Vector2d) -> Self { 127 | let zero = C::default(); 128 | Self { 129 | x: vector.x, 130 | y: vector.y, 131 | z: zero, 132 | } 133 | } 134 | } 135 | 136 | impl From<(C, C, C)> for Vector3d 137 | where 138 | C: Component, 139 | { 140 | fn from(vector: (C, C, C)) -> Self { 141 | Self { 142 | x: vector.0, 143 | y: vector.1, 144 | z: vector.2, 145 | } 146 | } 147 | } 148 | 149 | impl From> for (C, C, C) 150 | where 151 | C: Component, 152 | { 153 | fn from(vector: Vector3d) -> (C, C, C) { 154 | (vector.x, vector.y, vector.z) 155 | } 156 | } 157 | 158 | impl From<[C; 3]> for Vector3d 159 | where 160 | C: Component, 161 | { 162 | fn from(vector: [C; 3]) -> Self { 163 | Self { 164 | x: vector[0], 165 | y: vector[1], 166 | z: vector[2], 167 | } 168 | } 169 | } 170 | 171 | impl From> for [C; 3] 172 | where 173 | C: Component, 174 | { 175 | fn from(vector: Vector3d) -> [C; 3] { 176 | vector.to_array() 177 | } 178 | } 179 | 180 | impl Index for Vector3d 181 | where 182 | C: Component, 183 | { 184 | type Output = C; 185 | 186 | fn index(&self, i: usize) -> &C { 187 | match i { 188 | 0 => &self.x, 189 | 1 => &self.y, 190 | 2 => &self.z, 191 | _ => panic!("index out of range"), 192 | } 193 | } 194 | } 195 | 196 | impl Add for Vector3d 197 | where 198 | C: Component, 199 | { 200 | type Output = Self; 201 | 202 | fn add(self, rhs: Self) -> Self { 203 | Self { 204 | x: self.x + rhs.x, 205 | y: self.y + rhs.y, 206 | z: self.z + rhs.z, 207 | } 208 | } 209 | } 210 | 211 | impl AddAssign for Vector3d 212 | where 213 | C: Component, 214 | { 215 | fn add_assign(&mut self, other: Self) { 216 | *self = *self + other; 217 | } 218 | } 219 | 220 | impl Sub for Vector3d 221 | where 222 | C: Component, 223 | { 224 | type Output = Self; 225 | 226 | fn sub(self, rhs: Self) -> Self { 227 | Self { 228 | x: self.x - rhs.x, 229 | y: self.y - rhs.y, 230 | z: self.z - rhs.z, 231 | } 232 | } 233 | } 234 | 235 | impl SubAssign for Vector3d 236 | where 237 | C: Component, 238 | { 239 | fn sub_assign(&mut self, other: Self) { 240 | *self = *self - other; 241 | } 242 | } 243 | 244 | /// Compute the cross product of two vectors 245 | impl Mul for Vector3d 246 | where 247 | C: Component, 248 | { 249 | type Output = Self; 250 | 251 | fn mul(self, rhs: Self) -> Self { 252 | self.cross(rhs) 253 | } 254 | } 255 | 256 | /// Multiply vector by a scalar component 257 | impl Mul for Vector3d 258 | where 259 | C: Component, 260 | { 261 | type Output = Self; 262 | 263 | fn mul(self, rhs: C) -> Self { 264 | Self { 265 | x: self.x * rhs, 266 | y: self.y * rhs, 267 | z: self.z * rhs, 268 | } 269 | } 270 | } 271 | 272 | impl MulAssign for Vector3d 273 | where 274 | C: Component, 275 | { 276 | fn mul_assign(&mut self, rhs: C) { 277 | *self = *self * rhs; 278 | } 279 | } 280 | 281 | impl Div for Vector3d 282 | where 283 | C: Component, 284 | { 285 | type Output = Self; 286 | 287 | fn div(self, rhs: C) -> Self { 288 | Self { 289 | x: self.x / rhs, 290 | y: self.y / rhs, 291 | z: self.z / rhs, 292 | } 293 | } 294 | } 295 | 296 | impl DivAssign for Vector3d 297 | where 298 | C: Component, 299 | { 300 | fn div_assign(&mut self, rhs: C) { 301 | self.x = self.x / rhs; 302 | self.y = self.y / rhs; 303 | self.z = self.z / rhs; 304 | } 305 | } 306 | 307 | impl Sum> for Vector3d 308 | where 309 | C: Component, 310 | { 311 | /// Method which takes an iterator and generates `Self` from the elements by 312 | /// "summing up" the items. 313 | /// 314 | /// ## Example 315 | /// ``` 316 | /// use micromath::vector::Vector3d; 317 | /// let vectors = [ 318 | /// Vector3d { x: 1.0, y: 0.0, z: -2.0 }, 319 | /// Vector3d { x: 0.0, y: 2.0, z: -1.0 }, 320 | /// ]; 321 | /// let sum: Vector3d = vectors.iter().copied().sum(); 322 | /// assert_eq!(sum.x, 1.0); 323 | /// assert_eq!(sum.y, 2.0); 324 | /// assert_eq!(sum.z, -3.0); 325 | /// ``` 326 | fn sum>>(iter: I) -> Self { 327 | iter.fold(Vector3d::default(), |prev, current| prev + current) 328 | } 329 | } 330 | 331 | impl<'a, C> Sum<&'a Vector3d> for Vector3d 332 | where 333 | C: Component + 'a, 334 | { 335 | /// Method which takes an iterator and generates `Self` from the elements by 336 | /// "summing up" the items. 337 | /// 338 | /// ## Example 339 | /// ``` 340 | /// use micromath::vector::Vector3d; 341 | /// let vectors = [ 342 | /// Vector3d { x: 1.0, y: 0.0, z: -2.0 }, 343 | /// Vector3d { x: 0.0, y: 2.0, z: -1.0 }, 344 | /// ]; 345 | /// let sum: Vector3d = vectors.iter().copied().sum(); 346 | /// assert_eq!(sum.x, 1.0); 347 | /// assert_eq!(sum.y, 2.0); 348 | /// assert_eq!(sum.z, -3.0); 349 | /// ``` 350 | fn sum>>(iter: I) -> Self { 351 | iter.copied().sum() 352 | } 353 | } 354 | 355 | impl From for F32x3 { 356 | fn from(vector: I8x3) -> F32x3 { 357 | Self { 358 | x: vector.x.into(), 359 | y: vector.y.into(), 360 | z: vector.z.into(), 361 | } 362 | } 363 | } 364 | 365 | impl From for F32x3 { 366 | fn from(vector: I16x3) -> F32x3 { 367 | Self { 368 | x: vector.x.into(), 369 | y: vector.y.into(), 370 | z: vector.z.into(), 371 | } 372 | } 373 | } 374 | 375 | impl From for F32x3 { 376 | fn from(vector: U8x3) -> F32x3 { 377 | Self { 378 | x: vector.x.into(), 379 | y: vector.y.into(), 380 | z: vector.z.into(), 381 | } 382 | } 383 | } 384 | 385 | impl From for F32x3 { 386 | fn from(vector: U16x3) -> F32x3 { 387 | Self { 388 | x: vector.x.into(), 389 | y: vector.y.into(), 390 | z: vector.z.into(), 391 | } 392 | } 393 | } 394 | 395 | impl From> for F32x3 { 396 | fn from(vector: Vector3d) -> F32x3 { 397 | Self { 398 | x: vector.x.into(), 399 | y: vector.y.into(), 400 | z: vector.z.into(), 401 | } 402 | } 403 | } 404 | 405 | #[cfg(feature = "defmt")] 406 | #[cfg_attr(docsrs, doc(cfg(feature = "defmt")))] 407 | impl defmt::Format for Vector3d 408 | where 409 | C: Component + defmt::Format, 410 | { 411 | fn format(&self, fmt: defmt::Formatter<'_>) { 412 | defmt::write!(fmt, "({}, {}, {})", self.x, self.y, self.z) 413 | } 414 | } 415 | 416 | #[cfg(test)] 417 | mod tests { 418 | use super::*; 419 | 420 | #[test] 421 | fn from_tuple() { 422 | let vec: Vector3d<_> = (1, 2, 3).into(); 423 | assert_eq!(vec[0], 1); 424 | assert_eq!(vec[1], 2); 425 | assert_eq!(vec[2], 3); 426 | 427 | let (x, y, z) = vec.into(); 428 | assert_eq!(x, 1); 429 | assert_eq!(y, 2); 430 | assert_eq!(z, 3); 431 | } 432 | 433 | #[test] 434 | fn from_array() { 435 | let vec: Vector3d<_> = [1, 2, 3].into(); 436 | assert_eq!(vec[0], 1); 437 | assert_eq!(vec[1], 2); 438 | assert_eq!(vec[2], 3); 439 | 440 | let arr: [_; 3] = vec.into(); 441 | assert_eq!(arr[0], 1); 442 | assert_eq!(arr[1], 2); 443 | assert_eq!(arr[2], 3); 444 | } 445 | 446 | #[test] 447 | fn cross() { 448 | let lhs = Vector3d { x: 1, y: 2, z: 3 }; 449 | let rhs = Vector3d { x: 4, y: 5, z: 6 }; 450 | let cross = lhs.cross(rhs); 451 | assert_eq!(cross.x, -3); 452 | assert_eq!(cross.y, 6); 453 | assert_eq!(cross.z, -3); 454 | 455 | let mul = lhs * rhs; 456 | assert_eq!(mul, cross); 457 | } 458 | 459 | #[test] 460 | fn dot() { 461 | let lhs = Vector3d { x: 1, y: 2, z: 3 }; 462 | let rhs = Vector3d { x: 4, y: 5, z: 6 }; 463 | let dot = lhs.dot(rhs); 464 | assert_eq!(dot, 32); 465 | } 466 | 467 | #[test] 468 | fn div() { 469 | let vec = Vector3d { 470 | x: 10, 471 | y: 20, 472 | z: 30, 473 | }; 474 | let result = vec / 2; 475 | assert_eq!(result.x, 5); 476 | assert_eq!(result.y, 10); 477 | assert_eq!(result.z, 15); 478 | } 479 | 480 | #[test] 481 | fn div_assign() { 482 | let mut vec = Vector3d { 483 | x: 10, 484 | y: 20, 485 | z: 30, 486 | }; 487 | vec /= 2; 488 | assert_eq!(vec.x, 5); 489 | assert_eq!(vec.y, 10); 490 | assert_eq!(vec.z, 15); 491 | } 492 | } 493 | --------------------------------------------------------------------------------