feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"bfe3644bd2945754e483a74079a946dfd42404c5e15416b48ac43c6b6777be0b",".github/workflows/CI.yml":"809eb2df995a5ed6cc89c6a50c511e177ff267fa24dfdcbd29a5bbf809a6488a","CHANGELOG.md":"f038f23ffd311005ccae30418188f27b1580377c7ab36f10c64cc4efa038e051","CODE_OF_CONDUCT.md":"5d852afa1fc3aa06748c97bc1eee5672f26f705836375456a41ef5f504e5aa1b","Cargo.toml":"b7462c628f9efaba6895684be65bd4fc7a0b501feaee9fa005b096e4ad9ce6d3","Cargo.toml.orig":"e5588973c9049d6977d1152ead28575ee81b2b2c835e611012e494c993d81ca7","LICENSE-APACHE":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","LICENSE-MIT":"38f262c50f3decca88d8b380fd4f05d5aed8ec42dd7e783d065ce34b04fb171b","README.md":"b46508baf806e4fa78bc645715f17bcbb7f66922d908850541d4ebbea710637f","codecov.yml":"1ab0af118abc204032fc015b3e4e6dc0469ccb44a7309a5855652610a357f65e","img/micromath-sq.png":"0835b2db564d01fc6c8ff82c4bd723bd8e4d725e0153ec8cffe2a1a27370df65","img/micromath.png":"d9bf6fef0abd1a43831e211b5cf32c0850ad8152885e05aaeeccae7b16d7d0ee","src/f32ext.rs":"83ee35fecbb25ae4224e50391760c19d7084e6c23fe2252d00fa501312ffd3cd","src/float.rs":"6b0200550bcaaa4ed1dceb954f64290e3ed3d571aba0eae4221dad07e7a5a757","src/float/abs.rs":"3f7e915c97ad58e904c9b6b9b1a7aba76e52a264853fd39b9c40805ac88dd739","src/float/acos.rs":"997775245b6956f5750092e5cee87ffd3762cf0c3a510d02a2e0911b6c5fc259","src/float/asin.rs":"a42b39bf2031f4ebaa32771f083025b1a6f68e0bf891efd73fecd60486685663","src/float/atan.rs":"1bca779cb99707d41fc3918b062816607829d88ec9fe0987b1715ebc1f02bd6d","src/float/atan2.rs":"3f4ac53a2bbf342fd4ffe9b16e8856cc5df676b5e09652b3448778b290278a96","src/float/ceil.rs":"c25323d0dc9a72e554a53a9c8c81ddd085873d10584bda7a46e9cb108303b03b","src/float/copysign.rs":"a67fcb4a1037c35c6893399b33a141b048b72c83b210c0038256bdb7768ece45","src/float/cos.rs":"e12c85e64b4fe2c5f3808f7313e279b58098f19e0301325e8a9b18f6950caf80","src/float/div_euclid.rs":"263c7459bd35c91ba6fd381f4f4f63cb2cc54be98026b5a273561f8734721fdd","src/float/exp.rs":"5e2cd874eeb6bbde8bb7ef447c4f2d0f2ab0be0751dbc7bc0ee113bd6eafbcef","src/float/floor.rs":"f87cb020427ccd69d16147f934e224128e4d25b4844c04d883e3946daa883361","src/float/fract.rs":"3b9baccdc30d20928b5cdd4131f4cb57c1ded27470c0bf2aec375b934fb51d0f","src/float/hypot.rs":"99d5027d3d7e60e9e493813678a5a0c3dde93e8358296a93ba42276c321da543","src/float/inv.rs":"46568ea3b8eb7db4fd1015cf29b30a25454b9ef08a78b544388f5b488ce6db20","src/float/invsqrt.rs":"85adb9098403a8a4af5c06a6a65c4dc616a5ca34aeb708172e6c0042cb53f8de","src/float/ln.rs":"d075b20cce094d4fc7b2c4aadc3dcbfbf8d0727e59d00025fbf6c0f18bc7e98c","src/float/log.rs":"0dbc4b164c0580227ad99b70c2be1307348a1a5abcbf909433125912815b72c3","src/float/log10.rs":"72adc0b98a9f0529b807369a416d5fd7cae06c0d5ddc0272051f3cb71d6ea4b3","src/float/log2.rs":"5cfced777572513a1d9899526cb702cad33c531ec0dad73f95cd330f54f4213e","src/float/mul_add.rs":"a7ce48d29afa83ad055b6c23cad1b36bf815dbd66df7931641302af1e69d13f4","src/float/powf.rs":"8df7373bdc7481f8f602c41a4d4da38953ae4d62222130ffb0262aac285c3ed9","src/float/powi.rs":"841421709c121da44fea6d4b61c2853b7c8ba34a2ee94be6578ee7b55c8820b3","src/float/recip.rs":"f30bc9bbad4a2911fb06a349d6ca4eec0d6be2a8082340e725cea64dff56df90","src/float/rem_euclid.rs":"f3152d192e4aa36e00bde6a5f824a87e676d5a616d4d0c89bf805d957a00a0d7","src/float/round.rs":"37b60b6aeb45a8da0f2d897e884085356f8e7ce8ba64510421ed38fc0d08b536","src/float/signum.rs":"ce36aca111822fa4df3e7863b1876e4cce7eabfb946a0086336d5985b3e12ec4","src/float/sin.rs":"c7aa4b1f8aefb766f335f943d0b9b6f0ad9fa628b6326e47dd4038cc56956ab9","src/float/sin_cos.rs":"e9447329b1013f1e9385b0b793ce5f61ced24b4b1feea30c858c53edfb514b73","src/float/sqrt.rs":"0d4b84c5933bd98f78f5ceb86dded4bf5709e9ffa0926eea16a2360034e0a623","src/float/tan.rs":"5d8c87cc9364e1006f0f9ca086494dcecebc9bad23ecd47dbf789ac92d44841c","src/float/trunc.rs":"c068d61b191653fec1bc6a311b266397670c3231c33910112078acb456d1a5e2","src/lib.rs":"2736b9919aa6cc5bb594a9aee85169206ec32594111be137fc2301b1f78c1d38","src/quaternion.rs":"81c258c66679bcb25e46642c7fe0d992137c215bd85c12f7e9628d7675422953","src/statistics.rs":"d2f813c1df8f5f091e54ad6fc965b0cdfca3a804acc966a4f768368edafc7a60","src/statistics/mean.rs":"2cf3febb2ccce1da4724bcc25808b6e9ee88053d849e33f975a83d0996c6533c","src/statistics/stddev.rs":"55c855d3fe37327c86c11a499afe0b34aaee10f17bb1311df0ca7829432f772b","src/statistics/trim.rs":"31ca56383bf885dc6be211e6cfb4b1c838cdf2fcd26d950a0e81f25838620712","src/statistics/variance.rs":"fbd5a050401ad833bb442cb2955d4c46ffbc66e487ea67ab4f53d7bf30650939","src/vector.rs":"8848f6ea888802f3b4d966b02c91214bc03ee4971496f1e331932d2fb1c68e58","src/vector/component.rs":"7de6bd968f87610983205d25bcb6c69788982db992ea5361577337d20b4366aa","src/vector/iter.rs":"77345798058f94e44a651a029e1ecb73edfcbc6c28cca6e7dc29a8d1d04f4a4a","src/vector/vector2d.rs":"64875338314497180d06b9188a85cf9a088b89d21004c42372d9c5e9327a79a4","src/vector/vector3d.rs":"2d4eb3d7286b502b75d78754d905f6b6070fa4f3657c87a27fb98d94df47c260"},"paLine truncated
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "fc2df91be41bf204157d24b49b68fd2dedcfff34"
},
"path_in_vcs": ""
}
+100
View File
@@ -0,0 +1,100 @@
name: CI
on:
pull_request: {}
push:
branches: main
env:
CARGO_INCREMENTAL: 0
RUSTFLAGS: "-Dwarnings"
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
profile: minimal
- run: cargo check
build:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- 1.47.0 # MSRV
- stable
target:
# - armv7a-none-eabi
- thumbv7em-none-eabi
- wasm32-unknown-unknown
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
profile: minimal
- run: cargo build --target ${{ matrix.target }} --release --no-default-features
- run: cargo build --target ${{ matrix.target }} --release --no-default-features --features num-traits
test:
runs-on: ubuntu-latest
strategy:
matrix:
include:
# 32-bit Linux
- target: i686-unknown-linux-gnu
rust: 1.47.0 # MSRV
deps: sudo apt update && sudo apt install gcc-multilib
- target: i686-unknown-linux-gnu
rust: stable
deps: sudo apt update && sudo apt install gcc-multilib
# 64-bit Linux
- target: x86_64-unknown-linux-gnu
rust: 1.47.0 # MSRV
- target: x86_64-unknown-linux-gnu
rust: stable
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
override: true
profile: minimal
- run: ${{ matrix.deps }}
- run: cargo test --target ${{ matrix.target }} --release --all-features
rustfmt:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: rustfmt
override: true
profile: minimal
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: 1.47.0 # MSRV
components: clippy
override: true
profile: minimal
- run: cargo clippy -- -D warnings
+143
View File
@@ -0,0 +1,143 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.1.0 (2023-10-31)
### Added
- Accessors to quaternion elements ([#89])
- Quaternion -> Euler conversion ([#90])
- Method for normalizing a quaternion ([#91])
- Method to get the quaternion which is the smallest rotation between two vectors ([#94])
- `recip`, `mul_add` and `signum` ([#100])
[#89]: https://github.com/tarcieri/micromath/pull/89
[#90]: https://github.com/tarcieri/micromath/pull/90
[#91]: https://github.com/tarcieri/micromath/pull/91
[#94]: https://github.com/tarcieri/micromath/pull/94
[#100]: https://github.com/tarcieri/micromath/pull/100
## 2.0.0 (2021-05-15)
### Added
- `Vector` subtraction support ([#71])
- `F32` newtype ([#72], [#75])
- `num-traits` support ([#80])
- `Quaternion::dot` and `::inv` ([#81])
- `Vector3d` ops for `Quaternion` ([#82])
- `Quaternion::magnitude`, `::scale`, `::to_array`, and `::IDENTITY` ([#83])
- `Quaternion::axis_angle` ([#84])
- `Quaternion::new` ([#85])
### Changed
- Refactor `Vector` types ([#69])
- MSRV 1.47+ ([#75])
- Make `Quaternion` opaque and module private ([#70], [#85])
### Fixed
- `acos()` behavior for zero/negative ([#79])
[#69]: https://github.com/tarcieri/micromath/pull/69
[#70]: https://github.com/tarcieri/micromath/pull/70
[#71]: https://github.com/tarcieri/micromath/pull/71
[#72]: https://github.com/tarcieri/micromath/pull/72
[#75]: https://github.com/tarcieri/micromath/pull/75
[#79]: https://github.com/tarcieri/micromath/pull/79
[#80]: https://github.com/tarcieri/micromath/pull/80
[#81]: https://github.com/tarcieri/micromath/pull/81
[#82]: https://github.com/tarcieri/micromath/pull/82
[#83]: https://github.com/tarcieri/micromath/pull/83
[#84]: https://github.com/tarcieri/micromath/pull/84
[#85]: https://github.com/tarcieri/micromath/pull/85
## 1.1.1 (2021-03-27)
### Added
- `doc_cfg` ([#64])
[#64]: https://github.com/tarcieri/micromath/pull/64
## 1.1.0 (2020-09-30)
### Added
- `powi` support ([#53])
### Changed
- Bump `generic-array` dependency to v0.14; MSRV 1.36+ ([#54])
[#54]: https://github.com/tarcieri/micromath/pull/54
[#53]: https://github.com/tarcieri/micromath/pull/53
## 1.0.1 (2020-06-12)
### Added
- Support for `powf` with negative bases ([#51])
[#51]: https://github.com/tarcieri/micromath/pull/51
## 1.0.0 (2019-12-02)
- Initial 1.0 release! 🎉 (otherwise unchanged)
## 0.5.1 (2019-11-27)
- Cargo.toml: Add mathematics category ([#45])
[#45]: https://github.com/tarcieri/micromath/pull/45
## 0.5.0 (2019-11-13)
- Remove default cargo features ([#42])
- Add `asin`, `acos`, and `hypot` ([#39])
[#42]: https://github.com/tarcieri/micromath/pull/42
[#39]: https://github.com/tarcieri/micromath/pull/39
## 0.4.1 (2019-10-08)
- Implement `F32Ext::round` ([#37])
[#37]: https://github.com/tarcieri/micromath/pull/37
## 0.4.0 (2019-10-02)
- Add `powf`, `exp`, `log10`, `log2`, `log`, `ln`, `trunc`, `fract`, `copysign` ([#35])
[#35]: https://github.com/tarcieri/micromath/pull/35
## 0.3.1 (2019-05-11)
- Rust 1.31.0 support ([#33])
[#33]: https://github.com/tarcieri/micromath/pull/33
## 0.3.0 (2019-05-04)
- statistics: Add Trim trait for statistical outlier culling iterators ([#29])
- Quaternions ([#28])
- f32ext: fast `inv()` approximation ([#27])
- Improve documentation throughout the library ([#25], [#26])
[#29]: https://github.com/tarcieri/micromath/pull/29
[#28]: https://github.com/tarcieri/micromath/pull/28
[#27]: https://github.com/tarcieri/micromath/pull/27
[#26]: https://github.com/tarcieri/micromath/pull/26
[#25]: https://github.com/tarcieri/micromath/pull/25
## 0.2.2 (2019-05-04)
- Add `i32` and `u32` vectors ([#23])
[#23]: https://github.com/tarcieri/micromath/pull/23
## 0.2.1 (2019-05-03)
- Add `html_logo_url` and square icon ([#20])
- `README.md`: Update links to use 'develop' branch ([#19])
[#20]: https://github.com/tarcieri/micromath/pull/20
[#19]: https://github.com/tarcieri/micromath/pull/19
## 0.2.0 (2019-05-03)
- `tan(x)` ([#17])
- `invsqrt(x)` ([#16])
- `cos(x)` and `sin(x)` ([#15])
- `ceil(x)` and `floor(x)` ([#14])
- Update to `generic-array` v0.13 ([#12])
[#17]: https://github.com/tarcieri/micromath/pull/17
[#16]: https://github.com/tarcieri/micromath/pull/16
[#15]: https://github.com/tarcieri/micromath/pull/15
[#14]: https://github.com/tarcieri/micromath/pull/14
[#12]: https://github.com/tarcieri/micromath/pull/12
## 0.1.0 (2019-05-03)
- Initial release
+76
View File
@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at [bascule@gmail.com]. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
[bascule@gmail.com]: mailto:bascule@gmail.com
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
+55
View File
@@ -0,0 +1,55 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "micromath"
version = "2.1.0"
authors = ["Tony Arcieri <bascule@gmail.com>"]
description = """
Embedded-friendly math library featuring fast floating point approximations
(with small code size) for common arithmetic operations, trigonometry,
2D/3D vector types, statistical analysis, and quaternions.
Optimizes for performance and small code size at the cost of precision.
"""
readme = "README.md"
keywords = [
"math",
"quaternions",
"statistics",
"trigonometry",
"vector",
]
categories = [
"embedded",
"mathematics",
"science",
"no-std",
]
license = "Apache-2.0 OR MIT"
repository = "https://github.com/tarcieri/micromath"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = [
"--cfg",
"docsrs",
]
[dependencies.num-traits]
version = "0.2"
optional = true
default-features = false
[features]
quaternion = []
statistics = []
vector = []
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-2021 Tony Arcieri
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+174
View File
@@ -0,0 +1,174 @@
# <img src="https://raw.githubusercontent.com/tarcieri/micromath/main/img/micromath.png" width="800">
[![Crate][crate-img]][crate-link]
[![Docs][docs-img]][docs-link]
[![Build Status][build-image]][build-link]
[![Safety Dance][safety-image]][safety-link]
![MSRV][msrv-image]
[![Apache 2.0/MIT Licensed][license-image]][license-link]
Embedded-friendly (i.e. `no_std`) Rust math library featuring fast, safe
floating point approximations for common arithmetic operations, trigonometry,
2D/3D vector types, statistical analysis, and quaternions.
Optimizes for performance and small code size at the cost of precision.
[Documentation][docs-link]
## Minimum Supported Rust Version
Requires Rust **1.47** or newer.
## SemVer Policy
We reserve the right to change the following in future releases with a minor
version bump *only*:
- MSRV
- `num-traits` version (optional dependency)
## Features
- [`f32` extension]:
- Fast approximations:
- [asin]
- [acos]
- [atan]
- [atan2]
- [cos]
- [hypot]
- [inv]
- [invsqrt]
- [ln]
- [log]
- [log2]
- [log10]
- [powf]
- [exp]
- [sin]
- [sqrt]
- [tan]
- `std` polyfills:
- [abs]
- [ceil]
- [floor]
- [round]
- [trunc]
- [fract]
- [copysign]
- [signum]
- [sin_cos]
- [mul_add]
- [recip]
- [Algebraic vector types]:
- 2D:
- [I8x2]
- [I16x2]
- [I32x2]
- [U8x2]
- [U16x2]
- [U32x2]
- [F32x2]
- 3D:
- [I8x3]
- [I16x3]
- [I32x3]
- [U8x3]
- [U16x3]
- [U32x3]
- [F32x3]
- [Statistical analysis]:
- [mean]
- [stddev]
- [trim]
- [variance]
- [Quaternions]
## Code of Conduct
We abide by the [Contributor Covenant][cc] and ask that you do as well.
For more information, please see [CODE_OF_CONDUCT.md].
## License
Copyright © 2019-2021 Tony Arcieri
Dual licensed under your choice of either of:
- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
Incorporates portions of some tests from the [libm crate].
Copyright © 2018 Jorge Aparicio and also dual licensed under the
Apache 2.0 and MIT licenses.
[//]: # (badges)
[crate-img]: https://img.shields.io/crates/v/micromath.svg?logo=rust
[crate-link]: https://crates.io/crates/micromath
[docs-img]: https://docs.rs/micromath/badge.svg
[docs-link]: https://docs.rs/micromath/
[build-image]: https://github.com/tarcieri/micromath/workflows/CI/badge.svg
[build-link]: https://github.com/tarcieri/micromath/actions
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
[safety-link]: https://github.com/rust-secure-code/safety-dance/
[msrv-image]: https://img.shields.io/badge/rustc-1.47+-blue.svg
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[license-link]: https://github.com/tarcieri/micromath/blob/main/LICENSE-APACHE
[//]: # (general links)
[`f32` extension]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html
[asin]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.asin
[acos]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.acos
[atan]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.atan
[atan2]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.atan2
[cos]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.cos
[hypot]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.hypot
[inv]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.inv
[invsqrt]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.invsqrt
[ln]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.ln
[log]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log
[log2]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log2
[log10]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.log10
[powf]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.powf
[powi]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.powi
[exp]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.exp
[sin]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.sin
[sqrt]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.sqrt
[tan]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.tan
[abs]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.abs
[ceil]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.ceil
[floor]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.floor
[round]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.round
[trunc]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.trunc
[fract]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.fract
[copysign]: https://docs.rs/micromath/latest/micromath/trait.F32Ext.html#tymethod.copysign
[Algebraic vector types]: https://docs.rs/micromath/latest/micromath/vector/index.html
[I8x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I8x2.html
[I16x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I16x2.html
[I32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.I32x2.html
[U8x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U8x2.html
[U16x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U16x2.html
[U32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.U32x2.html
[F32x2]: https://docs.rs/micromath/latest/micromath/vector/struct.F32x2.html
[I8x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I8x3.html
[I16x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I16x3.html
[I32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.I32x3.html
[U8x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U8x3.html
[U16x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U16x3.html
[U32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.U32x3.html
[F32x3]: https://docs.rs/micromath/latest/micromath/vector/struct.F32x3.html
[Statistical analysis]: https://docs.rs/micromath/latest/micromath/statistics/index.html
[mean]: https://docs.rs/micromath/latest/micromath/statistics/trait.Mean.html
[stddev]: https://docs.rs/micromath/latest/micromath/statistics/trait.StdDev.html
[trim]: https://docs.rs/micromath/latest/micromath/statistics/trim/trait.Trim.html
[variance]: https://docs.rs/micromath/latest/micromath/statistics/trait.Variance.html
[Quaternions]: https://docs.rs/micromath/latest/micromath/quaternion/struct.Quaternion.html
[libm crate]: https://github.com/rust-lang-nursery/libm
[vek crate]: https://github.com/yoanlcq/vek
[approx crate]: https://crates.io/crates/approx
[cc]: https://contributor-covenant.org
[CODE_OF_CONDUCT.md]: https://github.com/tarcieri/micromath/blob/main/CODE_OF_CONDUCT.md
+4
View File
@@ -0,0 +1,4 @@
coverage:
status:
project: off
patch: off
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

+279
View File
@@ -0,0 +1,279 @@
//! `f32` extension
use crate::float::F32;
/// `f32` extension providing various arithmetic approximations and polyfills
/// for `std` functionality.
pub trait F32Ext: Sized {
/// Compute absolute value with a constant-time, data-independent
/// implementation.
fn abs(self) -> f32;
/// Approximates `acos(x)` in radians in the range `[0, pi]`
fn acos(self) -> f32;
/// Approximates `asin(x)` in radians in the range `[-pi/2, pi/2]`.
fn asin(self) -> f32;
/// Approximates `atan(x)` in radians with a maximum error of `0.002`.
fn atan(self) -> f32;
/// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum
/// error of `0.1620` degrees.
fn atan_norm(self) -> f32;
/// Approximates the four quadrant arctangent `atan2(x)` in radians, with
/// a maximum error of `0.002`.
fn atan2(self, other: f32) -> f32;
/// Approximates the four quadrant arctangent.
/// Normalized to the `[0,4)` range with a maximum error of `0.1620` degrees.
fn atan2_norm(self, other: f32) -> f32;
/// Approximates floating point ceiling.
fn ceil(self) -> f32;
/// Copies the sign from one number to another and returns it.
fn copysign(self, sign: f32) -> f32;
/// Approximates cosine in radians with a maximum error of `0.002`.
fn cos(self) -> f32;
/// Calculates Euclidean division, the matching method for `rem_euclid`.
fn div_euclid(self, other: f32) -> f32;
/// Approximates `e^x`.
fn exp(self) -> f32;
/// Approximates floating point floor.
fn floor(self) -> f32;
/// Retrieve the fractional part of floating point with sign.
fn fract(self) -> f32;
/// Approximates the length of the hypotenuse of a right-angle triangle given
/// legs of length `x` and `y`.
fn hypot(self, other: f32) -> f32;
/// Approximates `1/x` with an average deviation of ~8%.
fn inv(self) -> f32;
/// Approximates inverse square root with an average deviation of ~5%.
fn invsqrt(self) -> f32;
/// Approximates `ln(x)`.
fn ln(self) -> f32;
/// Approximates `log` with an arbitrary base.
fn log(self, base: f32) -> f32;
/// Approximates `log2`.
fn log2(self) -> f32;
/// Approximates `log10`.
fn log10(self) -> f32;
/// Computes `(self * a) + b`.
fn mul_add(self, a: f32, b: f32) -> f32;
/// Approximates `self^n`.
fn powf(self, n: f32) -> f32;
/// Approximates `self^n` where n is an `i32`
fn powi(self, n: i32) -> f32;
/// Returns the reciprocal (inverse) of a number, `1/x`.
fn recip(self) -> f32;
/// Calculates the least nonnegative remainder of `self (mod other)`.
fn rem_euclid(self, other: f32) -> f32;
/// Round the number part of floating point with sign.
fn round(self) -> f32;
/// Returns a number that represents the sign of `self`.
fn signum(self) -> f32;
/// Approximates sine in radians with a maximum error of `0.002`.
fn sin(self) -> f32;
/// Simultaneously computes the sine and cosine of the number, `x`.
/// Returns `(sin(x), cos(x))`.
fn sin_cos(self) -> (f32, f32);
/// Approximates square root with an average deviation of ~5%.
fn sqrt(self) -> f32;
/// Approximates `tan(x)` in radians with a maximum error of `0.6`.
fn tan(self) -> f32;
/// Retrieve whole number part of floating point with sign.
fn trunc(self) -> f32;
}
impl F32Ext for f32 {
#[inline]
fn abs(self) -> f32 {
F32(self).abs().0
}
#[inline]
fn acos(self) -> f32 {
F32(self).acos().0
}
#[inline]
fn asin(self) -> f32 {
F32(self).asin().0
}
#[inline]
fn atan(self) -> f32 {
F32(self).atan().0
}
#[inline]
fn atan_norm(self) -> f32 {
F32(self).atan_norm().0
}
#[inline]
fn atan2(self, other: f32) -> f32 {
F32(self).atan2(F32(other)).0
}
#[inline]
fn atan2_norm(self, other: f32) -> f32 {
F32(self).atan2_norm(F32(other)).0
}
#[inline]
fn ceil(self) -> f32 {
F32(self).ceil().0
}
#[inline]
fn copysign(self, sign: f32) -> f32 {
F32(self).copysign(F32(sign)).0
}
#[inline]
fn cos(self) -> f32 {
F32(self).cos().0
}
#[inline]
fn div_euclid(self, other: f32) -> f32 {
F32(self).div_euclid(F32(other)).0
}
#[inline]
fn exp(self) -> f32 {
F32(self).exp().0
}
#[inline]
fn floor(self) -> f32 {
F32(self).floor().0
}
#[inline]
fn fract(self) -> f32 {
F32(self).fract().0
}
#[inline]
fn hypot(self, other: f32) -> f32 {
F32(self).hypot(other.into()).0
}
#[inline]
fn inv(self) -> f32 {
F32(self).inv().0
}
#[inline]
fn invsqrt(self) -> f32 {
F32(self).invsqrt().0
}
#[inline]
fn ln(self) -> f32 {
F32(self).ln().0
}
#[inline]
fn log(self, base: f32) -> f32 {
F32(self).log(F32(base)).0
}
#[inline]
fn log2(self) -> f32 {
F32(self).log2().0
}
#[inline]
fn log10(self) -> f32 {
F32(self).log10().0
}
#[inline]
fn mul_add(self, a: f32, b: f32) -> f32 {
F32(self).mul_add(F32(a), F32(b)).0
}
#[inline]
fn powf(self, n: f32) -> f32 {
F32(self).powf(F32(n)).0
}
#[inline]
fn powi(self, n: i32) -> f32 {
F32(self).powi(n).0
}
#[inline]
fn recip(self) -> f32 {
F32(self).recip().0
}
#[inline]
fn rem_euclid(self, other: f32) -> f32 {
F32(self).rem_euclid(F32(other)).0
}
#[inline]
fn round(self) -> f32 {
F32(self).round().0
}
#[inline]
fn signum(self) -> f32 {
F32(self).signum().0
}
#[inline]
fn sin(self) -> f32 {
F32(self).sin().0
}
#[inline]
fn sin_cos(self) -> (f32, f32) {
(F32(self).sin().0, F32(self).cos().0)
}
#[inline]
fn sqrt(self) -> f32 {
F32(self).sqrt().0
}
#[inline]
fn tan(self) -> f32 {
F32(self).tan().0
}
#[inline]
fn trunc(self) -> f32 {
F32(self).trunc().0
}
}
+649
View File
@@ -0,0 +1,649 @@
//! Floating point operations
pub(crate) mod abs;
pub(crate) mod acos;
pub(crate) mod asin;
pub(crate) mod atan;
pub(crate) mod atan2;
pub(crate) mod ceil;
pub(crate) mod copysign;
pub(crate) mod cos;
pub(crate) mod div_euclid;
pub(crate) mod exp;
pub(crate) mod floor;
pub(crate) mod fract;
pub(crate) mod hypot;
pub(crate) mod inv;
pub(crate) mod invsqrt;
pub(crate) mod ln;
pub(crate) mod log;
pub(crate) mod log10;
pub(crate) mod log2;
pub(crate) mod mul_add;
pub(crate) mod powf;
pub(crate) mod powi;
pub(crate) mod recip;
pub(crate) mod rem_euclid;
pub(crate) mod round;
pub(crate) mod signum;
pub(crate) mod sin;
pub(crate) mod sin_cos;
pub(crate) mod sqrt;
pub(crate) mod tan;
pub(crate) mod trunc;
use core::{
cmp::Ordering,
fmt::{self, Display, LowerExp, UpperExp},
iter::{Product, Sum},
num::ParseFloatError,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign},
str::FromStr,
};
#[cfg(feature = "num-traits")]
use num_traits::{Inv, Num, One, Zero};
/// Sign mask.
pub(crate) const SIGN_MASK: u32 = 0b1000_0000_0000_0000_0000_0000_0000_0000;
/// Exponent mask.
pub(crate) const EXPONENT_MASK: u32 = 0b0111_1111_1000_0000_0000_0000_0000_0000;
/// Mantissa mask.
pub(crate) const MANTISSA_MASK: u32 = 0b0000_0000_0111_1111_1111_1111_1111_1111;
/// Exponent mask.
pub(crate) const EXPONENT_BIAS: u32 = 127;
/// Mantissa bits.
///
/// Note: `MANTISSA_DIGITS` is available in `core::f32`, but the actual bits taken up are 24 - 1.
pub(crate) const MANTISSA_BITS: u32 = 23;
/// 32-bit floating point wrapper which implements fast approximation-based
/// operations.
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
pub struct F32(pub f32);
impl F32 {
/// The value `0.0`.
pub const ZERO: Self = Self(0.0);
/// The value `1.0`.
pub const ONE: Self = Self(1.0);
/// The radix or base of the internal representation of `f32`.
pub const RADIX: u32 = f32::RADIX;
/// Number of significant digits in base 2.
pub const MANTISSA_DIGITS: u32 = f32::MANTISSA_DIGITS;
/// Approximate number of significant digits in base 10.
pub const DIGITS: u32 = f32::DIGITS;
/// [Machine epsilon] value for `f32`.
///
/// This is the difference between `1.0` and the next larger representable number.
///
/// [Machine epsilon]: https://en.wikipedia.org/wiki/Machine_epsilon
pub const EPSILON: Self = Self(f32::EPSILON);
/// Smallest finite `f32` value.
pub const MIN: Self = Self(f32::MIN);
/// Smallest positive normal `f32` value.
pub const MIN_POSITIVE: Self = Self(f32::MIN_POSITIVE);
/// Largest finite `f32` value.
pub const MAX: Self = Self(f32::MAX);
/// One greater than the minimum possible normal power of 2 exponent.
pub const MIN_EXP: i32 = f32::MIN_EXP;
/// Maximum possible power of 2 exponent.
pub const MAX_EXP: i32 = f32::MAX_EXP;
/// Minimum possible normal power of 10 exponent.
pub const MIN_10_EXP: i32 = f32::MIN_10_EXP;
/// Maximum possible power of 10 exponent.
pub const MAX_10_EXP: i32 = f32::MAX_10_EXP;
/// Not a Number (NaN).
pub const NAN: Self = Self(f32::NAN);
/// Infinity (∞).
pub const INFINITY: Self = Self(f32::INFINITY);
/// Negative infinity (−∞).
pub const NEG_INFINITY: Self = Self(f32::NEG_INFINITY);
/// Returns `true` if this value is `NaN`.
#[inline]
pub fn is_nan(self) -> bool {
self.0.is_nan()
}
/// Returns `true` if this value is positive infinity or negative infinity, and
/// `false` otherwise.
#[inline]
pub fn is_infinite(self) -> bool {
self.0.is_infinite()
}
/// Returns `true` if this number is neither infinite nor `NaN`.
#[inline]
pub fn is_finite(self) -> bool {
self.0.is_finite()
}
/// Returns `true` if `self` has a positive sign, including `+0.0`, `NaN`s with
/// positive sign bit and positive infinity.
#[inline]
pub fn is_sign_positive(self) -> bool {
self.0.is_sign_positive()
}
/// Returns `true` if `self` has a negative sign, including `-0.0`, `NaN`s with
/// negative sign bit and negative infinity.
#[inline]
pub fn is_sign_negative(self) -> bool {
self.0.is_sign_negative()
}
/// Raw transmutation to `u32`.
///
/// This is currently identical to `transmute::<f32, u32>(self)` on all platforms.
///
/// See [`F32::from_bits`] for some discussion of the portability of this operation
/// (there are almost no issues).
#[inline]
pub fn to_bits(self) -> u32 {
self.0.to_bits()
}
/// Raw transmutation from `u32`.
///
/// This is currently identical to `transmute::<u32, f32>(v)` on all platforms.
/// It turns out this is incredibly portable, for two reasons:
///
/// - Floats and Ints have the same endianness on all supported platforms.
/// - IEEE-754 very precisely specifies the bit layout of floats.
///
/// See [`f32::from_bits`] for more information.
#[inline]
pub fn from_bits(v: u32) -> Self {
Self(f32::from_bits(v))
}
/// Extract exponent bits.
pub(crate) fn extract_exponent_bits(self) -> u32 {
(self.to_bits() & EXPONENT_MASK)
.overflowing_shr(MANTISSA_BITS)
.0
}
/// Extract the exponent of a float's value.
pub(crate) fn extract_exponent_value(self) -> i32 {
(self.extract_exponent_bits() as i32) - EXPONENT_BIAS as i32
}
/// Remove sign.
pub(crate) fn without_sign(self) -> Self {
Self::from_bits(self.to_bits() & !SIGN_MASK)
}
/// Set the exponent to the given value.
pub(crate) fn set_exponent(self, exponent: i32) -> Self {
debug_assert!(exponent <= 127 && exponent >= -128);
let without_exponent: u32 = self.to_bits() & !EXPONENT_MASK;
let only_exponent: u32 = ((exponent + EXPONENT_BIAS as i32) as u32)
.overflowing_shl(MANTISSA_BITS)
.0;
Self::from_bits(without_exponent | only_exponent)
}
/// Is this floating point value equivalent to an integer?
pub(crate) fn is_integer(&self) -> bool {
let exponent = self.extract_exponent_value();
let self_bits = self.to_bits();
// if exponent is negative we shouldn't remove anything, this stops an opposite shift.
let exponent_clamped = i32::max(exponent, 0) as u32;
// find the part of the fraction that would be left over
let fractional_part = (self_bits).overflowing_shl(exponent_clamped).0 & MANTISSA_MASK;
// if fractional part contains anything, we know it *isn't* an integer.
// if zero there will be nothing in the fractional part
// if it is whole, there will be nothing in the fractional part
fractional_part == 0
}
/// Is this floating point value even?
fn is_even(&self) -> bool {
// any floating point value that doesn't fit in an i32 range is even,
// and will loose 1's digit precision at exp values of 23+
if self.extract_exponent_value() >= 31 {
true
} else {
(self.0 as i32) % 2 == 0
}
}
}
impl Add for F32 {
type Output = F32;
#[inline]
fn add(self, rhs: F32) -> F32 {
F32(self.0 + rhs.0)
}
}
impl Add<f32> for F32 {
type Output = F32;
#[inline]
fn add(self, rhs: f32) -> F32 {
F32(self.0 + rhs)
}
}
impl Add<F32> for f32 {
type Output = F32;
#[inline]
fn add(self, rhs: F32) -> F32 {
F32(self + rhs.0)
}
}
impl AddAssign for F32 {
#[inline]
fn add_assign(&mut self, rhs: F32) {
self.0 += rhs.0;
}
}
impl AddAssign<f32> for F32 {
#[inline]
fn add_assign(&mut self, rhs: f32) {
self.0 += rhs;
}
}
impl AddAssign<F32> for f32 {
#[inline]
fn add_assign(&mut self, rhs: F32) {
*self += rhs.0;
}
}
impl Display for F32 {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}", self.0)
}
}
impl Div for F32 {
type Output = F32;
#[inline]
fn div(self, rhs: F32) -> F32 {
F32(self.0 / rhs.0)
}
}
impl Div<f32> for F32 {
type Output = F32;
#[inline]
fn div(self, rhs: f32) -> F32 {
F32(self.0 / rhs)
}
}
impl Div<F32> for f32 {
type Output = F32;
#[inline]
fn div(self, rhs: F32) -> F32 {
F32(self / rhs.0)
}
}
impl DivAssign for F32 {
#[inline]
fn div_assign(&mut self, rhs: F32) {
self.0 /= rhs.0;
}
}
impl DivAssign<f32> for F32 {
#[inline]
fn div_assign(&mut self, rhs: f32) {
self.0 /= rhs;
}
}
impl DivAssign<F32> for f32 {
#[inline]
fn div_assign(&mut self, rhs: F32) {
*self /= rhs.0;
}
}
impl From<f32> for F32 {
#[inline]
fn from(n: f32) -> F32 {
F32(n)
}
}
impl From<F32> for f32 {
#[inline]
fn from(n: F32) -> f32 {
n.0
}
}
impl From<i8> for F32 {
#[inline]
fn from(n: i8) -> F32 {
F32(n.into())
}
}
impl From<i16> for F32 {
#[inline]
fn from(n: i16) -> F32 {
F32(n.into())
}
}
impl From<u8> for F32 {
#[inline]
fn from(n: u8) -> F32 {
F32(n.into())
}
}
impl From<u16> for F32 {
#[inline]
fn from(n: u16) -> F32 {
F32(n.into())
}
}
impl FromStr for F32 {
type Err = ParseFloatError;
#[inline]
fn from_str(src: &str) -> Result<F32, ParseFloatError> {
f32::from_str(src).map(F32)
}
}
impl LowerExp for F32 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:e}", self.0)
}
}
impl Mul for F32 {
type Output = F32;
#[inline]
fn mul(self, rhs: F32) -> F32 {
F32(self.0 * rhs.0)
}
}
impl Mul<f32> for F32 {
type Output = F32;
#[inline]
fn mul(self, rhs: f32) -> F32 {
F32(self.0 * rhs)
}
}
impl Mul<F32> for f32 {
type Output = F32;
#[inline]
fn mul(self, rhs: F32) -> F32 {
F32(self * rhs.0)
}
}
impl MulAssign for F32 {
#[inline]
fn mul_assign(&mut self, rhs: F32) {
self.0 *= rhs.0;
}
}
impl MulAssign<f32> for F32 {
#[inline]
fn mul_assign(&mut self, rhs: f32) {
self.0 *= rhs;
}
}
impl MulAssign<F32> for f32 {
#[inline]
fn mul_assign(&mut self, rhs: F32) {
*self *= rhs.0;
}
}
impl Neg for F32 {
type Output = F32;
#[inline]
fn neg(self) -> F32 {
F32(-self.0)
}
}
impl PartialEq<f32> for F32 {
fn eq(&self, other: &f32) -> bool {
self.0.eq(other)
}
}
impl PartialEq<F32> for f32 {
fn eq(&self, other: &F32) -> bool {
self.eq(&other.0)
}
}
impl PartialOrd<f32> for F32 {
fn partial_cmp(&self, other: &f32) -> Option<Ordering> {
self.0.partial_cmp(other)
}
}
impl PartialOrd<F32> for f32 {
fn partial_cmp(&self, other: &F32) -> Option<Ordering> {
self.partial_cmp(&other.0)
}
}
impl Product for F32 {
#[inline]
fn product<I>(iter: I) -> Self
where
I: Iterator<Item = F32>,
{
F32(f32::product(iter.map(f32::from)))
}
}
impl Rem for F32 {
type Output = F32;
#[inline]
fn rem(self, rhs: F32) -> F32 {
F32(self.0 % rhs.0)
}
}
impl Rem<f32> for F32 {
type Output = F32;
#[inline]
fn rem(self, rhs: f32) -> F32 {
F32(self.0 % rhs)
}
}
impl Rem<F32> for f32 {
type Output = F32;
#[inline]
fn rem(self, rhs: F32) -> F32 {
F32(self % rhs.0)
}
}
impl RemAssign for F32 {
#[inline]
fn rem_assign(&mut self, rhs: F32) {
self.0 %= rhs.0;
}
}
impl RemAssign<f32> for F32 {
#[inline]
fn rem_assign(&mut self, rhs: f32) {
self.0 %= rhs;
}
}
impl Sub for F32 {
type Output = F32;
#[inline]
fn sub(self, rhs: F32) -> F32 {
F32(self.0 - rhs.0)
}
}
impl Sub<f32> for F32 {
type Output = F32;
#[inline]
fn sub(self, rhs: f32) -> F32 {
F32(self.0 - rhs)
}
}
impl Sub<F32> for f32 {
type Output = F32;
#[inline]
fn sub(self, rhs: F32) -> F32 {
F32(self - rhs.0)
}
}
impl SubAssign for F32 {
#[inline]
fn sub_assign(&mut self, rhs: F32) {
self.0 -= rhs.0;
}
}
impl SubAssign<f32> for F32 {
#[inline]
fn sub_assign(&mut self, rhs: f32) {
self.0 -= rhs;
}
}
impl SubAssign<F32> for f32 {
#[inline]
fn sub_assign(&mut self, rhs: F32) {
*self -= rhs.0;
}
}
impl Sum for F32 {
#[inline]
fn sum<I>(iter: I) -> Self
where
I: Iterator<Item = F32>,
{
F32(f32::sum(iter.map(f32::from)))
}
}
impl UpperExp for F32 {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:E}", self.0)
}
}
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
impl Zero for F32 {
fn zero() -> Self {
Self::ZERO
}
fn is_zero(&self) -> bool {
Self::ZERO == *self
}
}
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
impl One for F32 {
fn one() -> Self {
Self::ONE
}
fn is_one(&self) -> bool {
Self::ONE == *self
}
}
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
impl Num for F32 {
type FromStrRadixErr = num_traits::ParseFloatError;
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
f32::from_str_radix(str, radix).map(Self)
}
}
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
impl Inv for F32 {
type Output = Self;
fn inv(self) -> Self {
self.inv()
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)] // remove when we have more tests
use super::F32;
#[cfg(feature = "num-traits")]
#[test]
fn inv_trait() {
assert_eq!(num_traits::Inv::inv(F32(2.0)), F32(0.5));
}
}
+31
View File
@@ -0,0 +1,31 @@
//! Compute the absolute value of a single-precision float.
//!
//! Method described at: <https://bits.stephan-brumme.com/absFloat.html>
use super::{F32, SIGN_MASK};
impl F32 {
/// Computes the absolute value of `self`.
///
/// Returns [`Self::NAN`] if the number is [`Self::NAN`].
pub fn abs(self) -> Self {
Self::from_bits(self.to_bits() & !SIGN_MASK)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32::ONE.abs(), 1.0);
assert_eq!(F32::ZERO.abs(), 0.0);
assert_eq!(F32(-1.0).abs(), 1.0);
}
#[test]
fn nan() {
assert!(F32::NAN.abs().is_nan());
}
}
+70
View File
@@ -0,0 +1,70 @@
//! arccos approximation for a single-precision float.
//!
//! Method described at:
//! <https://math.stackexchange.com/questions/2908908/express-arccos-in-terms-of-arctan>
use super::F32;
use core::f32::consts::PI;
impl F32 {
/// Computes `acos(x)` approximation in radians in the range `[0, pi]`.
pub(crate) fn acos(self) -> Self {
if self > 0.0 {
((Self::ONE - self * self).sqrt() / self).atan()
} else if self == 0.0 {
Self(PI / 2.0)
} else {
((Self::ONE - self * self).sqrt() / self).atan() + PI
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
use core::f32::consts;
const MAX_ERROR: f32 = 0.03;
#[test]
fn sanity_check() {
// Arccosine test vectors - `(input, output)`
let test_vectors: &[(f32, f32)] = &[
(2.000, f32::NAN),
(1.000, 0.0),
(0.866, consts::FRAC_PI_6),
(0.707, consts::FRAC_PI_4),
(0.500, consts::FRAC_PI_3),
(f32::EPSILON, consts::FRAC_PI_2),
(0.000, consts::FRAC_PI_2),
(-f32::EPSILON, consts::FRAC_PI_2),
(-0.500, 2.0 * consts::FRAC_PI_3),
(-0.707, 3.0 * consts::FRAC_PI_4),
(-0.866, 5.0 * consts::FRAC_PI_6),
(-1.000, consts::PI),
(-2.000, f32::NAN),
];
for &(x, expected) in test_vectors {
let actual = F32(x).acos();
if expected.is_nan() {
assert!(
actual.is_nan(),
"acos({}) returned {}, should be NAN",
x,
actual
);
} else {
let delta = (actual - expected).abs();
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
actual,
expected
);
}
}
}
}
+25
View File
@@ -0,0 +1,25 @@
//! arcsin approximation for a single-precision float.
//!
//! Method described at:
//! <https://dsp.stackexchange.com/questions/25770/looking-for-an-arcsin-algorithm>
use super::F32;
impl F32 {
/// Computes `asin(x)` approximation in radians in the range `[-pi/2, pi/2]`.
pub fn asin(self) -> Self {
(self * (Self::ONE - self * self).invsqrt()).atan()
}
}
#[cfg(test)]
mod tests {
use super::F32;
use core::f32::consts::FRAC_PI_2;
#[test]
fn sanity_check() {
let difference = F32(FRAC_PI_2).sin().asin() - FRAC_PI_2;
assert!(difference.abs() <= F32::EPSILON);
}
}
+80
View File
@@ -0,0 +1,80 @@
//! arctangent approximation for a single-precision float.
//!
//! Method described at:
//! <https://ieeexplore.ieee.org/document/6375931>
use super::F32;
use core::f32::consts::FRAC_PI_2;
impl F32 {
/// Approximates `atan(x)` approximation in radians with a maximum error of
/// `0.002`.
///
/// Returns [`Self::NAN`] if the number is [`Self::NAN`].
pub fn atan(self) -> Self {
FRAC_PI_2 * self.atan_norm()
}
/// Approximates `atan(x)` normalized to the `[−1,1]` range with a maximum
/// error of `0.1620` degrees.
pub fn atan_norm(self) -> Self {
const SIGN_MASK: u32 = 0x8000_0000;
const B: f32 = 0.596_227;
// Extract the sign bit
let ux_s = SIGN_MASK & self.to_bits();
// Calculate the arctangent in the first quadrant
let bx_a = (B * self).abs();
let n = bx_a + self * self;
let atan_1q = n / (1.0 + bx_a + n);
// Restore the sign bit and convert to float
Self::from_bits(ux_s | atan_1q.to_bits())
}
}
#[cfg(test)]
mod tests {
use super::F32;
use core::f32::consts;
/// 0.1620 degrees in radians
const MAX_ERROR: f32 = 0.003;
#[test]
fn sanity_check() {
// Arctangent test vectors - `(input, output)`
let test_vectors: &[(f32, f32)] = &[
(3.0_f32.sqrt() / 3.0, consts::FRAC_PI_6),
(1.0, consts::FRAC_PI_4),
(3.0_f32.sqrt(), consts::FRAC_PI_3),
(-(3.0_f32.sqrt()) / 3.0, -consts::FRAC_PI_6),
(-1.0, -consts::FRAC_PI_4),
(-(3.0_f32.sqrt()), -consts::FRAC_PI_3),
];
for &(x, expected) in test_vectors {
let actual = F32(x).atan().0;
let delta = actual - expected;
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
actual,
expected
);
}
}
#[test]
fn zero() {
assert_eq!(F32::ZERO.atan(), F32::ZERO);
}
#[test]
fn nan() {
assert!(F32::NAN.atan().is_nan());
}
}
+79
View File
@@ -0,0 +1,79 @@
//! Four quadrant arctangent approximation for a single-precision float.
//!
//! Method described at: <https://ieeexplore.ieee.org/document/6375931>
use super::F32;
use core::f32::consts::PI;
impl F32 {
/// Approximates the four quadrant arctangent of `self` (`y`) and
/// `rhs` (`x`) in radians with a maximum error of `0.002`.
///
/// - `x = 0`, `y = 0`: `0`
/// - `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]`
/// - `y >= 0`: `arctan(y/x) + pi` -> `(pi/2, pi]`
/// - `y < 0`: `arctan(y/x) - pi` -> `(-pi, -pi/2)`
pub fn atan2(self, rhs: Self) -> Self {
let n = self.atan2_norm(rhs);
PI / 2.0 * if n > 2.0 { n - 4.0 } else { n }
}
/// Approximates `atan2(y,x)` normalized to the `[0, 4)` range with a maximum
/// error of `0.1620` degrees.
pub(crate) fn atan2_norm(self, rhs: Self) -> Self {
const SIGN_MASK: u32 = 0x8000_0000;
const B: f32 = 0.596_227;
let y = self;
let x = rhs;
// Extract sign bits from floating point values
let ux_s = SIGN_MASK & x.to_bits();
let uy_s = SIGN_MASK & y.to_bits();
// Determine quadrant offset
let q = ((!ux_s & uy_s) >> 29 | ux_s >> 30) as f32;
// Calculate arctangent in the first quadrant
let bxy_a = (B * x * y).abs();
let n = bxy_a + y * y;
let atan_1q = n / (x * x + bxy_a + n);
// Translate it to the proper quadrant
let uatan_2q = (ux_s ^ uy_s) | atan_1q.to_bits();
Self(q) + Self::from_bits(uatan_2q)
}
}
#[cfg(test)]
mod tests {
use super::F32;
use core::f32::consts::PI;
/// 0.1620 degrees in radians
const MAX_ERROR: f32 = 0.003;
#[test]
fn sanity_check() {
let test_vectors: &[(f32, f32, f32)] = &[
(0.0, 1.0, 0.0),
(0.0, -1.0, PI),
(3.0, 2.0, (3.0f32 / 2.0).atan()),
(2.0, -1.0, (2.0f32 / -1.0).atan() + PI),
(-2.0, -1.0, (-2.0f32 / -1.0).atan() - PI),
];
for &(y, x, expected) in test_vectors {
let actual = F32(y).atan2(F32(x)).0;
let delta = actual - expected;
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
actual,
expected
);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
//! Floating point ceiling approximation for a single-precision float.
use super::F32;
impl F32 {
/// Returns the smallest integer greater than or equal to a number.
pub fn ceil(self) -> Self {
-(-self).floor()
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(-1.1).ceil().0, -1.0);
assert_eq!(F32(-0.1).ceil().0, 0.0);
assert_eq!(F32(0.0).ceil().0, 0.0);
assert_eq!(F32(1.0).ceil().0, 1.0);
assert_eq!(F32(1.1).ceil().0, 2.0);
assert_eq!(F32(2.9).ceil().0, 3.0);
}
}
+33
View File
@@ -0,0 +1,33 @@
//! Copy the sign over from another number.
use super::{F32, SIGN_MASK};
impl F32 {
/// Returns a number composed of the magnitude of `self` and the sign of
/// `sign`.
pub fn copysign(self, sign: Self) -> Self {
let source_bits = sign.to_bits();
let source_sign = source_bits & SIGN_MASK;
let signless_destination_bits = self.to_bits() & !SIGN_MASK;
Self::from_bits(signless_destination_bits | source_sign)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(1.0).copysign(F32(-1.0)).0, -1.0);
assert_eq!(F32(-1.0).copysign(F32(1.0)).0, 1.0);
assert_eq!(F32(1.0).copysign(F32(1.0)).0, 1.0);
assert_eq!(F32(-1.0).copysign(F32(-1.0)).0, -1.0);
let large_float = F32(100_000_000.13425345345);
assert_eq!(large_float.copysign(-large_float), -large_float);
assert_eq!((-large_float).copysign(large_float), large_float);
assert_eq!(large_float.copysign(large_float), large_float);
assert_eq!((-large_float).copysign(-large_float), -large_float);
}
}
+308
View File
@@ -0,0 +1,308 @@
//! Cosine function implemented using a parabolic approximation.
//!
//! Note that while this method is inspired by a [Taylor series] approximation,
//! it provides both better performance and accuracy.
//!
//! Most of the other trigonometric functions in this crate are implemented in
//! terms of this approximation.
//!
//! Method described: <https://stackoverflow.com/posts/28050328/revisions>
//!
//! Originally based on this approximation by Nick Capen:
//!
//! <https://web.archive.org/web/20180105200343if_/http://forum.devmaster.net/t/fast-and-accurate-sine-cosine/9648>
//!
//! Post quoted below for posterity as the original site is down:
//!
//! > In some situations you need a good approximation of sine and cosine that
//! > runs at very high performance. One example is to implement dynamic
//! > subdivision of round surfaces, comparable to those in Quake 3. Or to
//! > implement wave motion, in case there are no vertex shaders 2.0 available.
//! >
//! > The standard C `sinf()` and `cosf()` functions are terribly slow and offer
//! > much more precision than what we actually need. What we really want is an
//! > approximation that offers the best compromise between precision and
//! > performance. The most well known approximation method is to use a
//! > [Taylor series] about 0 (also known as a Maclaurin series), which for
//! > sine becomes:
//! >
//! > ```text
//! > x - 1/6 x^3 + 1/120 x^5 - 1/5040 x^7 + ...
//! > ```
//! >
//! > When we plot this we get: taylor.gif. The green line is the real sine,
//! > the red line is the first four terms of the Taylor series. This seems
//! > like an acceptable approximation, but lets have a closer look:
//! > taylor_zoom.gif. It behaves very nicely up till `pi/2`, but after that it
//! > quickly deviates. At pi, it evaluates to -0.075 instead of 0. Using this
//! > for wave simulation will result in jerky motion which is unacceptable.
//! >
//! > We could add another term, which in fact reduces the error significantly,
//! > but this makes the formula pretty lengthy. We already need 7
//! > multiplications and 3 additions for the 4-term version. The Taylor series
//! > just can't give us the precision and performance we're looking for.
//! >
//! > We did however note that we need `sin(pi) = 0`. And there's another thing
//! > we can see from taylor_zoom.gif: this looks very much like a parabola!
//! > So let's try to find the formula of a parabola that matches it as closely
//! > as possible. The generic formula for a parabola is `A + B x + C x²`. So
//! > this gives us three degrees of freedom. Obvious choices are that we want
//! > `sin(0) = 0`, `sin(pi/2) = 1` and `sin(pi) = 0`. This gives us the following
//! > three equations:
//! >
//! > ```text
//! > A + B 0 + C 0² = 0
//! > A + B pi/2 + C (pi/2)² = 1
//! > A + B pi + C pi² = 0
//! > ```
//! >
//! > Which has as solution `A = 0, B = 4/pi, C = -4/pi²`. So our parabola
//! > approximation becomes `4/pi x - 4/pi² x²`. Plotting this we get:
//! > parabola.gif. This looks worse than the 4-term Taylor series, right?
//! > Wrong! The maximum absolute error is 0.056. Furthermore, this
//! > approximation will give us smooth wave motion, and can be calculated
//! > in only 3 multiplications and 1 addition!
//! >
//! > Unfortunately it's not very practical yet. This is what we get in the
//! > `[-pi, pi]` range: negative.gif. It's quite obvious we want at least a
//! > full period. But it's also clear that it's just another parabola,
//! > mirrored around the origin. The formula for it is `4/pi x + 4/pi² x²`.
//! > So the straightforward (pseudo-C) solution is:
//! >
//! > ```text
//! > if(x > 0)
//! > {
//! > y = 4/pi x - 4/pi² x²;
//! > }
//! > else
//! > {
//! > y = 4/pi x + 4/pi² x²;
//! > }
//! > ```
//! >
//! > Adding a branch is not a good idea though. It makes the code
//! > significantly slower. But look at how similar the two parts really are.
//! > A subtraction becomes an addition depending on the sign of x. In a naive
//! > first attempt to eliminate the branch we can 'extract' the sign of x
//! > using `x / abs(x)`. The division is very expensive but look at the resulting
//! > formula: `4/pi x - x / abs(x) 4/pi² x²`. By inverting the division we can
//! > simplify this to a very nice and clean `4/pi x - 4/pi² x abs(x)`. So for
//! > just one extra operation we get both halves of our sine approximation!
//! > Here's the graph of this formula confirming the result: abs.gif.
//! >
//! > Now let's look at cosine. Basic trigonometry tells us that
//! > `cos(x) = sin(pi/2 + x)`. Is that all, add pi/2 to x? No, we actually get
//! > the unwanted part of the parabola again: shift_sine.gif. What we need to
//! > do is 'wrap around' when x > pi/2. This can be done by subtracting 2 pi.
//! > So the code becomes:
//! >
//! > ```text
//! > x += pi/2;
//! >
//! > if(x > pi) // Original x > pi/2
//! > {
//! > x -= 2 * pi; // Wrap: cos(x) = cos(x - 2 pi)
//! > }
//! >
//! > y = sin(x);
//! > ```
//! >
//! > Yet another branch. To eliminate it, we can use binary logic, like this:
//! >
//! > ```text
//! > x -= (x > pi) & (2 * pi);
//! > ```
//! >
//! > Note that this isn't valid C code at all. But it should clarify how this
//! > can work. When x > pi is false the & operation zeroes the right hand part
//! > so the subtraction does nothing, which is perfectly equivalent. I'll
//! > leave it as an excercise to the reader to create working C code for this
//! > (or just read on). Clearly the cosine requires a few more operations than
//! > the sine but there doesn't seem to be any other way and it's still
//! > extremely fast.
//! >
//! > Now, a maximum error of 0.056 is nice but clearly the 4-term Taylor series
//! > still has a smaller error on average. Recall what our sine looked like:
//! > abs.gif. So isn't there anything we can do to further increase precision
//! > at minimal cost? Of course the current version is already applicable for
//! > many situations where something that looks like a sine is just as good as
//! > the real sine. But for other situations that's just not good enough.
//! >
//! > Looking at the graphs you'll notice that our approximation always
//! > overestimates the real sine, except at 0, pi/2 and pi. So what we need is
//! > to 'scale it down' without touching these important points. The solution
//! > is to use the squared parabola, which looks like this: squared.gif. Note
//! > how it preserves those important points but it's always lower than the
//! > real sine. So we can use a weighted average of the two to get a better
//! > approximation:
//! >
//! > ```text
//! > Q (4/pi x - 4/pi² x²) + P (4/pi x - 4/pi² x²)²
//! > ```
//! >
//! > With `Q + P = 1`. You can use exact minimization of either the absolute
//! > or relative error but I'll save you the math. The optimal weights are
//! > `Q = 0.775`, `P = 0.225` for the absolute error and `Q = 0.782`,
//! > `P = 0.218` for the relative error. I will use the former. The resulting
//! > graph is: average.gif. Where did the red line go? It's almost entirely
//! > covered by the green line, which instantly shows how good this
//! > approximation really is. The maximum error is about 0.001, a 50x
//! > improvement! The formula looks lengthy but the part between parenthesis
//! > is the same value from the parabola, which needs to be computed only
//! > once. In fact only 2 extra multiplications and 2 additions are required
//! > to achieve this precision boost.
//! >
//! > It shouldn't come as a big surprise that to make it work for negative x
//! > as well we need a second abs() operation. The final C code for the sine
//! > becomes:
//! >
//! > ```text
//! > float sin(float x)
//! > {
//! > const float B = 4/pi;
//! > const float C = -4/(pi*pi);
//! >
//! > float y = B * x + C * x * abs(x);
//! >
//! > #ifdef EXTRA_PRECISION
//! > // const float Q = 0.775;
//! > const float P = 0.225;
//! >
//! > y = P * (y * abs(y) - y) + y; // Q * y + P * y * abs(y)
//! > #endif
//! > }
//! > ```
//! >
//! > So we need merely 5 multiplications and 3 additions; still faster than
//! > the 4-term Taylor if we neglect the abs(), and much more precise! The
//! > cosine version just needs the extra shift and wrap operations on x.
//! >
//! > Last but not least, I wouldn't be Nick if I didn't include the SIMD
//! > optimized assembly version. It allows to perform the wrap operation very
//! > efficiently so I'll give you the cosine:
//! >
//! > ```asm
//! > // cos(x) = sin(x + pi/2)
//! > addps xmm0, PI_2
//! > movaps xmm1, xmm0
//! > cmpnltps xmm1, PI
//! > andps xmm1, PIx2
//! > subps xmm0, xmm1
//! >
//! > // Parabola
//! > movaps xmm1, xmm0
//! > andps xmm1, abs
//! > mulps xmm1, xmm0
//! > mulps xmm0, B
//! > mulps xmm1, C
//! > addps xmm0, xmm1
//! >
//! > // Extra precision
//! > movaps xmm1, xmm0
//! > andps xmm1, abs
//! > mulps xmm1, xmm0
//! > subps xmm1, xmm0
//! > mulps xmm1, P
//! > addps xmm0, xmm1
//! > ```
//! >
//! > This code computes four cosines in parallel, resulting in a peak
//! > performance of about 9 clock cycles per cosine for most CPU architectures.
//! > Sines take only 6 clock cycles ideally. The lower precision version can
//! > even run at 3 clock cycles per sine... And lets not forget that all input
//! > between -pi and pi is valid and the formula is exact at -pi, -pi/2, 0,
//! > pi/2 and pi.
//! >
//! > So, the conclusion is don't ever again use a Taylor series to approximate
//! > a sine or cosine! To add a useful discussion to this article, I'd love to
//! > hear if anyone knows good approximations for other transcendental functions
//! > like the exponential, logarithm and power functions.
use super::F32;
use core::f32::consts::FRAC_1_PI;
impl F32 {
/// Approximates `cos(x)` in radians with a maximum error of `0.002`.
pub fn cos(self) -> Self {
let mut x = self;
x *= FRAC_1_PI / 2.0;
x -= 0.25 + (x + 0.25).floor().0;
x *= 16.0 * (x.abs() - 0.5);
x += 0.225 * x * (x.abs() - 1.0);
x
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::F32;
/// Maximum error in radians
pub(crate) const MAX_ERROR: f32 = 0.002;
/// Cosine test vectors - `(input, output)`
const TEST_VECTORS: &[(f32, f32)] = &[
(0.000, 1.000),
(0.140, 0.990),
(0.279, 0.961),
(0.419, 0.914),
(0.559, 0.848),
(0.698, 0.766),
(0.838, 0.669),
(0.977, 0.559),
(1.117, 0.438),
(1.257, 0.309),
(1.396, 0.174),
(1.536, 0.035),
(1.676, -0.105),
(1.815, -0.242),
(1.955, -0.375),
(2.094, -0.500),
(2.234, -0.616),
(2.374, -0.719),
(2.513, -0.809),
(2.653, -0.883),
(2.793, -0.940),
(2.932, -0.978),
(3.072, -0.998),
(3.211, -0.998),
(3.351, -0.978),
(3.491, -0.940),
(3.630, -0.883),
(3.770, -0.809),
(3.910, -0.719),
(4.049, -0.616),
(4.189, -0.500),
(4.328, -0.375),
(4.468, -0.242),
(4.608, -0.105),
(4.747, 0.035),
(4.887, 0.174),
(5.027, 0.309),
(5.166, 0.438),
(5.306, 0.559),
(5.445, 0.669),
(5.585, 0.766),
(5.725, 0.848),
(5.864, 0.914),
(6.004, 0.961),
(6.144, 0.990),
(6.283, 1.000),
];
#[test]
fn sanity_check() {
for &(x, expected) in TEST_VECTORS {
let cos_x = F32(x).cos();
let delta = (cos_x - expected).abs();
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
cos_x,
expected
);
}
}
}
+34
View File
@@ -0,0 +1,34 @@
//! Calculates Euclidian division for a single-precision float.
use super::F32;
impl F32 {
/// Calculates Euclidean division, the matching method for `rem_euclid`.
pub fn div_euclid(self, rhs: Self) -> Self {
let q = (self / rhs).trunc();
if self % rhs >= Self::ZERO {
q
} else if rhs > Self::ZERO {
q - Self::ONE
} else {
q + Self::ONE
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
let a = F32(7.0);
let b = F32(4.0);
assert_eq!(a.div_euclid(b), F32(1.0));
assert_eq!((-a).div_euclid(b), F32(-2.0));
assert_eq!(a.div_euclid(-b), F32(-1.0));
assert_eq!((-a).div_euclid(-b), F32(2.0));
}
}
+121
View File
@@ -0,0 +1,121 @@
//! Exp approximation for a single-precision float.
//!
//! Method described at: <https://stackoverflow.com/a/6985769/2036035>
use super::{EXPONENT_BIAS, F32};
use core::f32::consts;
impl F32 {
/// Returns `e^(self)`, (the exponential function).
#[inline]
pub fn exp(self) -> Self {
self.exp_ln2_approx(4)
}
/// Exp approximation for `f32`.
pub(crate) fn exp_ln2_approx(self, partial_iter: u32) -> Self {
if self == Self::ZERO {
return Self::ONE;
}
if (self - Self::ONE).abs() < f32::EPSILON {
return consts::E.into();
}
if (self - (-Self::ONE)).abs() < f32::EPSILON {
return Self::ONE / consts::E;
}
// log base 2(E) == 1/ln(2)
// x_fract + x_whole = x/ln2_recip
// ln2*(x_fract + x_whole) = x
let x_ln2recip = self * consts::LOG2_E;
let x_fract = x_ln2recip.fract();
let x_trunc = x_ln2recip.trunc();
//guaranteed to be 0 < x < 1.0
let x_fract = x_fract * consts::LN_2;
let fract_exp = x_fract.exp_smallx(partial_iter);
//need the 2^n portion, we can just extract that from the whole number exp portion
let fract_exponent: i32 = fract_exp
.extract_exponent_value()
.saturating_add(x_trunc.0 as i32);
if fract_exponent < -(EXPONENT_BIAS as i32) {
return Self::ZERO;
}
if fract_exponent > ((EXPONENT_BIAS + 1) as i32) {
return Self::INFINITY;
}
fract_exp.set_exponent(fract_exponent)
}
/// if x is between 0.0 and 1.0, we can approximate it with the a series
///
/// Series from here:
/// <https://stackoverflow.com/a/6984495>
///
/// e^x ~= 1 + x(1 + x/2(1 + (x?
#[inline]
pub(crate) fn exp_smallx(self, iter: u32) -> Self {
let mut total = 1.0;
for i in (1..=iter).rev() {
total = 1.0 + ((self.0 / (i as f32)) * total);
}
Self(total)
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 0.001;
/// exp test vectors - `(input, output)`
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(1e-07, 1.0000001),
(1e-06, 1.000001),
(1e-05, 1.00001),
(1e-04, 1.0001),
(0.001, 1.0010005),
(0.01, 1.0100502),
(0.1, 1.105171),
(1.0, 2.7182817),
(10.0, 22026.465),
(-1e-08, 1.0),
(-1e-07, 0.9999999),
(-1e-06, 0.999999),
(-1e-05, 0.99999),
(-1e-04, 0.9999),
(-0.001, 0.9990005),
(-0.01, 0.99004984),
(-0.1, 0.9048374),
(-1.0, 0.36787945),
(-10.0, 4.539_993e-5),
];
#[test]
fn sanity_check() {
assert_eq!(F32(-1000000.0).exp(), F32::ZERO);
for &(x, expected) in TEST_VECTORS {
let exp_x = F32(x).exp();
let relative_error = (exp_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large for input {} : {} vs {}",
relative_error,
x,
exp_x,
expected
);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
//! Floating point floor approximation for a single-precision float.
use super::F32;
impl F32 {
/// Returns the largest integer less than or equal to a number.
pub fn floor(self) -> Self {
let mut res = (self.0 as i32) as f32;
if self.0 < res {
res -= 1.0;
}
Self(res)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(-1.1).floor().0, -2.0);
assert_eq!(F32(-0.1).floor().0, -1.0);
assert_eq!(F32(0.0).floor().0, 0.0);
assert_eq!(F32(1.0).floor().0, 1.0);
assert_eq!(F32(1.1).floor().0, 1.0);
assert_eq!(F32(2.9).floor().0, 2.0);
}
}
+60
View File
@@ -0,0 +1,60 @@
//! Floating point fractional number for a single-precision float.
use super::F32;
use super::{EXPONENT_BIAS, MANTISSA_BITS, MANTISSA_MASK};
impl F32 {
/// Returns the fractional part of a number with sign.
pub fn fract(self) -> Self {
let x_bits = self.to_bits();
let exponent = self.extract_exponent_value();
// we know it is *only* fraction
if exponent < 0 {
return self;
}
// find the part of the fraction that would be left over
let fractional_part = x_bits.overflowing_shl(exponent as u32).0 & MANTISSA_MASK;
// if there isn't a fraction we can just return 0
if fractional_part == 0 {
// TODO: most people don't actually care about -0.0,
// so would it be better to just not copysign?
return Self(0.0).copysign(self);
}
// Note: alternatively this could use -1.0, but it's assumed subtraction would be more costly
// example: 'let new_exponent_bits = 127_u32.overflowing_shl(23_u32).0)) - 1.0'
let exponent_shift: u32 = (fractional_part.leading_zeros() - (32 - MANTISSA_BITS)) + 1;
let fractional_normalized: u32 =
fractional_part.overflowing_shl(exponent_shift).0 & MANTISSA_MASK;
let new_exponent_bits = (EXPONENT_BIAS - (exponent_shift))
.overflowing_shl(MANTISSA_BITS)
.0;
Self::from_bits(fractional_normalized | new_exponent_bits).copysign(self)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
// fraction check actually won't be the same, though technically exactly accurate
// so we test by adding back the number removed.
assert_eq!(F32(2.9).fract().0 + 2.0, 2.9);
assert_eq!(F32(-1.1).fract().0 - 1.0, -1.1);
assert_eq!(F32(-0.1).fract().0, -0.1);
assert_eq!(F32(0.0).fract().0, 0.0);
assert_eq!(F32(1.0).fract().0 + 1.0, 1.0);
assert_eq!(F32(1.1).fract().0 + 1.0, 1.1);
assert_eq!(F32(-100_000_000.13425345345).fract().0, -0.0);
assert_eq!(F32(100_000_000.13425345345).fract().0, 0.0);
}
}
+23
View File
@@ -0,0 +1,23 @@
//! Calculate length of the hypotenuse of a right triangle.
use super::F32;
impl F32 {
/// Calculate the length of the hypotenuse of a right-angle triangle.
pub fn hypot(self, rhs: Self) -> Self {
(self * self + rhs * rhs).sqrt()
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
let x = F32(3.0);
let y = F32(4.0);
let difference = x.hypot(y) - F32(25.0).sqrt();
assert!(difference.abs() <= F32::EPSILON);
}
}
+39
View File
@@ -0,0 +1,39 @@
//! Fast approximation of `1/x`.
//!
//! Method described at: <https://bits.stephan-brumme.com/inverse.html>
use super::F32;
impl F32 {
/// Fast approximation of `1/x`.
pub fn inv(self) -> Self {
Self(f32::from_bits(0x7f00_0000 - self.0.to_bits()))
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::F32;
/// Deviation from the actual value (8%)
pub(crate) const MAX_ERROR: f32 = 0.08;
#[test]
fn sanity_check() {
for x in 0..100 {
let x = F32(x as f32);
let inv_x = x.inv().0;
let expected = 1.0 / x;
let allowed_delta = x * MAX_ERROR;
let actual_delta = inv_x - expected;
assert!(
actual_delta <= allowed_delta,
"delta {} too large: {} vs {}",
actual_delta,
inv_x,
expected
);
}
}
}
+41
View File
@@ -0,0 +1,41 @@
//! Inverse square root approximation function for a single-precision float.
//!
//! Method described at: <https://bits.stephan-brumme.com/invSquareRoot.html>
use super::F32;
impl F32 {
/// Approximate inverse square root with an average deviation of ~5%.
pub fn invsqrt(self) -> Self {
Self::from_bits(0x5f37_5a86 - (self.to_bits() >> 1))
}
}
#[cfg(test)]
mod tests {
use super::F32;
use crate::float::sqrt::tests::TEST_VECTORS;
/// Deviation from the actual value (5%)
const MAX_ERROR: f32 = 0.05;
#[test]
fn sanity_check() {
for (x, expected) in TEST_VECTORS {
// The tests vectors are for sqrt(x), so invert the expected value
let expected = 1.0 / expected;
let invsqrt_x = F32(*x).invsqrt().0;
let allowed_delta = x * MAX_ERROR;
let actual_delta = invsqrt_x - expected;
assert!(
actual_delta <= allowed_delta,
"delta {} too large: {} vs {}",
actual_delta,
invsqrt_x,
expected
);
}
}
}
+121
View File
@@ -0,0 +1,121 @@
//! Natural log (ln) approximation for a single-precision float.
//!
//! Method described at: <https://stackoverflow.com/a/44232045/>
//!
//! Modified to not be restricted to int range and only values of x above 1.0.
//! Also got rid of most of the slow conversions. Should work for all positive values of x.
use super::{EXPONENT_MASK, F32};
use core::f32::consts::LN_2;
impl F32 {
/// Approximates the natural logarithm of the number.
// Note: excessive precision ignored because it hides the origin of the numbers used for the
// ln(1.0->2.0) polynomial
#[allow(clippy::excessive_precision)]
pub fn ln(self) -> Self {
// x may essentially be 1.0 but, as clippy notes, these kinds of
// floating point comparisons can fail when the bit pattern is not the sames
if (self - Self::ONE).abs() < f32::EPSILON {
return Self::ZERO;
}
let x_less_than_1 = self < 1.0;
// Note: we could use the fast inverse approximation here found in super::inv::inv_approx, but
// the precision of such an approximation is assumed not good enough.
let x_working = if x_less_than_1 { self.inv() } else { self };
// according to the SO post ln(x) = ln((2^n)*y)= ln(2^n) + ln(y) = ln(2) * n + ln(y)
// get exponent value
let base2_exponent = x_working.extract_exponent_value() as u32;
let divisor = f32::from_bits(x_working.to_bits() & EXPONENT_MASK);
// supposedly normalizing between 1.0 and 2.0
let x_working = x_working / divisor;
// approximate polynomial generated from maple in the post using Remez Algorithm:
// https://en.wikipedia.org/wiki/Remez_algorithm
let ln_1to2_polynomial = -1.741_793_9
+ (2.821_202_6
+ (-1.469_956_8 + (0.447_179_55 - 0.056_570_851 * x_working) * x_working)
* x_working)
* x_working;
// ln(2) * n + ln(y)
let result = (base2_exponent as f32) * LN_2 + ln_1to2_polynomial;
if x_less_than_1 {
-result
} else {
result
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 0.001;
/// ln(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(1e-20, -46.0517),
(1e-19, -43.749115),
(1e-18, -41.446533),
(1e-17, -39.143948),
(1e-16, -36.841362),
(1e-15, -34.538776),
(1e-14, -32.23619),
(1e-13, -29.933607),
(1e-12, -27.631021),
(1e-11, -25.328436),
(1e-10, -23.02585),
(1e-09, -20.723267),
(1e-08, -18.420681),
(1e-07, -16.118095),
(1e-06, -13.815511),
(1e-05, -11.512925),
(1e-04, -9.2103405),
(0.001, -6.9077554),
(0.01, -4.6051702),
(0.1, -2.3025851),
(10.0, 2.3025851),
(100.0, 4.6051702),
(1000.0, 6.9077554),
(10000.0, 9.2103405),
(100000.0, 11.512925),
(1000000.0, 13.815511),
(10000000.0, 16.118095),
(100000000.0, 18.420681),
(1000000000.0, 20.723267),
(10000000000.0, 23.02585),
(100000000000.0, 25.328436),
(1000000000000.0, 27.631021),
(10000000000000.0, 29.933607),
(100000000000000.0, 32.23619),
(1000000000000000.0, 34.538776),
(1e+16, 36.841362),
(1e+17, 39.143948),
(1e+18, 41.446533),
(1e+19, 43.749115),
];
#[test]
fn sanity_check() {
assert_eq!(F32::ONE.ln(), F32::ZERO);
for &(x, expected) in TEST_VECTORS {
let ln_x = F32(x).ln();
let relative_error = (ln_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
ln_x,
expected
);
}
}
}
+192
View File
@@ -0,0 +1,192 @@
//! log_b(a) approximation for a single-precision float.
use super::F32;
impl F32 {
/// Approximates the logarithm of the number with respect to an arbitrary base.
pub fn log(self, base: Self) -> Self {
(Self::ONE / base.ln()) * self.ln()
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 0.001;
/// log3(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS_BASE3: &[(f32, f32)] = &[
(1e-20, -41.918_064),
(1e-19, -39.822_16),
(1e-18, -37.726_26),
(1e-17, -35.630_356),
(1e-16, -33.534_454),
(1e-15, -31.438_549),
(1e-14, -29.342_646),
(1e-13, -27.246_744),
(1e-12, -25.150_839),
(1e-11, -23.054_935),
(1e-10, -20.959_032),
(1e-09, -18.863_13),
(1e-08, -16.767_227),
(1e-07, -14.671_323),
(1e-06, -12.575_419),
(1e-05, -10.479_516),
(1e-04, -8.383_614),
(0.001, -6.287_709_7),
(0.01, -4.191_807),
(0.1, -2.095_903_4),
(10.0, 2.095_903_4),
(100.0, 4.191_807),
(1000.0, 6.287_709_7),
(10000.0, 8.383_614),
(100000.0, 10.479_516),
(1000000.0, 12.575_419),
(10000000.0, 14.671_323),
(100000000.0, 16.767_227),
(1000000000.0, 18.863_13),
(10000000000.0, 20.959_032),
(100000000000.0, 23.054_935),
(1000000000000.0, 25.150_839),
(10000000000000.0, 27.246_744),
(100000000000000.0, 29.342_646),
(1000000000000000.0, 31.438_549),
(1e+16, 33.534_454),
(1e+17, 35.630_356),
(1e+18, 37.726_26),
(1e+19, 39.822_16),
];
/// log5.5(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS_BASE5_5: &[(f32, f32)] = &[
(1e-20, -27.013_786),
(1e-19, -25.663_097),
(1e-18, -24.312_408),
(1e-17, -22.961_72),
(1e-16, -21.611_03),
(1e-15, -20.260_34),
(1e-14, -18.909_65),
(1e-13, -17.558_962),
(1e-12, -16.208_273),
(1e-11, -14.857_583),
(1e-10, -13.506_893),
(1e-09, -12.156_204),
(1e-08, -10.805_515),
(1e-07, -9.454_825),
(1e-06, -8.104_136),
(1e-05, -6.753_446_6),
(1e-04, -5.402_757_6),
(0.001, -4.052_068),
(0.01, -2.701_378_8),
(0.1, -1.350_689_4),
(10.0, 1.350_689_4),
(100.0, 2.701_378_8),
(1000.0, 4.052_068),
(10000.0, 5.402_757_6),
(100000.0, 6.753_446_6),
(1000000.0, 8.104_136),
(10000000.0, 9.454_825),
(100000000.0, 10.805_515),
(1000000000.0, 12.156_204),
(10000000000.0, 13.506_893),
(100000000000.0, 14.857_583),
(1000000000000.0, 16.208_273),
(10000000000000.0, 17.558_962),
(100000000000000.0, 18.909_65),
(1000000000000000.0, 20.260_34),
(1e+16, 21.611_03),
(1e+17, 22.961_72),
(1e+18, 24.312_408),
(1e+19, 25.663_097),
];
/// log12.7(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS_BASE12_7: &[(f32, f32)] = &[
(1e-20, -18.119_164),
(1e-19, -17.213_205),
(1e-18, -16.307_247),
(1e-17, -15.401_289),
(1e-16, -14.495_331),
(1e-15, -13.589_373),
(1e-14, -12.683_414),
(1e-13, -11.777_456),
(1e-12, -10.871_498),
(1e-11, -9.965_54),
(1e-10, -9.059_582),
(1e-09, -8.153_624),
(1e-08, -7.247_665_4),
(1e-07, -6.341_707),
(1e-06, -5.435_749),
(1e-05, -4.529_791),
(1e-04, -3.623_832_7),
(0.001, -2.717_874_5),
(0.01, -1.811_916_4),
(0.1, -0.905_958_2),
(10.0, 0.905_958_2),
(100.0, 1.811_916_4),
(1000.0, 2.717_874_5),
(10000.0, 3.623_832_7),
(100000.0, 4.529_791),
(1000000.0, 5.435_749),
(10000000.0, 6.341_707),
(100000000.0, 7.247_665_4),
(1000000000.0, 8.153_624),
(10000000000.0, 9.059_582),
(100000000000.0, 9.965_54),
(1000000000000.0, 10.871_498),
(10000000000000.0, 11.777_456),
(100000000000000.0, 12.683_414),
(1000000000000000.0, 13.589_373),
(1e+16, 14.495_331),
(1e+17, 15.401_289),
(1e+18, 16.307_247),
(1e+19, 17.213_205),
];
#[test]
fn sanity_check() {
assert_eq!(F32::ONE.log(F32(3.0)), F32::ZERO);
assert_eq!(F32::ONE.log(F32(5.5)), F32::ZERO);
assert_eq!(F32::ONE.log(F32(12.7)), F32::ZERO);
for &(x, expected) in TEST_VECTORS_BASE3 {
let log_x = F32(x).log(F32(3.0));
let relative_error = (log_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
log_x,
expected
);
}
for &(x, expected) in TEST_VECTORS_BASE5_5 {
let log_x = F32(x).log(F32(5.5));
let relative_error = (log_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
log_x,
expected
);
}
for &(x, expected) in TEST_VECTORS_BASE12_7 {
let log_x = F32(x).log(F32(12.7));
let relative_error = (log_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
log_x,
expected
);
}
}
}
+78
View File
@@ -0,0 +1,78 @@
//! log base 2 approximation for a single-precision float.
use super::F32;
use core::f32::consts::LOG10_E;
impl F32 {
/// Approximates the base 10 logarithm of the number.
pub fn log10(self) -> Self {
self.ln() * LOG10_E
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 0.001;
/// log10(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(1e-20, -20.0),
(1e-19, -19.0),
(1e-18, -18.0),
(1e-17, -17.0),
(1e-16, -16.0),
(1e-15, -15.0),
(1e-14, -14.0),
(1e-13, -13.0),
(1e-12, -12.0),
(1e-11, -11.0),
(1e-10, -10.0),
(1e-09, -9.0),
(1e-08, -8.0),
(1e-07, -7.0),
(1e-06, -6.0),
(1e-05, -5.0),
(1e-04, -4.0),
(0.001, -3.0),
(0.01, -2.0),
(0.1, -1.0),
(10.0, 1.0),
(100.0, 2.0),
(1000.0, 3.0),
(10000.0, 4.0),
(100000.0, 5.0),
(1000000.0, 6.0),
(10000000.0, 7.0),
(100000000.0, 8.0),
(1000000000.0, 9.0),
(10000000000.0, 10.0),
(100000000000.0, 11.0),
(1000000000000.0, 12.0),
(10000000000000.0, 13.0),
(100000000000000.0, 14.0),
(1000000000000000.0, 15.0),
(1e+16, 16.0),
(1e+17, 17.0),
(1e+18, 18.0),
(1e+19, 19.0),
];
#[test]
fn sanity_check() {
assert_eq!(F32::ONE.log10(), F32::ZERO);
for &(x, expected) in TEST_VECTORS {
let ln_x = F32(x).log10();
let relative_error = (ln_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
ln_x,
expected
);
}
}
}
+79
View File
@@ -0,0 +1,79 @@
//! log base 2 approximation for a single-precision float.
use super::F32;
use core::f32::consts::LOG2_E;
impl F32 {
/// Approximates the base 2 logarithm of the number.
pub fn log2(self) -> Self {
self.ln() * LOG2_E
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 0.001;
/// log2(x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(1e-20, -66.43856),
(1e-19, -63.116634),
(1e-18, -59.794704),
(1e-17, -56.47278),
(1e-16, -53.15085),
(1e-15, -49.828922),
(1e-14, -46.506992),
(1e-13, -43.185066),
(1e-12, -39.863136),
(1e-11, -36.54121),
(1e-10, -33.21928),
(1e-09, -29.897352),
(1e-08, -26.575424),
(1e-07, -23.253496),
(1e-06, -19.931568),
(1e-05, -16.60964),
(1e-04, -13.287712),
(0.001, -9.965784),
(0.01, -6.643856),
(0.1, -3.321928),
(10.0, 3.321928),
(100.0, 6.643856),
(1000.0, 9.965784),
(10000.0, 13.287712),
(100000.0, 16.60964),
(1000000.0, 19.931568),
(10000000.0, 23.253496),
(100000000.0, 26.575424),
(1000000000.0, 29.897352),
(10000000000.0, 33.21928),
(100000000000.0, 36.54121),
(1000000000000.0, 39.863136),
(10000000000000.0, 43.185066),
(100000000000000.0, 46.506992),
(1000000000000000.0, 49.828922),
(1e+16, 53.15085),
(1e+17, 56.47278),
(1e+18, 59.794704),
(1e+19, 63.116634),
];
#[test]
fn sanity_check() {
assert_eq!(F32::ONE.log2(), F32::ZERO);
for &(x, expected) in TEST_VECTORS {
let ln_x = F32(x).log2().0;
let relative_error = (ln_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large: {} vs {}",
relative_error,
ln_x,
expected
);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
//! Fused multiply-add. Computes `(self * a) + b`
use super::F32;
impl F32 {
/// Computes `(self * a) + b`.
pub fn mul_add(self, a: Self, b: Self) -> Self {
self * a + b
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(0.0).mul_add(F32(0.0), F32(1.0)), F32(1.0));
assert_eq!(F32(1.0).mul_add(F32(2.0), F32(3.5)), F32(5.5));
assert_eq!(F32(1.0).mul_add(F32(-1.0), F32(0.0)), F32(-1.0));
}
}
+209
View File
@@ -0,0 +1,209 @@
//! `x^n` with fractional `n` approximation for a single-precision float.
use super::F32;
impl F32 {
/// Approximates a number raised to a floating point power.
pub fn powf(self, n: Self) -> Self {
// using x^n = exp(ln(x^n)) = exp(n*ln(x))
if self >= Self::ZERO {
(n * self.ln()).exp()
} else if !n.is_integer() {
Self::NAN
} else if n.is_even() {
// if n is even, then we know that the result will have no sign, so we can remove it
(n * self.without_sign().ln()).exp()
} else {
// if n isn't even, we need to multiply by -1.0 at the end.
-(n * self.without_sign().ln()).exp()
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
/// error builds up from both exp and ln approximation, so we double the error allowed.
pub(crate) const MAX_ERROR: f32 = 0.002;
/// powf(3,x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS_POW3: &[(f32, f32)] = &[
(-1e-20, 1.0),
(-1e-19, 1.0),
(-1e-18, 1.0),
(-1e-17, 1.0),
(-1e-16, 0.9999999999999999),
(-1e-15, 0.9999999999999989),
(-1e-14, 0.999999999999989),
(-1e-13, 0.9999999999998901),
(-1e-12, 0.9999999999989014),
(-1e-11, 0.9999999999890139),
(-1e-10, 0.9999999998901388),
(-1e-09, 0.9999999989013877),
(-1e-08, 0.9999999890138772),
(-1e-07, 0.999_999_9),
(-1e-06, 0.999_998_9),
(-1e-05, 0.999_989_03),
(-1e-04, 0.999_890_15),
(-0.001, 0.998_901_96),
(-0.01, 0.989_074),
(-0.1, 0.895_958_5),
(-1.0, 0.333_333_34),
(-10.0, 1.693_508_8e-5),
(-100.0, 0e0),
(-1000.0, 0.0),
(1e-20, 1.0),
(1e-19, 1.0),
(1e-18, 1.0),
(1e-17, 1.0),
(1e-16, 1.0),
(1e-15, 1.000000000000001),
(1e-14, 1.0000000000000109),
(1e-13, 1.00000000000011),
(1e-12, 1.0000000000010987),
(1e-11, 1.000000000010986),
(1e-10, 1.0000000001098612),
(1e-09, 1.0000000010986123),
(1e-08, 1.000000010986123),
(1e-07, 1.000_000_1),
(1e-06, 1.000_001_1),
(1e-05, 1.000_011),
(1e-04, 1.000_109_9),
(0.001, 1.001_099_2),
(0.01, 1.011_046_6),
(0.1, 1.116_123_2),
(1.0, 3.0),
(10.0, 59049.0),
];
/// powf(150,x) test vectors - `(input, output)`
pub(crate) const TEST_VECTORS_POW150: &[(f32, f32)] = &[
(-1e-20, 1.0),
(-1e-19, 1.0),
(-1e-18, 1.0),
(-1e-17, 1.0),
(-1e-16, 0.9999999999999994),
(-1e-15, 0.999999999999995),
(-1e-14, 0.9999999999999499),
(-1e-13, 0.999999999999499),
(-1e-12, 0.9999999999949893),
(-1e-11, 0.9999999999498936),
(-1e-10, 0.9999999994989365),
(-1e-09, 0.9999999949893649),
(-1e-08, 0.999_999_94),
(-1e-07, 0.999_999_5),
(-1e-06, 0.999_995),
(-1e-05, 0.999_949_9),
(-1e-04, 0.999_499_1),
(-0.001, 0.995_001_9),
(-0.01, 0.951_128_24),
(-0.1, 0.605_885_9),
(-1.0, 0.006_666_667),
(-10.0, 1.734_153e-22),
(-100.0, 0e0),
(-1000.0, 0.0),
(-10000.0, 0.0),
(-100000.0, 0.0),
(-1000000.0, 0.0),
(-10000000.0, 0.0),
(-100000000.0, 0.0),
(-1000000000.0, 0.0),
(-10000000000.0, 0.0),
(-100000000000.0, 0.0),
(-1000000000000.0, 0.0),
(-10000000000000.0, 0.0),
(-100000000000000.0, 0.0),
(-1000000000000000.0, 0.0),
(-1e+16, 0.0),
(-1e+17, 0.0),
(-1e+18, 0.0),
(-1e+19, 0.0),
(1e-20, 1.0),
(1e-19, 1.0),
(1e-18, 1.0),
(1e-17, 1.0),
(1e-16, 1.0000000000000004),
(1e-15, 1.000000000000005),
(1e-14, 1.0000000000000502),
(1e-13, 1.0000000000005012),
(1e-12, 1.0000000000050107),
(1e-11, 1.0000000000501064),
(1e-10, 1.0000000005010636),
(1e-09, 1.0000000050106352),
(1e-08, 1.000000050106354),
(1e-07, 1.000_000_5),
(1e-06, 1.000_005),
(1e-05, 1.000_050_1),
(1e-04, 1.000_501_2),
(0.001, 1.005_023_2),
(0.01, 1.051_382_9),
(0.1, 1.650_475_6),
(1.0, 150.0),
(10.0, 5.766_504e21),
];
/// misc powf(x,n) test vectors - `(base_input, power_input, output)`
pub(crate) const TEST_VECTORS_MISC: &[(f32, f32, f32)] = &[
(-0.5881598, 2.0, 0.345_931_95),
(-0.5881598, 3.2, f32::NAN),
(-0.5881598, 3.0, -0.203_463_27),
(-1000000.0, 4.0, 1e+24),
];
fn calc_relative_error(experimental: F32, expected: f32) -> F32 {
if experimental.is_nan() && expected.is_nan() {
F32::ZERO
} else if expected != 0.0 {
(experimental - expected) / expected
} else {
(experimental - expected) / (expected + 1.0e-20)
}
}
#[test]
fn sanity_check() {
for &(x, expected) in TEST_VECTORS_POW3 {
let exp_x = F32(3.0).powf(F32(x));
let relative_error = calc_relative_error(exp_x, expected);
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large for input {} : {} vs {}",
relative_error,
x,
exp_x,
expected
);
}
for &(x, expected) in TEST_VECTORS_POW150 {
let exp_x = F32(150.0).powf(F32(x));
let relative_error = calc_relative_error(exp_x, expected);
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large for input {} : {} vs {}",
relative_error,
x,
exp_x,
expected
);
}
for &(base_input, power_input, expected) in TEST_VECTORS_MISC {
let exp_x = F32(base_input).powf(F32(power_input));
let relative_error = calc_relative_error(exp_x, expected);
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large for input {}.powf({}) : {} vs {}",
relative_error,
base_input,
power_input,
exp_x,
expected
);
}
}
}
+2688
View File
File diff suppressed because it is too large. Load diff
+59
View File
@@ -0,0 +1,59 @@
//! Takes the reciprocal (inverse) of a number, `1/x`.
use super::F32;
impl F32 {
/// Returns the reciprocal (inverse) of a number, `1/x`.
pub fn recip(self) -> Self {
let mut x = self;
let sx = if x < 0.0 { F32(-1.0) } else { F32(1.0) };
x *= sx;
let mut v = F32(f32::from_bits(0x7EF1_27EAu32.wrapping_sub(x.to_bits())));
let w = x * v;
// v.0 *= 2.0 - w;
// v.0 *= 4.0 + w * (-6.0 + w * (4.0 - w));
v.0 *=
8.0 + w * (-28.0 + w * (56.0 + w * (-70.0 + w * (56.0 + w * (-28.0 + w * (8.0 - w))))));
v.0 * sx
}
}
#[cfg(test)]
mod tests {
use super::F32;
pub(crate) const MAX_ERROR: f32 = 1e-5;
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(0.00001, 100000.0),
(1.0, 1.0),
(2.0, 0.5),
(0.25, 4.0),
(-0.5, -2.0),
(core::f32::consts::PI, 1.0 / core::f32::consts::PI),
];
#[test]
fn sanity_check() {
assert_eq!(F32(0.0).recip(), F32(core::f32::INFINITY));
assert_eq!(F32(-0.0).recip(), F32(core::f32::NEG_INFINITY));
for &(x, expected) in TEST_VECTORS {
let recip_x = F32(x).recip();
let relative_error = (recip_x - expected).abs() / expected;
assert!(
relative_error <= MAX_ERROR,
"relative_error {} too large for input {} : {} vs {}",
relative_error,
x,
recip_x,
expected
);
}
}
}
+32
View File
@@ -0,0 +1,32 @@
//! Calculate Euclidian remainder for a single-precision float.
use super::F32;
impl F32 {
/// Calculates the least non-negative remainder of `self (mod rhs)`.
pub fn rem_euclid(self, rhs: Self) -> Self {
let r = self % rhs;
if r >= Self::ZERO {
r
} else {
r + rhs.abs()
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
let a = F32(7.0);
let b = F32(4.0);
assert_eq!(a.rem_euclid(b), F32(3.0));
assert_eq!((-a).rem_euclid(b), F32(1.0));
assert_eq!(a.rem_euclid(-b), F32(3.0));
assert_eq!((-a).rem_euclid(-b), F32(1.0));
}
}
+33
View File
@@ -0,0 +1,33 @@
//! Round a single-precision float.
use super::F32;
impl F32 {
/// Returns the nearest integer to a number.
pub fn round(self) -> Self {
Self(((self.0 + Self(0.5).copysign(self).0) as i32) as f32)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(0.0).round(), F32(0.0));
assert_eq!(F32(-0.0).round(), F32(-0.0));
assert_eq!(F32(0.49999).round(), F32(0.0));
assert_eq!(F32(-0.49999).round(), F32(-0.0));
assert_eq!(F32(0.5).round(), F32(1.0));
assert_eq!(F32(-0.5).round(), F32(-1.0));
assert_eq!(F32(9999.499).round(), F32(9999.0));
assert_eq!(F32(-9999.499).round(), F32(-9999.0));
assert_eq!(F32(9999.5).round(), F32(10000.0));
assert_eq!(F32(-9999.5).round(), F32(-10000.0));
}
}
+33
View File
@@ -0,0 +1,33 @@
//! Returns a number that represents the sign of `self`.
use super::F32;
impl F32 {
/// Returns a number that represents the sign of `self`.
///
/// * `1.0` if the number is positive, `+0.0` or `INFINITY`
/// * `-1.0` if the number is negative, `-0.0` or `NEG_INFINITY`
/// * `NAN` if the number is `NAN`
pub fn signum(self) -> Self {
if self.is_nan() {
Self::NAN
} else {
F32(1.0).copysign(self)
}
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32::INFINITY.signum(), F32(1.0));
assert_eq!(F32(0.0).signum(), F32(1.0));
assert_eq!(F32(1.0).signum(), F32(1.0));
assert_eq!(F32::NEG_INFINITY.signum(), F32(-1.0));
assert_eq!(F32(-0.0).signum(), F32(-1.0));
assert_eq!(F32(-1.0).signum(), F32(-1.0));
}
}
+83
View File
@@ -0,0 +1,83 @@
//! Sine approximation, implemented in terms of `cos(x)`.
use super::F32;
use core::f32::consts::PI;
impl F32 {
/// Approximates `sin(x)` in radians with a maximum error of `0.002`.
pub fn sin(self) -> Self {
(self - PI / 2.0).cos()
}
}
#[cfg(test)]
mod tests {
use super::F32;
use crate::float::cos::tests::MAX_ERROR;
/// Sine test vectors - `(input, output)`
const TEST_VECTORS: &[(f32, f32)] = &[
(0.000, 0.000),
(0.140, 0.139),
(0.279, 0.276),
(0.419, 0.407),
(0.559, 0.530),
(0.698, 0.643),
(0.838, 0.743),
(0.977, 0.829),
(1.117, 0.899),
(1.257, 0.951),
(1.396, 0.985),
(1.536, 0.999),
(1.676, 0.995),
(1.815, 0.970),
(1.955, 0.927),
(2.094, 0.866),
(2.234, 0.788),
(2.374, 0.695),
(2.513, 0.588),
(2.653, 0.469),
(2.793, 0.342),
(2.932, 0.208),
(3.072, 0.070),
(3.211, -0.070),
(3.351, -0.208),
(3.491, -0.342),
(3.630, -0.469),
(3.770, -0.588),
(3.910, -0.695),
(4.049, -0.788),
(4.189, -0.866),
(4.328, -0.927),
(4.468, -0.970),
(4.608, -0.995),
(4.747, -0.999),
(4.887, -0.985),
(5.027, -0.951),
(5.166, -0.899),
(5.306, -0.829),
(5.445, -0.743),
(5.585, -0.643),
(5.725, -0.530),
(5.864, -0.407),
(6.004, -0.276),
(6.144, -0.139),
(6.283, 0.000),
];
#[test]
fn sanity_check() {
for &(x, expected) in TEST_VECTORS {
let sin_x = F32(x).sin();
let delta = (sin_x - expected).abs();
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
sin_x,
expected
);
}
}
}
+33
View File
@@ -0,0 +1,33 @@
//! Simultaneously computes the sine and cosine of the number, `x`.
use super::F32;
impl F32 {
/// Simultaneously computes the sine and cosine of the number, `x`.
/// Returns `(sin(x), cos(x))`.
pub fn sin_cos(self) -> (Self, Self) {
(self.sin(), self.cos())
}
}
#[cfg(test)]
mod tests {
use super::F32;
const TEST_VECTORS: &[f32] = &[
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,
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,
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,
5.445, 5.585, 5.725, 5.864, 6.004, 6.144, 6.283,
];
#[test]
fn sanity_check() {
for &x in TEST_VECTORS {
let sin_x = F32(x).sin();
let cos_x = F32(x).cos();
assert_eq!(F32(x).sin_cos(), (sin_x, cos_x));
}
}
}
+71
View File
@@ -0,0 +1,71 @@
//! Square root approximation function for a single-precision float.
//!
//! Method described at: <https://bits.stephan-brumme.com/squareRoot.html>
use super::F32;
impl F32 {
/// Approximates the square root of a number with an average deviation of ~5%.
///
/// Returns [`Self::NAN`] if `self` is a negative number.
pub fn sqrt(self) -> Self {
if self >= Self::ZERO {
Self::from_bits((self.to_bits() + 0x3f80_0000) >> 1)
} else {
Self::NAN
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::F32;
/// Deviation from the actual value (5%)
pub(crate) const MAX_ERROR: f32 = 0.05;
/// Square root test vectors - `(input, output)`
pub(crate) const TEST_VECTORS: &[(f32, f32)] = &[
(1.0, 1.0),
(2.0, 1.414),
(3.0, 1.732),
(4.0, 2.0),
(5.0, 2.236),
(10.0, 3.162),
(100.0, 10.0),
(250.0, 15.811),
(500.0, 22.36),
(1000.0, 31.622),
(2500.0, 50.0),
(5000.0, 70.710),
(1000000.0, 1000.0),
(2500000.0, 1581.138),
(5000000.0, 2236.067),
(10000000.0, 3162.277),
(25000000.0, 5000.0),
(50000000.0, 7071.067),
(100000000.0, 10000.0),
];
#[test]
fn sanity_check() {
for &(x, expected) in TEST_VECTORS {
let sqrt_x = F32(x).sqrt();
let allowed_delta = x * MAX_ERROR;
let actual_delta = sqrt_x - expected;
assert!(
actual_delta <= allowed_delta,
"delta {} too large: {} vs {}",
actual_delta,
sqrt_x,
expected
);
}
}
#[test]
fn negative_is_nan() {
assert!(F32(-1.0).sqrt().is_nan());
}
}
+95
View File
@@ -0,0 +1,95 @@
//! Tangent approximation for a single-precision float.
use super::F32;
impl F32 {
/// Approximates `tan(x)` in radians with a maximum error of `0.6`.
pub fn tan(self) -> Self {
self.sin() / self.cos()
}
}
#[cfg(test)]
mod tests {
use super::F32;
/// Maximum error in radians
// TODO(tarcieri): this is kinda bad, find a better approximation?
const MAX_ERROR: f32 = 0.6;
/// Tangent test vectors - `(input, output)`
const TEST_VECTORS: &[(f32, f32)] = &[
(0.000, 0.000),
(0.140, 0.141),
(0.279, 0.287),
(0.419, 0.445),
(0.559, 0.625),
(0.698, 0.839),
(0.838, 1.111),
(0.977, 1.483),
(1.117, 2.050),
(1.257, 3.078),
(1.396, 5.671),
(1.536, 28.636),
(1.676, -9.514),
(1.815, -4.011),
(1.955, -2.475),
(2.094, -1.732),
(2.234, -1.280),
(2.374, -0.966),
(2.513, -0.727),
(2.653, -0.532),
(2.793, -0.364),
(2.932, -0.213),
(3.072, -0.070),
(3.211, 0.070),
(3.351, 0.213),
(3.491, 0.364),
(3.630, 0.532),
(3.770, 0.727),
(3.910, 0.966),
(4.049, 1.280),
(4.189, 1.732),
(4.328, 2.475),
(4.468, 4.011),
(4.608, 9.514),
(4.747, -28.636),
(4.887, -5.671),
(5.027, -3.078),
(5.166, -2.050),
(5.306, -1.483),
(5.445, -1.111),
(5.585, -0.839),
(5.725, -0.625),
(5.864, -0.445),
(6.004, -0.287),
(6.144, -0.141),
(6.283, 0.000),
];
#[test]
fn sanity_check() {
for &(x, expected) in TEST_VECTORS {
let tan_x = F32(x).tan();
let delta = (tan_x - expected).abs();
assert!(
delta <= MAX_ERROR,
"delta {} too large: {} vs {}",
delta,
tan_x,
expected
);
}
}
#[test]
fn zero() {
assert_eq!(F32::ZERO.tan(), F32::ZERO);
}
#[test]
fn nan() {
assert!(F32::NAN.tan().is_nan());
}
}
+48
View File
@@ -0,0 +1,48 @@
//! Floating point whole number for a single-precision float.
use super::{F32, MANTISSA_MASK};
impl F32 {
/// Returns the integer part of a number.
pub fn trunc(self) -> Self {
let x_bits = self.to_bits();
let exponent = self.extract_exponent_value();
// exponent is negative, there is no whole number, just return zero
if exponent < 0 {
return F32::ZERO.copysign(self);
}
let exponent_clamped = i32::max(exponent, 0) as u32;
// find the part of the fraction that would be left over
let fractional_part = x_bits.overflowing_shl(exponent_clamped).0 & MANTISSA_MASK;
// if there isn't a fraction we can just return the whole thing.
if fractional_part == 0_u32 {
return self;
}
let fractional_mask = fractional_part.overflowing_shr(exponent_clamped).0;
Self::from_bits(x_bits & !fractional_mask)
}
}
#[cfg(test)]
mod tests {
use super::F32;
#[test]
fn sanity_check() {
assert_eq!(F32(-1.1).trunc(), F32(-1.0));
assert_eq!(F32(-0.1).trunc(), F32(-0.0));
assert_eq!(F32(0.0).trunc(), F32(0.0));
assert_eq!(F32(1.0).trunc(), F32(1.0));
assert_eq!(F32(1.1).trunc(), F32(1.0));
assert_eq!(F32(2.9).trunc(), F32(2.0));
assert_eq!(F32(-100_000_000.13425345345).trunc(), F32(-100_000_000.0));
assert_eq!(F32(100_000_000.13425345345).trunc(), F32(100_000_000.0));
}
}
+104
View File
@@ -0,0 +1,104 @@
//! Embedded-friendly (i.e. `#![no_std]`) math library featuring fast, safe
//! floating point approximations for common arithmetic operations, as well as
//! 2D and 3D vector types, statistical analysis functions, and quaternions.
//!
//! ## Floating point approximations: `F32` and `F32Ext`
//!
//! `micromath` supports approximating many arithmetic operations on `f32`
//! using bitwise operations, providing great performance and small code size
//! at the cost of precision. For use cases like graphics and signal
//! processing, these approximations are often sufficient and the performance
//! gains worth the lost precision.
//!
//! These approximations are defined on the [`F32`] newtype wrapper.
//!
//! ### `F32Ext` extension trait
//!
//! Floating point approximations can used via the the [`F32Ext`] trait which
//! is impl'd for `f32`, providing a drop-in `std`-compatible API.
//!
//! ```
//! use micromath::F32Ext;
//!
//! let n = 2.0.sqrt();
//! assert_eq!(n, 1.5); // close enough
//! ```
//!
//! Since the `F32Ext` trait provides methods which are already defined in
//! `std`, in cases where your crate links `std` the `F32Ext` versions of
//! the same methods will not be used, in which case you will get an unused
//! import warning for `F32Ext`.
//!
//! If you encounter this, add an `#[allow(unused_imports)]` above the import.
//!
//! ```
//! #[allow(unused_imports)]
//! use micromath::F32Ext;
//! ```
//!
//! ## Vector types
//!
//! See the [`vector`] module for more information on vector types.
//!
//! The following vector types are available, all of which have `pub x` and
//! `pub y` (and on 3D vectors, `pub z`) members:
//!
//! | Rust | 2D | 3D |
//! |-------|---------|---------|
//! | `i8` | `I8x2` | `I8x3` |
//! | `i16` | `I16x2` | `I16x3` |
//! | `i32` | `I32x2` | `I32x3` |
//! | `u8` | `U8x2` | `U8x3` |
//! | `u16` | `U16x2` | `U16x3` |
//! | `u32` | `U32x2` | `U32x3` |
//! | `f32` | `F32x2` | `F32x3` |
//!
//! ## Statistical analysis
//!
//! See the [`statistics`] module for more information on statistical analysis
//! traits and functionality.
//!
//! The following traits are available and impl'd for slices and iterators of
//! `f32` (and can be impl'd for other types):
//!
//! - [`Mean`][`statistics::Mean`] - compute arithmetic mean with the `mean()` method.
//! - [`StdDev`][`statistics::StdDev`] - compute standard deviation with the `stddev()` method.
//! - [`Trim`][`statistics::Trim`] - cull outliers from a sample slice with the `trim()` method.
//! - [`Variance`][`statistics::Variance`] - compute variance with the `variance()` method.
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/tarcieri/micromath/main/img/micromath-sq.png",
html_root_url = "https://docs.rs/micromath/2.0.0"
)]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
unused_qualifications
)]
#[cfg(feature = "statistics")]
#[cfg_attr(docsrs, doc(cfg(feature = "statistics")))]
pub mod statistics;
#[cfg(feature = "vector")]
#[cfg_attr(docsrs, doc(cfg(feature = "vector")))]
pub mod vector;
mod f32ext;
mod float;
#[cfg(feature = "quaternion")]
mod quaternion;
pub use crate::{f32ext::F32Ext, float::F32};
#[cfg(feature = "quaternion")]
pub use crate::quaternion::Quaternion;
#[cfg(feature = "num-traits")]
#[cfg_attr(docsrs, doc(cfg(feature = "num-traits")))]
pub use num_traits;
+448
View File
@@ -0,0 +1,448 @@
//! Adapted from the `madgwick` crate: <https://github.com/japaric/madgwick>
//! Copyright (c) 2018 Jorge Aparicio
//!
//! Original sources dual licensed under your choice of the Apache 2.0
//! and/or MIT licenses, which matches this crate's licensing terms.
//!
//! See toplevel LICENSE-MIT for more information on the MIT license.
//! Apache 2.0 license follows:
//!
//! Licensed under the Apache License, Version 2.0 (the "License");
//! you may not use this file except in compliance with the License.
//! You may obtain a copy of the License at:
//!
//! <https://www.apache.org/licenses/LICENSE-2.0>
//!
//! Unless required by applicable law or agreed to in writing, software
//! distributed under the License is distributed on an "AS IS" BASIS,
//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//! See the License for the specific language governing permissions and
//! limitations under the License.
use crate::F32;
use core::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
#[cfg(feature = "vector")]
use crate::vector::{Component, F32x3, Vector3d};
/// Quaternions are a number system that extends the complex numbers which can
/// be used for efficiently computing spatial rotations.
///
/// They're computed as the quotient of two directed lines in a
/// three-dimensional space, or equivalently as the quotient of two vectors.
///
/// For given real numbers `a`, `b`, `c`, and `d`, they take the form:
///
/// `a + bi + cj + dk`
///
/// where `i`, `j`, and `k` are the fundamental quaternion units:
///
/// `i² = j² = k² = i*j*k = -1`
///
/// Quaternion multiplication is non-commutative:
///
/// | x | 1 | i | j | k |
/// |---|----|----|----|----|
/// | 1 | 1 | i | j | k |
/// | i | i | -1 | k | -j |
/// | j | j | -k | -1 | i |
/// | k | k | j | -i | -1 |
#[cfg_attr(docsrs, doc(cfg(feature = "quaternion")))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Quaternion(f32, f32, f32, f32);
impl Quaternion {
/// Identity quaternion.
pub const IDENTITY: Self = Self(1.0, 0.0, 0.0, 0.0);
/// Create a new quaternion.
pub const fn new(a: f32, b: f32, c: f32, d: f32) -> Self {
Self(a, b, c, d)
}
/// Get the quaternion that represents the smallest rotation between two vectors.
#[cfg(feature = "vector")]
pub fn from_two_vectors<C>(u: Vector3d<C>, v: Vector3d<C>) -> Self
where
C: Component + Into<f32>,
{
// Implementation from http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final
use crate::vector::Vector;
let n_uv = F32(u.dot(u).into() * v.dot(v).into()).sqrt();
let mut realpart = n_uv + u.dot(v).into();
let w = if realpart < 1e-6 * n_uv {
realpart = F32(0.);
if F32(u.x.into()).abs() > F32(u.z.into()).abs() {
Vector3d {
x: -u.y.into(),
y: u.x.into(),
z: 0.,
}
} else {
Vector3d {
x: 0.,
y: -u.z.into(),
z: u.y.into(),
}
}
} else {
F32x3 {
x: u.x.into(),
y: u.y.into(),
z: u.z.into(),
} * F32x3 {
x: v.x.into(),
y: v.y.into(),
z: v.z.into(),
}
};
let q = Quaternion(realpart.0, w.x, w.y, w.z);
let n = F32(q.norm()).invsqrt();
q * n.0
}
/// Returns the conjugate of this quaternion.
pub fn conj(self) -> Self {
Quaternion(self.0, -self.1, -self.2, -self.3)
}
/// Returns the dot product of this quaternion.
pub fn dot(self, rhs: Self) -> f32 {
self.0 * rhs.0 + self.1 * rhs.1 + self.2 * rhs.2 + self.3 * rhs.3
}
/// Compute the inverse of this quaternion.
///
/// Panics if [`Quaternion::norm`] is zero.
pub fn inv(self) -> Self {
let norm = self.norm();
assert_ne!(norm, 0.0, "quaternion norm is zero");
self.conj() * F32(norm).inv().0
}
/// Compute the magnitude (a.k.a length) of this quaternion.
pub fn magnitude(self) -> f32 {
F32(self.norm()).sqrt().0
}
/// Returns the norm of this quaternion, i.e. `a²+b²+c²+d²`.
///
/// <https://www.mathworks.com/help/aeroblks/quaternionnorm.html>
pub fn norm(self) -> f32 {
self.0 * self.0 + self.1 * self.1 + self.2 * self.2 + self.3 * self.3
}
/// Compute a quaternion for the given axis vector and angle.
#[cfg_attr(docsrs, doc(cfg(feature = "vector")))]
pub fn axis_angle<C>(v: Vector3d<C>, theta: C) -> Self
where
C: Component + Into<f32>,
{
let half_theta = F32(theta.into() * 0.5);
// TODO(tarcieri): refactor `Quaternion` to be (f32 + F32x3)
let v = F32x3 {
x: v.x.into(),
y: v.y.into(),
z: v.z.into(),
} * half_theta.sin().0;
Self(half_theta.cos().0, v.x, v.y, v.z)
}
/// Rotate a 3D vector using this quaternion, assumes the quaternion is of unit length.
#[cfg_attr(docsrs, doc(cfg(feature = "vector")))]
pub fn rotate<C>(self, v: Vector3d<C>) -> F32x3
where
C: Component + Into<f32>,
{
let Quaternion(qw, qx, qy, qz) = self;
let qwsq = qw * qw;
let qxsq = qx * qx;
let qysq = qy * qy;
let qzsq = qz * qz;
let x = (qwsq + qxsq - qysq - qzsq) * v.x.into()
+ (2. * (qx * qy - qw * qz)) * v.y.into()
+ (2. * (qx * qz + qw * qy)) * v.z.into();
let y = (2. * (qx * qy + qw * qz)) * v.x.into()
+ (qwsq - qxsq + qysq - qzsq) * v.y.into()
+ (2. * (qy * qz - qw * qx)) * v.z.into();
let z = (2. * (qx * qz - qw * qy)) * v.x.into()
+ (2. * (qy * qz + qw * qx)) * v.y.into()
+ (qwsq - qxsq - qysq + qzsq) * v.z.into();
F32x3 { x, y, z }
}
/// Scale by a scalar.
pub fn scale<S>(self, scalar: S) -> Self
where
S: Into<f32>,
{
let k = scalar.into();
Self(self.0 * k, self.1 * k, self.2 * k, self.3 * k)
}
/// Normalize the quaternion.
pub fn normalize(self) -> Self {
let norm = self.norm();
assert_ne!(norm, 0.0, "quaternion norm is zero");
let n = F32(norm).invsqrt();
self.scale(n)
}
/// Get the (roll, pitch, yaw) Euler angles, assumes the quaternion is normalized.
pub fn to_euler(&self) -> (f32, f32, f32) {
let r = F32(2. * (self.0 * self.1 + self.2 * self.3))
.atan2(F32(1. - 2. * (self.1 * self.1 + self.2 * self.2)));
let p = F32(2. * (self.0 * self.2 - self.1 * self.3)).asin();
let y = F32(2. * (self.0 * self.3 + self.1 * self.2))
.atan2(F32(1. - 2. * (self.2 * self.2 + self.3 * self.3)));
(r.0, p.0, y.0)
}
/// Convert this quaternion into an array.
pub fn to_array(&self) -> [f32; 4] {
[self.0, self.1, self.2, self.3]
}
/// Access the `w` / `a` real component
pub fn w(&self) -> f32 {
self.0
}
/// Access the `x` / `b` / `i` imaginary component
pub fn x(&self) -> f32 {
self.1
}
/// Access the `y` / `c` / `j` imaginary component
pub fn y(&self) -> f32 {
self.2
}
/// Access the `z` / `d` / `k` imaginary component
pub fn z(&self) -> f32 {
self.3
}
}
impl Add for Quaternion {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(
self.0 + rhs.0,
self.1 + rhs.1,
self.2 + rhs.2,
self.3 + rhs.3,
)
}
}
impl AddAssign for Quaternion {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Default for Quaternion {
fn default() -> Self {
Self::IDENTITY
}
}
impl From<(f32, f32, f32, f32)> for Quaternion {
fn from(q: (f32, f32, f32, f32)) -> Quaternion {
Self::new(q.0, q.1, q.2, q.3)
}
}
impl From<Quaternion> for (f32, f32, f32, f32) {
fn from(q: Quaternion) -> (f32, f32, f32, f32) {
(q.0, q.1, q.2, q.3)
}
}
impl Mul for Quaternion {
type Output = Self;
fn mul(self, other: Self) -> Self {
Self(
self.0 * other.0 - self.1 * other.1 - self.2 * other.2 - self.3 * other.3,
self.0 * other.1 + self.1 * other.0 + self.2 * other.3 - self.3 * other.2,
self.0 * other.2 - self.1 * other.3 + self.2 * other.0 + self.3 * other.1,
self.0 * other.3 + self.1 * other.2 - self.2 * other.1 + self.3 * other.0,
)
}
}
impl Mul<f32> for Quaternion {
type Output = Self;
fn mul(self, k: f32) -> Self {
self.scale(k)
}
}
impl Mul<Quaternion> for f32 {
type Output = Quaternion;
fn mul(self, q: Quaternion) -> Quaternion {
q.scale(self)
}
}
impl MulAssign<f32> for Quaternion {
fn mul_assign(&mut self, k: f32) {
*self = *self * k;
}
}
impl Sub for Quaternion {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(
self.0 - rhs.0,
self.1 - rhs.1,
self.2 - rhs.2,
self.3 - rhs.3,
)
}
}
impl SubAssign for Quaternion {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
#[cfg(feature = "vector")]
#[cfg_attr(docsrs, doc(cfg(feature = "vector")))]
impl<C> From<Vector3d<C>> for Quaternion
where
C: Component + Into<f32>,
{
fn from(v: Vector3d<C>) -> Quaternion {
Self(0.0, v.x.into(), v.y.into(), v.z.into())
}
}
#[cfg(feature = "vector")]
#[cfg_attr(docsrs, doc(cfg(feature = "vector")))]
impl<C> Mul<Vector3d<C>> for Quaternion
where
C: Component + Into<f32>,
{
type Output = F32x3;
fn mul(self, v: Vector3d<C>) -> F32x3 {
self.rotate(v)
}
}
#[cfg(test)]
mod tests {
use super::Quaternion;
use crate::vector::Vector3d;
use crate::F32Ext;
const MAX_ERROR: f32 = 0.05;
#[test]
fn conj_test() {
let q = Quaternion(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.conj(), Quaternion(1.0, -2.0, -3.0, -4.0));
}
#[test]
fn norm_test() {
let q = Quaternion(1.0, 2.0, 3.0, 4.0);
assert_eq!(q.norm(), 30.0);
let n = q.norm().invsqrt();
let r = q * n;
// The magnitude of the norm should be 1.0
let allowed_delta = 1.0 * MAX_ERROR;
let actual_delta = (r.norm() - 1.0).abs();
assert!(
actual_delta <= allowed_delta,
"delta {} too large: {} vs {}",
actual_delta,
r.norm(),
1.0
);
}
#[test]
fn add_assign() {
let mut q = Quaternion(1.0, 2.0, 3.0, 4.0);
q += Quaternion(1.0, 2.0, 3.0, 4.0);
assert_eq!(q, Quaternion(2.0, 4.0, 6.0, 8.0));
}
#[test]
fn mul_assign() {
let mut q = Quaternion(1.0, 2.0, 3.0, 4.0);
q *= 2.0;
assert_eq!(q, Quaternion(2.0, 4.0, 6.0, 8.0));
}
#[test]
fn sub_assign() {
let mut q = Quaternion(2.0, 4.0, 6.0, 8.0);
q -= Quaternion(1.0, 2.0, 3.0, 4.0);
assert_eq!(q, Quaternion(1.0, 2.0, 3.0, 4.0));
}
#[test]
fn mul_quaternion() {
let q = Quaternion(1.0, 2.0, 3.0, 4.0);
let r = Quaternion(4.0, 3.0, 2.0, 1.0);
assert_eq!(q * r, Quaternion(-12.0, 6.0, 24.0, 12.0));
}
#[test]
fn mul_f32() {
let q = Quaternion(1.0, 2.0, 3.0, 4.0);
let r = 2.0 * q;
assert_eq!(r, Quaternion(2.0, 4.0, 6.0, 8.0));
let s = r * 0.5;
assert_eq!(s, Quaternion(1.0, 2.0, 3.0, 4.0));
}
#[test]
fn smallest_rot() {
use crate::vector::Vector;
let v1 = Vector3d {
x: 0.,
y: 0.,
z: 1.,
};
let v2 = Vector3d {
x: 0.,
y: 1.,
z: 0.,
};
let q = Quaternion::from_two_vectors(v1, v2);
let v1_r = q.rotate(v1);
assert!((v1_r - v2).magnitude() < 1e-1);
}
}
+23
View File
@@ -0,0 +1,23 @@
//! Statistical analysis support.
//!
//! The `statistics` Cargo feature must be enabled to use this functionality.
//!
//! The following traits are available and impl'd for slices and iterators of
//! `f32` (and can be impl'd for other types):
//!
//! - [Mean] - compute arithmetic mean with the `mean()` method.
//! - [StdDev] - compute standard deviation with the `stddev()` method
//! - [Trim] - cull outliers from a sample slice with the `trim()` method.
//! - [Variance] - compute variance with the `variance() method.
//!
//! [Mean]: https://docs.rs/micromath/latest/micromath/statistics/trait.Mean.html
//! [StdDev]: https://docs.rs/micromath/latest/micromath/statistics/trait.StdDev.html
//! [Trim]: https://docs.rs/micromath/latest/micromath/statistics/trim/trait.Trim.html
//! [Variance]: https://docs.rs/micromath/latest/micromath/statistics/trait.Variance.html
mod mean;
mod stddev;
pub mod trim;
mod variance;
pub use self::{mean::Mean, stddev::StdDev, trim::Trim, variance::Variance};
+37
View File
@@ -0,0 +1,37 @@
/// Arithmetic mean
pub trait Mean {
/// Result type
type Result;
/// Compute the arithmetic mean
fn mean(self) -> Self::Result;
}
impl<I> Mean for I
where
I: Iterator<Item = f32>,
{
type Result = f32;
fn mean(self) -> f32 {
let mut num_items = 0;
let mut sum = 0.0;
for item in self {
num_items += 1;
sum += item;
}
sum / (num_items as f32)
}
}
#[cfg(test)]
mod tests {
use super::Mean;
#[test]
fn mean_test() {
assert_eq!([1.0, 3.0, 5.0].iter().cloned().mean(), 3.0);
}
}
+35
View File
@@ -0,0 +1,35 @@
use super::variance::Variance;
#[allow(unused_imports)]
use crate::F32Ext;
/// Compute standard deviation
pub trait StdDev {
/// Result type
type Result;
/// Compute standard deviation
fn stddev(self) -> Self::Result;
}
impl<T> StdDev for &[T]
where
T: Copy + Into<f32>,
{
/// Result type
type Result = f32;
fn stddev(self) -> f32 {
self.variance().sqrt()
}
}
#[cfg(test)]
mod tests {
use super::StdDev;
#[test]
fn stddev_test() {
assert_eq!([1.0, 3.0, 5.0].as_ref().stddev(), 2.0);
assert_eq!([1.0, 3.0, 5.0].as_ref().stddev(), 2.0);
}
}
+123
View File
@@ -0,0 +1,123 @@
//! Iterate over input slices after culling statistical outliers.
use super::mean::Mean;
#[allow(unused_imports)]
use crate::F32Ext;
use core::{iter, slice};
/// Default number of standard deviations at which a value should be
/// considered an outlier.
pub const DEFAULT_THRESHOLD: f32 = 2.0;
/// Iterate over the given input after culling outliers.
pub trait Trim {
/// Result type
type Result: Copy + Into<f32>;
/// Trim this collection (cull outliers) at the default threshold of 2 standard
/// deviations.
fn trim(&self) -> Iter<'_, Self::Result> {
self.trim_at(DEFAULT_THRESHOLD)
}
/// Trim this collection (cull outliers) at the specified number of standard deviations.
fn trim_at(&self, threshold: f32) -> Iter<'_, Self::Result>;
}
impl<N> Trim for &[N]
where
N: Copy,
f32: From<N>,
{
type Result = N;
fn trim_at(&self, threshold: f32) -> Iter<'_, Self::Result> {
Iter::new(self, threshold)
}
}
/// A "trimmed" iterator which culls outliers at a given number of standard
/// deviations from the mean.
pub struct Iter<'a, N: Copy> {
/// Iterator over the input values
input: iter::Cloned<slice::Iter<'a, N>>,
/// Arithmetic mean of the input values as a float
mean: f32,
/// Standard deviation of the input values
stddev: f32,
/// Number of standard deviations at which values are considered outliers
threshold: f32,
}
impl<'a, N> Iter<'a, N>
where
N: Copy,
f32: From<N>,
{
/// Create a new trimmed iterator over an input slice.
///
/// Inputs will be considered outliers at the given `threshold` number
/// of standard deviations (e.g. `2.0`).
pub fn new(input: &'a [N], threshold: f32) -> Self {
let len = input.len() as f32;
let input = input.iter().cloned();
let input_f32 = input.clone().map(f32::from);
// TODO(tarcieri): eliminate duplication with mean/variance/stddev in super
let mean = input_f32.clone().mean();
let sum = input_f32.fold(0.0, |sum, n| {
let n = n - mean;
sum + n * n
});
let variance = sum / (len - 1.0);
let stddev = variance.sqrt();
Self {
input,
mean,
stddev,
threshold,
}
}
}
impl<'a, N> Iterator for Iter<'a, N>
where
N: Copy,
f32: From<N>,
{
type Item = N;
fn next(&mut self) -> Option<N> {
while let Some(n) = self.input.next() {
let distance = (f32::from(n) - self.mean).abs();
// TODO(tarcieri): better method for finding outliers? (e.g. MAD, IQD)
if (distance / self.stddev) < self.threshold {
return Some(n);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::Trim;
#[test]
fn stddev_test() {
let input: &[f32] = &[1.0, 2.0, 3.0, 999.0, 5.0];
let mut trimmed = input.trim_at(1.0);
assert_eq!(trimmed.next().unwrap(), 1.0);
assert_eq!(trimmed.next().unwrap(), 2.0);
assert_eq!(trimmed.next().unwrap(), 3.0);
assert_eq!(trimmed.next().unwrap(), 5.0);
assert_eq!(trimmed.next(), None);
}
}
+40
View File
@@ -0,0 +1,40 @@
use super::mean::Mean;
/// Statistical variance
pub trait Variance {
/// Result type
type Result;
/// Compute the statistical variance
fn variance(self) -> Self::Result;
}
impl<T> Variance for &[T]
where
T: Copy + Into<f32>,
{
/// Result type
type Result = f32;
fn variance(self) -> f32 {
let mean = self.iter().map(|n| (*n).into()).mean();
let mut sum = 0.0;
for item in self {
let n = (*item).into() - mean;
sum += n * n;
}
sum / (self.len() as f32 - 1.0)
}
}
#[cfg(test)]
mod tests {
use super::Variance;
#[test]
fn variance_test() {
assert_eq!([1.0, 3.0, 5.0].as_ref().variance(), 4.0);
}
}
+75
View File
@@ -0,0 +1,75 @@
//! Algebraic vector types generic over a component type.
//!
//! All vectors types impl the [Vector] trait, and all vector components
//! impl the [Component] trait.
mod component;
mod iter;
mod vector2d;
mod vector3d;
pub use self::{
component::Component,
iter::Iter,
vector2d::{F32x2, I16x2, I32x2, I8x2, U16x2, U32x2, U8x2, Vector2d},
vector3d::{F32x3, I16x3, I32x3, I8x3, U16x3, U32x3, U8x3, Vector3d},
};
use core::{fmt::Debug, iter::FromIterator};
#[allow(unused_imports)]
use crate::F32Ext;
/// Algebraic vector generic over a given [`Component`] type.
pub trait Vector<C>: Copy + Debug + Default + FromIterator<C> + Send + Sync
where
C: Component,
{
/// Number of axes
const AXES: usize;
/// Get the component value for a particular index
fn get(self, index: usize) -> Option<C>;
/// Compute the dot product of two vectors
fn dot(self, rhs: Self) -> C;
/// Instantiate a vector from a slice of components.
///
/// Panics if the slice is not the right size.
fn from_slice(slice: &[C]) -> Self {
Self::from_iter(slice.iter().cloned())
}
/// Iterate over the components of this vector
fn iter(&self) -> Iter<'_, Self, C> {
Iter::new(self)
}
/// Compute the distance between two vectors
fn distance(self, rhs: Self) -> f32
where
C: Into<f32>,
{
let differences = self
.iter()
.zip(rhs.iter())
.map(|(a, b)| a.into() - b.into());
differences.map(|n| n * n).sum::<f32>().sqrt()
}
/// Compute the magnitude of a vector
fn magnitude(self) -> f32
where
C: Into<f32>,
{
self.iter()
.map(|n| {
let n = n.into();
n * n
})
.sum::<f32>()
.sqrt()
}
}
+43
View File
@@ -0,0 +1,43 @@
//! Components of numeric vectors.
use crate::F32;
use core::{
fmt::Debug,
ops::{Add, Div, Mul, Sub},
};
/// Components of numeric vectors.
///
/// All components must be [`Copy`] + [`Sized`] types which support a minimal
/// set of arithmetic operations ([`Add`], [`Sub`], [`Mul`], [`Div`]), as well as
/// [`Default`], [`PartialEq`] and [`PartialOrd`].
///
/// This trait is impl'd for the following primitive types:
///
/// - [`i8`], [`i16`], [`i32`]
/// - [`u8`], [`u16`], [`u32`]
/// - [`f32`]
pub trait Component:
Copy
+ Debug
+ Default
+ PartialEq
+ PartialOrd
+ Send
+ Sized
+ Sync
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
{
}
impl Component for i8 {}
impl Component for i16 {}
impl Component for i32 {}
impl Component for u8 {}
impl Component for u16 {}
impl Component for u32 {}
impl Component for f32 {}
impl Component for F32 {}
+54
View File
@@ -0,0 +1,54 @@
//! Iterator over the components of an algebraic vector
use super::{Component, Vector};
use core::marker::PhantomData;
/// Iterator over the components of an algebraic vector
#[derive(Clone, Debug)]
pub struct Iter<'a, V, C>
where
V: Vector<C>,
C: Component,
{
/// Reference to the original vector
vector: &'a V,
/// Iteration position within the vector
position: usize,
/// Component type
component: PhantomData<C>,
}
impl<'a, V, C> Iter<'a, V, C>
where
V: Vector<C>,
C: Component,
{
/// Create a new iterator over the vector's components
pub(super) fn new(vector: &'a V) -> Self {
Self {
vector,
position: 0,
component: PhantomData,
}
}
}
impl<'a, V, C> Iterator for Iter<'a, V, C>
where
V: Vector<C>,
C: Component,
{
type Item = C;
fn next(&mut self) -> Option<C> {
let item = self.vector.get(self.position);
if item.is_some() {
self.position += 1;
}
item
}
}
+222
View File
@@ -0,0 +1,222 @@
//! 2-dimensional vector
use super::{Component, Vector};
use core::{
iter::FromIterator,
ops::{Add, AddAssign, Index, Mul, MulAssign, Sub, SubAssign},
};
/// 2-dimensional XY vector of `i8` values
pub type I8x2 = Vector2d<i8>;
/// 2-dimensional XY vector of `i16` values
pub type I16x2 = Vector2d<i16>;
/// 2-dimensional XY vector of `i32` values
pub type I32x2 = Vector2d<i32>;
/// 2-dimensional XY vector of `u8` values
pub type U8x2 = Vector2d<u8>;
/// 2-dimensional XY vector of `u16` values
pub type U16x2 = Vector2d<u16>;
/// 2-dimensional XY vector of `u32` values
pub type U32x2 = Vector2d<u32>;
/// 2-dimensional XY vector of `f32` values
pub type F32x2 = Vector2d<f32>;
/// 2-dimensional vector
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Vector2d<C: Component> {
/// X component
pub x: C,
/// Y component
pub y: C,
}
impl<C> Vector2d<C>
where
C: Component,
{
/// Return a 2-element array containing the coordinates
// TODO(tarcieri): move this to the `Vector` trait leveraging const generics?
pub fn to_array(&self) -> [C; 2] {
[self.x, self.y]
}
}
impl<C> FromIterator<C> for Vector2d<C>
where
C: Component,
{
fn from_iter<T>(into_iter: T) -> Self
where
T: IntoIterator<Item = C>,
{
let mut iter = into_iter.into_iter();
let x = iter.next().expect("no x-axis component in slice");
let y = iter.next().expect("no y-axis component in slice");
assert!(
iter.next().is_none(),
"too many items for 2-dimensional vector"
);
Self { x, y }
}
}
impl<C> Vector<C> for Vector2d<C>
where
C: Component,
{
const AXES: usize = 2;
fn get(self, index: usize) -> Option<C> {
match index {
0 => Some(self.x),
1 => Some(self.y),
_ => None,
}
}
fn dot(self, rhs: Self) -> C {
(self.x * rhs.x) + (self.y * rhs.y)
}
}
impl<C> From<(C, C)> for Vector2d<C>
where
C: Component,
{
fn from(vector: (C, C)) -> Self {
Self {
x: vector.0,
y: vector.1,
}
}
}
impl<C> Index<usize> for Vector2d<C>
where
C: Component,
{
type Output = C;
fn index(&self, i: usize) -> &C {
match i {
0 => &self.x,
1 => &self.y,
_ => panic!("index out of range"),
}
}
}
impl<C> Add for Vector2d<C>
where
C: Component,
{
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl<C> AddAssign for Vector2d<C>
where
C: Component,
{
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl<C> Sub for Vector2d<C>
where
C: Component,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl<C> SubAssign for Vector2d<C>
where
C: Component,
{
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
impl<C> Mul<C> for Vector2d<C>
where
C: Component,
{
type Output = Self;
fn mul(self, rhs: C) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl<C> MulAssign<C> for Vector2d<C>
where
C: Component,
{
fn mul_assign(&mut self, rhs: C) {
*self = *self * rhs;
}
}
impl From<I8x2> for F32x2 {
fn from(vector: I8x2) -> F32x2 {
Self {
x: vector.x.into(),
y: vector.y.into(),
}
}
}
impl From<I16x2> for F32x2 {
fn from(vector: I16x2) -> F32x2 {
Self {
x: vector.x.into(),
y: vector.y.into(),
}
}
}
impl From<U8x2> for F32x2 {
fn from(vector: U8x2) -> F32x2 {
Self {
x: vector.x.into(),
y: vector.y.into(),
}
}
}
impl From<U16x2> for F32x2 {
fn from(vector: U16x2) -> F32x2 {
Self {
x: vector.x.into(),
y: vector.y.into(),
}
}
}
+264
View File
@@ -0,0 +1,264 @@
//! 3-dimensional vector
use super::{Component, Vector};
use crate::F32;
use core::{
iter::FromIterator,
ops::{Add, AddAssign, Index, Mul, MulAssign, Sub, SubAssign},
};
/// 3-dimensional XYZ vector of `i8` values
pub type I8x3 = Vector3d<i8>;
/// 3-dimensional XYZ vector of `i16` values
pub type I16x3 = Vector3d<i16>;
/// 3-dimensional XYZ vector of `i32` values
pub type I32x3 = Vector3d<i32>;
/// 3-dimensional XYZ vector of `u8` values
pub type U8x3 = Vector3d<u8>;
/// 3-dimensional XYZ vector of `u16` values
pub type U16x3 = Vector3d<u16>;
/// 3-dimensional XYZ vector of `u32` values
pub type U32x3 = Vector3d<u32>;
/// 3-dimensional XYZ vector of `f32` values
pub type F32x3 = Vector3d<f32>;
/// 3-dimensional vector
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Vector3d<C: Component> {
/// X component
pub x: C,
/// Y component
pub y: C,
/// Z component
pub z: C,
}
impl<C> Vector3d<C>
where
C: Component,
{
/// Return a 3-element array containing the coordinates
// TODO(tarcieri): move this to the `Vector` trait leveraging const generics?
pub fn to_array(&self) -> [C; 3] {
[self.x, self.y, self.z]
}
}
impl<C> FromIterator<C> for Vector3d<C>
where
C: Component,
{
fn from_iter<T>(into_iter: T) -> Self
where
T: IntoIterator<Item = C>,
{
let mut iter = into_iter.into_iter();
let x = iter.next().expect("no x-axis component in slice");
let y = iter.next().expect("no y-axis component in slice");
let z = iter.next().expect("no z-axis component in slice");
assert!(
iter.next().is_none(),
"too many items for 3-dimensional vector"
);
Self { x, y, z }
}
}
impl<C> Vector<C> for Vector3d<C>
where
C: Component,
{
const AXES: usize = 3;
fn get(self, index: usize) -> Option<C> {
match index {
0 => Some(self.x),
1 => Some(self.y),
2 => Some(self.z),
_ => None,
}
}
fn dot(self, rhs: Self) -> C {
(self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z)
}
}
impl<C> From<(C, C, C)> for Vector3d<C>
where
C: Component,
{
fn from(vector: (C, C, C)) -> Self {
Self {
x: vector.0,
y: vector.1,
z: vector.2,
}
}
}
impl<C> Index<usize> for Vector3d<C>
where
C: Component,
{
type Output = C;
fn index(&self, i: usize) -> &C {
match i {
0 => &self.x,
1 => &self.y,
2 => &self.z,
_ => panic!("index out of range"),
}
}
}
impl<C> Add for Vector3d<C>
where
C: Component,
{
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl<C> AddAssign for Vector3d<C>
where
C: Component,
{
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl<C> Sub for Vector3d<C>
where
C: Component,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl<C> SubAssign for Vector3d<C>
where
C: Component,
{
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
/// Compute the cross product of two vectors
impl<C> Mul for Vector3d<C>
where
C: Component,
{
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
x: (self.y * rhs.z) - (self.z * rhs.y),
y: (self.z * rhs.x) - (self.x * rhs.z),
z: (self.x * rhs.y) - (self.y * rhs.x),
}
}
}
/// Multiply vector by a scalar component
impl<C> Mul<C> for Vector3d<C>
where
C: Component,
{
type Output = Self;
fn mul(self, rhs: C) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
impl<C> MulAssign<C> for Vector3d<C>
where
C: Component,
{
fn mul_assign(&mut self, rhs: C) {
*self = *self * rhs;
}
}
impl From<I8x3> for F32x3 {
fn from(vector: I8x3) -> F32x3 {
Self {
x: vector.x.into(),
y: vector.y.into(),
z: vector.z.into(),
}
}
}
impl From<I16x3> for F32x3 {
fn from(vector: I16x3) -> F32x3 {
Self {
x: vector.x.into(),
y: vector.y.into(),
z: vector.z.into(),
}
}
}
impl From<U8x3> for F32x3 {
fn from(vector: U8x3) -> F32x3 {
Self {
x: vector.x.into(),
y: vector.y.into(),
z: vector.z.into(),
}
}
}
impl From<U16x3> for F32x3 {
fn from(vector: U16x3) -> F32x3 {
Self {
x: vector.x.into(),
y: vector.y.into(),
z: vector.z.into(),
}
}
}
impl From<Vector3d<F32>> for F32x3 {
fn from(vector: Vector3d<F32>) -> F32x3 {
Self {
x: vector.x.into(),
y: vector.y.into(),
z: vector.z.into(),
}
}
}