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":"dd1d14b6da451192c8c3a97b4d7b4a94d7e364c98a4480ccb02519c79c2e07ef","Cargo.lock":"d8cdb490c1b2b3e777b0c17f0ca57aca5efb0ef6a920ca8e7f991713c9b256cc","Cargo.toml":"d2e89873a2c47cfd53ab115f6ee70ca64bbd2d6782cb9eb88d8c2b2fa26b640a","Cargo.toml.orig":"51085709a9d5ba751ea75eab48e4276489e1071300d842fd1b089b6c1cee2868","LICENSE-APACHE":"a60eea817514531668d7e00765731449fe14d059d3249e0bc93b36de45f759f2","LICENSE-MIT":"a52bda4b9ecfd65f9f49413b49d500af3c3b2bd5c3fd9cb987969aecc302a1e4","README.md":"08419622bbf36255400fdf61e9482ce742d56d9b9d2e377784aeba253723ac43","RELEASES.md":"50b91c5e3276f19d284c57dddc46412e00f2ec6e8309b7946a29aa4919da602b","build.rs":"8b948aa8003b9f5fa420d22e9a4e2fb255bef27a5421d749379f491e15240b6d","etc/appveyor.yml":"0cfc501c08b3848b1f3112a1054c78247a1480c0de1e431b31b21e774092c076","etc/fixed-src.svg":"1c38b7381db2943da2721b4eb80ca8b641f5f6d17228970543ad5556df6be108","etc/fixed.png":"9ca6579ebbec8b6e535d6bb544b66b2a18d1c79f358db721bab6d2168b65bf1e","etc/fixed.svg":"0d888dc7d7808e6536312598a7a37688b9a063077bc985d189e7ef23fc7513ea","etc/fixed.svg.b64":"a39d5ae11b5174ea4dbba5d31b16817fefca5b7e1e55052acd1a9c8568983e6e","etc/gitlab-ci.yml":"cd368946a64e54d3f7712caf22b4e3ef6b73e64f39906c6c495e76eb6543fe6f","etc/index.html":"afff8e111aaf70f330f9a8502119de753d66f80ff255780db59847269efb4ff2","rustfmt.toml":"903c8aaa927b68e1397573dd6f5da449c2a6c125dc4a8489df2a7cd7a577a1dc","src/arith.rs":"c38686157a9f0397d1e26f7b6a27aaae4759b0a67a82e40bba1216ec8c860439","src/borshize.rs":"1225987224584ae616b51a4dfb9683d8c122fb2b5d9b424e0ce5eabffced30eb","src/bytes.rs":"85be003e77503b8a0033481fa8caafdd47e8fc8e755f29de97f64e115c9167ef","src/cast.rs":"42a2a67e5b92c96aa9b7717a2035ba9b71445d623d3d2f964284759f342563b1","src/cmp.rs":"131936bf8f9cc2e93543c591743f7d173616a666b24e6980ccaf801db38623ff","src/cmp_fixed.rs":"c40065f898be55e5454f4f60a9636f1e41af48e6950be26a5cf7e77bc84e69c0","src/consts.rs":"e7b9a57e076f59e92118aafd64ec541ebf0f68ecc40aa7733b7fd13745f84187","src/convert.rs":"c5c217d5cf9be4c30816f0569d620b8d7486ba85fbcf0a31ce34951a34011328","src/debug_hex.rs":"9bcc5d6555ed62c61e99ab7bd25314d68dd9d3d403bbdb02e48e1e1d92a576a9","src/display.rs":"baf6a04bd22574e63622a46e49c6bd3f030138c3f7900c551d52aac7572ff654","src/f128.rs":"8f504b6352f9390c63d0912b41776a04d6bd66e76c291840a5357878983b3381","src/float_helper.rs":"9388e55e488a3ee5b0c019c9b48f32dc1261274044d548c5bd345e9b3beea3da","src/from_str.rs":"7c9320ec7ca954260d98c7cdc6bfe4325692ebc25516f542c2470ae765a788ca","src/helpers.rs":"52f15bc9b999276eb214d8fe85a9fc13a09e34de266abac44a828d1be2373ed1","src/hypot.rs":"64fe935030daa5196cc1a8d7625c7d180f1a6bf14ae4c8fd086f553f1bee12fc","src/impl_arbitrary.rs":"5b6b7872490a8b39391b7cc1ac06e9483641d2bd9a18f20f6279474d53215608","src/impl_bytemuck.rs":"e7a6541b642169b123c250dc8aa212ed442390dfbfbb1da1fdec91dc26db2b26","src/impl_num_traits.rs":"2bd47b456ea713723c695be2afc852ab06ca2e0e0e866e739d09d2c4de1a77a4","src/int256.rs":"d5a1a8ee903c6265250a11bff4814ac29fb341ae5403edce5c8e5e1ba473f0f0","src/int_helper.rs":"cefe72dffe206436dfc1e5fe844402e6e932a2ef726daca888dec53e3b48bc67","src/inv_lerp.rs":"1d2a83463b908779e2dd8fc7c657e17e1727082cb7b57cb9a2f5001ce9039218","src/lerp.rs":"c80943e6c4ea6c7b4d987d71c9ddc15301e0e737310901e9236a585f83368806","src/lib.rs":"cf5feb39e242858c00adb440460add04f21b311ad59d4f8fd79eae596459d7ca","src/log.rs":"f801f764f7f257e802a0cc03da42efc05968d2916d14a8b9bcf59e4637c34af7","src/log10.rs":"75b430597c7479f7e17781f8976e04e1e23796562154abab55dd529c58cd6ffd","src/macros.rs":"a8e47706ceebcc171c42508a3c3aa5409fad8c1b45d48d466ade8d367604985c","src/macros_const.rs":"6294e3889cd485596ef719ada7c6477dc19f22d108064d08ddd272fa916b308b","src/macros_frac.rs":"a0edc753dc8db59c0d76eef573d59cc92af57010f3fe4d99546904c87cab4765","src/macros_from_to.rs":"411559c9e40fbd0a04911fc6b33efff920c5a9cb225522deb53556abe17a945f","src/macros_no_frac.rs":"b9ae6836742aece189ae75632ddeb8673b995c489f4b06948f5e24bcda5a6cb6","src/macros_round.rs":"6f31d46f704d1d3e872a4f382360f8947da42ee90f135a42aff2e91256876e77","src/prim_traits.rs":"5d9279512d9fcae14e988d6466252f5c7ace47a5519fa49645e8dbfbcb52079e","src/saturating.rs":"dbb1e4010cd127f467737e480ad05467cf3bf8a787254209b3ac30b13e90b0d3","src/serdeize.rs":"1ac26e2aee6cd950413fa0c0d44637a04eb3c8bced4b409374e2a2b01750f24d","src/sqrt.rs":"54b38c619301fc08ddf1eb29d049a8939f2f44d137aea2ad161006840d7ecac8","src/traits.rs":"bb67fbd39219d36dd234bad735632f7ed20f59dab9a7625290c2c43f86cdade9","src/traits_bits.rs":"e0d6440aeb656cc1a7833206b3f77128977eff8b22398424bdcccb495b7a4cc2","src/types/extra.rs":"06ed7d7fc7a0fe43249e45284dfee58c0931130250a50739212daffd405882c6","src/types/mod.rs":"a02766f72fa94af8066f7c4d58ca3c68ac11fb071eb93023a1049f3f418278c0","src/unwrapped.rs":"4abdf4a385219d33219b3ef3e5dbea43cb364be1b2bf99f474636f1b29e61833","src/wrapping.rs":"cc6b124cc54d3f417e41839831d92ea8815487fe2a079824074a99526c785efb"},"package":"707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "58c98388429f48cc8e48849718ec5377dad76a51"
},
"path_in_vcs": ""
}
Generated Vendored
+148
View File
@@ -0,0 +1,148 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "borsh"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbe5b10e214954177fb1dc9fbd20a1a2608fe99e6c832033bdc7cea287a20d77"
dependencies = [
"cfg_aliases",
]
[[package]]
name = "bytemuck"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "fixed"
version = "1.29.0"
dependencies = [
"arbitrary",
"az",
"borsh",
"bytemuck",
"half",
"num-traits",
"serde",
"typenum",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+102
View File
@@ -0,0 +1,102 @@
# 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 = "2021"
rust-version = "1.83"
name = "fixed"
version = "1.29.0"
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Fixed-point numbers."
documentation = "https://docs.rs/fixed"
readme = "README.md"
keywords = [
"dsp",
"embedded",
"fixed",
"math",
"numerics",
]
categories = [
"algorithms",
"embedded",
"mathematics",
"no-std",
]
license = "MIT/Apache-2.0"
repository = "https://gitlab.com/tspiteri/fixed"
[package.metadata.docs.rs]
features = [
"arbitrary",
"borsh",
"num-traits",
"serde",
"std",
]
[features]
arbitrary = ["dep:arbitrary"]
az = []
borsh = ["dep:borsh"]
f16 = []
fail-on-warnings = []
nightly-float = []
num-traits = ["dep:num-traits"]
serde = ["dep:serde"]
serde-str = [
"serde",
"serde/std",
"std",
]
std = []
[lib]
name = "fixed"
path = "src/lib.rs"
[dependencies.arbitrary]
version = "1"
optional = true
[dependencies.az]
version = "1.2"
[dependencies.borsh]
version = "1"
optional = true
default-features = false
[dependencies.bytemuck]
version = "1.2"
[dependencies.half]
version = ">= 1.8, < 3"
default-features = false
[dependencies.num-traits]
version = "0.2.18"
features = ["i128"]
optional = true
default-features = false
[dependencies.serde]
version = "1.0.210"
optional = true
default-features = false
[dependencies.typenum]
version = "1.14"
+201
View File
@@ -0,0 +1,201 @@
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.
+25
View File
@@ -0,0 +1,25 @@
Copyright © 2018–2025 Trevor Spiteri
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.
+403
View File
@@ -0,0 +1,403 @@
<!-- Copyright © 2018–2025 Trevor Spiteri -->
<!-- Copying and distribution of this file, with or without
modification, are permitted in any medium without royalty provided the
copyright notice and this notice are preserved. This file is offered
as-is, without any warranty. -->
# Fixed-point numbers
The [*fixed* crate] provides fixed-point numbers.
* [`FixedI8`] and [`FixedU8`] are eight-bit fixed-point numbers.
* [`FixedI16`] and [`FixedU16`] are 16-bit fixed-point numbers.
* [`FixedI32`] and [`FixedU32`] are 32-bit fixed-point numbers.
* [`FixedI64`] and [`FixedU64`] are 64-bit fixed-point numbers.
* [`FixedI128`] and [`FixedU128`] are 128-bit fixed-point numbers.
An <i>n</i>-bit fixed-point number has <i>f</i>&nbsp;=&nbsp;`Frac` fractional
bits where 0&nbsp;≤&nbsp;<i>f</i>&nbsp;≤&nbsp;<i>n</i>, and
<i>n</i>&nbsp;&minus;&nbsp;<i>f</i> integer bits. For example,
<code>[FixedI32]\<[U24]></code> is a 32-bit signed fixed-point number with
<i>n</i>&nbsp;=&nbsp;32 total bits, <i>f</i>&nbsp;=&nbsp;24 fractional bits, and
<i>n</i>&nbsp;&minus;&nbsp;<i>f</i>&nbsp;=&nbsp;8 integer bits.
<code>[FixedI32]\<[U0]></code> behaves like [`i32`], and
<code>[FixedU32]\<[U0]></code> behaves like [`u32`].
The difference between any two successive representable numbers is constant
throughout the possible range for a fixed-point number:
<i>Δ</i>&nbsp;=&nbsp;1/2<sup><i>f</i></sup>. When <i>f</i>&nbsp;=&nbsp;0, like
in <code>[FixedI32]\<[U0]></code>, <i>Δ</i>&nbsp;=&nbsp;1 because representable
numbers are integers, and the difference between two successive integers is 1.
When <i>f</i>&nbsp;=&nbsp;<i>n</i>, <i>Δ</i>&nbsp;=&nbsp;1/2<sup><i>n</i></sup>
and the value lies in the range &minus;0.5&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;0.5
for signed numbers like <code>[FixedI32]\<[U32]></code>, and in the range
0&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;1 for unsigned numbers like
<code>[FixedU32]\<[U32]></code>.
The main features are
* Representation of binary fixed-point numbers up to 128 bits wide.
* Conversions between fixed-point numbers and numeric primitives.
* Comparisons between fixed-point numbers and numeric primitives.
* Parsing from strings in decimal, binary, octal and hexadecimal.
* Display as decimal, binary, octal and hexadecimal.
* Arithmetic and logic operations.
This crate does *not* provide decimal fixed-point numbers. For example 0.001
cannot be represented exactly, as it is 1/10<sup>3</sup>. It is binary fractions
like 1/2<sup>4</sup> (0.0625) that can be represented exactly, provided there
are enough fractional bits.
This crate does *not* provide general analytic functions.
* No algebraic functions are provided, for example no `pow`.
* No trigonometric functions are provided, for example no `sin` or `cos`.
* No other transcendental functions are provided, for example no `log` or
`exp`.
These functions are not provided because different implementations can have
different trade-offs, for example trading some correctness for speed.
Implementations can be provided in other crates.
* The [*cordic* crate] provides various functions implemented using the
[CORDIC] algorithm.
The conversions supported cover the following cases.
* Infallible lossless conversions between fixed-point numbers and numeric
primitives are provided using [`From`] and [`Into`]. These never fail
(infallible) and do not lose any bits (lossless).
* Infallible lossy conversions between fixed-point numbers and numeric
primitives are provided using the [`LossyFrom`] and [`LossyInto`] traits.
The source can have more fractional bits than the destination.
* Checked lossless conversions between fixed-point numbers and numeric
primitives are provided using the [`LosslessTryFrom`] and
[`LosslessTryInto`] traits. The source cannot have more fractional bits than
the destination.
* Checked conversions between fixed-point numbers and numeric primitives are
provided using the [`FromFixed`] and [`ToFixed`] traits, or using the
[`from_num`] and [`to_num`] methods and [their checked
versions][`checked_from_num`].
* Additionally, [`az`] casts are implemented for conversion between
fixed-point numbers and numeric primitives.
* Fixed-point numbers can be parsed from decimal strings using [`FromStr`],
and from binary, octal and hexadecimal strings using the
[`from_str_binary`], [`from_str_octal`] and [`from_str_hex`] methods. The
result is rounded to the nearest, with ties rounded to even.
* Fixed-point numbers can be converted to strings using [`Display`],
[`Binary`], [`Octal`], [`LowerHex`], [`UpperHex`], [`LowerExp`] and
[`UpperExp`]. The output is rounded to the nearest, with ties rounded to
even.
* All fixed-point numbers are plain old data, so [`bytemuck`] bit casting
conversions can be used.
## What’s new
### Version 1.29.0 news (2025-02-26)
* The crate now requires rustc version 1.83.0 or later.
* The following methods were added to all fixed-point numbers, to the
[`Fixed`][tf-1-29] trait, and to the [`Saturating`][s-1-29],
[`Wrapping`][w-1-29] and [`Unwrapped`][u-1-29] wrappers:
* [`unbounded_shl`][f-ushl-1-29], [`unbounded_shr`][f-ushr-1-29]
* [`from_ascii`][f-fa-1-29], [`from_ascii_binary`][f-fab-1-29],
[`from_ascii_octal`][f-fao-1-29], [`from_ascii_hex`][f-fah-1-29]
* [`saturating_from_ascii`][f-sfa-1-29],
[`saturating_from_ascii_binary`][f-sfab-1-29],
[`saturating_from_ascii_octal`][f-sfao-1-29],
[`saturating_from_ascii_hex`][f-sfah-1-29]
* [`wrapping_from_ascii`][f-wfa-1-29],
[`wrapping_from_ascii_binary`][f-wfab-1-29],
[`wrapping_from_ascii_octal`][f-wfao-1-29],
[`wrapping_from_ascii_hex`][f-wfah-1-29]
* [`unwrapped_from_ascii`][f-ufa-1-29],
[`unwrapped_from_ascii_binary`][f-ufab-1-29],
[`unwrapped_from_ascii_octal`][f-ufao-1-29],
[`unwrapped_from_ascii_hex`][f-ufah-1-29]
* [`overflowing_from_ascii`][f-ofa-1-29],
[`overflowing_from_ascii_binary`][f-ofab-1-29],
[`overflowing_from_ascii_octal`][f-ofao-1-29],
[`overflowing_from_ascii_hex`][f-ofah-1-29]
* The [`cast_unsigned`][f-cu-1-29] method was added to all signed fixed-point
numbers, to their [`Saturating`][s-1-29], [`Wrapping`][w-1-29] and
[`Unwrapped`][u-1-29] wrappers, and to the [`Fixed`][tf-1-29] trait.
* The [`cast_signed`][f-cs-1-29] method was added to all unsigned fixed-point
numbers, to their [`Saturating`][s-1-29], [`Wrapping`][w-1-29] and
[`Unwrapped`][u-1-29] wrappers, and to the [`Fixed`][tf-1-29] trait.
* The following methods are now usable in constant context:
* [`mul_acc`][f-ma-1-29], [`checked_mul_acc`][f-cma-1-29],
[`saturating_mul_acc`][f-sma-1-29], [`wrapping_mul_acc`][f-wma-1-29],
[`unwrapped_mul_acc`][f-uma-1-29], [`overflowing_mul_acc`][f-oma-1-29]
* Now [`ParseFixedError`][pfe-1-29] implements [`Error`] even when the `std`
[optional feature][feat-1-29] is disabled.
[`Error`]: https://doc.rust-lang.org/nightly/core/error/trait.Error.html
[f-cma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.checked_mul_acc
[f-cs-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU32.html#method.cast_signed
[f-cu-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.cast_unsigned
[f-fa-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_ascii
[f-fab-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_ascii_binary
[f-fah-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_ascii_hex
[f-fao-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_ascii_octal
[f-ma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.mul_acc
[f-ofa-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.overflowing_from_ascii
[f-ofab-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.overflowing_from_ascii_binary
[f-ofah-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.overflowing_from_ascii_hex
[f-ofao-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.overflowing_from_ascii_octal
[f-oma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.overflowing_mul_acc
[f-sfa-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.saturating_from_ascii
[f-sfab-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.saturating_from_ascii_binary
[f-sfah-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.saturating_from_ascii_hex
[f-sfao-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.saturating_from_ascii_octal
[f-sma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.saturating_mul_acc
[f-ufa-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unwrapped_from_ascii
[f-ufab-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unwrapped_from_ascii_binary
[f-ufah-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unwrapped_from_ascii_hex
[f-ufao-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unwrapped_from_ascii_octal
[f-uma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unwrapped_mul_acc
[f-ushl-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unbounded_shl
[f-ushr-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.unbounded_shr
[f-wfa-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.wrapping_from_ascii
[f-wfab-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.wrapping_from_ascii_binary
[f-wfah-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.wrapping_from_ascii_hex
[f-wfao-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.wrapping_from_ascii_octal
[f-wma-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.wrapping_mul_acc
[feat-1-29]: https://docs.rs/fixed/~1.29/fixed/index.html#optional-features
[pfe-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.ParseFixedError.html
[s-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.Saturating.html
[tf-1-29]: https://docs.rs/fixed/~1.29/fixed/traits/trait.Fixed.html
[u-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.Unwrapped.html
[w-1-29]: https://docs.rs/fixed/~1.29/fixed/struct.Wrapping.html
### Other releases
Details on other releases can be found in [*RELEASES.md*].
[*RELEASES.md*]: https://gitlab.com/tspiteri/fixed/blob/master/RELEASES.md
## Quick examples
```rust
use fixed::types::I20F12;
// 19/3 = 6 1/3
let six_and_third = I20F12::from_num(19) / 3;
// four decimal digits for 12 binary digits
assert_eq!(six_and_third.to_string(), "6.3333");
// find the ceil and convert to i32
assert_eq!(six_and_third.ceil().to_num::<i32>(), 7);
// we can also compare directly to integers
assert_eq!(six_and_third.ceil(), 7);
```
The type [`I20F12`] is a 32-bit fixed-point signed number with 20 integer bits
and 12 fractional bits. It is an alias to <code>[FixedI32]\<[U12]></code>. The
unsigned counterpart would be [`U20F12`]. Aliases are provided for all
combinations of integer and fractional bits adding up to a total of eight, 16,
32, 64 or 128 bits.
```rust
use fixed::types::{I4F4, I4F12};
// -8 ≤ I4F4 < 8 with steps of 1/16 (~0.06)
let a = I4F4::from_num(1);
// multiplication and division by integers are possible
let ans1 = a / 5 * 17;
// 1 / 5 × 17 = 3 2/5 (3.4), but we get 3 3/16 (~3.2)
assert_eq!(ans1, I4F4::from_bits((3 << 4) + 3));
assert_eq!(ans1.to_string(), "3.2");
// -8 ≤ I4F12 < 8 with steps of 1/4096 (~0.0002)
let wider_a = I4F12::from(a);
let wider_ans = wider_a / 5 * 17;
let ans2 = I4F4::from_num(wider_ans);
// now the answer is the much closer 3 6/16 (~3.4)
assert_eq!(ans2, I4F4::from_bits((3 << 4) + 6));
assert_eq!(ans2.to_string(), "3.4");
```
The second example shows some precision and conversion issues. The low precision
of `a` means that `a / 5` is 3⁄16 instead of 1⁄5, leading to an inaccurate
result `ans1` = 3 3⁄16 (~3.2). With a higher precision, we get `wider_a / 5`
equal to 819⁄4096, leading to a more accurate intermediate result `wider_ans` =
3 1635⁄4096. When we convert back to four fractional bits, we get `ans2` = 3
6⁄16 (~3.4).
Note that we can convert from [`I4F4`] to [`I4F12`] using [`From`], as the
target type has the same number of integer bits and a larger number of
fractional bits. Converting from [`I4F12`] to [`I4F4`] cannot use [`From`] as we
have less fractional bits, so we use [`from_num`] instead.
## Writing fixed-point constants and values literally
The [`lit`] method, which is available as a `const` function, can be used to
parse literals. It supports
* underscores as separators;
* prefixes “`0b`”, “`0o`” and “`0x`” for binary, octal and hexadecimal
numbers;
* an optional decimal exponent with separator “`e`” or “`E`” for decimal,
binary and octal numbers, or with separator “`@`” for all supported radices
including hexadecimal.
```rust
use fixed::types::I16F16;
// 0.1275e2 is 12.75
const TWELVE_POINT_75: I16F16 = I16F16::lit("0.127_5e2");
// 1.8 hexadecimal is 1.5 decimal, and 18@-1 is 1.8
const ONE_POINT_5: I16F16 = I16F16::lit("0x_18@-1");
// 12.75 + 1.5 = 14.25
let sum = TWELVE_POINT_75 + ONE_POINT_5;
assert_eq!(sum, 14.25);
```
## Using the *fixed* crate
The *fixed* crate is available on [crates.io][*fixed* crate]. To use it in your
crate, add it as a dependency inside [*Cargo.toml*]:
```toml
[dependencies]
fixed = "1.29"
```
The *fixed* crate requires rustc version 1.83.0 or later.
## Optional features
The *fixed* crate has these optional feature:
1. `arbitrary`, disabled by default. This provides the generation of arbitrary
fixed-point numbers from raw, unstructured data. This feature requires the
[*arbitrary* crate].
2. `borsh`, disabled by default. This implements serialization and
deserialization using the [*borsh* crate].
3. `serde`, disabled by default. This provides serialization support for the
fixed-point types. This feature requires the [*serde* crate].
4. `std`, disabled by default. This is for features that are not possible under
`no_std`: currently this is only required for the `serde-str` feature.
5. `serde-str`, disabled by default. Fixed-point numbers are serialized as
strings showing the value when using human-readable formats. This feature
requires the `serde` and the `std` optional features. **Warning:** numbers
serialized when this feature is enabled cannot be deserialized when this
feature is disabled, and vice versa.
To enable features, you can add the dependency like this to [*Cargo.toml*]:
```toml
[dependencies.fixed]
features = ["serde"]
version = "1.29"
```
## Experimental optional features
It is not considered a breaking change if the following experimental features
are removed. The removal of experimental features would however require a minor
version bump. Similarly, on a minor version bump, optional dependencies can be
updated to an incompatible newer version.
1. `num-traits`, disabled by default. This implements some traits from the
[*num-traits* crate]. (The plan is to promote this to an optional feature
once the [*num-traits* crate] reaches version 1.0.0.)
2. `nightly-float`, disabled by default. This requires the nightly compiler,
and implements conversions and comparisons with the experimental [`f16`] and
[`f128`] primitives. (The plan is to always implement the conversions and
comparisons and remove this experimental feature once the primitives are
stabilized.)
[`f128`]: https://doc.rust-lang.org/nightly/std/primitive.f128.html
[`f16`]: https://doc.rust-lang.org/nightly/std/primitive.f16.html
## Deprecated optional features
The following optional features are deprecated and will be removed in the next
major version of the crate.
1. `az`, has no effect. Previously required for the [`az`] cast traits. Now
these cast traits are always provided.
2. `f16`, has no effect. Previously required for conversion to/from
<code>[half]::[f16][half::f16]</code> and
<code>[half]::[bf16][half::bf16]</code>. Now these conversions are always
provided.
## License
This crate is free software: you can redistribute it and/or modify it under the
terms of either
* the [Apache License, Version 2.0][LICENSE-APACHE] or
* the [MIT License][LICENSE-MIT]
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache License, Version 2.0,
shall be dual licensed as above, without any additional terms or conditions.
[*Cargo.toml*]: https://doc.rust-lang.org/cargo/guide/dependencies.html
[*arbitrary* crate]: https://crates.io/crates/arbitrary
[*borsh* crate]: https://crates.io/crates/borsh
[*cordic* crate]: https://crates.io/crates/cordic
[*fixed* crate]: https://crates.io/crates/fixed
[*half* crate]: https://crates.io/crates/half
[*num-traits* crate]: https://crates.io/crates/num-traits
[*serde* crate]: https://crates.io/crates/serde
[*typenum* crate]: https://crates.io/crates/typenum
[CORDIC]: https://en.wikipedia.org/wiki/CORDIC
[FixedI32]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html
[FixedU32]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU32.html
[LICENSE-APACHE]: https://www.apache.org/licenses/LICENSE-2.0
[LICENSE-MIT]: https://opensource.org/licenses/MIT
[U0]: https://docs.rs/fixed/~1.29/fixed/types/extra/type.U0.html
[U12]: https://docs.rs/fixed/~1.29/fixed/types/extra/type.U12.html
[U24]: https://docs.rs/fixed/~1.29/fixed/types/extra/type.U24.html
[U32]: https://docs.rs/fixed/~1.29/fixed/types/extra/type.U32.html
[`Binary`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Binary.html
[`Display`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Display.html
[`Error`]: https://doc.rust-lang.org/nightly/std/error/trait.Error.html
[`FixedI128`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI128.html
[`FixedI16`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI16.html
[`FixedI32`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html
[`FixedI64`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI64.html
[`FixedI8`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI8.html
[`FixedU128`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU128.html
[`FixedU16`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU16.html
[`FixedU32`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU32.html
[`FixedU64`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU64.html
[`FixedU8`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedU8.html
[`FromFixed`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.FromFixed.html
[`FromStr`]: https://doc.rust-lang.org/nightly/core/str/trait.FromStr.html
[`From`]: https://doc.rust-lang.org/nightly/core/convert/trait.From.html
[`I20F12`]: https://docs.rs/fixed/~1.29/fixed/types/type.I20F12.html
[`I4F12`]: https://docs.rs/fixed/~1.29/fixed/types/type.I4F12.html
[`I4F4`]: https://docs.rs/fixed/~1.29/fixed/types/type.I4F4.html
[`Into`]: https://doc.rust-lang.org/nightly/core/convert/trait.Into.html
[`LosslessTryFrom`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.LosslessTryFrom.html
[`LosslessTryInto`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.LosslessTryInto.html
[`LossyFrom`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.LossyFrom.html
[`LossyInto`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.LossyInto.html
[`LowerExp`]: https://doc.rust-lang.org/nightly/core/fmt/trait.LowerExp.html
[`LowerHex`]: https://doc.rust-lang.org/nightly/core/fmt/trait.LowerHex.html
[`Octal`]: https://doc.rust-lang.org/nightly/core/fmt/trait.Octal.html
[`ToFixed`]: https://docs.rs/fixed/~1.29/fixed/traits/trait.ToFixed.html
[`U20F12`]: https://docs.rs/fixed/~1.29/fixed/types/type.U20F12.html
[`UpperExp`]: https://doc.rust-lang.org/nightly/core/fmt/trait.UpperExp.html
[`UpperHex`]: https://doc.rust-lang.org/nightly/core/fmt/trait.UpperHex.html
[`az`]: https://docs.rs/az/^1/az/index.html
[`bytemuck`]: https://docs.rs/bytemuck/^1/bytemuck/index.html
[`checked_from_num`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.checked_from_num
[`from_num`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_num
[`from_str_binary`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_str_binary
[`from_str_hex`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_str_hex
[`from_str_octal`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.from_str_octal
[`i32`]: https://doc.rust-lang.org/nightly/core/primitive.i32.html
[`lit`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.lit
[`to_num`]: https://docs.rs/fixed/~1.29/fixed/struct.FixedI32.html#method.to_num
[`u32`]: https://doc.rust-lang.org/nightly/core/primitive.u32.html
[half::bf16]: https://docs.rs/half/^2/half/struct.bf16.html
[half::f16]: https://docs.rs/half/^2/half/struct.f16.html
[half]: https://docs.rs/half/^2/half/index.html
+1563
View File
File diff suppressed because it is too large. Load diff
+133
View File
@@ -0,0 +1,133 @@
// Copyright © 2018–2025 Trevor Spiteri
// Copying and distribution of this file, with or without
// modification, are permitted in any medium without royalty provided
// the copyright notice and this notice are preserved. This file is
// offered as-is, without any warranty.
#![allow(dead_code)]
#![allow(unused_variables)]
#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
use std::env;
use std::ffi::OsString;
use std::fs;
use std::fs::File;
use std::io::{Result as IoResult, Write};
use std::path::{Path, PathBuf};
use std::process::Command;
struct Environment {
out_dir: PathBuf,
rustc: OsString,
}
fn main() {
let env = Environment {
out_dir: PathBuf::from(cargo_env("OUT_DIR")),
rustc: cargo_env("RUSTC"),
};
}
#[derive(Clone, Copy, PartialEq)]
struct Optional(bool);
impl Environment {
// 1. If optional feature is availble (both with and without flag), output:
// cargo::rustc-cfg=<name>
// 2. If feature is available with flag (both optional and not), output:
// cargo::rustc-cfg=nightly_<name>
// 3. If non-optional feature is not available, panic.
fn check_feature(
&self,
name: &str,
optional: Optional,
contents: &str,
nightly_features: Option<&str>,
) {
let try_dir = self.out_dir.join(format!("try_{name}"));
let filename = format!("try_{name}.rs");
create_dir_or_panic(&try_dir);
println!("$ cd {try_dir:?}");
#[derive(PartialEq)]
enum Iteration {
Stable,
Unstable,
}
let mut found = false;
for i in &[Iteration::Stable, Iteration::Unstable] {
let s;
let file_contents = match *i {
Iteration::Stable => contents,
Iteration::Unstable => match nightly_features {
Some(features) => {
s = format!("#![feature({features})]\n{contents}");
&s
}
None => continue,
},
};
create_file_or_panic(&try_dir.join(&filename), file_contents);
let mut cmd = Command::new(&self.rustc);
cmd.current_dir(&try_dir)
.args([&filename, "--emit=dep-info,metadata"]);
println!("$ {cmd:?}");
let status = cmd
.status()
.unwrap_or_else(|_| panic!("Unable to execute: {cmd:?}"));
println!("cargo::rustc-cfg=cfg({name})");
println!("cargo::rustc-cfg=cfg(nightly_{name})");
if status.success() {
if optional.0 {
println!("cargo::rustc-cfg={name}");
}
if *i == Iteration::Unstable {
println!("cargo::rustc-cfg=nightly_{name}");
}
found = true;
break;
}
}
remove_dir_or_panic(&try_dir);
assert!(
found || optional.0,
"essential feature not supported by compiler: {name}"
);
}
}
fn cargo_env(name: &str) -> OsString {
env::var_os(name)
.unwrap_or_else(|| panic!("environment variable not found: {name}, please use cargo"))
}
fn remove_dir(dir: &Path) -> IoResult<()> {
if !dir.exists() {
return Ok(());
}
assert!(dir.is_dir(), "Not a directory: {dir:?}");
println!("$ rm -r {dir:?}");
fs::remove_dir_all(dir)
}
fn remove_dir_or_panic(dir: &Path) {
remove_dir(dir).unwrap_or_else(|_| panic!("Unable to remove directory: {dir:?}"));
}
fn create_dir(dir: &Path) -> IoResult<()> {
println!("$ mkdir -p {dir:?}");
fs::create_dir_all(dir)
}
fn create_dir_or_panic(dir: &Path) {
create_dir(dir).unwrap_or_else(|_| panic!("Unable to create directory: {dir:?}"));
}
fn create_file_or_panic(filename: &Path, contents: &str) {
println!("$ printf '%s' {:?}... > {:?}", &contents[0..20], filename);
let mut file =
File::create(filename).unwrap_or_else(|_| panic!("Unable to create file: {filename:?}"));
file.write_all(contents.as_bytes())
.unwrap_or_else(|_| panic!("Unable to write to file: {filename:?}"));
}
+45
View File
@@ -0,0 +1,45 @@
# Copyright © 2018–2025 Trevor Spiteri
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
skip_tags: true
branches:
except:
- www
environment:
MSRV: 1.83.0
matrix:
- TARGET: x86_64-pc-windows-msvc
- TARGET: x86_64-pc-windows-gnu
- TARGET: i686-pc-windows-msvc
cache:
- '%USERPROFILE%\.cargo\registry\cache'
install:
- set PATH=C:\msys64\usr\bin;%USERPROFILE%\.cargo\bin;%PATH%
- curl -sSf -o rustup-init.exe https://win.rustup.rs
- rustup-init.exe -y --default-host %TARGET% --default-toolchain none --no-modify-path
- rustup --version
- rustup toolchain install --profile minimal beta-%TARGET% %MSRV%-%TARGET%
- rustup component add --toolchain beta-%TARGET% clippy
build: false
before_test:
- bash -c "if [ -d $USERPROFILE/.cargo/registry/src ]; then rm -r $USERPROFILE/.cargo/src; fi"
- bash -c "if [ -d $USERPROFILE/.cargo/registry/cache ]; then cd $USERPROFILE/.cargo/registry; find cache -name \*.crate | sort; fi"
test_script:
- cargo +beta-%TARGET% clippy --all-targets --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +beta-%TARGET% test --release --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +%MSRV%-%TARGET% test --lib --features "fail-on-warnings num-traits std"
after_test:
# ugh, appveyor can't handle percentage signs, so we need to use $(sed 's/\.crate$//' <<< $s)
- bash -c "if [ -d $USERPROFILE/.cargo/registry/cache ]; then cd $USERPROFILE/.cargo/registry; for c in cache/*/*.crate; do s=src/${c#cache/}; if [ ! -e $(sed 's/\.crate$//' <<< $s) ]; then rm -v $c; fi; done; find cache -name \*.crate | sort; fi"
+181
View File
@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="128"
height="128"
viewBox="0 0 33.866666 33.866668"
version="1.1"
id="svg8"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="fixed-src.svg"
inkscape:export-filename="fixed.png"
inkscape:export-xdpi="300"
inkscape:export-ydpi="300"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<inkscape:path-effect
effect="powerclip"
id="path-effect268"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Use fill-rule evenodd on &lt;b&gt;fill and stroke&lt;/b&gt; dialog if no flatten result after convert clip to paths." />
<inkscape:path-effect
effect="powerclip"
id="path-effect1252"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Use fill-rule evenodd on &lt;b&gt;fill and stroke&lt;/b&gt; dialog if no flatten result after convert clip to paths." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath299">
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.9333px;line-height:6.10576px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3 Bold';letter-spacing:0px;word-spacing:0px;fill:#0072b2;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="20.717939"
y="285.34137"
id="text303"><tspan
sodipodi:role="line"
id="tspan301"
x="20.717939"
y="285.34137"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.9333px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3 Bold';fill:#0072b2;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-opacity:1">28</tspan></text>
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath318">
<circle
style="fill:#0072b2;fill-opacity:1;stroke:none;stroke-width:0.504669;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="circle320"
cx="16.933334"
cy="280.06665"
r="16.933334" />
</clipPath>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6568542"
inkscape:cx="13.523417"
inkscape:cy="72.920387"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1011"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:snap-nodes="true"
inkscape:snap-others="false"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="2"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid10" />
<sodipodi:guide
position="4.2333333,4.4979168"
orientation="1,0"
id="guide839"
inkscape:locked="false" />
<sodipodi:guide
position="-1.3229166,4.2333334"
orientation="0,1"
id="guide916"
inkscape:locked="false" />
<sodipodi:guide
position="16.933333,24.077084"
orientation="1,0"
id="guide4539"
inkscape:locked="false" />
<sodipodi:guide
position="11.90625,16.933334"
orientation="0.009999519,-0.99995"
id="guide4541"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-263.13331)">
<circle
style="display:inline;fill:#f7f1a1;fill-opacity:1;stroke:none;stroke-width:0.504668;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4492"
cx="16.933334"
cy="280.06665"
r="16.933334" />
<path
id="rect1588-3"
style="fill:#0072b2;stroke-width:0.529167"
d="m 15.345829,283.50622 h 3.175 c 0,0 0,0.79376 0.52917,1.8521 h -4.23333 c 0.52916,-1.05834 0.52916,-1.8521 0.52916,-1.8521 z"
sodipodi:nodetypes="ccccc" />
<circle
style="fill:#0072b2;fill-opacity:1;stroke:none;stroke-width:0.504668;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4492-3"
cx="16.933334"
cy="280.06665"
r="16.933334"
clip-path="url(#clipPath299)" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.8667px;line-height:12.2115px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3 Bold';letter-spacing:0px;word-spacing:0px;fill:#0072b2;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="11.853333"
y="283.75388"
id="text1010-7"><tspan
sodipodi:role="line"
id="tspan1008-9"
x="11.853333"
y="283.75388"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:33.8667px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3 Bold';fill:#0072b2;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-opacity:1">.</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.9333px;line-height:6.10576px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3, Bold';letter-spacing:0px;word-spacing:0px;fill:#0072b2;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal"
x="-4.6820688"
y="285.34137"
id="text1010"
clip-path="url(#clipPath318)"><tspan
sodipodi:role="line"
id="tspan1008"
x="-4.6820688"
y="285.34137"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:16.9333335px;font-family:'Source Sans 3';-inkscape-font-specification:'Source Sans 3, Bold';fill:#0072b2;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-opacity:1;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">06</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+1
View File
@@ -0,0 +1 @@
<svg width="128" height="128" version="1.1" viewBox="0 0 33.867 33.867" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="b"><circle cx="16.933" cy="280.07" r="16.933" fill="#0072b2"/></clipPath><clipPath id="a"><circle cx="16.933" cy="280.07" r="16.933" fill="#0072b2"/></clipPath></defs><g transform="translate(0 -263.13)"><circle cx="16.933" cy="280.07" r="16.933" fill="#f7f1a1"/><g fill="#0072b2"><path d="m15.346 283.51h3.175s0 0.79376 0.52917 1.8521h-4.2333c0.52916-1.0583 0.52916-1.8521 0.52916-1.8521z" stroke-width=".52917"/><path d="m34.112 285.54c2.286 0 3.81-1.2869 3.81-2.9803 0-1.4224-0.82973-2.2521-1.8627-2.8109v-0.0677c0.74507-0.49107 1.4055-1.3208 1.4055-2.3537 0-1.778-1.3039-2.9464-3.2681-2.9464-1.9981 0-3.4375 1.1345-3.4375 2.9633 0 1.1345 0.6096 1.9135 1.4393 2.54v0.0677c-1.016 0.54187-1.8288 1.3377-1.8288 2.6416 0 1.7441 1.5917 2.9464 3.7423 2.9464zm0.67733-6.4685c-1.1007-0.42333-1.8457-0.84667-1.8457-1.7272 0-0.82973 0.54187-1.2361 1.2023-1.2361 0.8128 0 1.3039 0.5588 1.3039 1.3885 0 0.5588-0.23707 1.0837-0.6604 1.5748zm-0.62653 4.7413c-0.89746 0-1.6595-0.5588-1.6595-1.5071 0-0.6604 0.3556-1.27 0.82973-1.7103 1.3547 0.57573 2.2691 0.93133 2.2691 1.8796 0 0.89747-0.6096 1.3377-1.4393 1.3377z" clip-path="url(#b)"/><path d="m21.344 285.34h7.5692v-2.0997h-2.4045c-0.49107 0-1.2361 0.0677-1.7949 0.13547 1.9135-1.8796 3.6745-3.6407 3.6745-5.588 0-2.0489-1.4393-3.4036-3.556-3.4036-1.5071 0-2.5231 0.59267-3.556 1.7611l1.3039 1.2869c0.54187-0.57573 1.1345-1.1007 1.9135-1.1007 0.93133 0 1.524 0.57574 1.524 1.6256 0 1.5071-1.9643 3.4375-4.6736 5.842z"/><path d="m16.933 284.16c1.7272 0 3.0141-1.3547 3.0141-3.1157 0-1.7949-1.2869-3.1496-3.0141-3.1496-1.7272 0-3.0141 1.3547-3.0141 3.1496 0 1.7611 1.2869 3.1157 3.0141 3.1157z"/><path d="m8.9154 280.38c0.8128 0 1.4055 0.42334 1.4055 1.5579 0 1.2192-0.6096 1.7441-1.3547 1.7441s-1.4563-0.54186-1.6764-2.2352c0.44027-0.762 1.0668-1.0668 1.6256-1.0668zm0.1016 5.1647c1.9473 0 3.5729-1.3716 3.5729-3.6068 0-2.2691-1.3547-3.3189-3.2004-3.3189-0.6604 0-1.5917 0.42333-2.1505 1.1345 0.084667-2.5061 1.0329-3.3528 2.2183-3.3528 0.62653 0 1.3039 0.3556 1.6764 0.762l1.3039-1.4901c-0.67733-0.69427-1.7103-1.2869-3.1327-1.2869-2.2691 0-4.3519 1.8119-4.3519 5.8251 0 3.7761 1.9812 5.334 4.064 5.334z"/><path d="m-0.21167 285.54c2.3029 0 3.8439-1.9643 3.8439-5.6219s-1.5409-5.5372-3.8439-5.5372c-2.3029 0-3.8439 1.8796-3.8439 5.5372s1.5409 5.6219 3.8439 5.6219zm0-1.9304c-0.82973 0-1.4901-0.762-1.4901-3.6915 0-2.9464 0.6604-3.6068 1.4901-3.6068 0.84667 0 1.4901 0.6604 1.4901 3.6068 0 2.9295-0.64347 3.6915-1.4901 3.6915z" clip-path="url(#a)"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+36
View File
@@ -0,0 +1,36 @@
PHN2ZyB3aWR0aD0iMTI4IiBoZWlnaHQ9IjEyOCIgdmVyc2lvbj0iMS4xIiB2aWV3Qm94PSIwIDAgMzMuODY3IDMzLjg2NyIgeG1s
bnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjxjaXJjbGUgY3g9IjE2LjkzMyIg
Y3k9IjI4MC4wNyIgcj0iMTYuOTMzIiBmaWxsPSIjMDA3MmIyIi8+PC9jbGlwUGF0aD48Y2xpcFBhdGggaWQ9ImEiPjxjaXJjbGUg
Y3g9IjE2LjkzMyIgY3k9IjI4MC4wNyIgcj0iMTYuOTMzIiBmaWxsPSIjMDA3MmIyIi8+PC9jbGlwUGF0aD48L2RlZnM+PGcgdHJh
bnNmb3JtPSJ0cmFuc2xhdGUoMCAtMjYzLjEzKSI+PGNpcmNsZSBjeD0iMTYuOTMzIiBjeT0iMjgwLjA3IiByPSIxNi45MzMiIGZp
bGw9IiNmN2YxYTEiLz48ZyBmaWxsPSIjMDA3MmIyIj48cGF0aCBkPSJtMTUuMzQ2IDI4My41MWgzLjE3NXMwIDAuNzkzNzYgMC41
MjkxNyAxLjg1MjFoLTQuMjMzM2MwLjUyOTE2LTEuMDU4MyAwLjUyOTE2LTEuODUyMSAwLjUyOTE2LTEuODUyMXoiIHN0cm9rZS13
aWR0aD0iLjUyOTE3Ii8+PHBhdGggZD0ibTM0LjExMiAyODUuNTRjMi4yODYgMCAzLjgxLTEuMjg2OSAzLjgxLTIuOTgwMyAwLTEu
NDIyNC0wLjgyOTczLTIuMjUyMS0xLjg2MjctMi44MTA5di0wLjA2NzdjMC43NDUwNy0wLjQ5MTA3IDEuNDA1NS0xLjMyMDggMS40
MDU1LTIuMzUzNyAwLTEuNzc4LTEuMzAzOS0yLjk0NjQtMy4yNjgxLTIuOTQ2NC0xLjk5ODEgMC0zLjQzNzUgMS4xMzQ1LTMuNDM3
NSAyLjk2MzMgMCAxLjEzNDUgMC42MDk2IDEuOTEzNSAxLjQzOTMgMi41NHYwLjA2NzdjLTEuMDE2IDAuNTQxODctMS44Mjg4IDEu
MzM3Ny0xLjgyODggMi42NDE2IDAgMS43NDQxIDEuNTkxNyAyLjk0NjQgMy43NDIzIDIuOTQ2NHptMC42NzczMy02LjQ2ODVjLTEu
MTAwNy0wLjQyMzMzLTEuODQ1Ny0wLjg0NjY3LTEuODQ1Ny0xLjcyNzIgMC0wLjgyOTczIDAuNTQxODctMS4yMzYxIDEuMjAyMy0x
LjIzNjEgMC44MTI4IDAgMS4zMDM5IDAuNTU4OCAxLjMwMzkgMS4zODg1IDAgMC41NTg4LTAuMjM3MDcgMS4wODM3LTAuNjYwNCAx
LjU3NDh6bS0wLjYyNjUzIDQuNzQxM2MtMC44OTc0NiAwLTEuNjU5NS0wLjU1ODgtMS42NTk1LTEuNTA3MSAwLTAuNjYwNCAwLjM1
NTYtMS4yNyAwLjgyOTczLTEuNzEwMyAxLjM1NDcgMC41NzU3MyAyLjI2OTEgMC45MzEzMyAyLjI2OTEgMS44Nzk2IDAgMC44OTc0
Ny0wLjYwOTYgMS4zMzc3LTEuNDM5MyAxLjMzNzd6IiBjbGlwLXBhdGg9InVybCgjYikiLz48cGF0aCBkPSJtMjEuMzQ0IDI4NS4z
NGg3LjU2OTJ2LTIuMDk5N2gtMi40MDQ1Yy0wLjQ5MTA3IDAtMS4yMzYxIDAuMDY3Ny0xLjc5NDkgMC4xMzU0NyAxLjkxMzUtMS44
Nzk2IDMuNjc0NS0zLjY0MDcgMy42NzQ1LTUuNTg4IDAtMi4wNDg5LTEuNDM5My0zLjQwMzYtMy41NTYtMy40MDM2LTEuNTA3MSAw
LTIuNTIzMSAwLjU5MjY3LTMuNTU2IDEuNzYxMWwxLjMwMzkgMS4yODY5YzAuNTQxODctMC41NzU3MyAxLjEzNDUtMS4xMDA3IDEu
OTEzNS0xLjEwMDcgMC45MzEzMyAwIDEuNTI0IDAuNTc1NzQgMS41MjQgMS42MjU2IDAgMS41MDcxLTEuOTY0MyAzLjQzNzUtNC42
NzM2IDUuODQyeiIvPjxwYXRoIGQ9Im0xNi45MzMgMjg0LjE2YzEuNzI3MiAwIDMuMDE0MS0xLjM1NDcgMy4wMTQxLTMuMTE1NyAw
LTEuNzk0OS0xLjI4NjktMy4xNDk2LTMuMDE0MS0zLjE0OTYtMS43MjcyIDAtMy4wMTQxIDEuMzU0Ny0zLjAxNDEgMy4xNDk2IDAg
MS43NjExIDEuMjg2OSAzLjExNTcgMy4wMTQxIDMuMTE1N3oiLz48cGF0aCBkPSJtOC45MTU0IDI4MC4zOGMwLjgxMjggMCAxLjQw
NTUgMC40MjMzNCAxLjQwNTUgMS41NTc5IDAgMS4yMTkyLTAuNjA5NiAxLjc0NDEtMS4zNTQ3IDEuNzQ0MXMtMS40NTYzLTAuNTQx
ODYtMS42NzY0LTIuMjM1MmMwLjQ0MDI3LTAuNzYyIDEuMDY2OC0xLjA2NjggMS42MjU2LTEuMDY2OHptMC4xMDE2IDUuMTY0N2Mx
Ljk0NzMgMCAzLjU3MjktMS4zNzE2IDMuNTcyOS0zLjYwNjggMC0yLjI2OTEtMS4zNTQ3LTMuMzE4OS0zLjIwMDQtMy4zMTg5LTAu
NjYwNCAwLTEuNTkxNyAwLjQyMzMzLTIuMTUwNSAxLjEzNDUgMC4wODQ2NjctMi41MDYxIDEuMDMyOS0zLjM1MjggMi4yMTgzLTMu
MzUyOCAwLjYyNjUzIDAgMS4zMDM5IDAuMzU1NiAxLjY3NjQgMC43NjJsMS4zMDM5LTEuNDkwMWMtMC42NzczMy0wLjY5NDI3LTEu
NzEwMy0xLjI4NjktMy4xMzI3LTEuMjg2OS0yLjI2OTEgMC00LjM1MTkgMS44MTE5LTQuMzUxOSA1LjgyNTEgMCAzLjc3NjEgMS45
ODEyIDUuMzM0IDQuMDY0IDUuMzM0eiIvPjxwYXRoIGQ9Im0tMC4yMTE2NyAyODUuNTRjMi4zMDI5IDAgMy44NDM5LTEuOTY0MyAz
Ljg0MzktNS42MjE5cy0xLjU0MDktNS41MzcyLTMuODQzOS01LjUzNzJjLTIuMzAyOSAwLTMuODQzOSAxLjg3OTYtMy44NDM5IDUu
NTM3MnMxLjU0MDkgNS42MjE5IDMuODQzOSA1LjYyMTl6bTAtMS45MzA0Yy0wLjgyOTczIDAtMS40OTAxLTAuNzYyLTEuNDkwMS0z
LjY5MTUgMC0yLjk0NjQgMC42NjA0LTMuNjA2OCAxLjQ5MDEtMy42MDY4IDAuODQ2NjcgMCAxLjQ5MDEgMC42NjA0IDEuNDkwMSAz
LjYwNjggMCAyLjkyOTUtMC42NDM0NyAzLjY5MTUtMS40OTAxIDMuNjkxNXoiIGNsaXAtcGF0aD0idXJsKCNhKSIvPjwvZz48L2c+
PC9zdmc+Cg==
+142
View File
@@ -0,0 +1,142 @@
# Copyright © 2018–2025 Trevor Spiteri
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
variables:
MSRV: 1.83.0
before_script:
- getconf LONG_BIT
- rustup self update
- rustup --version
- if [ -d cargo/registry/cache ]; then rm -rf $CARGO_HOME/registry/cache; mkdir -p $CARGO_HOME/registry; cp -R cargo/registry/cache $CARGO_HOME/registry/; echo Copied registry/cache; fi
- if [ -d $CARGO_HOME/registry/src ]; then rm -r $CARGO_HOME/registry/src; fi
- if [ -d $CARGO_HOME/registry/cache ]; then (cd $CARGO_HOME/registry; find cache -name \*.crate | sort) fi
after_script:
- if [ -d $CARGO_HOME/registry/cache ]; then (cd $CARGO_HOME/registry; for c in cache/*/*.crate; do s=src/${c#cache/}; if [ ! -e ${s%.crate} ]; then rm -v $c; fi; done; find cache -name \*.crate | sort) fi
- rm -rf cargo
- mkdir -p cargo/registry
- if [ -d $CARGO_HOME/registry/cache ]; then cp -R $CARGO_HOME/registry/cache cargo/registry/; echo Updated registry/cache; fi
x86_64-gnulinux:
image: amd64/rust:1
variables:
TARGET: x86_64
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal beta-$TARGET $MSRV-$TARGET nightly-$TARGET
- cargo +beta-$TARGET test --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +beta-$TARGET test --release --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +$MSRV-$TARGET test --lib --features "fail-on-warnings num-traits std"
- cargo +$MSRV-$TARGET test --release --lib --features "fail-on-warnings num-traits std"
- cargo +nightly-$TARGET test --features "fail-on-warnings nightly-float"
only:
- ci
i686-gnulinux:
image: i386/rust:1
variables:
TARGET: i686
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal beta-$TARGET $MSRV-$TARGET nightly-$TARGET
- cargo +beta-$TARGET test --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +beta-$TARGET test --release --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +$MSRV-$TARGET test --lib --features "fail-on-warnings num-traits std"
- cargo +$MSRV-$TARGET test --release --lib --features "fail-on-warnings num-traits std"
- cargo +nightly-$TARGET test --features "fail-on-warnings nightly-float"
only:
- ci
x86_64-gnulinux-tidy:
image: amd64/rust:1
variables:
TARGET: x86_64
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal beta-$TARGET
- rustup component add --toolchain beta-$TARGET rustfmt clippy
- cargo +beta-$TARGET fmt -- --check
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings arbitrary"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings num-traits"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings borsh"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings serde"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings serde-str"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings std"
- cargo +beta-$TARGET clippy --all-targets --features fail-on-warnings
only:
- ci
i686-gnulinux-tidy:
image: i386/rust:1
variables:
TARGET: i686
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal beta-$TARGET
- rustup component add --toolchain beta-$TARGET rustfmt clippy
- cargo +beta-$TARGET fmt -- --check
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings arbitrary borsh num-traits serde std"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings arbitrary"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings num-traits"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings borsh"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings serde"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings serde-str"
- cargo +beta-$TARGET clippy --all-targets --features "fail-on-warnings std"
- cargo +beta-$TARGET clippy --all-targets --features fail-on-warnings
only:
- ci
x86_64-gnulinux-tarpaulin:
image: amd64/rust:1
variables:
TARGET: x86_64
REQ_COVERAGE: "70"
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal beta-$TARGET
- cargo +beta-$TARGET install --version 0.32.0 --locked cargo-tarpaulin
- cargo +beta-$TARGET tarpaulin --verbose --ignore-tests --fail-under $REQ_COVERAGE --exclude-files build.rs --features "arbitrary borsh num-traits serde std"
only:
- ci
pages:
image: amd64/rust:1
variables:
TARGET: x86_64
cache:
key: $CI_JOB_NAME
paths:
- cargo/
script:
- rustup toolchain install --profile minimal nightly-$TARGET
- rm -rf public
- mkdir public
- cp etc/index.html public
- cargo +nightly-$TARGET doc --no-deps --features "arbitrary borsh num-traits serde std" -Z rustdoc-map
- mv target/doc public/dev
artifacts:
paths:
- public
only:
- www
+17
View File
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<title>Fixed-point numbers</title>
</head>
<body>
<div style="margin-right: auto; margin-left: auto; max-width: 600px;">
<h1>Fixed-point numbers</h1>
<p>The <a href="https://crates.io/crates/fixed"><i>fixed</i> crate</a> provides fixed-point numbers. Here you can find the documentation for the <a href="https://gitlab.com/tspiteri/fixed">current development version</a> of the crate.</p>
<h2>Documentation</h2>
<ul>
<li><a href="dev/fixed/index.html">Documentation for the current development version.</a></li>
<li><a href="https://docs.rs/fixed">Documentation for the published versions.</a></li>
</ul>
</div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
style_edition = "2024"
+1625
View File
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use borsh::io::{Read, Result, Write};
use borsh::{BorshDeserialize, BorshSerialize};
macro_rules! borsh_fixed {
($Fixed:ident is $TBits:ident) => {
impl<Frac> BorshSerialize for $Fixed<Frac> {
#[inline]
fn serialize<W: Write>(&self, writer: &mut W) -> Result<()> {
<$TBits as BorshSerialize>::serialize(&self.bits, writer)
}
}
impl<Frac> BorshDeserialize for $Fixed<Frac> {
#[inline]
fn deserialize_reader<R: Read>(reader: &mut R) -> Result<Self> {
<$TBits as BorshDeserialize>::deserialize_reader(reader).map($Fixed::from_bits)
}
}
};
}
borsh_fixed! { FixedI8 is i8 }
borsh_fixed! { FixedI16 is i16 }
borsh_fixed! { FixedI32 is i32 }
borsh_fixed! { FixedI64 is i64 }
borsh_fixed! { FixedI128 is i128 }
borsh_fixed! { FixedU8 is u8 }
borsh_fixed! { FixedU16 is u16 }
borsh_fixed! { FixedU32 is u32 }
borsh_fixed! { FixedU64 is u64 }
borsh_fixed! { FixedU128 is u128 }
+380
View File
@@ -0,0 +1,380 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// Kept trimmed: no underscores at beginning or end of slice
#[derive(Clone, Copy, Debug)]
pub struct DigitsUnds<'a> {
bytes: &'a [u8],
digits: usize,
}
impl<'a> DigitsUnds<'a> {
pub const EMPTY: DigitsUnds<'a> = DigitsUnds::new(&[]);
pub const fn new(bytes: &'a [u8]) -> DigitsUnds<'a> {
let mut digits = 0;
let mut leading_unds = 0;
let mut trailing_unds = 0;
let mut rem_bytes = bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
trailing_unds += 1;
} else {
if digits == 0 {
leading_unds = trailing_unds;
}
digits += 1;
trailing_unds = 0;
}
}
let without_trailing_unds = bytes.split_at(bytes.len() - trailing_unds).0;
let without_leading_unds = without_trailing_unds.split_at(leading_unds).1;
DigitsUnds {
bytes: without_leading_unds,
digits,
}
}
#[inline]
pub const fn len(self) -> usize {
self.digits
}
#[inline]
pub const fn is_empty(self) -> bool {
self.digits == 0
}
pub const fn split_at(self, mid: usize) -> (DigitsUnds<'a>, DigitsUnds<'a>) {
let mut remaining_digits = mid;
let mut unds = 0;
let mut rem_bytes = self.bytes;
while let Some((&byte, rem)) = rem_bytes.split_first() {
rem_bytes = rem;
if byte == b'_' {
unds += 1;
} else {
remaining_digits -= 1;
if remaining_digits == 0 {
break;
}
}
}
if remaining_digits > 0 {
panic!("index out of bounds");
}
let first = DigitsUnds {
bytes: self.bytes.split_at(mid + unds).0,
digits: mid,
};
// skip over underscores between first part and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
(
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - mid,
},
)
}
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&first, mut rem_bytes)) = self.bytes.split_first() else {
return None;
};
// first byte is never underscore
debug_assert!(first != b'_');
// skip over underscores between first digit and last part
while let Some((&b'_', rem)) = rem_bytes.split_first() {
rem_bytes = rem;
}
Some((
first,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
#[inline]
const fn split_last(self) -> Option<(u8, DigitsUnds<'a>)> {
let Some((&last, mut rem_bytes)) = self.bytes.split_last() else {
return None;
};
// last byte is never underscore
debug_assert!(last != b'_');
// skip over underscores between first part and last digit
while let Some((&b'_', rem)) = rem_bytes.split_last() {
rem_bytes = rem;
}
Some((
last,
DigitsUnds {
bytes: rem_bytes,
digits: self.digits - 1,
},
))
}
const fn split_leading_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_first() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
const fn split_trailing_zeros(self) -> (usize, DigitsUnds<'a>) {
let mut zeros = 0;
let mut rem = self;
while let Some((b'0', rest)) = rem.split_last() {
zeros += 1;
rem = rest;
}
(zeros, rem)
}
}
#[derive(Clone, Copy, Debug)]
pub struct DigitsExp<'a> {
leading_zeros: usize,
part1: DigitsUnds<'a>,
part2: DigitsUnds<'a>,
trailing_zeros: usize,
}
impl<'a> DigitsExp<'a> {
const EMPTY: DigitsExp<'a> = DigitsExp {
leading_zeros: 0,
part1: DigitsUnds::EMPTY,
part2: DigitsUnds::EMPTY,
trailing_zeros: 0,
};
const fn new1(digits: DigitsUnds<'a>) -> DigitsExp<'a> {
let (leading_zeros, rest) = digits.split_leading_zeros();
let (trailing_zeros, rest) = rest.split_trailing_zeros();
DigitsExp {
leading_zeros,
part1: rest,
part2: DigitsUnds::EMPTY,
trailing_zeros,
}
}
const fn new2(digits1: DigitsUnds<'a>, digits2: DigitsUnds<'a>) -> DigitsExp<'a> {
let (mut leading_zeros, mut digits1) = digits1.split_leading_zeros();
let digits2 = if digits1.is_empty() {
let (more_leading_zeros, new_digits1) = digits2.split_leading_zeros();
leading_zeros += more_leading_zeros;
digits1 = new_digits1;
DigitsUnds::EMPTY
} else {
digits2
};
let (mut trailing_zeros, digits2) = digits2.split_trailing_zeros();
if digits2.is_empty() {
let (more_trailing_zeros, new_digits1) = digits1.split_trailing_zeros();
trailing_zeros += more_trailing_zeros;
digits1 = new_digits1;
}
DigitsExp {
leading_zeros,
part1: digits1,
part2: digits2,
trailing_zeros,
}
}
// exp.unsigned_abs() must fit in usize, and results must have lengths that fit in usize
pub const fn new_int_frac(
int: DigitsUnds<'a>,
frac: DigitsUnds<'a>,
exp: i32,
) -> Option<(DigitsExp<'a>, DigitsExp<'a>)> {
if int.len() > usize::MAX - frac.len() {
return None;
}
let abs_exp = exp.unsigned_abs() as usize;
if abs_exp as u32 != exp.unsigned_abs() {
return None;
}
let (mut int, mut frac) = if exp == 0 {
(DigitsExp::new1(int), DigitsExp::new1(frac))
} else if exp < 0 {
if let Some(extra_zeros) = abs_exp.checked_sub(int.len()) {
let mut frac = DigitsExp::new2(int, frac);
frac.trailing_zeros = 0;
if extra_zeros > usize::MAX - frac.len() {
return None;
}
frac.leading_zeros += extra_zeros;
(DigitsExp::EMPTY, frac)
} else {
let int = int.split_at(int.len() - abs_exp);
(DigitsExp::new1(int.0), DigitsExp::new2(int.1, frac))
}
} else {
// exp > 0
if let Some(extra_zeros) = abs_exp.checked_sub(frac.len()) {
let mut int = DigitsExp::new2(int, frac);
int.leading_zeros = 0;
if extra_zeros > usize::MAX - int.len() {
return None;
}
int.trailing_zeros += extra_zeros;
(int, DigitsExp::EMPTY)
} else {
let frac = frac.split_at(abs_exp);
(DigitsExp::new2(int, frac.0), DigitsExp::new1(frac.1))
}
};
int.leading_zeros = 0;
if int.part1.is_empty() && int.part2.is_empty() {
int.trailing_zeros = 0;
}
frac.trailing_zeros = 0;
if frac.part2.is_empty() && frac.part1.is_empty() {
frac.leading_zeros = 0;
}
Some((int, frac))
}
#[inline]
pub const fn len(self) -> usize {
self.leading_zeros + self.part1.len() + self.part2.len() + self.trailing_zeros
}
#[inline]
pub const fn is_empty(self) -> bool {
self.len() == 0
}
pub const fn split_at(self, mut mid: usize) -> (DigitsExp<'a>, DigitsExp<'a>) {
let mut first = DigitsExp::EMPTY;
let mut last = self;
if mid == 0 {
return (first, last);
}
if mid < self.leading_zeros {
(first.leading_zeros, last.leading_zeros) = (mid, self.leading_zeros - mid);
return (first, last);
}
(first.leading_zeros, last.leading_zeros) = (self.leading_zeros, 0);
mid -= self.leading_zeros;
if mid == 0 {
return (first, last);
}
if mid < self.part1.len() {
(first.part1, last.part1) = self.part1.split_at(mid);
return (first, last);
}
first.part1 = self.part1;
last.part1 = self.part2;
last.part2 = DigitsUnds::EMPTY;
mid -= self.part1.len();
if mid == 0 {
return (first, last);
}
if mid < self.part2.len() {
(first.part2, last.part1) = self.part2.split_at(mid);
return (first, last);
}
first.part2 = self.part2;
last.leading_zeros = self.trailing_zeros;
last.part1 = DigitsUnds::EMPTY;
last.trailing_zeros = 0;
mid -= self.part2.len();
if mid == 0 {
return (first, last);
}
if mid < self.trailing_zeros {
(first.trailing_zeros, last.leading_zeros) = (mid, self.trailing_zeros - mid);
return (first, last);
}
(first.trailing_zeros, last.leading_zeros) = (self.trailing_zeros, 0);
mid -= self.trailing_zeros;
if mid == 0 {
return (first, last);
}
panic!("index out of bounds");
}
// no automatic renormalization done after split_first
#[inline]
pub const fn split_first(self) -> Option<(u8, DigitsExp<'a>)> {
if self.leading_zeros > 0 {
return Some((
b'0',
DigitsExp {
leading_zeros: self.leading_zeros - 1,
..self
},
));
}
if let Some((first, rest)) = self.part1.split_first() {
return Some((
first,
DigitsExp {
part1: rest,
..self
},
));
}
if let Some((first, rest)) = self.part2.split_first() {
return Some((
first,
DigitsExp {
part2: rest,
..self
},
));
}
if self.trailing_zeros > 0 {
return Some((
b'0',
DigitsExp {
trailing_zeros: self.trailing_zeros - 1,
..self
},
));
}
None
}
}
+200
View File
@@ -0,0 +1,200 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use az::{Cast, CheckedCast, OverflowingCast, SaturatingCast, UnwrappedCast, WrappingCast};
use half::{bf16 as half_bf16, f16 as half_f16};
macro_rules! cast {
($Src:ident($LeEqUSrc:ident); $Dst:ident($LeEqUDst:ident)) => {
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> Cast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn cast(self) -> $Dst<FracDst> {
self.to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> CheckedCast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn checked_cast(self) -> Option<$Dst<FracDst>> {
self.checked_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> SaturatingCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
fn saturating_cast(self) -> $Dst<FracDst> {
self.saturating_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> WrappingCast<$Dst<FracDst>> for $Src<FracSrc> {
#[inline]
fn wrapping_cast(self) -> $Dst<FracDst> {
self.wrapping_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> OverflowingCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
fn overflowing_cast(self) -> ($Dst<FracDst>, bool) {
self.overflowing_to_num()
}
}
impl<FracSrc: $LeEqUSrc, FracDst: $LeEqUDst> UnwrappedCast<$Dst<FracDst>>
for $Src<FracSrc>
{
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Dst<FracDst> {
self.unwrapped_to_num()
}
}
};
($Fixed:ident($LeEqU:ident); $Dst:ident) => {
impl<Frac: $LeEqU> Cast<$Dst> for $Fixed<Frac> {
#[inline]
fn cast(self) -> $Dst {
self.to_num()
}
}
impl<Frac: $LeEqU> CheckedCast<$Dst> for $Fixed<Frac> {
#[inline]
fn checked_cast(self) -> Option<$Dst> {
self.checked_to_num()
}
}
impl<Frac: $LeEqU> SaturatingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn saturating_cast(self) -> $Dst {
self.saturating_to_num()
}
}
impl<Frac: $LeEqU> WrappingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn wrapping_cast(self) -> $Dst {
self.wrapping_to_num()
}
}
impl<Frac: $LeEqU> OverflowingCast<$Dst> for $Fixed<Frac> {
#[inline]
fn overflowing_cast(self) -> ($Dst, bool) {
self.overflowing_to_num()
}
}
impl<Frac: $LeEqU> UnwrappedCast<$Dst> for $Fixed<Frac> {
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Dst {
self.unwrapped_to_num()
}
}
};
($Src:ident; $Fixed:ident($LeEqU:ident)) => {
impl<Frac: $LeEqU> Cast<$Fixed<Frac>> for $Src {
#[inline]
fn cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::from_num(self)
}
}
impl<Frac: $LeEqU> CheckedCast<$Fixed<Frac>> for $Src {
#[inline]
fn checked_cast(self) -> Option<$Fixed<Frac>> {
<$Fixed<Frac>>::checked_from_num(self)
}
}
impl<Frac: $LeEqU> SaturatingCast<$Fixed<Frac>> for $Src {
#[inline]
fn saturating_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::saturating_from_num(self)
}
}
impl<Frac: $LeEqU> WrappingCast<$Fixed<Frac>> for $Src {
#[inline]
fn wrapping_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::wrapping_from_num(self)
}
}
impl<Frac: $LeEqU> OverflowingCast<$Fixed<Frac>> for $Src {
#[inline]
fn overflowing_cast(self) -> ($Fixed<Frac>, bool) {
<$Fixed<Frac>>::overflowing_from_num(self)
}
}
impl<Frac: $LeEqU> UnwrappedCast<$Fixed<Frac>> for $Src {
#[inline]
#[track_caller]
fn unwrapped_cast(self) -> $Fixed<Frac> {
<$Fixed<Frac>>::unwrapped_from_num(self)
}
}
};
}
macro_rules! cast_num {
($Src:ident($LeEqUSrc:ident); $($Dst:ident($LeEqUDst:ident),)*) => { $(
cast! { $Src($LeEqUSrc); $Dst($LeEqUDst) }
)* };
($Fixed:ident($LeEqU:ident); $($Num:ident,)*) => { $(
cast! { $Fixed($LeEqU); $Num }
cast! { $Num; $Fixed($LeEqU) }
)* };
($($Fixed:ident($LeEqU:ident),)*) => { $(
cast_num! {
$Fixed($LeEqU);
FixedI8(LeEqU8), FixedI16(LeEqU16), FixedI32(LeEqU32), FixedI64(LeEqU64),
FixedI128(LeEqU128),
FixedU8(LeEqU8), FixedU16(LeEqU16), FixedU32(LeEqU32), FixedU64(LeEqU64),
FixedU128(LeEqU128),
}
cast! { bool; $Fixed($LeEqU) }
cast_num! {
$Fixed($LeEqU);
i8, i16, i32, i64, i128, isize,
u8, u16, u32, u64, u128, usize,
half_f16, half_bf16, f32, f64, F128, F128Bits,
}
#[cfg(feature = "nightly-float")]
cast_num! { $Fixed($LeEqU); f16, f128, }
)* };
}
cast_num! {
FixedI8(LeEqU8), FixedI16(LeEqU16), FixedI32(LeEqU32), FixedI64(LeEqU64), FixedI128(LeEqU128),
FixedU8(LeEqU8), FixedU16(LeEqU16), FixedU32(LeEqU32), FixedU64(LeEqU64), FixedU128(LeEqU128),
}
+695
View File
@@ -0,0 +1,695 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::float_helper;
use crate::int_helper;
use crate::int_helper::IntFixed;
use crate::types::extra::Unsigned;
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use core::cmp::Ordering;
use core::ops::{Shl, Shr};
use half::{bf16 as half_bf16, f16 as half_f16};
macro_rules! fixed_cmp_int {
($Fixed:ident, $Int:ident) => {
impl<Frac: Unsigned> PartialEq<$Int> for $Fixed<Frac> {
#[inline]
fn eq(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialEq::eq(self, &fixed_rhs)
}
}
impl<Frac: Unsigned> PartialEq<$Fixed<Frac>> for $Int {
#[inline]
fn eq(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialEq::eq(&fixed_lhs, rhs)
}
}
impl<Frac: Unsigned> PartialOrd<$Int> for $Fixed<Frac> {
#[inline]
fn partial_cmp(&self, rhs: &$Int) -> Option<Ordering> {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::partial_cmp(self, &fixed_rhs)
}
#[inline]
fn lt(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::lt(self, &fixed_rhs)
}
#[inline]
fn le(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::le(self, &fixed_rhs)
}
#[inline]
fn gt(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::gt(self, &fixed_rhs)
}
#[inline]
fn ge(&self, rhs: &$Int) -> bool {
let fixed_rhs = IntFixed(*rhs).fixed();
PartialOrd::ge(self, &fixed_rhs)
}
}
impl<Frac: Unsigned> PartialOrd<$Fixed<Frac>> for $Int {
#[inline]
fn partial_cmp(&self, rhs: &$Fixed<Frac>) -> Option<Ordering> {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::partial_cmp(&fixed_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::lt(&fixed_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::le(&fixed_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::gt(&fixed_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$Fixed<Frac>) -> bool {
let fixed_lhs = IntFixed(*self).fixed();
PartialOrd::ge(&fixed_lhs, rhs)
}
}
};
}
// Zero must NOT be neg
struct Value<U> {
neg: bool,
abs: U,
bits: u32,
frac_bits: i32,
}
#[inline]
// lhs_frac >= rhs_frac
fn float_rhs_shl<U>(rhs_abs: U, bits: u32, lhs_frac: i32, rhs_frac: i32) -> Option<U>
where
U: Copy + Eq + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
debug_assert!(lhs_frac >= rhs_frac);
let rhs_shl = lhs_frac.wrapping_sub(rhs_frac) as u32;
let Ok(rhs_zero) = U::try_from(0u32) else {
unreachable!();
};
if rhs_abs == rhs_zero {
Some(rhs_zero)
} else if rhs_shl >= bits {
None
} else {
let shifted = rhs_abs << rhs_shl;
if (shifted >> rhs_shl) == rhs_abs {
Some(shifted)
} else {
None
}
}
}
#[inline]
fn float_eq_even<U>(lhs: Value<U>, rhs: Value<U>) -> bool
where
U: Copy + Eq + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
if lhs.frac_bits < rhs.frac_bits {
return float_eq_even(rhs, lhs);
}
if lhs.neg != rhs.neg {
return false;
}
// lhs.frac_bits >= rhs.frac_bits
match float_rhs_shl(rhs.abs, rhs.bits, lhs.frac_bits, rhs.frac_bits) {
None => false,
Some(shifted_rhs_abs) => lhs.abs == shifted_rhs_abs,
}
}
#[inline]
fn float_eq<Lhs, Rhs>(lhs: Value<Lhs>, rhs: Value<Rhs>) -> bool
where
Lhs: Copy + Eq + TryFrom<u32> + TryFrom<Rhs> + Shl<u32, Output = Lhs> + Shr<u32, Output = Lhs>,
Rhs: Copy + Eq + TryFrom<u32> + TryFrom<Lhs> + Shl<u32, Output = Rhs> + Shr<u32, Output = Rhs>,
{
if lhs.bits >= rhs.bits {
let Ok(rhs_abs) = Lhs::try_from(rhs.abs) else {
unreachable!();
};
let rhs = Value {
neg: rhs.neg,
abs: rhs_abs,
bits: lhs.bits,
frac_bits: rhs.frac_bits,
};
float_eq_even(lhs, rhs)
} else {
let Ok(lhs_abs) = Rhs::try_from(lhs.abs) else {
unreachable!();
};
let lhs = Value {
neg: lhs.neg,
abs: lhs_abs,
bits: rhs.bits,
frac_bits: lhs.frac_bits,
};
float_eq_even(lhs, rhs)
}
}
#[inline]
fn float_cmp_even<U>(lhs: Value<U>, rhs: Value<U>) -> Ordering
where
U: Copy + Ord + TryFrom<u32> + Shl<u32, Output = U> + Shr<u32, Output = U>,
{
if lhs.frac_bits < rhs.frac_bits {
return float_cmp_even(rhs, lhs).reverse();
}
if !lhs.neg && rhs.neg {
return Ordering::Greater;
}
if lhs.neg && !rhs.neg {
return Ordering::Less;
}
match float_rhs_shl(rhs.abs, rhs.bits, lhs.frac_bits, rhs.frac_bits) {
None => {
// rhs is so large it doesn't fit
if lhs.neg {
// both lhs and rhs are negative, and rhs is even more negative
Ordering::Greater
} else {
Ordering::Less
}
}
Some(shifted_rhs_abs) => {
if lhs.neg {
// both lhs are negative, so reverse order
shifted_rhs_abs.cmp(&lhs.abs)
} else {
lhs.abs.cmp(&shifted_rhs_abs)
}
}
}
}
#[inline]
fn float_cmp<Lhs, Rhs>(lhs: Value<Lhs>, rhs: Value<Rhs>) -> Ordering
where
Lhs: Copy + Ord + TryFrom<u32> + TryFrom<Rhs> + Shl<u32, Output = Lhs> + Shr<u32, Output = Lhs>,
Rhs: Copy + Ord + TryFrom<u32> + TryFrom<Lhs> + Shl<u32, Output = Rhs> + Shr<u32, Output = Rhs>,
{
if lhs.bits >= rhs.bits {
let Ok(rhs_abs) = Lhs::try_from(rhs.abs) else {
unreachable!();
};
let rhs = Value {
neg: rhs.neg,
abs: rhs_abs,
bits: lhs.bits,
frac_bits: rhs.frac_bits,
};
float_cmp_even(lhs, rhs)
} else {
let Ok(lhs_abs) = Rhs::try_from(lhs.abs) else {
unreachable!();
};
let lhs = Value {
neg: lhs.neg,
abs: lhs_abs,
bits: rhs.bits,
frac_bits: lhs.frac_bits,
};
float_cmp_even(lhs, rhs)
}
}
macro_rules! fixed_cmp_float {
($Fix:ident($Inner:ident), $Float:ident, $FloatBits:ident) => {
impl<Frac: Unsigned> PartialEq<$Float> for $Fix<Frac> {
#[inline]
fn eq(&self, rhs: &$Float) -> bool {
use float_helper::$Float::Kind;
let (lhs_neg, lhs_abs) = int_helper::$Inner::neg_abs(self.to_bits());
let lhs = Value {
neg: lhs_neg,
abs: lhs_abs,
bits: $Inner::BITS,
frac_bits: Frac::to_i32(),
};
let Kind::Finite {
neg: rhs_neg,
abs: rhs_abs,
frac_bits: rhs_frac,
} = float_helper::$Float::kind(*rhs)
else {
return false;
};
let rhs = Value {
neg: rhs_neg,
abs: rhs_abs,
bits: $FloatBits::BITS,
frac_bits: rhs_frac,
};
float_eq(lhs, rhs)
}
}
impl<Frac: Unsigned> PartialEq<$Fix<Frac>> for $Float {
#[inline]
fn eq(&self, rhs: &$Fix<Frac>) -> bool {
rhs.eq(self)
}
}
impl<Frac: Unsigned> PartialOrd<$Float> for $Fix<Frac> {
#[inline]
fn partial_cmp(&self, rhs: &$Float) -> Option<Ordering> {
use float_helper::$Float::Kind;
let (lhs_neg, lhs_abs) = int_helper::$Inner::neg_abs(self.to_bits());
let lhs = Value {
neg: lhs_neg,
abs: lhs_abs,
bits: $Inner::BITS,
frac_bits: Frac::to_i32(),
};
let (rhs_neg, rhs_abs, rhs_frac) = match float_helper::$Float::kind(*rhs) {
Kind::Finite {
neg,
abs,
frac_bits,
} => (neg, abs, frac_bits),
Kind::Infinite { neg } => {
return if neg {
Some(Ordering::Greater)
} else {
Some(Ordering::Less)
};
}
Kind::NaN => return None,
};
let rhs = Value {
neg: rhs_neg,
abs: rhs_abs,
bits: $FloatBits::BITS,
frac_bits: rhs_frac,
};
Some(float_cmp(lhs, rhs))
}
}
impl<Frac: Unsigned> PartialOrd<$Fix<Frac>> for $Float {
#[inline]
fn partial_cmp(&self, rhs: &$Fix<Frac>) -> Option<Ordering> {
rhs.partial_cmp(self).map(Ordering::reverse)
}
}
};
}
macro_rules! fixed_cmp_all {
($Fix:ident($LeEqU:ident, $Inner:ident)) => {
fixed_cmp_int! { $Fix, i8 }
fixed_cmp_int! { $Fix, i16 }
fixed_cmp_int! { $Fix, i32 }
fixed_cmp_int! { $Fix, i64 }
fixed_cmp_int! { $Fix, i128 }
fixed_cmp_int! { $Fix, isize }
fixed_cmp_int! { $Fix, u8 }
fixed_cmp_int! { $Fix, u16 }
fixed_cmp_int! { $Fix, u32 }
fixed_cmp_int! { $Fix, u64 }
fixed_cmp_int! { $Fix, u128 }
fixed_cmp_int! { $Fix, usize }
#[cfg(feature = "nightly-float")]
fixed_cmp_float! { $Fix($Inner), f16, u16 }
fixed_cmp_float! { $Fix($Inner), half_f16, u16 }
fixed_cmp_float! { $Fix($Inner), half_bf16, u16 }
fixed_cmp_float! { $Fix($Inner), f32, u32 }
fixed_cmp_float! { $Fix($Inner), f64, u64 }
#[cfg(feature = "nightly-float")]
fixed_cmp_float! { $Fix($Inner), f128, u128 }
fixed_cmp_float! { $Fix($Inner), F128, u128 }
fixed_cmp_float! { $Fix($Inner), F128Bits, u128 }
};
}
fixed_cmp_all! { FixedI8(LeEqU8, i8) }
fixed_cmp_all! { FixedI16(LeEqU16, i16) }
fixed_cmp_all! { FixedI32(LeEqU32, i32) }
fixed_cmp_all! { FixedI64(LeEqU64, i64) }
fixed_cmp_all! { FixedI128(LeEqU128, i128) }
fixed_cmp_all! { FixedU8(LeEqU8, u8) }
fixed_cmp_all! { FixedU16(LeEqU16, u16) }
fixed_cmp_all! { FixedU32(LeEqU32, u32) }
fixed_cmp_all! { FixedU64(LeEqU64, u64) }
fixed_cmp_all! { FixedU128(LeEqU128, u128) }
#[cfg(test)]
mod tests {
use crate::*;
use core::cmp::Ordering;
#[test]
fn cmp_signed() {
use core::cmp::Ordering::*;
let neg1_16 = FixedI32::<types::extra::U16>::NEG_ONE;
let neg1_20 = FixedI32::<types::extra::U20>::NEG_ONE;
let mut a = neg1_16;
let mut b = neg1_20;
// a = ffff.0000 = -1, b = fff.00000 = -1
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, -1i8);
assert_eq!(b, -1i128);
a >>= 16;
b >>= 16;
// a = ffff.ffff = -2^-16, b = fff.ffff0 = -2^-16
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert!(a < 0.0);
assert_eq!(a.partial_cmp(&f32::INFINITY), Some(Less));
assert!(a < f32::INFINITY);
assert!(a != f32::INFINITY);
assert_eq!(a.partial_cmp(&f32::NEG_INFINITY), Some(Greater));
assert!(a > f32::NEG_INFINITY);
assert_eq!(a, -(-16f32).exp2());
assert!(a <= -(-16f32).exp2());
assert!(a >= -(-16f32).exp2());
assert!(a < (-16f32).exp2());
assert_ne!(a, -0.75 * (-16f32).exp2());
assert!(a < -0.75 * (-16f32).exp2());
assert!(a <= -0.75 * (-16f32).exp2());
assert!(a > -1.25 * (-16f32).exp2());
assert!(a >= -1.25 * (-16f32).exp2());
a >>= 1;
b >>= 1;
// a = ffff.ffff = -2^-16, b = fff.ffff8 = -2^-17
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
a = neg1_16 << 11;
b = neg1_20 << 11;
// a = f800.0000 = -2^11, b = 800.00000 = -2^11
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, -1i16 << 11);
assert_eq!(b, -1i64 << 11);
a <<= 1;
b <<= 1;
// a = f000.0000 = -2^-12, b = 000.00000 = 0
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
assert!(a < 1u8);
assert_eq!(b, 0);
}
#[test]
fn cmp_unsigned() {
use core::cmp::Ordering::*;
let one_16 = FixedU32::<types::extra::U16>::ONE;
let one_20 = FixedU32::<types::extra::U20>::ONE;
let mut a = one_16;
let mut b = one_20;
// a = 0001.0000 = 1, b = 001.00000 = 1
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, 1u8);
assert_eq!(b, 1i128);
a >>= 16;
b >>= 16;
// a = 0000.0001 = 2^-16, b = 000.00010 = 2^-16
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert!(a > 0.0);
assert_eq!(a.partial_cmp(&f32::INFINITY), Some(Less));
assert!(a < f32::INFINITY);
assert!(a != f32::INFINITY);
assert_eq!(a.partial_cmp(&f32::NEG_INFINITY), Some(Greater));
assert!(a > f32::NEG_INFINITY);
assert_eq!(a, (-16f64).exp2());
assert!(a <= (-16f64).exp2());
assert!(a >= (-16f64).exp2());
assert!(a > -(-16f64).exp2());
assert_ne!(a, 0.75 * (-16f64).exp2());
assert!(a > 0.75 * (-16f64).exp2());
assert!(a >= 0.75 * (-16f64).exp2());
assert!(a < 1.25 * (-16f64).exp2());
assert!(a <= 1.25 * (-16f64).exp2());
a >>= 1;
b >>= 1;
// a = 0000.0000 = 0, b = 000.00008 = 2^-17
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Less));
assert_eq!(b.partial_cmp(&a), Some(Greater));
a = one_16 << 11;
b = one_20 << 11;
// a = 0800.0000 = 2^11, b = 800.00000 = 2^11
assert!(a.eq(&b) && b.eq(&a));
assert_eq!(a.partial_cmp(&b), Some(Equal));
assert_eq!(b.partial_cmp(&a), Some(Equal));
assert_eq!(a, 1i16 << 11);
assert_eq!(b, 1u64 << 11);
a <<= 1;
b <<= 1;
// a = 1000.0000 = 2^12, b = 000.00000 = 0
assert!(a.ne(&b) && b.ne(&a));
assert_eq!(a.partial_cmp(&b), Some(Greater));
assert_eq!(b.partial_cmp(&a), Some(Less));
assert!(a > -1i8);
assert_eq!(a, 1i32 << 12);
assert_eq!(b, 0);
}
#[test]
fn cmp_i0() {
use crate::types::*;
assert_eq!(I0F32::checked_from_num(0.5), None);
for &float in &[
-0.5,
-0.5 + f32::EPSILON,
-0.25,
-f32::EPSILON,
0.0,
f32::EPSILON,
0.25,
0.5 - f32::EPSILON,
] {
let fixed = I0F32::from_num(float);
let half = U0F32::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = I1F31::from_num(0.5);
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let half = 0.5f32;
assert_eq!(fixed < half, float < 0.5, "{fixed} < {half}");
assert_eq!(fixed <= half, float <= 0.5, "{fixed} <= {half}");
assert_eq!(fixed == half, float == 0.5, "{fixed} == {half}");
assert_eq!(fixed >= half, float >= 0.5, "{fixed} >= {half}");
assert_eq!(fixed > half, float > 0.5, "{fixed} > {half}");
assert_eq!(
fixed.partial_cmp(&half),
float.partial_cmp(&0.5),
"{fixed}.partial_cmp(&{half})"
);
assert_eq!(half < fixed, fixed > half);
assert_eq!(half <= fixed, fixed >= half);
assert_eq!(half == fixed, fixed == half);
assert_eq!(half >= fixed, fixed <= half);
assert_eq!(half > fixed, fixed < half);
assert_eq!(
half.partial_cmp(&fixed),
fixed.partial_cmp(&half).map(Ordering::reverse)
);
let m1 = I32F0::from_num(-1.0);
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let m1 = I1F31::from_num(-1.0);
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let m1 = -1.0f32;
assert_eq!(fixed < m1, float < -1.0, "{fixed} < {m1}");
assert_eq!(fixed <= m1, float <= -1.0, "{fixed} <= {m1}");
assert_eq!(fixed == m1, float == -1.0, "{fixed} == {m1}");
assert_eq!(fixed >= m1, float >= -1.0, "{fixed} >= {m1}");
assert_eq!(fixed > m1, float > -1.0, "{fixed} > {m1}");
assert_eq!(
fixed.partial_cmp(&m1),
float.partial_cmp(&-1.0),
"{fixed}.partial_cmp(&{m1})"
);
assert_eq!(m1 < fixed, fixed > m1);
assert_eq!(m1 <= fixed, fixed >= m1);
assert_eq!(m1 == fixed, fixed == m1);
assert_eq!(m1 >= fixed, fixed <= m1);
assert_eq!(m1 > fixed, fixed < m1);
assert_eq!(
m1.partial_cmp(&fixed),
fixed.partial_cmp(&m1).map(Ordering::reverse)
);
let mhalf = I1F31::from_num(-0.5);
assert_eq!(fixed < mhalf, float < -0.5, "{fixed} < {mhalf}");
assert_eq!(fixed <= mhalf, float <= -0.5, "{fixed} <= {mhalf}");
assert_eq!(fixed == mhalf, float == -0.5, "{fixed} == {mhalf}");
assert_eq!(fixed >= mhalf, float >= -0.5, "{fixed} >= {mhalf}");
assert_eq!(fixed > mhalf, float > -0.5, "{fixed} > {mhalf}");
assert_eq!(
fixed.partial_cmp(&mhalf),
float.partial_cmp(&-0.5),
"{fixed}.partial_cmp(&{mhalf})"
);
assert_eq!(mhalf < fixed, fixed > mhalf);
assert_eq!(mhalf <= fixed, fixed >= mhalf);
assert_eq!(mhalf == fixed, fixed == mhalf);
assert_eq!(mhalf >= fixed, fixed <= mhalf);
assert_eq!(mhalf > fixed, fixed < mhalf);
assert_eq!(
mhalf.partial_cmp(&fixed),
fixed.partial_cmp(&mhalf).map(Ordering::reverse)
);
let mhalf = -0.5f32;
assert_eq!(fixed < mhalf, float < -0.5, "{fixed} < {mhalf}");
assert_eq!(fixed <= mhalf, float <= -0.5, "{fixed} <= {mhalf}");
assert_eq!(fixed == mhalf, float == -0.5, "{fixed} == {mhalf}");
assert_eq!(fixed >= mhalf, float >= -0.5, "{fixed} >= {mhalf}");
assert_eq!(fixed > mhalf, float > -0.5, "{fixed} > {mhalf}");
assert_eq!(
fixed.partial_cmp(&mhalf),
float.partial_cmp(&-0.5),
"{fixed}.partial_cmp(&{mhalf})"
);
assert_eq!(mhalf < fixed, fixed > mhalf);
assert_eq!(mhalf <= fixed, fixed >= mhalf);
assert_eq!(mhalf == fixed, fixed == mhalf);
assert_eq!(mhalf >= fixed, fixed <= mhalf);
assert_eq!(mhalf > fixed, fixed < mhalf);
assert_eq!(
mhalf.partial_cmp(&fixed),
fixed.partial_cmp(&mhalf).map(Ordering::reverse)
);
}
}
}
+498
View File
@@ -0,0 +1,498 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::Unsigned;
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use core::cmp::Ordering;
// Works by converting the signed number to unsigned, but does not change size
// of either number.
macro_rules! diff_sign {
($Sig:ident($Uns:ident, $UnsInner:ident), $OtherUns:ident) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Sig<FracRhs>> for $OtherUns<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialEq::eq(self, &unsigned_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$OtherUns<FracRhs>> for $Sig<FracLhs> {
#[inline]
fn eq(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialEq::eq(&unsigned_lhs, rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Sig<FracRhs>>
for $OtherUns<FracLhs>
{
#[inline]
fn partial_cmp(&self, rhs: &$Sig<FracRhs>) -> Option<Ordering> {
if rhs.is_negative() {
return Some(Ordering::Greater);
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::partial_cmp(self, &unsigned_rhs)
}
#[inline]
fn lt(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::lt(self, &unsigned_rhs)
}
#[inline]
fn le(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return false;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::le(self, &unsigned_rhs)
}
#[inline]
fn gt(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return true;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::gt(self, &unsigned_rhs)
}
#[inline]
fn ge(&self, rhs: &$Sig<FracRhs>) -> bool {
if rhs.is_negative() {
return true;
}
let unsigned_rhs = $Uns::<FracRhs>::from_bits(rhs.to_bits() as $UnsInner);
PartialOrd::ge(self, &unsigned_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$OtherUns<FracRhs>>
for $Sig<FracLhs>
{
#[inline]
fn partial_cmp(&self, rhs: &$OtherUns<FracRhs>) -> Option<Ordering> {
if self.is_negative() {
return Some(Ordering::Less);
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::partial_cmp(&unsigned_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return true;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::lt(&unsigned_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return true;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::le(&unsigned_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::gt(&unsigned_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$OtherUns<FracRhs>) -> bool {
if self.is_negative() {
return false;
}
let unsigned_lhs = $Uns::<FracLhs>::from_bits(self.to_bits() as $UnsInner);
PartialOrd::ge(&unsigned_lhs, rhs)
}
}
};
($Sig:ident($Uns:ident, $UnsInner:ident)) => {
diff_sign! { $Sig($Uns, $UnsInner), FixedU8 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU16 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU32 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU64 }
diff_sign! { $Sig($Uns, $UnsInner), FixedU128 }
};
}
diff_sign! { FixedI8(FixedU8, u8) }
diff_sign! { FixedI16(FixedU16, u16) }
diff_sign! { FixedI32(FixedU32, u32) }
diff_sign! { FixedI64(FixedU64, u64) }
diff_sign! { FixedI128(FixedU128, u128) }
// Both numbers must have the same sign (both signed or both unsigned). Works by
// widening the narrow number to have the same width as the wide number.
macro_rules! diff_size {
($Nar:ident, $Wid:ident($WidInner:ident)) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Nar<FracRhs>> for $Wid<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialEq::eq(self, &widened_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Wid<FracRhs>> for $Nar<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialEq::eq(&widened_lhs, rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Nar<FracRhs>> for $Wid<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Nar<FracRhs>) -> Option<Ordering> {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::partial_cmp(self, &widened_rhs)
}
#[inline]
fn lt(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::lt(self, &widened_rhs)
}
#[inline]
fn le(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::le(self, &widened_rhs)
}
#[inline]
fn gt(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::gt(self, &widened_rhs)
}
#[inline]
fn ge(&self, rhs: &$Nar<FracRhs>) -> bool {
let widened_rhs = $Wid::<FracRhs>::from_bits(rhs.to_bits() as $WidInner);
PartialOrd::ge(self, &widened_rhs)
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Wid<FracRhs>> for $Nar<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Wid<FracRhs>) -> Option<Ordering> {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::partial_cmp(&widened_lhs, rhs)
}
#[inline]
fn lt(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::lt(&widened_lhs, rhs)
}
#[inline]
fn le(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::le(&widened_lhs, rhs)
}
#[inline]
fn gt(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::gt(&widened_lhs, rhs)
}
#[inline]
fn ge(&self, rhs: &$Wid<FracRhs>) -> bool {
let widened_lhs = $Wid::<FracLhs>::from_bits(self.to_bits() as $WidInner);
PartialOrd::ge(&widened_lhs, rhs)
}
}
};
}
diff_size! { FixedI8, FixedI16(i16) }
diff_size! { FixedI8, FixedI32(i32) }
diff_size! { FixedI8, FixedI64(i64) }
diff_size! { FixedI8, FixedI128(i128) }
diff_size! { FixedI16, FixedI32(i32) }
diff_size! { FixedI16, FixedI64(i64) }
diff_size! { FixedI16, FixedI128(i128) }
diff_size! { FixedI32, FixedI64(i64) }
diff_size! { FixedI32, FixedI128(i128) }
diff_size! { FixedI64, FixedI128(i128) }
diff_size! { FixedU8, FixedU16(u16) }
diff_size! { FixedU8, FixedU32(u32) }
diff_size! { FixedU8, FixedU64(u64) }
diff_size! { FixedU8, FixedU128(u128) }
diff_size! { FixedU16, FixedU32(u32) }
diff_size! { FixedU16, FixedU64(u64) }
diff_size! { FixedU16, FixedU128(u128) }
diff_size! { FixedU32, FixedU64(u64) }
diff_size! { FixedU32, FixedU128(u128) }
diff_size! { FixedU64, FixedU128(u128) }
macro_rules! cmp {
($Fixed:ident($Inner:ident)) => {
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialEq<$Fixed<FracRhs>> for $Fixed<FracLhs> {
#[inline]
fn eq(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
lhs == shifted_rhs && !rhs_is_reduced
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
lhs == shifted_rhs && !rhs_is_reduced
} else if lhs_extra_frac == 0 {
lhs == rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
shifted_lhs == rhs && !lhs_is_reduced
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
shifted_lhs == rhs && !lhs_is_reduced
}
}
}
impl<FracLhs: Unsigned, FracRhs: Unsigned> PartialOrd<$Fixed<FracRhs>> for $Fixed<FracLhs> {
#[inline]
fn partial_cmp(&self, rhs: &$Fixed<FracRhs>) -> Option<Ordering> {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
if lhs == shifted_rhs && rhs_is_reduced {
Some(Ordering::Less)
} else {
Some(Ord::cmp(&lhs, &shifted_rhs))
}
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
if lhs == shifted_rhs && rhs_is_reduced {
Some(Ordering::Less)
} else {
Some(Ord::cmp(&lhs, &shifted_rhs))
}
} else if lhs_extra_frac == 0 {
Some(Ord::cmp(&lhs, &rhs))
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
if shifted_lhs == rhs && lhs_is_reduced {
Some(Ordering::Greater)
} else {
Some(Ord::cmp(&shifted_lhs, &rhs))
}
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
if shifted_lhs == rhs && lhs_is_reduced {
Some(Ordering::Greater)
} else {
Some(Ord::cmp(&shifted_lhs, &rhs))
}
}
}
#[inline]
fn lt(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
(lhs == shifted_rhs && rhs_is_reduced) || lhs < shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
(lhs == shifted_rhs && rhs_is_reduced) || lhs < shifted_rhs
} else if lhs_extra_frac == 0 {
lhs < rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
shifted_lhs < rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
shifted_lhs < rhs
}
}
#[inline]
fn le(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
lhs <= shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
lhs <= shifted_rhs
} else if lhs_extra_frac == 0 {
lhs <= rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
!(shifted_lhs == rhs && lhs_is_reduced) && shifted_lhs <= rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
!(shifted_lhs == rhs && lhs_is_reduced) && shifted_lhs <= rhs
}
}
#[inline]
fn gt(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
lhs > shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
lhs > shifted_rhs
} else if lhs_extra_frac == 0 {
lhs > rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
let lhs_is_reduced = lhs != (shifted_lhs << lhs_extra_frac);
(shifted_lhs == rhs && lhs_is_reduced) || shifted_lhs > rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
let lhs_is_reduced = lhs != 0;
(shifted_lhs == rhs && lhs_is_reduced) || shifted_lhs > rhs
}
}
#[inline]
fn ge(&self, rhs: &$Fixed<FracRhs>) -> bool {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
// lhs_extra_frac and nbits are known exactly at compile time,
// so with optimizations the branch is selected at compile time
let lhs_extra_frac = FracLhs::to_i32().saturating_sub(FracRhs::to_i32());
let nbits = $Inner::BITS as i32;
if lhs_extra_frac <= -nbits {
let shifted_rhs = rhs >> (nbits - 1) >> 1;
let rhs_is_reduced = rhs != 0;
!(lhs == shifted_rhs && rhs_is_reduced) && lhs >= shifted_rhs
} else if lhs_extra_frac < 0 {
let shifted_rhs = rhs >> -lhs_extra_frac;
let rhs_is_reduced = rhs != (shifted_rhs << -lhs_extra_frac);
!(lhs == shifted_rhs && rhs_is_reduced) && lhs >= shifted_rhs
} else if lhs_extra_frac == 0 {
lhs >= rhs
} else if lhs_extra_frac < nbits {
let shifted_lhs = lhs >> lhs_extra_frac;
shifted_lhs >= rhs
} else {
let shifted_lhs = lhs >> (nbits - 1) >> 1;
shifted_lhs >= rhs
}
}
}
impl<Frac: Unsigned> Eq for $Fixed<Frac> {}
impl<Frac: Unsigned> Ord for $Fixed<Frac> {
#[inline]
fn cmp(&self, rhs: &$Fixed<Frac>) -> Ordering {
let lhs = self.to_bits();
let rhs = rhs.to_bits();
Ord::cmp(&lhs, &rhs)
}
}
};
}
cmp! { FixedI8(i8) }
cmp! { FixedI16(i16) }
cmp! { FixedI32(i32) }
cmp! { FixedI64(i64) }
cmp! { FixedI128(i128) }
cmp! { FixedU8(u8) }
cmp! { FixedU16(u16) }
cmp! { FixedU32(u32) }
cmp! { FixedU64(u64) }
cmp! { FixedU128(u128) }
#[cfg(test)]
mod tests {
#[test]
fn issue_57() {
use crate::types::I80F48;
let a: u64 = 66000;
let b: u64 = 1000;
assert!(I80F48::from(a) > b);
}
}
+606
View File
@@ -0,0 +1,606 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
/*!
Mathematical constants.
The constants have the maximum precision possible for a fixed-point
number, and are rounded down at that precision.
# Examples
```rust
use fixed::consts;
use fixed::types::I4F28;
let tau = I4F28::from_num(consts::TAU);
println!("τ = 2π with eight binary places is {tau:.8b}");
assert_eq!(format!("{tau:.8b}"), "110.01001000");
println!("τ = 2π with eight decimal places is {tau:.8}");
assert_eq!(format!("{tau:.8}"), "6.28318531");
```
*/
use crate::types::{U0F128, U1F127, U2F126, U3F125};
/*
```rust
use core::cmp::Ord;
use rug::float::{Constant, Round};
use rug::{Assign, Float};
fn decimal_string(val: &Float, num_digits: usize) -> String {
let (sign, mut digits, exp) = val.to_sign_string_exp_round(10, Some(num_digits), Round::Down);
if let Some(exp) = exp {
if exp <= 0 {
let one_minus_exp = usize::try_from(1 - exp).unwrap();
digits.insert_str(0, &"0".repeat(one_minus_exp));
digits.insert(1, '.');
} else {
let exp = usize::try_from(exp).unwrap();
if exp >= num_digits {
digits.push_str(&"0".repeat(exp - num_digits));
} else {
digits.insert(exp, '.');
}
}
}
if sign {
digits.insert(0, '-');
}
digits
}
fn hex_bits(val: &Float, frac_bits: i32) -> String {
let val = val.clone() << frac_bits;
let int = val.to_integer_round(Round::Down).unwrap().0;
let mut s = format!("0x{int:016X}");
for i in 0..7 {
s.insert(6 + 5 * i, '_');
}
s
}
fn print(doc: &str, name: &str, val: Float) {
println!("/// {} = {}…", doc, decimal_string(&val, 6));
println!("// {} = {}...", name, decimal_string(&val, 40));
let int_bits = val.get_exp().unwrap().max(0);
let frac_bits = 128 - int_bits;
print!("pub const {}: U{}F{} = U{1}F{2}", name, int_bits, frac_bits,);
println!("::from_bits({});", hex_bits(&val, frac_bits));
println!();
}
fn float<T>(t: T) -> Float
where
Float: Assign<T>,
{
Float::with_val(1000, t)
}
fn main() {
print("A turn, τ", "TAU", float(Constant::Pi) * 2);
print("τ/2", "FRAC_TAU_2", float(Constant::Pi));
print("τ/3", "FRAC_TAU_3", float(Constant::Pi) * 2 / 3);
print("τ/4", "FRAC_TAU_4", float(Constant::Pi) / 2);
print("τ/6", "FRAC_TAU_6", float(Constant::Pi) / 3);
print("τ/8", "FRAC_TAU_8", float(Constant::Pi) / 4);
print("τ/12", "FRAC_TAU_12", float(Constant::Pi) / 6);
print("1/τ", "FRAC_1_TAU", 0.5 / float(Constant::Pi));
print("2/τ", "FRAC_2_TAU", 1 / float(Constant::Pi));
print("4/τ", "FRAC_4_TAU", 2 / float(Constant::Pi));
print("Archimedes’ constant, π", "PI", float(Constant::Pi));
print("π/2", "FRAC_PI_2", float(Constant::Pi) / 2);
print("π/3", "FRAC_PI_3", float(Constant::Pi) / 3);
print("π/4", "FRAC_PI_4", float(Constant::Pi) / 4);
print("π/6", "FRAC_PI_6", float(Constant::Pi) / 6);
print("π/8", "FRAC_PI_8", float(Constant::Pi) / 8);
print("1/π", "FRAC_1_PI", 1 / float(Constant::Pi));
print("2/π", "FRAC_2_PI", 2 / float(Constant::Pi));
print("√π", "SQRT_PI", float(Constant::Pi).sqrt());
print("√2π", "SQRT_2PI", (float(Constant::Pi) * 2u32).sqrt());
print("1/√π", "FRAC_1_SQRT_PI", 1 / float(Constant::Pi).sqrt());
print("1/√2π", "FRAC_1_SQRT_2PI", 1 / (float(Constant::Pi) * 2u32).sqrt());
print("2/√π", "FRAC_2_SQRT_PI", 2 / float(Constant::Pi).sqrt());
print("√2", "SQRT_2", float(2).sqrt());
print("1/√2", "FRAC_1_SQRT_2", float(0.5).sqrt());
print("√3", "SQRT_3", float(3).sqrt());
print("1/√3", "FRAC_1_SQRT_3", float(3).recip().sqrt());
print("Euler’s number, e", "E", float(1).exp());
print("√e", "SQRT_E", float(0.5).exp());
print("log<sub>2</sub> 10", "LOG2_10", float(10).log2());
print("log<sub>2</sub> e", "LOG2_E", float(1).exp().log2());
print("log<sub>10</sub> 2", "LOG10_2", float(2).log10());
print("log<sub>10</sub> e", "LOG10_E", float(1).exp().log10());
print("ln 2", "LN_2", float(2).ln());
print("ln 10", "LN_10", float(10).ln());
print("The golden ratio, φ", "PHI", float(1.25).sqrt() + 0.5);
print("The golden ratio conjugate, Φ = 1/φ", "FRAC_1_PHI", float(1.25).sqrt() - 0.5);
print("√φ", "SQRT_PHI", (float(1.25).sqrt() + 0.5f32).sqrt());
print("The Euler-Mascheroni constant, γ", "GAMMA", float(Constant::Euler));
print("Catalan’s constant", "CATALAN", float(Constant::Catalan));
}
```
*/
/// A turn, τ = 6.28318…
// TAU = 6.283185307179586476925286766559005768394...
pub const TAU: U3F125 = U3F125::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/2 = 3.14159…
// FRAC_TAU_2 = 3.141592653589793238462643383279502884197...
pub const FRAC_TAU_2: U2F126 = U2F126::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/3 = 2.09439…
// FRAC_TAU_3 = 2.094395102393195492308428922186335256131...
pub const FRAC_TAU_3: U2F126 = U2F126::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// τ/4 = 1.57079…
// FRAC_TAU_4 = 1.570796326794896619231321691639751442098...
pub const FRAC_TAU_4: U1F127 = U1F127::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/6 = 1.04719…
// FRAC_TAU_6 = 1.047197551196597746154214461093167628065...
pub const FRAC_TAU_6: U1F127 = U1F127::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// τ/8 = 0.785398…
// FRAC_TAU_8 = 0.7853981633974483096156608458198757210492...
pub const FRAC_TAU_8: U0F128 = U0F128::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// τ/12 = 0.523598…
// FRAC_TAU_12 = 0.5235987755982988730771072305465838140328...
pub const FRAC_TAU_12: U0F128 = U0F128::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// 1/τ = 0.159154…
// FRAC_1_TAU = 0.1591549430918953357688837633725143620344...
pub const FRAC_1_TAU: U0F128 = U0F128::from_bits(0x28BE_60DB_9391_054A_7F09_D5F4_7D4D_3770);
/// 2/τ = 0.318309…
// FRAC_2_TAU = 0.3183098861837906715377675267450287240689...
pub const FRAC_2_TAU: U0F128 = U0F128::from_bits(0x517C_C1B7_2722_0A94_FE13_ABE8_FA9A_6EE0);
/// 4/τ = 0.636619…
// FRAC_4_TAU = 0.6366197723675813430755350534900574481378...
pub const FRAC_4_TAU: U0F128 = U0F128::from_bits(0xA2F9_836E_4E44_1529_FC27_57D1_F534_DDC0);
/// Archimedes’ constant, π = 3.14159…
// PI = 3.141592653589793238462643383279502884197...
pub const PI: U2F126 = U2F126::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/2 = 1.57079…
// FRAC_PI_2 = 1.570796326794896619231321691639751442098...
pub const FRAC_PI_2: U1F127 = U1F127::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/3 = 1.04719…
// FRAC_PI_3 = 1.047197551196597746154214461093167628065...
pub const FRAC_PI_3: U1F127 = U1F127::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// π/4 = 0.785398…
// FRAC_PI_4 = 0.7853981633974483096156608458198757210492...
pub const FRAC_PI_4: U0F128 = U0F128::from_bits(0xC90F_DAA2_2168_C234_C4C6_628B_80DC_1CD1);
/// π/6 = 0.523598…
// FRAC_PI_6 = 0.5235987755982988730771072305465838140328...
pub const FRAC_PI_6: U0F128 = U0F128::from_bits(0x860A_91C1_6B9B_2C23_2DD9_9707_AB3D_688B);
/// π/8 = 0.392699…
// FRAC_PI_8 = 0.3926990816987241548078304229099378605246...
pub const FRAC_PI_8: U0F128 = U0F128::from_bits(0x6487_ED51_10B4_611A_6263_3145_C06E_0E68);
/// 1/π = 0.318309…
// FRAC_1_PI = 0.3183098861837906715377675267450287240689...
pub const FRAC_1_PI: U0F128 = U0F128::from_bits(0x517C_C1B7_2722_0A94_FE13_ABE8_FA9A_6EE0);
/// 2/π = 0.636619…
// FRAC_2_PI = 0.6366197723675813430755350534900574481378...
pub const FRAC_2_PI: U0F128 = U0F128::from_bits(0xA2F9_836E_4E44_1529_FC27_57D1_F534_DDC0);
/// √π = 1.77245…
// SQRT_PI = 1.772453850905516027298167483341145182797...
pub const SQRT_PI: U1F127 = U1F127::from_bits(0xE2DF_C48D_A77B_553C_E1D8_2906_AEDC_9C1F);
/// √2π = 2.50662…
// SQRT_2PI = 2.506628274631000502415765284811045253006...
pub const SQRT_2PI: U2F126 = U2F126::from_bits(0xA06C_98FF_B138_2CB2_BE52_0FD7_3916_7717);
/// 1/√π = 0.564189…
// FRAC_1_SQRT_PI = 0.5641895835477562869480794515607725858440...
pub const FRAC_1_SQRT_PI: U0F128 = U0F128::from_bits(0x906E_BA82_14DB_688D_71D4_8A7F_6BFE_C344);
/// 1/√2π = 0.398942…
// FRAC_1_SQRT_2PI = 0.3989422804014326779399460599343818684758...
pub const FRAC_1_SQRT_2PI: U0F128 = U0F128::from_bits(0x6621_14CF_50D9_4234_3F2C_F140_2EAE_38BF);
/// 2/√π = 1.12837…
// FRAC_2_SQRT_PI = 1.128379167095512573896158903121545171688...
pub const FRAC_2_SQRT_PI: U1F127 = U1F127::from_bits(0x906E_BA82_14DB_688D_71D4_8A7F_6BFE_C344);
/// √2 = 1.41421…
// SQRT_2 = 1.414213562373095048801688724209698078569...
pub const SQRT_2: U1F127 = U1F127::from_bits(0xB504_F333_F9DE_6484_597D_89B3_754A_BE9F);
/// 1/√2 = 0.707106…
// FRAC_1_SQRT_2 = 0.7071067811865475244008443621048490392848...
pub const FRAC_1_SQRT_2: U0F128 = U0F128::from_bits(0xB504_F333_F9DE_6484_597D_89B3_754A_BE9F);
/// √3 = 1.73205…
// SQRT_3 = 1.732050807568877293527446341505872366942...
pub const SQRT_3: U1F127 = U1F127::from_bits(0xDDB3_D742_C265_539D_92BA_16B8_3C5C_1DC4);
/// 1/√3 = 0.577350…
// FRAC_1_SQRT_3 = 0.5773502691896257645091487805019574556476...
pub const FRAC_1_SQRT_3: U0F128 = U0F128::from_bits(0x93CD_3A2C_8198_E269_0C7C_0F25_7D92_BE83);
/// Euler’s number, e = 2.71828…
// E = 2.718281828459045235360287471352662497757...
pub const E: U2F126 = U2F126::from_bits(0xADF8_5458_A2BB_4A9A_AFDC_5620_273D_3CF1);
/// √e = 1.64872…
// SQRT_E = 1.648721270700128146848650787814163571653...
pub const SQRT_E: U1F127 = U1F127::from_bits(0xD309_4C70_F034_DE4B_96FF_7D5B_6F99_FCD8);
/// log<sub>2</sub> 10 = 3.32192…
// LOG2_10 = 3.321928094887362347870319429489390175864...
pub const LOG2_10: U2F126 = U2F126::from_bits(0xD49A_784B_CD1B_8AFE_492B_F6FF_4DAF_DB4C);
/// log<sub>2</sub> e = 1.44269…
// LOG2_E = 1.442695040888963407359924681001892137426...
pub const LOG2_E: U1F127 = U1F127::from_bits(0xB8AA_3B29_5C17_F0BB_BE87_FED0_691D_3E88);
/// log<sub>10</sub> 2 = 0.301029…
// LOG10_2 = 0.3010299956639811952137388947244930267681...
pub const LOG10_2: U0F128 = U0F128::from_bits(0x4D10_4D42_7DE7_FBCC_47C4_ACD6_05BE_48BC);
/// log<sub>10</sub> e = 0.434294…
// LOG10_E = 0.4342944819032518276511289189166050822943...
pub const LOG10_E: U0F128 = U0F128::from_bits(0x6F2D_EC54_9B94_38CA_9AAD_D557_D699_EE19);
/// ln 2 = 0.693147…
// LN_2 = 0.6931471805599453094172321214581765680755...
pub const LN_2: U0F128 = U0F128::from_bits(0xB172_17F7_D1CF_79AB_C9E3_B398_03F2_F6AF);
/// ln 10 = 2.30258…
// LN_10 = 2.302585092994045684017991454684364207601...
pub const LN_10: U2F126 = U2F126::from_bits(0x935D_8DDD_AAA8_AC16_EA56_D62B_82D3_0A28);
/// The golden ratio, φ = 1.61803…
// PHI = 1.618033988749894848204586834365638117720...
pub const PHI: U1F127 = U1F127::from_bits(0xCF1B_BCDC_BFA5_3E0A_F9CE_6030_2E76_E41A);
/// The golden ratio conjugate, Φ = 1/φ = 0.618033…
// FRAC_1_PHI = 0.6180339887498948482045868343656381177203...
pub const FRAC_1_PHI: U0F128 = U0F128::from_bits(0x9E37_79B9_7F4A_7C15_F39C_C060_5CED_C834);
/// √φ = 1.27201…
// SQRT_PHI = 1.272019649514068964252422461737491491715...
pub const SQRT_PHI: U1F127 = U1F127::from_bits(0xA2D1_8A35_4422_AF49_2AA2_8089_0F62_6C86);
/// The Euler-Mascheroni constant, γ = 0.577215…
// GAMMA = 0.5772156649015328606065120900824024310421...
pub const GAMMA: U0F128 = U0F128::from_bits(0x93C4_67E3_7DB0_C7A4_D1BE_3F81_0152_CB56);
/// Catalan’s constant = 0.915965…
// CATALAN = 0.9159655941772190150546035149323841107741...
pub const CATALAN: U0F128 = U0F128::from_bits(0xEA7C_B89F_409A_E845_2158_22E3_7D32_D0C6);
#[cfg(test)]
mod tests {
use crate::consts::*;
use crate::traits::{Fixed, FromFixed};
use core::{f32, f64};
#[test]
fn cmp_f16() {
use half;
use half::f16 as half_f16;
assert_eq!(
half_f16::from_fixed(TAU),
half_f16::from_f32(f32::consts::PI * 2.0)
);
assert_eq!(half_f16::from_fixed(FRAC_TAU_2), half_f16::PI);
assert_eq!(
half_f16::from_fixed(FRAC_TAU_3),
half_f16::from_f32(f32::consts::FRAC_PI_3 * 2.0)
);
assert_eq!(half_f16::from_fixed(FRAC_TAU_4), half_f16::FRAC_PI_2);
assert_eq!(half_f16::from_fixed(FRAC_TAU_6), half_f16::FRAC_PI_3);
assert_eq!(half_f16::from_fixed(FRAC_TAU_8), half_f16::FRAC_PI_4);
assert_eq!(half_f16::from_fixed(FRAC_TAU_12), half_f16::FRAC_PI_6);
assert_eq!(
half_f16::from_fixed(FRAC_1_TAU),
half_f16::from_f32(f32::consts::FRAC_1_PI * 0.5)
);
assert_eq!(half_f16::from_fixed(FRAC_2_TAU), half_f16::FRAC_1_PI);
assert_eq!(half_f16::from_fixed(FRAC_4_TAU), half_f16::FRAC_2_PI);
assert_eq!(half_f16::from_fixed(PI), half_f16::PI);
assert_eq!(half_f16::from_fixed(FRAC_PI_2), half_f16::FRAC_PI_2);
assert_eq!(half_f16::from_fixed(FRAC_PI_3), half_f16::FRAC_PI_3);
assert_eq!(half_f16::from_fixed(FRAC_PI_4), half_f16::FRAC_PI_4);
assert_eq!(half_f16::from_fixed(FRAC_PI_6), half_f16::FRAC_PI_6);
assert_eq!(half_f16::from_fixed(FRAC_PI_8), half_f16::FRAC_PI_8);
assert_eq!(half_f16::from_fixed(FRAC_1_PI), half_f16::FRAC_1_PI);
assert_eq!(half_f16::from_fixed(FRAC_2_PI), half_f16::FRAC_2_PI);
assert_eq!(
half_f16::from_fixed(SQRT_PI),
half_f16::from_f32(f32::consts::PI.sqrt())
);
assert_eq!(
half_f16::from_fixed(FRAC_1_SQRT_PI),
half_f16::from_f32(f32::consts::FRAC_2_SQRT_PI / 2.0)
);
assert_eq!(
half_f16::from_fixed(FRAC_2_SQRT_PI),
half_f16::FRAC_2_SQRT_PI
);
assert_eq!(half_f16::from_fixed(SQRT_2), half_f16::SQRT_2);
assert_eq!(half_f16::from_fixed(FRAC_1_SQRT_2), half_f16::FRAC_1_SQRT_2);
assert_eq!(
half_f16::from_fixed(SQRT_3),
half_f16::from_f32(3f32.sqrt())
);
assert_eq!(
half_f16::from_fixed(FRAC_1_SQRT_3),
half_f16::from_f32(3f32.powf(-0.5))
);
assert_eq!(half_f16::from_fixed(E), half_f16::E);
assert_eq!(
half_f16::from_fixed(SQRT_E),
half_f16::from_f32(0.5f32.exp())
);
assert_eq!(half_f16::from_fixed(LOG2_10), half_f16::LOG2_10);
assert_eq!(half_f16::from_fixed(LOG2_E), half_f16::LOG2_E);
assert_eq!(half_f16::from_fixed(LOG10_2), half_f16::LOG10_2);
assert_eq!(half_f16::from_fixed(LOG10_E), half_f16::LOG10_E);
assert_eq!(half_f16::from_fixed(LN_2), half_f16::LN_2);
assert_eq!(half_f16::from_fixed(LN_10), half_f16::LN_10);
assert_eq!(
half_f16::from_fixed(PHI),
half_f16::from_f32(1.25f32.sqrt() + 0.5)
);
assert_eq!(
half_f16::from_fixed(FRAC_1_PHI),
half_f16::from_f32(1.25f32.sqrt() - 0.5)
);
assert_eq!(
half_f16::from_fixed(SQRT_PHI),
half_f16::from_f32((1.25f32.sqrt() + 0.5f32).sqrt())
);
}
#[test]
fn cmp_bf16() {
use half;
use half::bf16 as half_bf16;
assert_eq!(
half_bf16::from_fixed(TAU),
half_bf16::from_f32(f32::consts::PI * 2.0)
);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_2), half_bf16::PI);
assert_eq!(
half_bf16::from_fixed(FRAC_TAU_3),
half_bf16::from_f32(f32::consts::FRAC_PI_3 * 2.0)
);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_4), half_bf16::FRAC_PI_2);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_6), half_bf16::FRAC_PI_3);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_8), half_bf16::FRAC_PI_4);
assert_eq!(half_bf16::from_fixed(FRAC_TAU_12), half_bf16::FRAC_PI_6);
assert_eq!(
half_bf16::from_fixed(FRAC_1_TAU),
half_bf16::from_f32(f32::consts::FRAC_1_PI * 0.5)
);
assert_eq!(half_bf16::from_fixed(FRAC_2_TAU), half_bf16::FRAC_1_PI);
assert_eq!(half_bf16::from_fixed(FRAC_4_TAU), half_bf16::FRAC_2_PI);
assert_eq!(half_bf16::from_fixed(PI), half_bf16::PI);
assert_eq!(half_bf16::from_fixed(FRAC_PI_2), half_bf16::FRAC_PI_2);
assert_eq!(half_bf16::from_fixed(FRAC_PI_3), half_bf16::FRAC_PI_3);
assert_eq!(half_bf16::from_fixed(FRAC_PI_4), half_bf16::FRAC_PI_4);
assert_eq!(half_bf16::from_fixed(FRAC_PI_6), half_bf16::FRAC_PI_6);
assert_eq!(half_bf16::from_fixed(FRAC_PI_8), half_bf16::FRAC_PI_8);
assert_eq!(half_bf16::from_fixed(FRAC_1_PI), half_bf16::FRAC_1_PI);
assert_eq!(half_bf16::from_fixed(FRAC_2_PI), half_bf16::FRAC_2_PI);
assert_eq!(
half_bf16::from_fixed(SQRT_PI),
half_bf16::from_f32(f32::consts::PI.sqrt())
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_PI),
half_bf16::from_f32(f32::consts::FRAC_2_SQRT_PI / 2.0)
);
assert_eq!(
half_bf16::from_fixed(FRAC_2_SQRT_PI),
half_bf16::FRAC_2_SQRT_PI
);
assert_eq!(half_bf16::from_fixed(SQRT_2), half_bf16::SQRT_2);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_2),
half_bf16::FRAC_1_SQRT_2
);
assert_eq!(
half_bf16::from_fixed(SQRT_3),
half_bf16::from_f32(3f32.sqrt())
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_SQRT_3),
half_bf16::from_f32(3f32.powf(-0.5))
);
assert_eq!(half_bf16::from_fixed(E), half_bf16::E);
assert_eq!(
half_bf16::from_fixed(SQRT_E),
half_bf16::from_f32(0.5f32.exp())
);
assert_eq!(half_bf16::from_fixed(LOG2_10), half_bf16::LOG2_10);
assert_eq!(half_bf16::from_fixed(LOG2_E), half_bf16::LOG2_E);
assert_eq!(half_bf16::from_fixed(LOG10_2), half_bf16::LOG10_2);
assert_eq!(half_bf16::from_fixed(LOG10_E), half_bf16::LOG10_E);
assert_eq!(half_bf16::from_fixed(LN_2), half_bf16::LN_2);
assert_eq!(half_bf16::from_fixed(LN_10), half_bf16::LN_10);
assert_eq!(
half_bf16::from_fixed(PHI),
half_bf16::from_f32(1.25f32.sqrt() + 0.5)
);
assert_eq!(
half_bf16::from_fixed(FRAC_1_PHI),
half_bf16::from_f32(1.25f32.sqrt() - 0.5)
);
assert_eq!(
half_bf16::from_fixed(SQRT_PHI),
half_bf16::from_f32((1.25f32.sqrt() + 0.5f32).sqrt())
);
}
#[test]
fn cmp_f32() {
assert_eq!(f32::from_fixed(TAU), f32::consts::PI * 2.0);
assert_eq!(f32::from_fixed(FRAC_TAU_2), f32::consts::PI);
assert_eq!(f32::from_fixed(FRAC_TAU_3), f32::consts::FRAC_PI_3 * 2.0);
assert_eq!(f32::from_fixed(FRAC_TAU_4), f32::consts::FRAC_PI_2);
assert_eq!(f32::from_fixed(FRAC_TAU_6), f32::consts::FRAC_PI_3);
assert_eq!(f32::from_fixed(FRAC_TAU_8), f32::consts::FRAC_PI_4);
assert_eq!(f32::from_fixed(FRAC_TAU_12), f32::consts::FRAC_PI_6);
assert_eq!(f32::from_fixed(FRAC_1_TAU), f32::consts::FRAC_1_PI * 0.5);
assert_eq!(f32::from_fixed(FRAC_2_TAU), f32::consts::FRAC_1_PI);
assert_eq!(f32::from_fixed(FRAC_4_TAU), f32::consts::FRAC_2_PI);
assert_eq!(f32::from_fixed(PI), f32::consts::PI);
assert_eq!(f32::from_fixed(FRAC_PI_2), f32::consts::FRAC_PI_2);
assert_eq!(f32::from_fixed(FRAC_PI_3), f32::consts::FRAC_PI_3);
assert_eq!(f32::from_fixed(FRAC_PI_4), f32::consts::FRAC_PI_4);
assert_eq!(f32::from_fixed(FRAC_PI_6), f32::consts::FRAC_PI_6);
assert_eq!(f32::from_fixed(FRAC_PI_8), f32::consts::FRAC_PI_8);
assert_eq!(f32::from_fixed(FRAC_1_PI), f32::consts::FRAC_1_PI);
assert_eq!(f32::from_fixed(FRAC_2_PI), f32::consts::FRAC_2_PI);
assert_eq!(f32::from_fixed(SQRT_PI), f64::consts::PI.sqrt() as f32);
assert_eq!(
f32::from_fixed(FRAC_1_SQRT_PI),
f32::consts::FRAC_2_SQRT_PI / 2.0
);
assert_eq!(f32::from_fixed(FRAC_2_SQRT_PI), f32::consts::FRAC_2_SQRT_PI);
assert_eq!(f32::from_fixed(SQRT_2), f32::consts::SQRT_2);
assert_eq!(f32::from_fixed(FRAC_1_SQRT_2), f32::consts::FRAC_1_SQRT_2);
assert_eq!(f32::from_fixed(SQRT_3), 3f32.sqrt());
assert_eq!(f32::from_fixed(FRAC_1_SQRT_3), 3f32.powf(-0.5));
assert_eq!(f32::from_fixed(E), f32::consts::E);
assert_eq!(f32::from_fixed(SQRT_E), 0.5f32.exp());
assert_eq!(f32::from_fixed(LOG2_10), f32::consts::LOG2_10);
assert_eq!(f32::from_fixed(LOG2_E), f32::consts::LOG2_E);
assert_eq!(f32::from_fixed(LOG10_2), f32::consts::LOG10_2);
assert_eq!(f32::from_fixed(LOG10_E), f32::consts::LOG10_E);
assert_eq!(f32::from_fixed(LN_2), f32::consts::LN_2);
assert_eq!(f32::from_fixed(LN_10), f32::consts::LN_10);
assert_eq!(f32::from_fixed(PHI), 1.25f32.sqrt() + 0.5);
assert_eq!(f32::from_fixed(FRAC_1_PHI), (1.25f64.sqrt() - 0.5) as f32);
assert_eq!(
f32::from_fixed(SQRT_PHI),
(1.25f64.sqrt() + 0.5f64).sqrt() as f32
);
}
#[test]
fn cmp_f64() {
assert_eq!(f64::from_fixed(TAU), f64::consts::PI * 2.0);
assert_eq!(f64::from_fixed(FRAC_TAU_2), f64::consts::PI);
assert_eq!(f64::from_fixed(FRAC_TAU_3), f64::consts::FRAC_PI_3 * 2.0);
assert_eq!(f64::from_fixed(FRAC_TAU_4), f64::consts::FRAC_PI_2);
assert_eq!(f64::from_fixed(FRAC_TAU_6), f64::consts::FRAC_PI_3);
assert_eq!(f64::from_fixed(FRAC_TAU_8), f64::consts::FRAC_PI_4);
assert_eq!(f64::from_fixed(FRAC_TAU_12), f64::consts::FRAC_PI_6);
assert_eq!(f64::from_fixed(FRAC_1_TAU), f64::consts::FRAC_1_PI * 0.5);
assert_eq!(f64::from_fixed(FRAC_2_TAU), f64::consts::FRAC_1_PI);
assert_eq!(f64::from_fixed(FRAC_4_TAU), f64::consts::FRAC_2_PI);
assert_eq!(f64::from_fixed(PI), f64::consts::PI);
assert_eq!(f64::from_fixed(FRAC_PI_2), f64::consts::FRAC_PI_2);
assert_eq!(f64::from_fixed(FRAC_PI_3), f64::consts::FRAC_PI_3);
assert_eq!(f64::from_fixed(FRAC_PI_4), f64::consts::FRAC_PI_4);
assert_eq!(f64::from_fixed(FRAC_PI_6), f64::consts::FRAC_PI_6);
assert_eq!(f64::from_fixed(FRAC_PI_8), f64::consts::FRAC_PI_8);
assert_eq!(f64::from_fixed(FRAC_1_PI), f64::consts::FRAC_1_PI);
assert_eq!(f64::from_fixed(FRAC_2_PI), f64::consts::FRAC_2_PI);
// Since 1 < SQRT_PI < 2, we use EPSILON.
assert!((f64::from_fixed(SQRT_PI) - f64::consts::PI.sqrt()).abs() <= f64::EPSILON);
assert_eq!(
f64::from_fixed(FRAC_1_SQRT_PI),
f64::consts::FRAC_2_SQRT_PI / 2.0
);
assert_eq!(f64::from_fixed(FRAC_2_SQRT_PI), f64::consts::FRAC_2_SQRT_PI);
assert_eq!(f64::from_fixed(SQRT_2), f64::consts::SQRT_2);
assert_eq!(f64::from_fixed(FRAC_1_SQRT_2), f64::consts::FRAC_1_SQRT_2);
assert_eq!(f64::from_fixed(SQRT_3), 3f64.sqrt());
assert_eq!(f64::from_fixed(FRAC_1_SQRT_3), 3f64.powf(-0.5));
assert_eq!(f64::from_fixed(E), f64::consts::E);
assert_eq!(f64::from_fixed(SQRT_E), 0.5f64.exp());
assert_eq!(f64::from_fixed(LOG2_10), f64::consts::LOG2_10);
assert_eq!(f64::from_fixed(LOG2_E), f64::consts::LOG2_E);
assert_eq!(f64::from_fixed(LOG10_2), f64::consts::LOG10_2);
assert_eq!(f64::from_fixed(LOG10_E), f64::consts::LOG10_E);
assert_eq!(f64::from_fixed(LN_2), f64::consts::LN_2);
assert_eq!(f64::from_fixed(LN_10), f64::consts::LN_10);
assert_eq!(f64::from_fixed(PHI), 1.25f64.sqrt() + 0.5);
// Since 0.5 < FRAC_1_PHI < 1, we use EPSILON / 2.
assert!((f64::from_fixed(FRAC_1_PHI) - (1.25f64.sqrt() - 0.5)).abs() <= f64::EPSILON / 2.0);
// Since 1 < SQRT_PHI < 2, we use EPSILON.
assert!(
(f64::from_fixed(SQRT_PHI) - (1.25f64.sqrt() + 0.5f64).sqrt()).abs() <= f64::EPSILON
);
}
fn compare_parse<F: Fixed>(f: F, s: &str)
where
F::Bits: TryFrom<u8>,
{
let sf = F::from_str(s).unwrap();
let f_plus = f + F::from_bits(F::Bits::try_from(1).ok().unwrap());
assert!(f <= sf && sf <= f_plus);
}
#[test]
fn cmp_parse() {
compare_parse(TAU, "6.283185307179586476925286766559005768394");
compare_parse(FRAC_TAU_2, "3.141592653589793238462643383279502884197");
compare_parse(FRAC_TAU_3, "2.094395102393195492308428922186335256131");
compare_parse(FRAC_TAU_4, "1.570796326794896619231321691639751442098");
compare_parse(FRAC_TAU_6, "1.047197551196597746154214461093167628065");
compare_parse(FRAC_TAU_8, "0.7853981633974483096156608458198757210492");
compare_parse(FRAC_TAU_12, "0.5235987755982988730771072305465838140328");
compare_parse(FRAC_1_TAU, "0.1591549430918953357688837633725143620344");
compare_parse(FRAC_2_TAU, "0.3183098861837906715377675267450287240689");
compare_parse(FRAC_4_TAU, "0.6366197723675813430755350534900574481378");
compare_parse(PI, "3.141592653589793238462643383279502884197");
compare_parse(FRAC_PI_2, "1.570796326794896619231321691639751442098");
compare_parse(FRAC_PI_3, "1.047197551196597746154214461093167628065");
compare_parse(FRAC_PI_4, "0.7853981633974483096156608458198757210492");
compare_parse(FRAC_PI_6, "0.5235987755982988730771072305465838140328");
compare_parse(FRAC_PI_8, "0.3926990816987241548078304229099378605246");
compare_parse(FRAC_1_PI, "0.3183098861837906715377675267450287240689");
compare_parse(FRAC_2_PI, "0.6366197723675813430755350534900574481378");
compare_parse(SQRT_PI, "1.772453850905516027298167483341145182797");
compare_parse(FRAC_1_SQRT_PI, "0.5641895835477562869480794515607725858440");
compare_parse(FRAC_2_SQRT_PI, "1.128379167095512573896158903121545171688");
compare_parse(SQRT_2, "1.414213562373095048801688724209698078569");
compare_parse(FRAC_1_SQRT_2, "0.7071067811865475244008443621048490392848");
compare_parse(SQRT_3, "1.732050807568877293527446341505872366942");
compare_parse(FRAC_1_SQRT_3, "0.5773502691896257645091487805019574556476");
compare_parse(E, "2.718281828459045235360287471352662497757");
compare_parse(SQRT_E, "1.648721270700128146848650787814163571653");
compare_parse(LOG2_10, "3.321928094887362347870319429489390175864");
compare_parse(LOG2_E, "1.442695040888963407359924681001892137426");
compare_parse(LOG10_2, "0.3010299956639811952137388947244930267681");
compare_parse(LOG10_E, "0.4342944819032518276511289189166050822943");
compare_parse(LN_2, "0.6931471805599453094172321214581765680755");
compare_parse(LN_10, "2.302585092994045684017991454684364207601");
compare_parse(PHI, "1.618033988749894848204586834365638117720");
compare_parse(FRAC_1_PHI, "0.6180339887498948482045868343656381177203");
compare_parse(SQRT_PHI, "1.272019649514068964252422461737491491715");
compare_parse(GAMMA, "0.5772156649015328606065120900824024310421");
compare_parse(CATALAN, "0.9159655941772190150546035149323841107741");
}
}
+1924
View File
File diff suppressed because it is too large. Load diff
+134
View File
@@ -0,0 +1,134 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::cell::Cell;
use core::fmt::{Debug, Formatter, Result as FmtResult, Write};
#[cfg(target_has_atomic = "32")]
use core::sync::atomic::{AtomicU32, Ordering};
// This is an ugly hack to check whether a `Formatter` has `debug_lower_hex` or
// `debug_upper_hex`.
//
// We do a dummy write with format string "{:x?}" to get `debug_lower_hex`, and
// a dummy write with format string "{:X?}" to get `debug_upper_hex`. Each time,
// we get the flags using the deprecated `Formatter::flags`.
//
// If AtomicU32 is supported, we cache the flags.
#[cfg(target_has_atomic = "32")]
static LOWER_FLAGS: AtomicU32 = AtomicU32::new(0);
#[cfg(target_has_atomic = "32")]
static UPPER_FLAGS: AtomicU32 = AtomicU32::new(0);
fn get_flags(f: &Formatter) -> u32 {
#[allow(deprecated)]
f.flags()
}
struct StoreFlags(Cell<u32>);
impl Debug for StoreFlags {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
self.0.set(get_flags(f));
Ok(())
}
}
struct Discard;
impl Write for Discard {
fn write_str(&mut self, _s: &str) -> FmtResult {
Ok(())
}
}
pub enum IsDebugHex {
No,
Lower,
Upper,
}
pub fn is_debug_hex(f: &Formatter) -> IsDebugHex {
let flags = get_flags(f);
// avoid doing unnecessary work if flags is zero
if flags == 0 {
return IsDebugHex::No;
}
let (lower_mask, upper_mask) = get_flag_masks();
if flags & lower_mask != 0 {
IsDebugHex::Lower
} else if flags & upper_mask != 0 {
IsDebugHex::Upper
} else {
IsDebugHex::No
}
}
#[cfg(target_has_atomic = "32")]
fn load_cache() -> Option<(u32, u32)> {
let cached_lower = LOWER_FLAGS.load(Ordering::Relaxed);
let cached_upper = UPPER_FLAGS.load(Ordering::Relaxed);
if cached_lower == u32::MAX || cached_upper == u32::MAX {
// error was detected, so no need to repeat the error generation
return Some((0, 0));
}
if cached_lower == 0 || cached_upper == 0 {
return None;
}
Some((cached_lower, cached_upper))
}
#[cfg(target_has_atomic = "32")]
fn store_cache(mut lower: u32, mut upper: u32) {
if lower == 0 || upper == 0 {
lower = u32::MAX;
upper = u32::MAX;
}
LOWER_FLAGS.store(lower, Ordering::Relaxed);
UPPER_FLAGS.store(upper, Ordering::Relaxed);
}
#[cfg(not(target_has_atomic = "32"))]
fn load_cache() -> Option<(u32, u32)> {
None
}
#[cfg(not(target_has_atomic = "32"))]
fn store_cache(_lower: u32, _upper: u32) {}
fn get_flag_masks() -> (u32, u32) {
if let Some(cached) = load_cache() {
return cached;
}
let store_flags = StoreFlags(Cell::new(0));
if write!(Discard, "{store_flags:x?}").is_err() {
store_cache(u32::MAX, u32::MAX);
return (0, 0);
}
let lower_flags = store_flags.0.get();
if write!(Discard, "{store_flags:X?}").is_err() {
store_cache(u32::MAX, u32::MAX);
return (0, 0);
}
let upper_flags = store_flags.0.get();
let lower_mask = lower_flags & !upper_flags;
let upper_mask = upper_flags & !lower_flags;
store_cache(lower_mask, upper_mask);
(lower_mask, upper_mask)
}
+1193
View File
File diff suppressed because it is too large. Load diff
+1265
View File
File diff suppressed because it is too large. Load diff
+232
View File
@@ -0,0 +1,232 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
macro_rules! make_helper {
($Float:ident($Bits:ty, $IBits:ident, $prec:expr) $(; use $path:path)?) => {
#[allow(non_snake_case)]
pub mod $Float {
use crate::helpers::{FloatKind, ToFixedHelper, ToFloatHelper, Widest};
use crate::int_helper;
use core::cmp::Ordering;
$(use $path as $Float;)?
const PREC: u32 = $prec;
const EXP_BIAS: i32 = (1 << (<$Bits>::BITS - PREC - 1)) - 1;
const EXP_MIN: i32 = 1 - EXP_BIAS;
const EXP_MAX: i32 = EXP_BIAS;
pub const SIGN_MASK: $Bits = 1 << (<$Bits>::BITS - 1);
pub const EXP_MASK: $Bits = !(SIGN_MASK | MANT_MASK);
const MANT_MASK: $Bits = (1 << (PREC - 1)) - 1;
// zero is NOT negative, that is zero is represented as
// Kind::Finite { neg: false, abs: 0, frac_bits: 0 },
pub enum Kind {
NaN,
Infinite { neg: bool },
Finite { neg: bool, abs: $Bits, frac_bits: i32 },
}
#[inline]
pub fn kind(val: $Float) -> Kind {
let (neg, mut exp, mut mantissa) = parts(val);
if exp > EXP_MAX {
return if mantissa == 0 {
Kind::Infinite { neg }
} else {
Kind::NaN
};
}
// if not subnormal, add implicit bit
if exp >= EXP_MIN {
mantissa |= 1 << (PREC - 1);
} else {
exp = EXP_MIN;
}
if mantissa == 0 {
return Kind::Finite {
neg: false,
abs: 0,
frac_bits: 0,
};
}
Kind::Finite {
neg,
abs: mantissa,
frac_bits: PREC as i32 - 1 - exp,
}
}
#[inline]
fn parts(val: $Float) -> (bool, i32, $Bits) {
let bits = val.to_bits();
let neg = bits & SIGN_MASK != 0;
let biased_exp = (bits & EXP_MASK) >> (PREC - 1);
let exp = biased_exp as i32 - EXP_BIAS;
let mant = bits & MANT_MASK;
(neg, exp, mant)
}
#[inline]
pub fn from_to_float_helper(
val: ToFloatHelper,
frac_bits: u32,
int_bits: u32,
) -> $Float {
let fix_bits = frac_bits + int_bits;
let bits_sign = if val.neg { SIGN_MASK } else { 0 };
let extra_zeros = 128 - fix_bits;
let leading_zeros = val.abs.leading_zeros() - extra_zeros;
let signif_bits = fix_bits - leading_zeros;
if signif_bits == 0 {
return $Float::from_bits(bits_sign);
}
// remove leading zeros and implicit one
let mut mantissa = val.abs << leading_zeros << 1;
let exponent = int_bits as i32 - 1 - leading_zeros as i32;
let biased_exponent = if exponent > EXP_MAX {
return $Float::from_bits(EXP_MASK | bits_sign);
} else if exponent < EXP_MIN {
let lost_prec = EXP_MIN - exponent;
if lost_prec as u32 >= (int_bits + frac_bits) {
mantissa = 0;
} else {
// reinsert implicit one
mantissa = (mantissa >> 1) | !(!0 >> 1);
mantissa >>= lost_prec - 1;
}
0
} else {
(exponent + EXP_MAX) as $Bits
};
// check for rounding
let round_up = (fix_bits >= PREC) && {
let shift = PREC - 1;
let mid_bit = !(!0 >> 1) >> (shift + extra_zeros);
let lower_bits = mid_bit - 1;
if mantissa & mid_bit == 0 {
false
} else if mantissa & lower_bits != 0 {
true
} else {
// round to even
mantissa & (mid_bit << 1) != 0
}
};
let bits_exp = biased_exponent << (PREC - 1);
let bits_mantissa = (if fix_bits >= PREC - 1 {
(mantissa >> (fix_bits - (PREC - 1))) as $Bits
} else {
(mantissa as $Bits) << (PREC - 1 - fix_bits)
}) & !(!0 << (PREC - 1));
let mut bits_exp_mantissa = bits_exp | bits_mantissa;
if round_up {
bits_exp_mantissa += 1;
}
$Float::from_bits(bits_sign | bits_exp_mantissa)
}
#[inline]
pub fn to_float_kind(val: $Float, dst_frac_bits: u32, dst_int_bits: u32) -> FloatKind {
let prec = PREC as i32;
let (neg, mut exp, mut mantissa) = parts(val);
if exp > EXP_MAX {
return if mantissa == 0 {
FloatKind::Infinite { neg }
} else {
FloatKind::NaN
};
}
// if not subnormal add implicit bit, otherwise update exp
if exp >= EXP_MIN {
mantissa |= 1 << (prec - 1);
} else {
exp = EXP_MIN;
}
if mantissa == 0 {
let conv = ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
return FloatKind::Finite { neg, conv };
}
let mut src_frac_bits = prec - 1 - exp;
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
if need_to_shr > prec {
let dir = if neg {
Ordering::Greater
} else {
Ordering::Less
};
let conv = ToFixedHelper {
bits: Widest::Unsigned(0),
dir,
overflow: false,
};
return FloatKind::Finite { neg, conv };
}
let mut dir = Ordering::Equal;
if need_to_shr > 0 {
let removed_bits = mantissa & !(!0 << need_to_shr);
let will_be_lsb = 1 << need_to_shr;
let tie = will_be_lsb >> 1;
if removed_bits == 0 {
// removed nothing
} else if removed_bits < tie {
dir = Ordering::Less;
} else if removed_bits > tie || mantissa & will_be_lsb != 0 {
mantissa += will_be_lsb;
dir = Ordering::Greater;
} else {
dir = Ordering::Less;
};
mantissa >>= need_to_shr;
src_frac_bits -= need_to_shr;
}
let mut mantissa = mantissa as $IBits;
if neg {
mantissa = -mantissa;
dir = dir.reverse();
}
let mut conv = int_helper::$IBits::to_fixed_helper(
mantissa,
src_frac_bits,
dst_frac_bits,
dst_int_bits,
);
conv.dir = dir;
FloatKind::Finite { neg, conv }
}
}
};
}
#[cfg(feature = "nightly-float")]
make_helper! { f16(u16, i16, 11) }
make_helper! { half_f16(u16, i16, 11); use half::f16 }
make_helper! { half_bf16(u16, i16, 8); use half::bf16 }
make_helper! { f32(u32, i32, 24) }
make_helper! { f64(u64, i64, 53) }
#[cfg(feature = "nightly-float")]
make_helper! { f128(u128, i128, 113) }
make_helper! { F128(u128, i128, 113); use crate::F128 }
make_helper! { F128Bits(u128, i128, 113); use crate::F128Bits }
+2821
View File
File diff suppressed because it is too large. Load diff
+159
View File
@@ -0,0 +1,159 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int_helper;
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
use core::cmp::Ordering;
// Unsigned can have 0 ≤ x < 2^128, that is its msb can be 0 or 1.
// Negative can have -2^127 ≤ x < 0, that is its msb must be 1.
pub enum Widest {
Unsigned(u128),
Negative(i128),
}
pub struct ToFixedHelper {
pub(crate) bits: Widest,
pub(crate) dir: Ordering,
pub(crate) overflow: bool,
}
pub struct ToFloatHelper {
pub(crate) neg: bool,
pub(crate) abs: u128,
}
pub struct FromFloatHelper {
pub(crate) kind: FloatKind,
}
pub enum FloatKind {
NaN,
Infinite { neg: bool },
Finite { neg: bool, conv: ToFixedHelper },
}
pub struct Private;
pub trait Sealed: Copy {
fn to_fixed_helper(self, _: Private, dst_frac_nbits: u32, dst_int_nbits: u32) -> ToFixedHelper;
fn to_float_helper(self, _: Private) -> ToFloatHelper;
fn saturating_from_float_helper(_: Private, src: FromFloatHelper) -> Self;
fn overflowing_from_float_helper(_: Private, src: FromFloatHelper) -> (Self, bool);
}
macro_rules! impl_sealed {
($Fixed:ident($LeEqU:ident, $Signedness:ident, $Inner:ident)) => {
impl<Frac: $LeEqU> Sealed for $Fixed<Frac> {
#[inline]
fn to_fixed_helper(
self,
_: Private,
dst_frac_nbits: u32,
dst_int_nbits: u32,
) -> ToFixedHelper {
int_helper::$Inner::to_fixed_helper(
self.to_bits(),
Self::FRAC_NBITS as i32,
dst_frac_nbits,
dst_int_nbits,
)
}
#[inline]
fn to_float_helper(self, _: Private) -> ToFloatHelper {
let (neg, abs) = int_helper::$Inner::neg_abs(self.to_bits());
let abs = abs.into();
ToFloatHelper { neg, abs }
}
#[inline]
fn saturating_from_float_helper(_: Private, src: FromFloatHelper) -> Self {
let neg = match src.kind {
FloatKind::NaN => panic!("NaN"),
FloatKind::Infinite { neg } => neg,
FloatKind::Finite { neg, .. } => neg,
};
let saturated = if neg { Self::MIN } else { Self::MAX };
let FloatKind::Finite { conv, .. } = src.kind else {
return saturated;
};
if conv.overflow {
return saturated;
}
let bits = if_signed_unsigned!(
$Signedness,
match conv.bits {
Widest::Unsigned(bits) => {
let bits = bits as _;
if bits < 0 {
return Self::MAX;
}
bits
}
Widest::Negative(bits) => bits as _,
},
match conv.bits {
Widest::Unsigned(bits) => bits as _,
Widest::Negative(_) => return Self::MIN,
},
);
Self::from_bits(bits)
}
#[inline]
#[track_caller]
fn overflowing_from_float_helper(_: Private, src: FromFloatHelper) -> (Self, bool) {
let conv = match src.kind {
FloatKind::NaN => panic!("NaN"),
FloatKind::Infinite { .. } => panic!("infinite"),
FloatKind::Finite { conv, .. } => conv,
};
let mut new_overflow = false;
let bits = if_signed_unsigned!(
$Signedness,
match conv.bits {
Widest::Unsigned(bits) => {
let bits = bits as _;
if bits < 0 {
new_overflow = true;
}
bits
}
Widest::Negative(bits) => bits as _,
},
match conv.bits {
Widest::Unsigned(bits) => bits as _,
Widest::Negative(bits) => {
new_overflow = true;
bits as _
}
},
);
(Self::from_bits(bits), conv.overflow || new_overflow)
}
}
};
}
impl_sealed! { FixedI8(LeEqU8, Signed, i8) }
impl_sealed! { FixedI16(LeEqU16, Signed, i16) }
impl_sealed! { FixedI32(LeEqU32, Signed, i32) }
impl_sealed! { FixedI64(LeEqU64, Signed, i64) }
impl_sealed! { FixedI128(LeEqU128, Signed, i128) }
impl_sealed! { FixedU8(LeEqU8, Unsigned, u8) }
impl_sealed! { FixedU16(LeEqU16, Unsigned, u16) }
impl_sealed! { FixedU32(LeEqU32, Unsigned, u32) }
impl_sealed! { FixedU64(LeEqU64, Unsigned, u64) }
impl_sealed! { FixedU128(LeEqU128, Unsigned, u128) }
+246
View File
@@ -0,0 +1,246 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
use crate::int256::U256;
use core::num::NonZero;
// The square root method used is based on code by Martin Guy @UKC, June 1985.
// His method of square root by abacus method is from a book on programming
// abaci by Mr C. Woo.
// http://medialab.freaknet.org/martin/src/sqrt/
macro_rules! impl_hypot {
($Single:ident, $Double:ident $(, $Half:ident)?) => {
pub const fn $Single(a: $Single, b: $Single) -> ($Single, bool) {
$(
if a <= ($Half::MAX as $Single) && b <= ($Half::MAX as $Single) {
let val = match $Half(a as $Half, b as $Half) {
(val, false) => val as $Single,
(val, true) => (val as $Single) + (1 << $Half::BITS),
};
return (val, false);
}
)?
let aa = (a as $Double) * (a as $Double);
let bb = (b as $Double) * (b as $Double);
let (sum, overflow) = aa.overflowing_add(bb);
let mut x = sum;
let mut y;
let mut bit;
if overflow {
// Perform initialization and two iterations assuming word wider than n bits.
// Initialization:
// Set y = 0
// Set bit = 1 << n
// First iteration:
// Since x >= y + bit (because the overflow is equivalent to 1 << n on x):
// x -= y + bit (removing the effect of the overflow on x)
// y = (y >> 1) + bit (y becomes 1 << n)
// bit >>= 2 (bit becomes 1 << (n - 2))
// Second iteration:
// Now x < y (because y is 1 << n):
// y >>= 1 (y becomes 1 << (n - 1))
// bit >>= 2 (bit becomes 1 << (n - 4))
// In effect, we can set y = 1 << (n - 1), and bit = 1 << (n - 4)
y = 1 << ($Double::BITS - 1);
bit = 1 << ($Double::BITS - 4);
} else {
let sum_lz = match NonZero::<$Double>::new(sum) {
None => return (0, false),
Some(s) => s.leading_zeros(),
};
y = 0;
bit = 1 << ($Double::BITS - 2 - sum_lz / 2 * 2);
}
while bit != 0 {
let y_plus_bit = y + bit;
y >>= 1;
if x >= y_plus_bit {
x -= y_plus_bit;
y += bit;
}
bit >>= 2;
}
debug_assert!((y >> $Single::BITS) as $Single == overflow as $Single);
(y as $Single, overflow)
}
};
}
impl_hypot! { u8, u16 }
impl_hypot! { u16, u32, u8 }
impl_hypot! { u32, u64, u16 }
impl_hypot! { u64, u128, u32 }
pub const fn u128(a: u128, b: u128) -> (u128, bool) {
if a <= (u64::MAX as u128) && b <= (u64::MAX as u128) {
let val = match u64(a as u64, b as u64) {
(val, false) => val as u128,
(val, true) => (val as u128) + (1 << 64),
};
return (val, false);
}
let aa = int256::wide_mul_u128(a, a);
let bb = int256::wide_mul_u128(b, b);
let (sum, overflow) = int256::overflowing_add_u256(aa, bb);
let mut x = sum;
let mut y;
let mut bit;
if overflow {
// Perform initialization and two iterations assuming word wider than n bits.
// Initialization:
// Set y = 0
// Set bit = 1 << n
// First iteration:
// Since x >= y + bit (because the overflow is equivalent to 1 << n on x):
// x -= y + bit (removing the effect of the overflow on x)
// y = (y >> 1) + bit (y becomes 1 << n)
// bit >>= 2 (bit becomes 1 << (n - 2))
// Second iteration:
// Now x < y (because y is 1 << n):
// y >>= 1 (y becomes 1 << (n - 1))
// bit >>= 2 (bit becomes 1 << (n - 4))
// In effect, we can set y = 1 << (n - 1), and bit = 1 << (n - 4)
y = U256 {
lo: 0,
hi: 1 << 127,
};
bit = 1 << 124;
} else {
y = U256 { lo: 0, hi: 0 };
bit = match NonZero::<u128>::new(sum.hi) {
None => panic!("small operands; should have used crate::hypot::u64"),
Some(s) => 1 << (126 - s.leading_zeros() / 2 * 2),
};
}
while bit != 0 {
let y_hi_plus_bit = y.hi + bit;
y.hi >>= 1;
if x.hi >= y_hi_plus_bit {
x.hi -= y_hi_plus_bit;
y.hi += bit;
}
bit >>= 2;
}
bit = 1 << 126;
while bit != 0 {
let y_plus_bit = U256 {
lo: y.lo + bit,
hi: y.hi,
};
y.lo = (y.lo >> 1) + ((y.hi & 1) << 127);
y.hi >>= 1;
if (x.hi > y_plus_bit.hi) || (x.hi == y_plus_bit.hi && x.lo >= y_plus_bit.lo) {
x = int256::wrapping_sub_u256(x, y_plus_bit);
y.lo += bit;
}
bit >>= 2
}
debug_assert!(y.hi == overflow as u128);
(y.lo, overflow)
}
#[cfg(test)]
mod tests {
use crate::hypot;
use crate::types::{U1F7, U1F15, U1F31, U1F63, U1F127};
#[test]
fn check_max() {
assert_eq!(hypot::u8(u8::MAX, u8::MAX), (104, true));
assert_eq!(hypot::u16(u16::MAX, u16::MAX), (27_144, true));
assert_eq!(hypot::u32(u32::MAX, u32::MAX), (1_779_033_702, true));
assert_eq!(
hypot::u64(u64::MAX, u64::MAX),
(7_640_891_576_956_012_807, true)
);
assert_eq!(
hypot::u128(u128::MAX, u128::MAX),
(140_949_571_415_070_559_626_692_937_523_481_902_396, true)
);
}
#[test]
fn check_zero() {
assert_eq!(hypot::u8(0, 0), (0, false));
assert_eq!(hypot::u16(0, 0), (0, false));
assert_eq!(hypot::u32(0, 0), (0, false));
assert_eq!(hypot::u64(0, 0), (0, false));
assert_eq!(hypot::u128(0, 0), (0, false));
}
#[test]
fn check_zero_max() {
assert_eq!(hypot::u8(u8::MAX, 0), (u8::MAX, false));
assert_eq!(hypot::u8(0, u8::MAX), (u8::MAX, false));
assert_eq!(hypot::u16(u16::MAX, 0), (u16::MAX, false));
assert_eq!(hypot::u16(0, u16::MAX), (u16::MAX, false));
assert_eq!(hypot::u32(u32::MAX, 0), (u32::MAX, false));
assert_eq!(hypot::u32(0, u32::MAX), (u32::MAX, false));
assert_eq!(hypot::u64(u64::MAX, 0), (u64::MAX, false));
assert_eq!(hypot::u64(0, u64::MAX), (u64::MAX, false));
assert_eq!(hypot::u128(u128::MAX, 0), (u128::MAX, false));
assert_eq!(hypot::u128(0, u128::MAX), (u128::MAX, false));
}
#[test]
fn check_max_plus() {
// hypot(2^n - 1, x) = 2^n; x = sqrt(2^(n+1) - 1)
// e.g. for u32, sqrt(2^33 - 1) = 92681.9
assert_eq!(hypot::u8(u8::MAX, 22), (u8::MAX, false));
assert_eq!(hypot::u8(u8::MAX, 23), (0, true));
assert_eq!(hypot::u16(u16::MAX, 362), (u16::MAX, false));
assert_eq!(hypot::u16(u16::MAX, 363), (0, true));
assert_eq!(hypot::u32(u32::MAX, 92_681), (u32::MAX, false));
assert_eq!(hypot::u32(u32::MAX, 92_682), (0, true));
assert_eq!(hypot::u64(u64::MAX, 6_074_000_999), (u64::MAX, false));
assert_eq!(hypot::u64(u64::MAX, 6_074_001_000), (0, true));
assert_eq!(
hypot::u128(u128::MAX, 26_087_635_650_665_564_424),
(u128::MAX, false)
);
assert_eq!(
hypot::u128(u128::MAX, 26_087_635_650_665_564_425),
(0, true)
);
}
#[test]
fn check_sqrt_2() {
assert_eq!(hypot::u8(1 << 7, 1 << 7), (U1F7::SQRT_2.to_bits(), false));
assert_eq!(
hypot::u16(1 << 15, 1 << 15),
(U1F15::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u32(1 << 31, 1 << 31),
(U1F31::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u64(1 << 63, 1 << 63),
(U1F63::SQRT_2.to_bits(), false)
);
assert_eq!(
hypot::u128(1 << 127, 1 << 127),
(U1F127::SQRT_2.to_bits(), false)
);
}
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use arbitrary::{Arbitrary, Result as ArbitraryResult, Unstructured};
macro_rules! impl_trait {
($Fixed:ident, $LeEqU:ident, $Inner:ident) => {
impl<'a, Frac> Arbitrary<'a> for $Fixed<Frac> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
impl<'a, Frac: $LeEqU> Arbitrary<'a> for Wrapping<$Fixed<Frac>> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
impl<'a, Frac: $LeEqU> Arbitrary<'a> for Unwrapped<$Fixed<Frac>> {
#[inline]
fn arbitrary(u: &mut Unstructured<'a>) -> ArbitraryResult<Self> {
Ok(Self::from_bits(<$Inner as Arbitrary<'a>>::arbitrary(u)?))
}
#[inline]
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<$Inner as Arbitrary<'a>>::size_hint(depth)
}
}
};
}
impl_trait! { FixedI8, LeEqU8, i8 }
impl_trait! { FixedI16, LeEqU16, i16 }
impl_trait! { FixedI32, LeEqU32, i32 }
impl_trait! { FixedI64, LeEqU64, i64 }
impl_trait! { FixedI128, LeEqU128, i128 }
impl_trait! { FixedU8, LeEqU8, u8 }
impl_trait! { FixedU16, LeEqU16, u16 }
impl_trait! { FixedU32, LeEqU32, u32 }
impl_trait! { FixedU64, LeEqU64, u64 }
impl_trait! { FixedU128, LeEqU128, u128 }
+66
View File
@@ -0,0 +1,66 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use bytemuck::{Contiguous, Pod, TransparentWrapper, Zeroable};
macro_rules! unsafe_impl_traits {
($Fixed:ident, $LeEqU:ident, $Inner:ident) => {
unsafe impl<Frac> Zeroable for $Fixed<Frac> {}
unsafe impl<Frac: 'static> Pod for $Fixed<Frac> {}
unsafe impl<Frac: 'static> Contiguous for $Fixed<Frac> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac> TransparentWrapper<$Inner> for $Fixed<Frac> {}
unsafe impl<Frac: $LeEqU> Zeroable for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Pod for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Contiguous for Wrapping<$Fixed<Frac>> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac: $LeEqU> TransparentWrapper<$Fixed<Frac>> for Wrapping<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Zeroable for Unwrapped<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Pod for Unwrapped<$Fixed<Frac>> {}
unsafe impl<Frac: $LeEqU> Contiguous for Unwrapped<$Fixed<Frac>> {
type Int = $Inner;
const MAX_VALUE: $Inner = $Inner::MAX;
const MIN_VALUE: $Inner = $Inner::MIN;
}
unsafe impl<Frac: $LeEqU> TransparentWrapper<$Fixed<Frac>> for Unwrapped<$Fixed<Frac>> {}
};
}
// SAFETY: all fixed-point numbers are repr(transparent) over primitive integer
// types which are both Pod and Zeroable, and Wrapping and Unwrapped are both
// repr(transparent) over fixed-point numbers.
unsafe_impl_traits! { FixedI8, LeEqU8, i8 }
unsafe_impl_traits! { FixedI16, LeEqU16, i16 }
unsafe_impl_traits! { FixedI32, LeEqU32, i32 }
unsafe_impl_traits! { FixedI64, LeEqU64, i64 }
unsafe_impl_traits! { FixedI128, LeEqU128, i128 }
unsafe_impl_traits! { FixedU8, LeEqU8, u8 }
unsafe_impl_traits! { FixedU16, LeEqU16, u16 }
unsafe_impl_traits! { FixedU32, LeEqU32, u32 }
unsafe_impl_traits! { FixedU64, LeEqU64, u64 }
unsafe_impl_traits! { FixedU128, LeEqU128, u128 }
+587
View File
@@ -0,0 +1,587 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::consts;
use crate::traits::Fixed;
use crate::types::extra::{
IsLessOrEqual, LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128, True, U6, U7, U14, U15, U30, U31,
U62, U63, U126, U127,
};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, ParseFixedError,
};
use core::fmt::{Display, Formatter, Result as FmtResult};
use num_traits::bounds::Bounded;
use num_traits::cast::{FromPrimitive, ToPrimitive};
use num_traits::float::FloatConst;
use num_traits::identities::{ConstOne, ConstZero, One, Zero};
use core::error::Error;
use num_traits::Num;
use num_traits::ops::bytes::{FromBytes, ToBytes};
use num_traits::ops::checked::{
CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub,
};
use num_traits::ops::inv::Inv;
use num_traits::ops::mul_add::{MulAdd, MulAddAssign};
use num_traits::ops::overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub};
use num_traits::ops::saturating::{SaturatingAdd, SaturatingMul, SaturatingSub};
use num_traits::ops::wrapping::{
WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub,
};
use num_traits::sign::{Signed, Unsigned};
/// An error which can be returned when parsing a fixed-point number
/// with a given radix.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RadixParseFixedError {
/// The radix is not 2, 8, 10 or 16.
UnsupportedRadix,
/// The string could not be parsed as a fixed-point number.
ParseFixedError(ParseFixedError),
}
impl RadixParseFixedError {
fn message(&self) -> &str {
match self {
RadixParseFixedError::UnsupportedRadix => "unsupported radix",
RadixParseFixedError::ParseFixedError(e) => e.message(),
}
}
}
impl Display for RadixParseFixedError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Display::fmt(self.message(), f)
}
}
impl Error for RadixParseFixedError {
fn description(&self) -> &str {
self.message()
}
}
macro_rules! impl_traits {
($Fixed:ident, $LeEqU:ident, $OneMaxFrac:ident, $Signedness:ident) => {
impl<Frac> Bounded for $Fixed<Frac> {
#[inline]
fn min_value() -> Self {
Self::MIN
}
#[inline]
fn max_value() -> Self {
Self::MAX
}
}
impl<Frac> Zero for $Fixed<Frac> {
#[inline]
fn zero() -> Self {
Self::ZERO
}
#[inline]
fn is_zero(&self) -> bool {
(*self).is_zero()
}
}
impl<Frac> ConstZero for $Fixed<Frac> {
const ZERO: Self = Self::ZERO;
}
impl<Frac: $LeEqU> One for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
#[inline]
fn one() -> Self {
Self::ONE
}
}
impl<Frac: $LeEqU> ConstOne for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
const ONE: Self = Self::ONE;
}
impl<Frac: $LeEqU> Num for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
type FromStrRadixErr = RadixParseFixedError;
#[inline]
fn from_str_radix(str: &str, radix: u32) -> Result<Self, Self::FromStrRadixErr> {
match radix {
2 => Self::from_str_binary(str),
8 => Self::from_str_octal(str),
10 => str.parse(),
16 => Self::from_str_hex(str),
_ => return Err(RadixParseFixedError::UnsupportedRadix),
}
.map_err(RadixParseFixedError::ParseFixedError)
}
}
impl<Frac: $LeEqU> Inv for $Fixed<Frac> {
type Output = Self;
#[inline]
fn inv(self) -> Self::Output {
self.recip()
}
}
if_signed! {
$Signedness;
impl<Frac: $LeEqU> Signed for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
#[inline]
fn abs(&self) -> Self {
(*self).abs()
}
#[inline]
fn abs_sub(&self, other: &Self) -> Self {
if *self < *other {
Self::ZERO
} else {
*self - *other
}
}
#[inline]
fn signum(&self) -> Self {
(*self).signum()
}
#[inline]
fn is_positive(&self) -> bool {
(*self).is_positive()
}
#[inline]
fn is_negative(&self) -> bool {
(*self).is_negative()
}
}
}
if_unsigned! {
$Signedness;
impl<Frac: $LeEqU> Unsigned for $Fixed<Frac>
where
Frac: IsLessOrEqual<$OneMaxFrac, Output = True>,
{
}
}
impl<Frac> CheckedAdd for $Fixed<Frac> {
#[inline]
fn checked_add(&self, v: &Self) -> Option<Self> {
(*self).checked_add(*v)
}
}
impl<Frac> CheckedSub for $Fixed<Frac> {
#[inline]
fn checked_sub(&self, v: &Self) -> Option<Self> {
(*self).checked_sub(*v)
}
}
impl<Frac> CheckedNeg for $Fixed<Frac> {
#[inline]
fn checked_neg(&self) -> Option<Self> {
(*self).checked_neg()
}
}
impl<Frac: $LeEqU> CheckedMul for $Fixed<Frac> {
#[inline]
fn checked_mul(&self, v: &Self) -> Option<Self> {
(*self).checked_mul(*v)
}
}
impl<Frac: $LeEqU> CheckedDiv for $Fixed<Frac> {
#[inline]
fn checked_div(&self, v: &Self) -> Option<Self> {
(*self).checked_div(*v)
}
}
impl<Frac> CheckedRem for $Fixed<Frac> {
#[inline]
fn checked_rem(&self, v: &Self) -> Option<Self> {
(*self).checked_rem(*v)
}
}
impl<Frac> CheckedShl for $Fixed<Frac> {
#[inline]
fn checked_shl(&self, rhs: u32) -> Option<Self> {
(*self).checked_shl(rhs)
}
}
impl<Frac> CheckedShr for $Fixed<Frac> {
#[inline]
fn checked_shr(&self, rhs: u32) -> Option<Self> {
(*self).checked_shr(rhs)
}
}
impl<Frac> SaturatingAdd for $Fixed<Frac> {
#[inline]
fn saturating_add(&self, v: &Self) -> Self {
(*self).saturating_add(*v)
}
}
impl<Frac> SaturatingSub for $Fixed<Frac> {
#[inline]
fn saturating_sub(&self, v: &Self) -> Self {
(*self).saturating_sub(*v)
}
}
impl<Frac: $LeEqU> SaturatingMul for $Fixed<Frac> {
#[inline]
fn saturating_mul(&self, v: &Self) -> Self {
(*self).saturating_mul(*v)
}
}
impl<Frac> WrappingAdd for $Fixed<Frac> {
#[inline]
fn wrapping_add(&self, v: &Self) -> Self {
(*self).wrapping_add(*v)
}
}
impl<Frac> WrappingSub for $Fixed<Frac> {
#[inline]
fn wrapping_sub(&self, v: &Self) -> Self {
(*self).wrapping_sub(*v)
}
}
impl<Frac> WrappingNeg for $Fixed<Frac> {
#[inline]
fn wrapping_neg(&self) -> Self {
(*self).wrapping_neg()
}
}
impl<Frac: $LeEqU> WrappingMul for $Fixed<Frac> {
#[inline]
fn wrapping_mul(&self, v: &Self) -> Self {
(*self).wrapping_mul(*v)
}
}
impl<Frac> WrappingShl for $Fixed<Frac> {
#[inline]
fn wrapping_shl(&self, rhs: u32) -> Self {
(*self).wrapping_shl(rhs)
}
}
impl<Frac> WrappingShr for $Fixed<Frac> {
#[inline]
fn wrapping_shr(&self, rhs: u32) -> Self {
(*self).wrapping_shr(rhs)
}
}
impl<Frac> OverflowingAdd for $Fixed<Frac> {
#[inline]
fn overflowing_add(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_add(*v)
}
}
impl<Frac> OverflowingSub for $Fixed<Frac> {
#[inline]
fn overflowing_sub(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_sub(*v)
}
}
impl<Frac: $LeEqU> OverflowingMul for $Fixed<Frac> {
#[inline]
fn overflowing_mul(&self, v: &Self) -> (Self, bool) {
(*self).overflowing_mul(*v)
}
}
impl<Frac, MulFrac: $LeEqU> MulAdd<$Fixed<MulFrac>> for $Fixed<Frac> {
type Output = $Fixed<Frac>;
#[inline]
fn mul_add(self, a: $Fixed<MulFrac>, b: $Fixed<Frac>) -> $Fixed<Frac> {
self.mul_add(a, b)
}
}
impl<Frac, MulFrac: $LeEqU> MulAddAssign<$Fixed<MulFrac>> for $Fixed<Frac> {
#[inline]
fn mul_add_assign(&mut self, a: $Fixed<MulFrac>, b: $Fixed<Frac>) {
*self = self.mul_add(a, b)
}
}
impl<Frac: $LeEqU> FloatConst for $Fixed<Frac> {
#[inline]
fn E() -> Self {
consts::E.to_num()
}
#[inline]
fn FRAC_1_PI() -> Self {
consts::FRAC_1_PI.to_num()
}
#[inline]
fn FRAC_1_SQRT_2() -> Self {
consts::FRAC_1_SQRT_2.to_num()
}
#[inline]
fn FRAC_2_PI() -> Self {
consts::FRAC_2_PI.to_num()
}
#[inline]
fn FRAC_2_SQRT_PI() -> Self {
consts::FRAC_2_SQRT_PI.to_num()
}
#[inline]
fn FRAC_PI_2() -> Self {
consts::FRAC_PI_2.to_num()
}
#[inline]
fn FRAC_PI_3() -> Self {
consts::FRAC_PI_3.to_num()
}
#[inline]
fn FRAC_PI_4() -> Self {
consts::FRAC_PI_4.to_num()
}
#[inline]
fn FRAC_PI_6() -> Self {
consts::FRAC_PI_6.to_num()
}
#[inline]
fn FRAC_PI_8() -> Self {
consts::FRAC_PI_8.to_num()
}
#[inline]
fn LN_10() -> Self {
consts::LN_10.to_num()
}
#[inline]
fn LN_2() -> Self {
consts::LN_2.to_num()
}
#[inline]
fn LOG10_E() -> Self {
consts::LOG10_E.to_num()
}
#[inline]
fn LOG2_E() -> Self {
consts::LOG2_E.to_num()
}
#[inline]
fn PI() -> Self {
consts::PI.to_num()
}
#[inline]
fn SQRT_2() -> Self {
consts::SQRT_2.to_num()
}
#[inline]
fn TAU() -> Self {
consts::TAU.to_num()
}
#[inline]
fn LOG10_2() -> Self {
consts::LOG10_2.to_num()
}
#[inline]
fn LOG2_10() -> Self {
consts::LOG2_10.to_num()
}
}
impl<Frac: $LeEqU> ToPrimitive for $Fixed<Frac> {
#[inline]
fn to_i64(&self) -> Option<i64> {
self.checked_to_num()
}
#[inline]
fn to_u64(&self) -> Option<u64> {
self.checked_to_num()
}
#[inline]
fn to_isize(&self) -> Option<isize> {
self.checked_to_num()
}
#[inline]
fn to_i8(&self) -> Option<i8> {
self.checked_to_num()
}
#[inline]
fn to_i16(&self) -> Option<i16> {
self.checked_to_num()
}
#[inline]
fn to_i32(&self) -> Option<i32> {
self.checked_to_num()
}
#[inline]
fn to_i128(&self) -> Option<i128> {
self.checked_to_num()
}
#[inline]
fn to_usize(&self) -> Option<usize> {
self.checked_to_num()
}
#[inline]
fn to_u8(&self) -> Option<u8> {
self.checked_to_num()
}
#[inline]
fn to_u16(&self) -> Option<u16> {
self.checked_to_num()
}
#[inline]
fn to_u32(&self) -> Option<u32> {
self.checked_to_num()
}
#[inline]
fn to_u128(&self) -> Option<u128> {
self.checked_to_num()
}
#[inline]
fn to_f32(&self) -> Option<f32> {
self.checked_to_num()
}
#[inline]
fn to_f64(&self) -> Option<f64> {
self.checked_to_num()
}
}
impl<Frac: $LeEqU> FromPrimitive for $Fixed<Frac> {
#[inline]
fn from_i64(n: i64) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u64(n: u64) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_isize(n: isize) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i8(n: i8) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i16(n: i16) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i32(n: i32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_i128(n: i128) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_usize(n: usize) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u8(n: u8) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u16(n: u16) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u32(n: u32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_u128(n: u128) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_f32(n: f32) -> Option<Self> {
Self::checked_from_num(n)
}
#[inline]
fn from_f64(n: f64) -> Option<Self> {
Self::checked_from_num(n)
}
}
impl<Frac: $LeEqU> ToBytes for $Fixed<Frac> {
type Bytes = <Self as Fixed>::Bytes;
fn to_be_bytes(&self) -> Self::Bytes {
(*self).to_be_bytes()
}
fn to_le_bytes(&self) -> Self::Bytes {
(*self).to_le_bytes()
}
fn to_ne_bytes(&self) -> Self::Bytes {
(*self).to_ne_bytes()
}
}
impl<Frac: $LeEqU> FromBytes for $Fixed<Frac> {
type Bytes = <Self as Fixed>::Bytes;
fn from_be_bytes(bytes: &Self::Bytes) -> Self {
Self::from_be_bytes(*bytes)
}
fn from_le_bytes(bytes: &Self::Bytes) -> Self {
Self::from_le_bytes(*bytes)
}
fn from_ne_bytes(bytes: &Self::Bytes) -> Self {
Self::from_ne_bytes(*bytes)
}
}
};
}
impl_traits! { FixedI8, LeEqU8, U6, Signed }
impl_traits! { FixedI16, LeEqU16, U14, Signed }
impl_traits! { FixedI32, LeEqU32, U30, Signed }
impl_traits! { FixedI64, LeEqU64, U62, Signed }
impl_traits! { FixedI128, LeEqU128, U126, Signed }
impl_traits! { FixedU8, LeEqU8, U7, Unsigned }
impl_traits! { FixedU16, LeEqU16, U15, Unsigned }
impl_traits! { FixedU32, LeEqU32, U31, Unsigned }
impl_traits! { FixedU64, LeEqU64, U63, Unsigned }
impl_traits! { FixedU128, LeEqU128, U127, Unsigned }
+435
View File
@@ -0,0 +1,435 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::hint;
use core::num::NonZero;
#[derive(Clone, Copy, Debug)]
pub struct U256 {
pub lo: u128,
pub hi: u128,
}
#[derive(Clone, Copy, Debug)]
pub struct I256 {
pub lo: u128,
pub hi: i128,
}
#[inline]
pub const fn u256_wrapping_as_i256(a: U256) -> I256 {
I256 {
lo: a.lo,
hi: a.hi as i128,
}
}
#[inline]
pub const fn wrapping_add_u256_u128(a: U256, b: u128) -> U256 {
let (lo, carry) = a.lo.overflowing_add(b);
let hi = a.hi.wrapping_add(carry as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_add_u128_u256(a: u128, b: U256) -> (u128, bool) {
let (lo, carry) = a.overflowing_add(b.lo);
(lo, carry | (b.hi != 0))
}
#[inline]
pub const fn overflowing_sub_u128_u256(a: u128, b: U256) -> (u128, bool) {
let (lo, borrow) = a.overflowing_sub(b.lo);
(lo, borrow | (b.hi != 0))
}
#[inline]
pub const fn wrapping_neg_u256(a: U256) -> U256 {
let (lo, carry) = (!a.lo).overflowing_add(1);
let hi = (!a.hi).wrapping_add(carry as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_add_i256_i128(a: I256, b: i128) -> (I256, bool) {
let b = I256 {
lo: b as u128,
hi: b >> 127,
};
let (lo, carry) = a.lo.overflowing_add(b.lo);
// b.hi is in {-1, 0}, and carry is in {0, 1}, so we can add them wrappingly
let b_hi_plus_carry = b.hi.wrapping_add(carry as i128);
let (hi, overflow) = a.hi.overflowing_add(b_hi_plus_carry);
(I256 { lo, hi }, overflow)
}
#[inline]
const fn u128_lo_hi(u: u128) -> (u64, u64) {
(u as u64, (u >> 64) as u64)
}
#[inline]
const fn i128_lo_hi(i: i128) -> (u64, i64) {
(i as u64, (i >> 64) as i64)
}
#[inline]
const fn mul_u64_u64(a: u64, b: u64) -> u128 {
(a as u128).wrapping_mul(b as u128)
}
#[inline]
const fn mul_i64_i64(a: i64, b: i64) -> i128 {
(a as i128).wrapping_mul(b as i128)
}
#[inline]
const fn mul_u64_i64(a: u64, b: i64) -> i128 {
let a = a as i64;
// if a has become negative, we need to add 2^64 * b to the answer
let correction = if a.is_negative() {
(b as i128) << 64
} else {
0
};
(a as i128).wrapping_mul(b as i128).wrapping_add(correction)
}
#[inline]
pub const fn wide_mul_u128(lhs: u128, rhs: u128) -> U256 {
let (ll, lh) = u128_lo_hi(lhs);
let (rl, rh) = u128_lo_hi(rhs);
// 0 <= ll_rl <= 2^128 - 2^65 + 1; ll_rl unit is 1
let ll_rl = mul_u64_u64(ll, rl);
// 0 <= lh_rl <= 2^128 - 2^65 + 1; lh_rl unit is 2^64
let lh_rl = mul_u64_u64(lh, rl);
// 0 <= ll_rh <= 2^128 - 2^65 + 1; ll_rh unit is 2^64
let ll_rh = mul_u64_u64(ll, rh);
// 0 <= lh_rh <= 2^128 - 2^65 + 1; lh_rh unit is 2^128
let lh_rh = mul_u64_u64(lh, rh);
// 0 <= col0 <= 2^64 - 1
// 0 <= col64a <= 2^64 - 2
let (col0, col64a) = u128_lo_hi(ll_rl);
// 0 <= col64b <= 2^128 - 2^64 - 1
let col64b = (col64a as u128).wrapping_add(lh_rl);
// 0 <= col64c <= 2^64 - 1
// 0 <= col128a <= 2^64 - 2
let (col64c, col128a) = u128_lo_hi(col64b);
// 0 <= col64d <= 2^128 - 2^64
let col64d = (col64c as u128).wrapping_add(ll_rh);
// 0 <= col64 <= 2^64 - 1
// 0 <= col128b <= 2^64 - 1
let (col64, col128b) = u128_lo_hi(col64d);
// Since both col0 and col64 fit in 64 bits, ans0 sum will never overflow.
let ans0 = (col0 as u128) | ((col64 as u128) << 64);
// Since lhs * rhs fits in 256 bits, ans128 sum will never overflow.
let ans128 = lh_rh
.wrapping_add(col128a as u128)
.wrapping_add(col128b as u128);
U256 {
lo: ans0,
hi: ans128,
}
}
#[inline]
pub const fn wide_mul_i128(lhs: i128, rhs: i128) -> I256 {
let (ll, lh) = i128_lo_hi(lhs);
let (rl, rh) = i128_lo_hi(rhs);
// 0 <= ll_rl <= 2^128 - 2^65 + 1; ll_rl unit is 1; must be unsigned to hold all range!
let ll_rl = mul_u64_u64(ll, rl);
// -2^127 + 2^63 <= lh_rl <= 2^127 - 2^64 - 2^63 + 1; lh_rl unit is 2^64
let lh_rl = mul_u64_i64(rl, lh);
// -2^127 + 2^63 <= ll_rh <= 2^127 - 2^64 - 2^63 + 1; ll_rh unit is 2^64
let ll_rh = mul_u64_i64(ll, rh);
// -2^126 + 2^63 <= lh_rh <= 2^126; lh_rh unit is 2^128
let lh_rh = mul_i64_i64(lh, rh);
// 0 <= col0 <= 2^64 - 1
// 0 <= col64a <= 2^64 - 2
let (col0, col64a) = u128_lo_hi(ll_rl);
// -2^127 + 2^63 <= col64b <= 2^127 - 2^63 - 1
let col64b = (col64a as i128).wrapping_add(lh_rl);
// 0 <= col64c <= 2^64 - 1
// -2^63 <= col128a <= 2^63 - 1
let (col64c, col128a) = i128_lo_hi(col64b);
// -2^127 + 2^63 <= col64d <= 2^127 - 2^63
let col64d = (col64c as i128).wrapping_add(ll_rh);
// 0 <= col64 <= 2^64 - 1
// -2^63 <= col128b <= 2^63 - 1
let (col64, col128b) = i128_lo_hi(col64d);
// Since both col0 and col64 fit in 64 bits, ans0 sum will never overflow.
let ans0 = (col0 as u128) | ((col64 as u128) << 64);
// Since lhs * rhs fits in 256 bits, ans128 sum will never overflow.
let ans128 = lh_rh
.wrapping_add(col128a as i128)
.wrapping_add(col128b as i128);
I256 {
lo: ans0,
hi: ans128,
}
}
#[inline]
pub const fn shl_u256_max_128(a: U256, sh: u32) -> U256 {
if sh == 0 {
a
} else if sh == 128 {
U256 { lo: a.hi, hi: 0 }
} else {
U256 {
lo: (a.lo >> sh) | (a.hi << (128 - sh)),
hi: a.hi >> sh,
}
}
}
#[inline]
pub const fn shl_i256_max_128(a: I256, sh: u32) -> I256 {
if sh == 0 {
a
} else if sh == 128 {
I256 {
lo: a.hi as u128,
hi: a.hi >> 127,
}
} else {
I256 {
lo: (a.lo >> sh) | (a.hi << (128 - sh)) as u128,
hi: a.hi >> sh,
}
}
}
/// # Safety
///
/// d must have msb set.
#[inline]
const unsafe fn div_half_u128(r: u128, d: u128, next_half: u64) -> (u128, u128) {
let (dl, dh) = u128_lo_hi(d);
// SAFETY: we know d has the most significant bit set because we normalized
unsafe {
hint::assert_unchecked(dh != 0);
}
let (mut q, rr) = (r / (dh as u128), r % (dh as u128));
let m = q * (dl as u128);
let mut r = (next_half as u128) + (rr << 64);
if r < m {
q -= 1;
let (new_r, overflow) = r.overflowing_add(d);
r = if !overflow && new_r < m {
q -= 1;
new_r.wrapping_add(d)
} else {
new_r
};
}
r = r.wrapping_sub(m);
(r, q)
}
#[inline]
pub const fn div_rem_u256_u128(mut n: U256, d: NonZero<u128>) -> (U256, u128) {
let zeros = d.leading_zeros();
let (mut r, d) = if zeros == 0 {
(0, d.get())
} else {
let n2 = n.hi >> (128 - zeros);
n.hi = (n.hi << zeros) | (n.lo >> (128 - zeros));
n.lo <<= zeros;
(n2, d.get() << zeros)
};
// SAFETY: we know that d has msb set because it is not zero and it was
// shifted to the right by the number of leading zeros.
let (nhl, nhh) = u128_lo_hi(n.hi);
let (new_r, qhh) = unsafe { div_half_u128(r, d, nhh) };
r = new_r;
let (new_r, qhl) = unsafe { div_half_u128(r, d, nhl) };
r = new_r;
let (nll, nlh) = u128_lo_hi(n.lo);
let (new_r, qlh) = unsafe { div_half_u128(r, d, nlh) };
r = new_r;
let (new_r, qll) = unsafe { div_half_u128(r, d, nll) };
r = new_r;
let q = U256 {
lo: qll + (qlh << 64),
hi: qhl + (qhh << 64),
};
r >>= zeros;
(q, r)
}
// must not result in overflow
#[inline]
pub const fn div_rem_i256_i128_no_overflow(n: I256, d: NonZero<i128>) -> (I256, i128) {
let (n_neg, n_abs) = if n.hi < 0 {
let (nl, overflow) = n.lo.overflowing_neg();
let nh = n.hi.wrapping_neg().wrapping_sub(overflow as i128) as u128;
(true, U256 { lo: nl, hi: nh })
} else {
let nl = n.lo;
let nh = n.hi as u128;
(false, U256 { lo: nl, hi: nh })
};
let (d_neg, d_abs) = if d.get() < 0 {
// SAFETY: d and -d are not zero
let ud = unsafe { NonZero::<u128>::new_unchecked(d.get().wrapping_neg() as u128) };
(true, ud)
} else {
// SAFETY: d is not zero
let ud = unsafe { NonZero::<u128>::new_unchecked(d.get() as u128) };
(false, ud)
};
let (q_abs, r_abs) = div_rem_u256_u128(n_abs, d_abs);
let q = if n_neg == d_neg {
let ql = q_abs.lo;
let qh = q_abs.hi as i128;
I256 { lo: ql, hi: qh }
} else {
let (ql, overflow) = q_abs.lo.overflowing_neg();
let qh = q_abs.hi.wrapping_neg().wrapping_sub(overflow as u128) as i128;
I256 { lo: ql, hi: qh }
};
let r = if n_neg {
r_abs.wrapping_neg() as i128
} else {
r_abs as i128
};
(q, r)
}
#[inline]
pub const fn overflowing_add_u256(a: U256, b: U256) -> (U256, bool) {
let (lo, carry0) = a.lo.overflowing_add(b.lo);
let (hi, carry1_a) = a.hi.overflowing_add(b.hi);
let (hi, carry1_b) = hi.overflowing_add(carry0 as u128);
(U256 { lo, hi }, carry1_a | carry1_b)
}
#[inline]
pub const fn wrapping_sub_u256(a: U256, b: U256) -> U256 {
let (lo, borrow) = a.lo.overflowing_sub(b.lo);
let hi = a.hi.wrapping_sub(b.hi).wrapping_sub(borrow as u128);
U256 { lo, hi }
}
#[inline]
pub const fn overflowing_shl_u256_into_u128(a: U256, sh: u32) -> (u128, bool) {
if sh == 128 {
(a.hi, false)
} else if sh == 0 {
(a.lo, a.hi != 0)
} else {
let lo = a.lo >> sh;
let hi = a.hi << (128 - sh);
(lo | hi, a.hi >> sh != 0)
}
}
#[inline]
pub const fn overflowing_shl_i256_into_i128(a: I256, sh: u32) -> (i128, bool) {
if sh == 128 {
(a.hi, false)
} else if sh == 0 {
let ans = a.lo as i128;
(ans, a.hi != ans >> 127)
} else {
let lo = (a.lo >> sh) as i128;
let hi = a.hi << (128 - sh);
let ans = lo | hi;
(ans, a.hi >> sh != ans >> 127)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn check_udiv_rem(num: U256, den: u128) {
let (quot, rem) = div_rem_u256_u128(num, NonZero::<u128>::new(den).unwrap());
assert!(rem <= den);
let ql_d = wide_mul_u128(quot.lo, den);
let qh_d = wide_mul_u128(quot.hi, den);
assert!(qh_d.hi == 0);
let prod_lo = ql_d.lo;
let prod_hi = ql_d.hi.checked_add(qh_d.lo).unwrap();
let (sum_lo, carry) = prod_lo.overflowing_add(rem);
let sum_hi = prod_hi.checked_add(u128::from(carry)).unwrap();
assert!(sum_lo == num.lo && sum_hi == num.hi);
}
fn check_idiv_rem_signs(num: I256, den: i128) {
let (quot, rem) = div_rem_i256_i128_no_overflow(num, NonZero::<i128>::new(den).unwrap());
assert!(rem.unsigned_abs() <= den.unsigned_abs());
if num.hi < 0 {
assert!(rem <= 0);
} else {
assert!(rem >= 0);
}
if (num.hi < 0) == (den < 0) {
assert!(quot.hi >= 0);
} else {
assert!(quot.hi <= 0);
}
}
#[test]
fn test_udiv_rem() {
for d in 1u8..=255 {
for n1 in (0u8..=255).step_by(15) {
for n0 in (0u8..=255).step_by(15) {
let d = (u128::from(d) << 120) | 1;
let n1 = (u128::from(n1) << 120) | 1;
let n0 = (u128::from(n0) << 120) | 1;
check_udiv_rem(U256 { lo: n0, hi: n1 }, d);
}
}
}
}
#[test]
fn test_idiv_rem_signs() {
for d in -128..=127 {
if d == 0 {
continue;
}
for n1 in (-120..=120).step_by(15) {
for n0 in (0u8..=255).step_by(15) {
let d = (i128::from(d) << 120) | 1;
let n1 = (i128::from(n1) << 120) | 1;
let n0 = (u128::from(n0) << 120) | 1;
check_idiv_rem_signs(I256 { lo: n0, hi: n1 }, d);
}
}
}
}
}
+207
View File
@@ -0,0 +1,207 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::U0;
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
macro_rules! make_helper_common {
($t:ident) => {
use crate::helpers::{ToFixedHelper, Widest};
use core::cmp::Ordering;
};
}
macro_rules! make_helper {
($i: ident, $u:ident) => {
pub mod $i {
make_helper_common! { $i }
#[inline]
pub const fn neg_abs(val: $i) -> (bool, $u) {
(val.is_negative(), val.unsigned_abs())
}
#[inline]
pub fn to_fixed_helper(
val: $i,
src_frac_bits: i32,
dst_frac_bits: u32,
dst_int_bits: u32,
) -> ToFixedHelper {
let src_bits = $i::BITS as i32;
let dst_bits = (dst_frac_bits + dst_int_bits) as i32;
if val == 0 {
return ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
}
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
let leading = if val >= 0 {
val.leading_zeros()
} else {
(!val).leading_zeros() - 1
};
let overflow = src_bits - dst_bits > need_to_shr + leading as i32;
let bits_128 = i128::from(val);
let (bits, lost_bits) = match need_to_shr {
-0x7fff_ffff..=-128 => (0, false),
-127..=-1 => (bits_128 << -need_to_shr, false),
0 => (bits_128, false),
1..=127 => {
let shifted = bits_128 >> need_to_shr;
(shifted, shifted << need_to_shr != bits_128)
}
128..=0x7fff_ffff => (bits_128 >> 127, true),
_ => unreachable!(),
};
let dir = if lost_bits {
Ordering::Less
} else {
Ordering::Equal
};
let bits = if val >= 0 {
Widest::Unsigned(bits as u128)
} else {
Widest::Negative(bits)
};
ToFixedHelper {
bits,
dir,
overflow,
}
}
}
pub mod $u {
make_helper_common! { $u }
#[inline]
pub fn neg_abs(val: $u) -> (bool, $u) {
(false, val)
}
#[inline]
pub fn to_fixed_helper(
val: $u,
src_frac_bits: i32,
dst_frac_bits: u32,
dst_int_bits: u32,
) -> ToFixedHelper {
let src_bits = $u::BITS as i32;
let dst_bits = (dst_frac_bits + dst_int_bits) as i32;
if val == 0 {
return ToFixedHelper {
bits: Widest::Unsigned(0),
dir: Ordering::Equal,
overflow: false,
};
}
let leading_zeros = val.leading_zeros();
let need_to_shr = src_frac_bits - dst_frac_bits as i32;
let overflow = src_bits - dst_bits > need_to_shr + leading_zeros as i32;
let bits_128 = u128::from(val);
let (bits, lost_bits) = match need_to_shr {
-0x7fff_ffff..=-128 => (0, false),
-127..=-1 => (bits_128 << -need_to_shr, false),
0 => (bits_128, false),
1..=127 => {
let shifted = bits_128 >> need_to_shr;
(shifted, shifted << need_to_shr != bits_128)
}
128..=0x7fff_ffff => (0, true),
_ => unreachable!(),
};
let dir = if lost_bits {
Ordering::Less
} else {
Ordering::Equal
};
ToFixedHelper {
bits: Widest::Unsigned(bits),
dir,
overflow,
}
}
}
};
}
make_helper! { i8, u8 }
make_helper! { i16, u16 }
make_helper! { i32, u32 }
make_helper! { i64, u64 }
make_helper! { i128, u128 }
pub struct IntFixed<T>(pub T);
macro_rules! make_int_fixed {
($I:ident -> $F:ident) => {
impl IntFixed<$I> {
#[inline]
pub fn fixed(self) -> $F<U0> {
$F::<U0>::from_bits(self.0)
}
#[inline]
pub fn int(f: $F<U0>) -> $I {
f.to_bits()
}
}
};
($T:ident as $I:ident -> $F:ident) => {
impl IntFixed<$T> {
#[inline]
pub fn fixed(self) -> $F<U0> {
$F::<U0>::from_bits(self.0 as $I)
}
#[inline]
pub fn int(f: $F<U0>) -> $T {
f.to_bits() as $T
}
}
};
}
make_int_fixed! { i8 -> FixedI8 }
make_int_fixed! { i16 -> FixedI16 }
make_int_fixed! { i32 -> FixedI32 }
make_int_fixed! { i64 -> FixedI64 }
make_int_fixed! { i128 -> FixedI128 }
#[cfg(target_pointer_width = "16")]
make_int_fixed! { isize as i16 -> FixedI16 }
#[cfg(target_pointer_width = "32")]
make_int_fixed! { isize as i32 -> FixedI32 }
#[cfg(target_pointer_width = "64")]
make_int_fixed! { isize as i64 -> FixedI64 }
make_int_fixed! { u8 -> FixedU8 }
make_int_fixed! { u16 -> FixedU16 }
make_int_fixed! { u32 -> FixedU32 }
make_int_fixed! { u64 -> FixedU64 }
make_int_fixed! { u128 -> FixedU128 }
#[cfg(target_pointer_width = "16")]
make_int_fixed! { usize as u16 -> FixedU16 }
#[cfg(target_pointer_width = "32")]
make_int_fixed! { usize as u32 -> FixedU32 }
#[cfg(target_pointer_width = "64")]
make_int_fixed! { usize as u64 -> FixedU64 }
+306
View File
@@ -0,0 +1,306 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
use crate::int256::U256;
use core::num::NonZeroU128;
macro_rules! make_inv_lerp {
($i:ident, $u:ident, $ii:ident, $uu:ident, $uuf:expr) => {
pub const fn $i(v: $i, start: $i, end: $i, frac_bits: u32) -> ($i, bool) {
assert!(start != end, "empty range");
// 0x00 ≤ diff_abs ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(v) as $u, true)
};
let wide_diff_abs = (diff_abs as $uu) << $u::BITS;
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(end) as $u, true)
};
let neg = diff_neg != range_neg;
let (wide_ret, overflow1) = if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let ceil_div = (wide_diff_abs - 1) / (range_abs as $uu) + 1;
// 0x0000 ≤ add ≤ 0x00ff
let add = $uuf >> frac_bits;
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let shifted = (ceil_div + add) >> ($u::BITS - frac_bits);
let wide_ret = shifted.wrapping_neg() as $ii;
(wide_ret, wide_ret > 0)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let floor_div = wide_diff_abs / (range_abs as $uu);
let shifted = floor_div >> ($u::BITS - frac_bits);
let wide_ret = shifted as $ii;
(wide_ret, wide_ret < 0)
};
let ret = wide_ret as $i;
let overflow2 = wide_ret != ret as $ii;
(ret, overflow1 | overflow2)
}
pub const fn $u(v: $u, start: $u, end: $u, frac_bits: u32) -> ($u, bool) {
assert!(start != end, "empty range");
// 0x00 ≤ diff ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v - start, false)
} else {
(start - v, true)
};
let wide_diff_abs = (diff_abs as $uu) << $u::BITS;
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
let neg = diff_neg != range_neg;
let (wide_ret, overflow1) = if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let ceil_div = (wide_diff_abs - 1) / (range_abs as $uu) + 1;
// 0x0000 ≤ add ≤ 0x00ff
let add = $uuf >> frac_bits;
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let shifted = (ceil_div + add) >> ($u::BITS - frac_bits);
let wide_ret = shifted.wrapping_neg();
(wide_ret, wide_ret > 0)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let floor_div = wide_diff_abs / (range_abs as $uu);
let shifted = floor_div >> ($u::BITS - frac_bits);
(shifted, false)
};
let ret = wide_ret as $u;
let overflow2 = wide_ret != ret as $uu;
(ret, overflow1 | overflow2)
}
};
}
make_inv_lerp! { i8, u8, i16, u16, 0xff_u16 }
make_inv_lerp! { i16, u16, i32, u32, 0xffff_u32 }
make_inv_lerp! { i32, u32, i64, u64, 0xffff_ffff_u64 }
make_inv_lerp! { i64, u64, i128, u128, 0xffff_ffff_ffff_ffff_u128 }
pub const fn i128(v: i128, start: i128, end: i128, frac_bits: u32) -> (i128, bool) {
// 0x00 ≤ diff_abs ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(v) as u128, true)
};
let wide_diff_abs = U256 {
lo: 0,
hi: diff_abs,
};
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(end) as u128, true)
};
let Some(range_abs) = NonZeroU128::new(range_abs) else {
panic!("empty range");
};
let neg = diff_neg != range_neg;
if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let (div, rem) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let ceil_div = if rem > 0 {
int256::wrapping_add_u256_u128(div, 1)
} else {
div
};
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
0
} else {
u128::MAX >> frac_bits
};
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let sum = int256::wrapping_add_u256_u128(ceil_div, add);
let shifted = int256::shl_u256_max_128(sum, 128 - frac_bits);
let neg_shifted = int256::wrapping_neg_u256(shifted);
let ret = neg_shifted.lo as i128;
let overflow = if ret.is_negative() {
neg_shifted.hi != u128::MAX
} else if ret == 0 {
neg_shifted.hi != 0
} else {
true
};
(ret, overflow)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let (floor_div, _) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let shifted = int256::shl_u256_max_128(floor_div, 128 - frac_bits);
let ret = shifted.lo as i128;
let overflow = (ret < 0) | (shifted.hi != 0);
(ret, overflow)
}
}
pub const fn u128(v: u128, start: u128, end: u128, frac_bits: u32) -> (u128, bool) {
// 0x00 ≤ diff ≤ 0xff
let (diff_abs, diff_neg) = if v >= start {
(v - start, false)
} else {
(start - v, true)
};
let wide_diff_abs = U256 {
lo: 0,
hi: diff_abs,
};
// 0x01 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
let Some(range_abs) = NonZeroU128::new(range_abs) else {
panic!("empty range");
};
let neg = diff_neg != range_neg;
if neg {
// ensure that difference is not 0
if diff_abs == 0 {
return (0, false);
}
// 0x0001 ≤ ceil_div ≤ 0xff00
let (div, rem) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let ceil_div = if rem > 0 {
int256::wrapping_add_u256_u128(div, 1)
} else {
div
};
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
0
} else {
u128::MAX >> frac_bits
};
// If frac_bits is 0: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
// If frac_bits is max: add = 0x0000; 0x0000 ≤ shifted ≤ 0xff00
let sum = int256::wrapping_add_u256_u128(ceil_div, add);
let shifted = int256::shl_u256_max_128(sum, 128 - frac_bits);
let neg_shifted = int256::wrapping_neg_u256(shifted);
let ret = neg_shifted.lo;
let overflow = (ret != 0) | (neg_shifted.hi != 0);
(ret, overflow)
} else {
// 0x0000 ≤ floor_div ≤ 0xff00
let (floor_div, _) = int256::div_rem_u256_u128(wide_diff_abs, range_abs);
let shifted = int256::shl_u256_max_128(floor_div, 128 - frac_bits);
let ret = shifted.lo;
let overflow = shifted.hi != 0;
(ret, overflow)
}
}
#[cfg(test)]
mod tests {
use crate::inv_lerp;
#[test]
fn inv_lerp_i8() {
assert_eq!(inv_lerp::i8(-128, -128, 127, 0), (0, false));
assert_eq!(inv_lerp::i8(127, 127, -128, 8), (0, false));
assert_eq!(inv_lerp::i8(-1, -128, 127, 6), (31, false));
assert_eq!(inv_lerp::i8(-90, -100, -110, 6), (-64, false));
assert_eq!(inv_lerp::i8(50, 0, 2, 2), (100, false));
assert_eq!(inv_lerp::i8(100, 0, 2, 2), (-56, true));
assert_eq!(inv_lerp::i8(-26, -1, 0, 2), (-100, false));
assert_eq!(inv_lerp::i8(50, 0, -1, 2), (56, true));
}
#[test]
fn inv_lerp_u8() {
assert_eq!(inv_lerp::u8(0, 0, 255, 0), (0, false));
assert_eq!(inv_lerp::u8(255, 255, 0, 8), (0, false));
assert_eq!(inv_lerp::u8(255, 0, 255, 0), (1, false));
assert_eq!(inv_lerp::u8(128, 255, 0, 7), (63, false));
assert_eq!(inv_lerp::u8(51, 52, 53, 6), (192, true));
assert_eq!(inv_lerp::u8(100, 0, 2, 2), (200, false));
assert_eq!(inv_lerp::u8(200, 0, 2, 2), (200 - 56, true));
}
#[test]
fn inv_lerp_i128() {
assert_eq!(inv_lerp::i128(-128, -128, 127, 0), (0, false));
assert_eq!(inv_lerp::i128(127, 127, -128, 128), (0, false));
assert_eq!(inv_lerp::i128(-1, -128, 127, 6), (31, false));
assert_eq!(inv_lerp::i128(-90, -100, -110, 126), ((-64) << 120, false));
assert_eq!(
inv_lerp::i128(i128::MAX / 2, 0, 2, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
inv_lerp::i128(i128::MAX, 0, 2, 2),
(i128::MAX.wrapping_mul(2), true)
);
assert_eq!(
inv_lerp::i128(i128::MIN / 4 - 1, -1, 0, 2),
(i128::MIN, false)
);
assert_eq!(
inv_lerp::i128(i128::MAX, 0, -1, 2),
(i128::MAX.wrapping_mul(-4), true)
);
}
#[test]
fn inv_lerp_u128() {
assert_eq!(inv_lerp::u128(0, 0, 255, 0), (0, false));
assert_eq!(inv_lerp::u128(255, 255, 0, 128), (0, false));
assert_eq!(inv_lerp::u128(255, 0, 255, 0), (1, false));
assert_eq!(
inv_lerp::u128(1 << 127, u128::MAX, 0, 127),
((1 << 126) - 1, false)
);
assert_eq!(inv_lerp::u128(51, 52, 53, 126), (192 << 120, true));
assert_eq!(
inv_lerp::u128(u128::MAX / 2, 0, 2, 2),
(u128::MAX / 2 * 2, false)
);
assert_eq!(
inv_lerp::u128(u128::MAX, 0, 2, 2),
(u128::MAX.wrapping_mul(2), true)
);
}
}
+224
View File
@@ -0,0 +1,224 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::int256;
macro_rules! make_lerp {
($i:ident, $u:ident, $ii:ident, $uu:ident, $uu1:expr) => {
pub const fn $i(r: $i, start: $i, end: $i, frac_bits: u32) -> ($i, bool) {
// 0x00 ≤ r_abs ≤ 0x80
let (r_abs, r_neg) = if r >= 0 {
(r as $u, false)
} else {
(r.wrapping_neg() as $u, true)
};
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as $u, false)
} else {
(start.wrapping_sub(end) as $u, true)
};
// 0x0000 ≤ wide_abs ≤ 0x7f80
let wide_abs = (r_abs as $uu) * (range_abs as $uu);
let wide_neg = r_neg != range_neg;
let wide_uns = if wide_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = ($uu1 << frac_bits) - 1;
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0x7f80
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x0080
let shifted = (wide_abs + add) >> frac_bits;
shifted.wrapping_neg()
} else {
wide_abs >> frac_bits
};
let wide = wide_uns as $ii;
let (wide_ret, overflow1) = wide.overflowing_add(start as $ii);
let ret = wide_ret as $i;
let overflow2 = wide_ret != ret as $ii;
(ret, overflow1 | overflow2)
}
pub const fn $u(r: $u, start: $u, end: $u, frac_bits: u32) -> ($u, bool) {
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
// 0x0000 ≤ wide_abs ≤ 0xfe01
let wide_abs = (r as $uu) * (range_abs as $uu);
let (wide_ret, overflow1) = if range_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = ($uu1 << frac_bits) - 1;
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0xfe01
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
let shifted = (wide_abs + add) >> frac_bits;
(start as $uu).overflowing_sub(shifted)
} else {
let shifted = wide_abs >> frac_bits;
(start as $uu).overflowing_add(shifted)
};
let ret = wide_ret as $u;
let overflow2 = wide_ret != ret as $uu;
(ret, overflow1 | overflow2)
}
};
}
make_lerp! { i8, u8, i16, u16, 1u16 }
make_lerp! { i16, u16, i32, u32, 1u32 }
make_lerp! { i32, u32, i64, u64, 1u64 }
make_lerp! { i64, u64, i128, u128, 1u128 }
pub const fn i128(r: i128, start: i128, end: i128, frac_bits: u32) -> (i128, bool) {
// 0x00 ≤ r_abs ≤ 0x80
let (r_abs, r_neg) = if r >= 0 {
(r as u128, false)
} else {
(r.wrapping_neg() as u128, true)
};
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end.wrapping_sub(start) as u128, false)
} else {
(start.wrapping_sub(end) as u128, true)
};
// 0x0000 ≤ wide_abs ≤ 0x7f80
let wide_abs = int256::wide_mul_u128(r_abs, range_abs);
let wide_neg = r_neg != range_neg;
let wide_uns = if wide_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
u128::MAX
} else {
(1u128 << frac_bits) - 1
};
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0x7f80
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x0080
let sum = int256::wrapping_add_u256_u128(wide_abs, add);
let shifted = int256::shl_u256_max_128(sum, frac_bits);
int256::wrapping_neg_u256(shifted)
} else {
int256::shl_u256_max_128(wide_abs, frac_bits)
};
let wide = int256::u256_wrapping_as_i256(wide_uns);
let (wide_ret, overflow1) = int256::overflowing_add_i256_i128(wide, start);
let ret = wide_ret.lo as i128;
let overflow2 = if ret < 0 {
wide_ret.hi != -1
} else {
wide_ret.hi != 0
};
(ret, overflow1 | overflow2)
}
pub const fn u128(r: u128, start: u128, end: u128, frac_bits: u32) -> (u128, bool) {
// 0x00 ≤ range_abs ≤ 0xff
let (range_abs, range_neg) = if end >= start {
(end - start, false)
} else {
(start - end, true)
};
// 0x0000 ≤ wide_abs ≤ 0xfe01
let wide_abs = int256::wide_mul_u128(r, range_abs);
if range_neg {
// 0x0000 ≤ add ≤ 0x00ff
let add = if frac_bits == 128 {
u128::MAX
} else {
(1u128 << frac_bits) - 1
};
// If frac_bits is 0: add = 0x0000; 0x0000 ≤ shifted ≤ 0xfe01
// If frac_bits is max: add = 0x00ff; 0x0000 ≤ shifted ≤ 0x00ff
let sum = int256::wrapping_add_u256_u128(wide_abs, add);
let shifted = int256::shl_u256_max_128(sum, frac_bits);
int256::overflowing_sub_u128_u256(start, shifted)
} else {
let shifted = int256::shl_u256_max_128(wide_abs, frac_bits);
int256::overflowing_add_u128_u256(start, shifted)
}
}
#[cfg(test)]
mod tests {
use crate::lerp;
#[test]
fn lerp_i8() {
assert_eq!(lerp::i8(0, -128, 127, 0), (-128, false));
assert_eq!(lerp::i8(0, 127, -128, 8), (127, false));
assert_eq!(lerp::i8(1, -128, 127, 1), (-1, false));
assert_eq!(lerp::i8(-64, -100, -110, 6), (-90, false));
assert_eq!(lerp::i8(2 << 2, 0, 50, 2), (100, false));
assert_eq!(lerp::i8(2 << 2, 0, 100, 2), (-56, true));
assert_eq!(lerp::i8((-1) << 2, 50, 0, 2), (100, false));
assert_eq!(lerp::i8((-1) << 2, 100, 0, 2), (-56, true));
}
#[test]
fn lerp_u8() {
assert_eq!(lerp::u8(0, 0, 255, 0), (0, false));
assert_eq!(lerp::u8(0, 255, 0, 8), (255, false));
assert_eq!(lerp::u8(1, 0, 255, 0), (255, false));
assert_eq!(lerp::u8(128, 255, 0, 7), (0, false));
assert_eq!(lerp::u8(2 << 2, 0, 100, 2), (200, false));
assert_eq!(lerp::u8(2 << 2, 0, 200, 2), (200 - 56, true));
}
#[test]
fn lerp_i128() {
assert_eq!(lerp::i128(0, -128, 127, 0), (-128, false));
assert_eq!(lerp::i128(0, 127, -128, 128), (127, false));
assert_eq!(lerp::i128(1, -128, 127, 1), (-1, false));
assert_eq!(lerp::i128(1 << 98, 127, -128, 100), (63, false));
assert_eq!(lerp::i128((-64) << 120, -100, -110, 126), (-90, false));
assert_eq!(
lerp::i128(2 << 2, 0, i128::MAX / 2, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::i128(2 << 2, 0, i128::MAX, 2),
(i128::MAX.wrapping_mul(2), true)
);
assert_eq!(
lerp::i128((-1) << 2, i128::MAX / 2, 0, 2),
(i128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::i128((-1) << 2, i128::MAX, 0, 2),
(i128::MAX.wrapping_mul(2), true)
);
}
#[test]
fn lerp_u128() {
assert_eq!(lerp::u128(0, 0, 255, 0), (0, false));
assert_eq!(lerp::u128(0, 255, 0, 128), (255, false));
assert_eq!(lerp::u128(1, 0, 255, 0), (255, false));
assert_eq!(lerp::u128(128 << 120, 255, 0, 127), (0, false));
assert_eq!(
lerp::u128(2 << 2, 0, u128::MAX / 2, 2),
(u128::MAX / 2 * 2, false)
);
assert_eq!(
lerp::u128(2 << 2, 0, u128::MAX, 2),
(u128::MAX.wrapping_mul(2), true)
);
}
}
+1635
View File
File diff suppressed because it is too large. Load diff
+178
View File
@@ -0,0 +1,178 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#[derive(Clone, Copy, Debug)]
pub struct Base(u32);
impl Base {
pub const fn new(base: u32) -> Option<Base> {
if base >= 2 { Some(Base(base)) } else { None }
}
pub const fn get(self) -> u32 {
self.0
}
}
macro_rules! impl_int_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val < baseu {
return 0;
}
// base^1, base^2, base^4, etc.
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val >= s => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val >= mid {
partial_val = mid;
partial_log += dlog;
}
}
}
return partial_log as i32;
}
};
}
pub mod int_part {
use crate::log::Base;
use core::num::NonZero;
impl_int_part! { u8 }
impl_int_part! { u16 }
impl_int_part! { u32 }
impl_int_part! { u64 }
impl_int_part! { u128 }
}
macro_rules! impl_frac_part {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, base: Base) -> i32 {
const MAX_TABLE_SIZE: usize = ($u::BITS.ilog2() - 1) as usize;
let val = val.get();
let base = base.get();
let baseu = base as $u;
if baseu as u32 != base || val.checked_mul(baseu).is_none() {
return -1;
}
// base^1, base^2, base^4, etc.
let mut base_powers: [$u; MAX_TABLE_SIZE] = [0; MAX_TABLE_SIZE];
let mut i = 0;
let mut partial_log = 1u32;
let mut partial_val = baseu;
loop {
let square = match partial_val.checked_mul(partial_val) {
Some(s) if val.checked_mul(s).is_some() => s,
_ => break,
};
base_powers[i] = partial_val;
i += 1;
partial_log *= 2;
partial_val = square;
}
let mut dlog = partial_log;
while i > 0 {
i -= 1;
dlog /= 2;
if let Some(mid) = partial_val.checked_mul(base_powers[i]) {
if val.checked_mul(mid).is_some() {
partial_val = mid;
partial_log += dlog;
}
}
}
return -1 - partial_log as i32;
}
};
}
pub mod frac_part {
use crate::log::Base;
use core::num::NonZero;
impl_frac_part! { u8 }
impl_frac_part! { u16 }
impl_frac_part! { u32 }
impl_frac_part! { u64 }
impl_frac_part! { u128 }
}
#[cfg(test)]
mod tests {
use crate::log;
use crate::log::Base;
use core::num::NonZero;
// these tests require the maximum table sizes
#[test]
fn check_table_size_is_sufficient() {
let bin = Base::new(2).unwrap();
assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, bin), 7);
assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, bin), 15);
assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, bin), 31);
assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, bin), 63);
assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, bin), 127);
assert_eq!(log::frac_part::u8(NonZero::<u8>::new(1).unwrap(), bin), -8);
assert_eq!(
log::frac_part::u16(NonZero::<u16>::new(1).unwrap(), bin),
-16
);
assert_eq!(
log::frac_part::u32(NonZero::<u32>::new(1).unwrap(), bin),
-32
);
assert_eq!(
log::frac_part::u64(NonZero::<u64>::new(1).unwrap(), bin),
-64
);
assert_eq!(
log::frac_part::u128(NonZero::<u128>::new(1).unwrap(), bin),
-128
);
}
}
+325
View File
@@ -0,0 +1,325 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
// the value must be positive for all public functions
pub mod frac_part {
use core::num::NonZero;
// MAX / 1000 (0) < val <= MAX (255)
// -3 <= log <= -1
#[inline]
pub const fn u8(val: NonZero<u8>) -> i32 {
let val = val.get();
if val > 25 {
-1
} else if val > 2 {
-2
} else {
-3
}
}
// MAX / 100_000 (0) < val <= MAX (65_535)
// -5 <= log <= -1
#[inline]
pub const fn u16(val: NonZero<u16>) -> i32 {
let val = val.get();
if val > 6553 {
-1
} else if val > 655 {
-2
} else if val > 65 {
-3
} else if val > 6 {
-4
} else {
-5
}
}
// 0 < val <= MAX
// -10 <= log <= -1
pub const fn u32(val: NonZero<u32>) -> i32 {
const MAX: u32 = u32::MAX;
let mut val = val.get();
if val <= MAX / 100_000_000 {
val *= 100_000_000;
// At this point, we have shifted out 8 digits, and we can only shift out 2 more.
// We can only check up to -2 more because -10 <= log <= -8.
if val > MAX / 10 {
-9
} else {
debug_assert!(val > MAX / 100);
-10
}
} else {
greater_equal_m8_u32(val)
}
}
// 0 < val <= MAX
// -20 <= log <= -1
pub const fn u64(val: NonZero<u64>) -> i32 {
const MAX: u64 = u64::MAX;
let mut val = val.get();
let mut log = 0;
if val <= MAX / 10_000_000_000_000_000 {
// After this, we can only check up to -4 more because -20 <= log <= -16.
// That is, we can skip the checks against MAX / 100_000_000 and MAX / 10_000.
val *= 10_000_000_000_000_000;
log += -16;
} else {
if val <= MAX / 100_000_000 {
val *= 100_000_000;
log += -8;
}
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// 0 < val <= MAX
// -39 <= log <= -1
pub const fn u128(val: NonZero<u128>) -> i32 {
const MAX: u128 = u128::MAX;
let mut val = val.get();
let mut log = 0;
if val <= MAX / 100_000_000_000_000_000_000_000_000_000_000 {
val *= 100_000_000_000_000_000_000_000_000_000_000;
// At this point we have shifted out 32 digits, and we can only shift out 7 more.
// We can
// * use val >> 96 because we have shifted out 32 decimal digits (106 bits)
// * only check up to -8 more because -39 <= log <= -32
return -32 + greater_equal_m8_u32((val >> 96) as u32);
}
if val <= MAX / 10_000_000_000_000_000 {
val *= 10_000_000_000_000_000;
log += -16;
}
if val <= MAX / 100_000_000 {
val *= 100_000_000;
log += -8;
}
if log == -24 {
// At this point we have shifted out 24 digits, and we can only shift out 15 more.
// We can
// * use val >> 64 because we have shifted out 24 decimal digits (79 bits)
// * only check up to -8 more because -32 <= log <= -24
return -24 + greater_equal_m8_u64((val >> 64) as u64);
}
// We have *not* shifted out enough decimal digits, so we must *not* convert to u32 or u64.
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// MAX / 100_000_000 < val <= MAX
// -8 <= log <= -1
const fn greater_equal_m8_u32(mut val: u32) -> i32 {
const MAX: u32 = u32::MAX;
debug_assert!(val > MAX / 100_000_000);
let mut log = 0;
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
// MAX / 100_000_000 < val <= MAX
// -8 <= log <= 1
const fn greater_equal_m8_u64(mut val: u64) -> i32 {
const MAX: u64 = u64::MAX;
debug_assert!(val > MAX / 100_000_000);
let mut log = 0;
if val <= MAX / 10_000 {
val *= 10_000;
log += -4;
}
log + if val > MAX / 10 {
-1
} else if val > MAX / 100 {
-2
} else if val > MAX / 1000 {
-3
} else {
debug_assert!(val > MAX / 10_000);
-4
}
}
}
// check log10() and log(10) in tandem
#[cfg(test)]
mod tests {
use crate::log;
use crate::log::Base;
use crate::log10;
use core::num::NonZero;
const DEC: Base = match Base::new(10) {
Some(s) => s,
None => unreachable!(),
};
macro_rules! check_loop {
($T:ident) => {
for i in 0..=$T::MAX.ilog10() as i32 {
let p = (10 as $T).pow(i as u32);
let nz = NonZero::<$T>::new(p).unwrap();
if i > 0 {
let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
assert_eq!((p - 1).ilog10() as i32, i - 1);
assert_eq!(log::int_part::$T(nz_m1, DEC), i - 1);
}
assert_eq!(p.ilog10() as i32, i);
assert_eq!(log::int_part::$T(nz, DEC), i);
let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
assert_eq!((p + 1).ilog10() as i32, i);
assert_eq!(log::int_part::$T(nz_p1, DEC), i);
}
for i in 0..-log10::frac_part::$T(NonZero::<$T>::new(1).unwrap()) {
let p = <$T>::MAX / (10 as $T).pow(i as u32);
let nz = NonZero::<$T>::new(p).unwrap();
if p > 1 {
let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
assert_eq!(log10::frac_part::$T(nz_m1), -1 - i);
assert_eq!(log::frac_part::$T(nz_m1, DEC), -1 - i);
}
assert_eq!(log10::frac_part::$T(nz), -1 - i);
assert_eq!(log::frac_part::$T(nz, DEC), -1 - i);
if i > 0 {
let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
assert_eq!(log10::frac_part::$T(nz_p1), -i);
assert_eq!(log::frac_part::$T(nz_p1, DEC), -i);
}
}
};
}
#[test]
fn log10_u8() {
let one = NonZero::<u8>::new(1).unwrap();
assert_eq!(1u8.ilog10(), 0);
assert_eq!(log::int_part::u8(one, DEC), 0);
assert_eq!(u8::MAX.ilog10(), 2);
assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, DEC), 2);
assert_eq!(log10::frac_part::u8(one), -3);
assert_eq!(log::frac_part::u8(one, DEC), -3);
assert_eq!(log10::frac_part::u8(NonZero::<u8>::MAX), -1);
assert_eq!(log::frac_part::u8(NonZero::<u8>::MAX, DEC), -1);
check_loop! { u8 }
}
#[test]
fn log10_u16() {
let one = NonZero::<u16>::new(1).unwrap();
assert_eq!(1u16.ilog10(), 0);
assert_eq!(log::int_part::u16(one, DEC), 0);
assert_eq!(u16::MAX.ilog10(), 4);
assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, DEC), 4);
assert_eq!(log10::frac_part::u16(one), -5);
assert_eq!(log::frac_part::u16(one, DEC), -5);
assert_eq!(log10::frac_part::u16(NonZero::<u16>::MAX), -1);
assert_eq!(log::frac_part::u16(NonZero::<u16>::MAX, DEC), -1);
check_loop! { u16 }
}
#[test]
fn log10_u32() {
let one = NonZero::<u32>::new(1).unwrap();
assert_eq!(1u32.ilog10(), 0);
assert_eq!(log::int_part::u32(NonZero::<u32>::new(1).unwrap(), DEC), 0);
assert_eq!(u32::MAX.ilog10(), 9);
assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, DEC), 9);
assert_eq!(log10::frac_part::u32(one), -10);
assert_eq!(log::frac_part::u32(one, DEC), -10);
assert_eq!(log10::frac_part::u32(NonZero::<u32>::MAX), -1);
assert_eq!(log::frac_part::u32(NonZero::<u32>::MAX, DEC), -1);
check_loop! { u32 }
}
#[test]
fn log10_u64() {
let one = NonZero::<u64>::new(1).unwrap();
assert_eq!(1u64.ilog10(), 0);
assert_eq!(log::int_part::u64(one, DEC), 0);
assert_eq!(u64::MAX.ilog10(), 19);
assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, DEC), 19);
assert_eq!(log10::frac_part::u64(one), -20);
assert_eq!(log::frac_part::u64(one, DEC), -20);
assert_eq!(log10::frac_part::u64(NonZero::<u64>::MAX), -1);
assert_eq!(log::frac_part::u64(NonZero::<u64>::MAX, DEC), -1);
check_loop! { u64 }
}
#[test]
fn log10_u128() {
let one = NonZero::<u128>::new(1).unwrap();
assert_eq!(1u128.ilog10(), 0);
assert_eq!(log::int_part::u128(one, DEC), 0);
assert_eq!(u128::MAX.ilog10(), 38);
assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, DEC), 38);
assert_eq!(log10::frac_part::u128(one), -39);
assert_eq!(log::frac_part::u128(one, DEC), -39);
assert_eq!(log10::frac_part::u128(NonZero::<u128>::MAX), -1);
assert_eq!(log::frac_part::u128(NonZero::<u128>::MAX, DEC), -1);
check_loop! { u128 }
}
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
macro_rules! if_signed {
(Signed; $($rem:tt)+) => {
$($rem)+
};
(Unsigned; $($rem:tt)+) => {};
}
macro_rules! if_unsigned {
(Signed; $($rem:tt)+) => {};
(Unsigned; $($rem:tt)+) => {
$($rem)+
};
}
macro_rules! if_signed_unsigned {
(Signed, $signed:expr, $unsigned:expr $(,)?) => {
$signed
};
(Unsigned, $signed:expr, $unsigned:expr $(,)?) => {
$unsigned
};
}
macro_rules! if_signed_else_empty_str {
(Signed; $($signed:tt)*) => {
concat!($($signed)*)
};
(Unsigned; $($signed:tt)*) => {
""
};
}
macro_rules! if_unsigned_else_empty_str {
(Signed; $($unsigned:tt)*) => {
""
};
(Unsigned; $($unsigned:tt)*) => {
concat!($($unsigned)*)
};
}
macro_rules! doc_comment {
($comment:expr; $($tt:tt)*) => {
#[doc = $comment]
$($tt)*
};
}
macro_rules! comment {
($($comment:expr),* $(,)?; $($tt:tt)*) => {
doc_comment! {
concat!($($comment),*);
$($tt)*
}
};
}
+413
View File
@@ -0,0 +1,413 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
macro_rules! fixed_const {
(
Self = $Self:ident,
Signedness = $Signedness:ident,
LeEqU = $LeEqU:ident,
[nm4 ..= n] = [$nm4:literal, $nm3:literal, $nm2:literal, $nm1:literal, $n:literal],
[LeEqUC0 ..= LeEqUC3] = [$LeEqUC0:ident, $LeEqUC1:ident, $LeEqUC2:ident, $LeEqUC3:ident],
) => {
impl<Frac: Unsigned> $Self<Frac> {
const fn from_const<SrcFrac: Unsigned>(src: FixedU128<SrcFrac>) -> $Self<Frac> {
let right = SrcFrac::U32 - Frac::U32;
let bits128 = if right == 128 {
0
} else {
src.to_bits() >> right
};
$Self::from_bits(bits128 as _)
}
}
comment! {
"This block contains constants in the range 0&nbsp;<&nbsp;<i>x</i>&nbsp;<&nbsp;0.5.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U", $n, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $n, ">;
assert_eq!(Fix::LOG10_2, Fix::from_num(consts::LOG10_2));
```
";
impl<Frac: $LeEqU> $Self<Frac> {
/// 1/τ = 0.159154…
pub const FRAC_1_TAU: $Self<Frac> = Self::from_const(consts::FRAC_1_TAU);
/// 2/τ = 0.318309…
pub const FRAC_2_TAU: $Self<Frac> = Self::from_const(consts::FRAC_2_TAU);
/// π/8 = 0.392699…
pub const FRAC_PI_8: $Self<Frac> = Self::from_const(consts::FRAC_PI_8);
/// 1/π = 0.318309…
pub const FRAC_1_PI: $Self<Frac> = Self::from_const(consts::FRAC_1_PI);
/// 1/√2π = 0.398942…
pub const FRAC_1_SQRT_2PI: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_2PI);
/// log<sub>10</sub> 2 = 0.301029…
pub const LOG10_2: $Self<Frac> = Self::from_const(consts::LOG10_2);
/// log<sub>10</sub> e = 0.434294…
pub const LOG10_E: $Self<Frac> = Self::from_const(consts::LOG10_E);
}
}
comment! {
"This block contains constants in the range 0.5&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;1",
if_signed_else_empty_str! {
$Signedness;
", and &minus;1.
These constants are not representable in signed fixed-point numbers with less
than 1 integer bit"
},
".
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm1, $n),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm1, $n),
">;
assert_eq!(Fix::LN_2, Fix::from_num(consts::LN_2));
assert!(0.5 <= Fix::LN_2 && Fix::LN_2 < 1);
```
",
if_signed_else_empty_str! {
$Signedness;
"
The following example fails to compile, since the maximum
representable value with ", $n, " fractional bits and 0 integer
bits is <&nbsp;0.5.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U", $n, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $n, ">;
let _ = Fix::LN_2;
```
"
};
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC0, Output = True>,
{
if_signed! {
$Signedness;
comment! {
"Negative one.
# Examples
```rust
use fixed::types::extra::U", $nm1, ";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U", $nm1, ">;
assert_eq!(Fix::NEG_ONE, Fix::from_num(-1));
```
The following would fail as
<code>[", stringify!($Self), "]&lt;[U", $nm1, "]></code>
cannot represent 1, so there is no
<code>[", stringify!($Self), "]::&lt;[U", $nm1, "]>::[ONE]</code>.
[ONE]: ", stringify!($Self), "::ONE
```rust,compile_fail
use fixed::types::extra::U", $nm1, ";
use fixed::", stringify!($Self), ";
const _ERROR: ", stringify!($Self), "<U", $nm1, "> = ", stringify!($Self), "::ONE.unwrapped_neg();
```
";
pub const NEG_ONE: $Self<Frac> = Self::from_bits(-1 << Frac::U32);
}
}
/// τ/8 = 0.785398…
pub const FRAC_TAU_8: $Self<Frac> = Self::from_const(consts::FRAC_TAU_8);
/// τ/12 = 0.523598…
pub const FRAC_TAU_12: $Self<Frac> = Self::from_const(consts::FRAC_TAU_12);
/// 4/τ = 0.636619…
pub const FRAC_4_TAU: $Self<Frac> = Self::from_const(consts::FRAC_4_TAU);
/// π/4 = 0.785398…
pub const FRAC_PI_4: $Self<Frac> = Self::from_const(consts::FRAC_PI_4);
/// π/6 = 0.523598…
pub const FRAC_PI_6: $Self<Frac> = Self::from_const(consts::FRAC_PI_6);
/// 2/π = 0.636619…
pub const FRAC_2_PI: $Self<Frac> = Self::from_const(consts::FRAC_2_PI);
/// 1/√π = 0.564189…
pub const FRAC_1_SQRT_PI: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_PI);
/// 1/√2 = 0.707106…
pub const FRAC_1_SQRT_2: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_2);
/// 1/√3 = 0.577350…
pub const FRAC_1_SQRT_3: $Self<Frac> = Self::from_const(consts::FRAC_1_SQRT_3);
/// ln 2 = 0.693147…
pub const LN_2: $Self<Frac> = Self::from_const(consts::LN_2);
/// The golden ratio conjugate, Φ = 1/φ = 0.618033…
pub const FRAC_1_PHI: $Self<Frac> = Self::from_const(consts::FRAC_1_PHI);
/// The Euler-Mascheroni constant, γ = 0.577215…
pub const GAMMA: $Self<Frac> = Self::from_const(consts::GAMMA);
/// Catalan’s constant = 0.915965…
pub const CATALAN: $Self<Frac> = Self::from_const(consts::CATALAN);
}
}
comment! {
"This block contains constants in the range 1&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;2.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, "2 integer bits", "1 integer bit"),
".
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
">;
assert_eq!(Fix::LOG2_E, Fix::from_num(consts::LOG2_E));
assert!(1 <= Fix::LOG2_E && Fix::LOG2_E < 2);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm1, $n),
" fractional bits and ",
if_signed_unsigned!($Signedness, "1 integer bit", "0 integer bits"),
" is <&nbsp;1.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm1, $n),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm1, $n),
">;
let _ = Fix::LOG2_E;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC1, Output = True>,
{
comment! {
"One.
# Examples
```rust
use fixed::types::extra::U4;
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U4>;
assert_eq!(Fix::ONE, Fix::from_num(1));
```
";
pub const ONE: $Self<Frac> = Self::from_bits(1 << Frac::U32);
}
/// τ/4 = 1.57079…
pub const FRAC_TAU_4: $Self<Frac> = Self::from_const(consts::FRAC_TAU_4);
/// τ/6 = 1.04719…
pub const FRAC_TAU_6: $Self<Frac> = Self::from_const(consts::FRAC_TAU_6);
/// π/2 = 1.57079…
pub const FRAC_PI_2: $Self<Frac> = Self::from_const(consts::FRAC_PI_2);
/// π/3 = 1.04719…
pub const FRAC_PI_3: $Self<Frac> = Self::from_const(consts::FRAC_PI_3);
/// √π = 1.77245…
pub const SQRT_PI: $Self<Frac> = Self::from_const(consts::SQRT_PI);
/// 2/√π = 1.12837…
pub const FRAC_2_SQRT_PI: $Self<Frac> = Self::from_const(consts::FRAC_2_SQRT_PI);
/// √2 = 1.41421…
pub const SQRT_2: $Self<Frac> = Self::from_const(consts::SQRT_2);
/// √3 = 1.73205…
pub const SQRT_3: $Self<Frac> = Self::from_const(consts::SQRT_3);
/// √e = 1.64872…
pub const SQRT_E: $Self<Frac> = Self::from_const(consts::SQRT_E);
/// log<sub>2</sub> e = 1.44269…
pub const LOG2_E: $Self<Frac> = Self::from_const(consts::LOG2_E);
/// The golden ratio, φ = 1.61803…
pub const PHI: $Self<Frac> = Self::from_const(consts::PHI);
/// √φ = 1.27201…
pub const SQRT_PHI: $Self<Frac> = Self::from_const(consts::SQRT_PHI);
}
}
comment! {
"This block contains constants in the range 2&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;4.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, 3, 2),
" integer bits.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
">;
assert_eq!(Fix::E, Fix::from_num(consts::E));
assert!(2 <= Fix::E && Fix::E < 4);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm2, $nm1),
" fractional bits and ",
if_signed_unsigned!($Signedness, "2 integer bits", "1 integer bit"),
" is <&nbsp;2.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm2, $nm1),
">;
let _ = Fix::E;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC2, Output = True>,
{
/// τ/2 = 3.14159…
pub const FRAC_TAU_2: $Self<Frac> = Self::from_const(consts::FRAC_TAU_2);
/// τ/3 = 2.09439…
pub const FRAC_TAU_3: $Self<Frac> = Self::from_const(consts::FRAC_TAU_3);
/// Archimedes’ constant, π = 3.14159…
pub const PI: $Self<Frac> = Self::from_const(consts::PI);
/// √2π = 2.50662…
pub const SQRT_2PI: $Self<Frac> = Self::from_const(consts::SQRT_2PI);
/// Euler’s number, e = 2.71828…
pub const E: $Self<Frac> = Self::from_const(consts::E);
/// log<sub>2</sub> 10 = 3.32192…
pub const LOG2_10: $Self<Frac> = Self::from_const(consts::LOG2_10);
/// ln 10 = 2.30258…
pub const LN_10: $Self<Frac> = Self::from_const(consts::LN_10);
}
}
comment! {
"This block contains constants in the range 4&nbsp;≤&nbsp;<i>x</i>&nbsp;<&nbsp;8.
These constants are not representable in ",
if_signed_unsigned!($Signedness, "signed", "unsigned"),
" fixed-point numbers with less than ",
if_signed_unsigned!($Signedness, 4, 3),
" integer bits.
# Examples
```rust
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm4, $nm3),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm4, $nm3),
">;
assert_eq!(Fix::TAU, Fix::from_num(consts::TAU));
assert!(4 <= Fix::TAU && Fix::TAU < 8);
```
The following example fails to compile, since the maximum
representable value with ",
if_signed_unsigned!($Signedness, $nm3, $nm2),
" fractional bits and ",
if_signed_unsigned!($Signedness, 3, 2),
" integer bits is <&nbsp;4.
```rust,compile_fail
use fixed::consts;
use fixed::types::extra::U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
";
use fixed::", stringify!($Self), ";
type Fix = ", stringify!($Self), "<U",
if_signed_unsigned!($Signedness, $nm3, $nm2),
">;
let _ = Fix::TAU;
```
";
impl<Frac: Unsigned> $Self<Frac>
where
Frac: IsLessOrEqual<$LeEqUC3, Output = True>,
{
/// A turn, τ = 6.28318…
pub const TAU: $Self<Frac> = Self::from_const(consts::TAU);
}
}
};
}
+3411
View File
File diff suppressed because it is too large. Load diff
+2523
View File
File diff suppressed because it is too large. Load diff
+5626
View File
File diff suppressed because it is too large. Load diff
+1117
View File
File diff suppressed because it is too large. Load diff
+503
View File
@@ -0,0 +1,503 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or modify it under
// the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT License
// along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
#![allow(deprecated)]
use crate::float_helper;
use crate::helpers::{FloatKind, FromFloatHelper, Private};
use crate::int_helper::IntFixed;
use crate::traits::{Fixed, FixedEquiv, FromFixed, ToFixed};
use crate::types::extra::U0;
use crate::{
F128, F128Bits, FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32,
FixedU64, FixedU128,
};
use bytemuck::TransparentWrapper;
use half::{bf16 as half_bf16, f16 as half_f16};
impl ToFixed for bool {
/// Converts a [`bool`] to a fixed-point number.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value does
/// not fit. When debug assertions are not enabled, the wrapped
/// value can be returned, but it is not considered a breaking
/// change if in the future it panics; if wrapping is required use
/// [`wrapping_to_fixed`] instead.
///
/// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
ToFixed::to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number if it fits, otherwise returns [`None`].
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
ToFixed::checked_to_fixed(u8::from(self))
}
/// Convert a [`bool`] to a fixed-point number, saturating if it does not fit.
#[inline]
fn saturating_to_fixed<F: Fixed>(self) -> F {
ToFixed::saturating_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number, wrapping if it does not fit.
#[inline]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
ToFixed::wrapping_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number.
///
/// Returns a [tuple] of the fixed-point number and a [`bool`]
/// indicating whether an overflow has occurred. On overflow, the
/// wrapped value is returned.
#[inline]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
ToFixed::overflowing_to_fixed(u8::from(self))
}
/// Converts a [`bool`] to a fixed-point number, panicking if it
/// does not fit.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug assertions
/// are not enabled.
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
ToFixed::unwrapped_to_fixed(u8::from(self))
}
}
macro_rules! impl_int {
($Int:ident $(, $Equiv:ident)?) => {
impl FromFixed for $Int {
/// Converts a fixed-point number to an integer.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_from_fixed`] instead.
///
/// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
#[inline]
#[track_caller]
fn from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::from_fixed(src))
}
/// Converts a fixed-point number to an integer if it fits, otherwise returns [`None`].
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
FromFixed::checked_from_fixed(src).map(IntFixed::<$Int>::int)
}
/// Converts a fixed-point number to an integer, saturating if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::saturating_from_fixed(src))
}
/// Converts a fixed-point number to an integer, wrapping if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::wrapping_from_fixed(src))
}
/// Converts a fixed-point number to an integer.
///
/// Returns a [tuple] of the value and a [`bool`] indicating whether
/// an overflow has occurred. On overflow, the wrapped value is
/// returned.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
#[inline]
fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
let (fixed, overflow) = FromFixed::overflowing_from_fixed(src);
(IntFixed::<$Int>::int(fixed), overflow)
}
/// Converts a fixed-point number to an integer, panicking if it does not fit.
///
/// Any fractional bits are discarded, which rounds towards &minus;∞.
///
/// # Panics
///
/// Panics if the value
/// does not fit, even when debug assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
IntFixed::<$Int>::int(FromFixed::unwrapped_from_fixed(src))
}
}
impl ToFixed for $Int {
/// Converts an integer to a fixed-point number.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_to_fixed`] instead.
///
/// [`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
ToFixed::to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number if it fits, otherwise returns [`None`].
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
ToFixed::checked_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, saturating if it does not fit.
#[inline]
fn saturating_to_fixed<F: Fixed>(self) -> F {
ToFixed::saturating_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, wrapping if it does not fit.
#[inline]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
ToFixed::wrapping_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number.
///
/// Returns a [tuple] of the fixed-point number and a [`bool`]
/// indicating whether an overflow has occurred. On overflow, the
/// wrapped value is returned.
#[inline]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
ToFixed::overflowing_to_fixed(IntFixed(self).fixed())
}
/// Converts an integer to a fixed-point number, panicking if it does not fit.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug
/// assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
ToFixed::unwrapped_to_fixed(IntFixed(self).fixed())
}
}
$(
impl FixedEquiv for $Int {
type Equiv = $Equiv<U0>;
#[inline]
fn to_fixed_equiv(self) -> $Equiv<U0> {
$Equiv::from_bits(self)
}
#[inline]
fn as_fixed_equiv(&self) -> &$Equiv<U0> {
$Equiv::wrap_ref(self)
}
#[inline]
fn as_fixed_equiv_mut(&mut self) -> &mut $Equiv<U0> {
$Equiv::wrap_mut(self)
}
#[inline]
fn from_fixed_equiv(f: $Equiv<U0>) -> $Int {
f.to_bits()
}
#[inline]
fn ref_from_fixed_equiv(f: &$Equiv<U0>) -> &$Int {
&f.bits
}
#[inline]
fn mut_from_fixed_equiv(f: &mut $Equiv<U0>) -> &mut $Int {
&mut f.bits
}
}
)*
};
}
impl_int! { i8, FixedI8 }
impl_int! { i16, FixedI16 }
impl_int! { i32, FixedI32 }
impl_int! { i64, FixedI64 }
impl_int! { i128, FixedI128 }
impl_int! { isize }
impl_int! { u8, FixedU8 }
impl_int! { u16, FixedU16 }
impl_int! { u32, FixedU32 }
impl_int! { u64, FixedU64 }
impl_int! { u128, FixedU128 }
impl_int! { usize }
macro_rules! impl_float {
($Float:ident, $link:expr, $overflows_fmt:expr, $overflows_filt:expr) => {
impl FromFixed for $Float {
/// Converts a fixed-point number to a floating-point number.
///
/// Rounding is to the nearest, with ties rounded to even.
///
/// # Panics
///
/// When debug assertions are enabled, panics if the value
/// does not fit. When debug assertions are not enabled,
/// the wrapped value can be returned, but it is not
/// considered a breaking change if in the future it
/// panics; if wrapping is required use
/// [`wrapping_from_fixed`] instead.
///
/// [`wrapping_from_fixed`]: FromFixed::wrapping_from_fixed
#[inline]
#[track_caller]
fn from_fixed<F: Fixed>(src: F) -> Self {
let helper = src.to_float_helper(Private);
float_helper::$Float::from_to_float_helper(helper, F::FRAC_NBITS, F::INT_NBITS)
}
/// Converts a fixed-point number to a floating-point
/// number if it fits, otherwise returns [`None`].
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn checked_from_fixed<F: Fixed>(src: F) -> Option<Self> {
Some(FromFixed::from_fixed(src))
}
/// Converts a fixed-point number to a floating-point
/// number, saturating if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn saturating_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
/// Converts a fixed-point number to a floating-point
/// number, wrapping if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn wrapping_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
/// Converts a fixed-point number to a floating-point number.
///
/// Returns a [tuple] of the value and a [`bool`]
/// indicating whether an overflow has occurred. On
/// overflow, the wrapped value is returned.
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn overflowing_from_fixed<F: Fixed>(src: F) -> (Self, bool) {
(FromFixed::from_fixed(src), false)
}
/// Converts a fixed-point number to a floating-point
/// number, panicking if it does not fit.
///
/// Rounding is to the nearest, with ties rounded to even.
///
/// # Panics
///
/// Panics if the value does not fit, even when debug
/// assertions are not enabled.
#[inline]
#[track_caller]
fn unwrapped_from_fixed<F: Fixed>(src: F) -> Self {
FromFixed::from_fixed(src)
}
}
impl ToFixed for $Float {
comment! {
"Converts a floating-point number to a fixed-point number.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
When debug assertions are enabled, also panics if the value does not
fit. When debug assertions are not enabled, the wrapped value can be
returned, but it is not considered a breaking change if in the future
it panics; if wrapping is required use [`wrapping_to_fixed`] instead.
[`wrapping_to_fixed`]: ToFixed::wrapping_to_fixed
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn to_fixed<F: Fixed>(self) -> F {
let (wrapped, overflow) = ToFixed::overflowing_to_fixed(self);
debug_assert!(!overflow, $overflows_fmt, $overflows_filt(self));
wrapped
}
}
/// Converts a floating-point number to a fixed-point
/// number if it fits, otherwise returns [`None`].
///
/// Rounding is to the nearest, with ties rounded to even.
#[inline]
fn checked_to_fixed<F: Fixed>(self) -> Option<F> {
let kind = float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
match kind {
FloatKind::Finite { .. } => {
let helper = FromFloatHelper { kind };
match F::overflowing_from_float_helper(Private, helper) {
(_, true) => None,
(wrapped, false) => Some(wrapped),
}
}
_ => None,
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, saturating if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is [NaN].
[NaN]: ", $link, "::is_nan
";
#[inline]
#[track_caller]
fn saturating_to_fixed<F: Fixed>(self) -> F {
let kind =
float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
let helper = FromFloatHelper { kind };
F::saturating_from_float_helper(Private, helper)
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, wrapping if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn wrapping_to_fixed<F: Fixed>(self) -> F {
let (wrapped, _) = ToFixed::overflowing_to_fixed(self);
wrapped
}
}
comment! {
"Converts a floating-point number to a fixed-point number.
Returns a [tuple] of the fixed-point number and a [`bool`] indicating
whether an overflow has occurred. On overflow, the wrapped value is
returned.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite].
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn overflowing_to_fixed<F: Fixed>(self) -> (F, bool) {
let kind =
float_helper::$Float::to_float_kind(self, F::FRAC_NBITS, F::INT_NBITS);
let helper = FromFloatHelper { kind };
F::overflowing_from_float_helper(Private, helper)
}
}
comment! {
"Converts a floating-point number to a fixed-point
number, panicking if it does not fit.
Rounding is to the nearest, with ties rounded to even.
# Panics
Panics if `self` is not [finite] or if the value does not fit, even
when debug assertions are not enabled.
[finite]: ", $link, "::is_finite
";
#[inline]
#[track_caller]
fn unwrapped_to_fixed<F: Fixed>(self) -> F {
match ToFixed::overflowing_to_fixed(self) {
(val, false) => val,
(_, true) => panic!("overflow"),
}
}
}
}
};
}
#[cfg(feature = "nightly-float")]
impl_float! { f16, "f16", "f16::from_bits(0x{:04X}) overflows", |x: f16| x.to_bits() }
impl_float! { half_f16, "half::f16", "{} overflows", |x| x }
impl_float! { half_bf16, "half::bf16", "{} overflows", |x| x }
impl_float! { f32, "f32", "{} overflows", |x| x }
impl_float! { f64, "f64", "{} overflows", |x| x }
#[cfg(feature = "nightly-float")]
impl_float! { f128, "f128", "f128::from_bits(0x{:032X}) overflows", |x: f128| x.to_bits() }
impl_float! { F128, "F128", "F128::from_bits(0x{:032X}) overflows", |x: F128| x.to_bits() }
impl_float! { F128Bits, "f64", "F128Bits({}) overflows", |x: F128Bits| x.0 }
+2257
View File
File diff suppressed because it is too large. Load diff
+171
View File
@@ -0,0 +1,171 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::types::extra::{LeEqU8, LeEqU16, LeEqU32, LeEqU64, LeEqU128};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128, Unwrapped, Wrapping,
};
use serde::de::{Deserialize, Deserializer, Error as DeError};
use serde::ser::{Serialize, Serializer};
#[cfg(not(feature = "serde-str"))]
use {
core::fmt::{Formatter, Result as FmtResult},
serde::de::{MapAccess, SeqAccess, Visitor},
serde::ser::SerializeStruct,
};
macro_rules! serde_fixed {
($Fixed:ident($LeEqU:ident) is $TBits:ident name $Name:expr) => {
impl<Frac: $LeEqU> Serialize for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let bits = self.to_bits();
let mut state = serializer.serialize_struct($Name, 1)?;
state.serialize_field("bits", &bits)?;
state.end()
}
#[cfg(feature = "serde-str")]
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if serializer.is_human_readable() {
self.to_string().serialize(serializer)
} else {
self.to_bits().serialize(serializer)
}
}
}
impl<Frac: $LeEqU> Serialize for Wrapping<$Fixed<Frac>> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<Frac: $LeEqU> Serialize for Unwrapped<$Fixed<Frac>> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for $Fixed<Frac> {
#[cfg(not(feature = "serde-str"))]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct FixedVisitor;
impl<'de> Visitor<'de> for FixedVisitor {
type Value = $TBits;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
formatter.write_str("struct ")?;
formatter.write_str($Name)
}
fn visit_seq<V: SeqAccess<'de>>(self, mut seq: V) -> Result<$TBits, V::Error> {
let bits = seq
.next_element()?
.ok_or_else(|| DeError::invalid_length(0, &self))?;
Ok(bits)
}
fn visit_map<V: MapAccess<'de>>(self, mut map: V) -> Result<$TBits, V::Error> {
let mut bits = None;
while let Some(key) = map.next_key()? {
match key {
Field::Bits => {
if bits.is_some() {
return Err(DeError::duplicate_field("bits"));
}
bits = Some(map.next_value()?);
}
}
}
let bits = bits.ok_or_else(|| DeError::missing_field("bits"))?;
Ok(bits)
}
}
let bits = deserializer.deserialize_struct($Name, FIELDS, FixedVisitor)?;
Ok($Fixed::from_bits(bits))
}
#[cfg(feature = "serde-str")]
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
if deserializer.is_human_readable() {
String::deserialize(deserializer)?
.parse()
.map_err(|e| DeError::custom(format_args!("parse error: {e}")))
} else {
$TBits::deserialize(deserializer).map($Fixed::from_bits)
}
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for Wrapping<$Fixed<Frac>> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
$Fixed::deserialize(deserializer).map(Wrapping)
}
}
impl<'de, Frac: $LeEqU> Deserialize<'de> for Unwrapped<$Fixed<Frac>> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
$Fixed::deserialize(deserializer).map(Unwrapped)
}
}
};
}
serde_fixed! { FixedI8(LeEqU8) is i8 name "FixedI8" }
serde_fixed! { FixedI16(LeEqU16) is i16 name "FixedI16" }
serde_fixed! { FixedI32(LeEqU32) is i32 name "FixedI32" }
serde_fixed! { FixedI64(LeEqU64) is i64 name "FixedI64" }
serde_fixed! { FixedI128(LeEqU128) is i128 name "FixedI128" }
serde_fixed! { FixedU8(LeEqU8) is u8 name "FixedU8" }
serde_fixed! { FixedU16(LeEqU16) is u16 name "FixedU16" }
serde_fixed! { FixedU32(LeEqU32) is u32 name "FixedU32" }
serde_fixed! { FixedU64(LeEqU64) is u64 name "FixedU64" }
serde_fixed! { FixedU128(LeEqU128) is u128 name "FixedU128" }
#[cfg(not(feature = "serde-str"))]
const FIELDS: &[&str] = &["bits"];
#[cfg(not(feature = "serde-str"))]
enum Field {
Bits,
}
#[cfg(not(feature = "serde-str"))]
impl<'de> Deserialize<'de> for Field {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Field, D::Error> {
struct FieldVisitor;
impl Visitor<'_> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut Formatter) -> FmtResult {
formatter.write_str("`bits`")
}
fn visit_str<E: DeError>(self, value: &str) -> Result<Field, E> {
match value {
"bits" => Ok(Field::Bits),
_ => Err(DeError::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
+521
View File
@@ -0,0 +1,521 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use core::num::NonZero;
// The mathematics below is based on the comments from FreeBSD's
// /usr/src/lib/msun/src/e_sqrt.c.
// q_i = sqrt(y) truncated to i bits after point.
// q_0 = 1
// y_i = 2^i (y - q_i^2)
// y_0 = y - 1
//
// If (q_i + 1>>(i+1))^2 <= y:
// q_(i+1) = q_i + 1>>(i+1)
// Else:
// q_(i+1) = q_i
//
// Equivalently:
//
// If q_i + 1>>(i+2) <= y_i:
// q_(i+1) = q_i + 1>>(i+1)
// y_(i+1) = 2 (y_i - q_i - 1>>(i+2))
// Else:
// q_i+1 = q_i
// y_i+1 = 2 y_i
//
// * Iterations do not include q_0, y_0 as they are initialization.
// * i goes from 1 to iter.
// * Both q and y are stored with 2 integer bits. q is in range [1, 2); y is
// in range [1, 4).
// * 1>>(i+2) needs special code when i + 2 > nbits - 2. Since maximum iter is
// nbits - 1, i + 2 can be nbits + 1 which is > nbits - 2 by 2.
//
// Some examples for u8.
//
// frac_nbits == 0:
// sip = 4 - leading / 2
// 4 significant int pairs: 0100 0000. -> 0000 1000. (y << 0, 3 iter, q >> 3)
// 3 significant int pairs: 0001 0000. -> 0000 0100. (y << 2, 2 iter, q >> 4)
// 2 significant int pairs: 0000 0100. -> 0000 0010. (y << 4, 1 iter, q >> 5)
// 1 significant int pairs: 0000 0001. -> 0000 0001. (y << 6, 0 iter, q >> 6)
// General: y << 8 - 2sip, -1 + sip iter, q >> 7 - sip
//
// frac_nbits == 1:
// sip = 4 - (leading + 1) / 2
// 4 significant int pairs: 100 0000.0 -> 000 1000.0 (y >> 1, 4 iter, q >> 2)
// 3 significant int pairs: 001 0000.0 -> 000 0100.0 (y << 1, 3 iter, q >> 3)
// 2 significant int pairs: 000 0100.0 -> 000 0010.0 (y << 3, 2 iter, q >> 4)
// 1 significant int pairs: 000 0001.0 -> 000 0001.0 (y << 5, 1 iter, q >> 5)
// 0 significant int pairs: 000 0000.1 -> 000 0000.1 (y << 7, 0 iter, q >> 6)
// General: y << 7 - 2sip, sip iter, q >> 6 - sip
//
// frac_nbits == 2:
// sip = 3 - leading / 2
// 3 significant int pairs: 01 0000.00 -> 00 0100.00 (y << 0, 4 iter, q >> 2)
// 2 significant int pairs: 00 0100.00 -> 00 0010.00 (y << 2, 3 iter, q >> 3)
// 1 significant int pairs: 00 0001.00 -> 00 0001.00 (y << 4, 2 iter, q >> 4)
// 0 significant int pairs: 00 0000.01 -> 00 0000.10 (y << 6, 1 iter, q >> 5)
// General: y << 6 - 2sip, 1 + sip iter, q >> 5 - sip
//
// frac_nbits = 3:
// sip = 3 - (leading + 1) / 2
// 3 significant int pairs: 1 0000.000 -> 0 0100.000 (y >> 1, 5 iter, q >> 1)
// 2 significant int pairs: 0 0100.000 -> 0 0010.000 (y << 1, 4 iter, q >> 2)
// 1 significant int pairs: 0 0001.000 -> 0 0001.000 (y << 3, 3 iter, q >> 3)
// 0 significant int pairs: 0 0000.010 -> 0 0000.100 (y << 5, 2 iter, q >> 4)
// -1 significant int pairs: 0 0000.001 -> 0 0000.010 (y << 7, 1 iter, q >> 5)
// General: y << 5 - 2sip, 2 + sip iter, q >> 4 - sip
//
// frac_nbits == 4:
// sip = 2 - leading / 2
// 2 significant int pairs: 0100.0000 -> 0010.0000 (y << 0, 5 iter, q >> 1)
// 1 significant int pairs: 0001.0000 -> 0001.0000 (y << 2, 4 iter, q >> 2)
// 0 significant int pairs: 0000.0100 -> 0000.1000 (y << 4, 3 iter, q >> 3)
// -1 significant int pairs: 0000.0001 -> 0000.0100 (y << 6, 2 iter, q >> 4)
// General: y << 4 - 2sip, 3 + sip iter, q >> 3 - sip
//
// frac_nbits = 5:
// sip = 2 - (leading + 1) / 2
// 2 significant int pairs: 100.0000 0 -> 010.0000 0 (y >> 1, 6 iter, q >> 0)
// 1 significant int pairs: 001.0000 0 -> 001.0000 0 (y << 1, 5 iter, q >> 1)
// 0 significant int pairs: 000.0100 0 -> 000.1000 0 (y << 3, 4 iter, q >> 2)
// -1 significant int pairs: 000.0001 0 -> 000.0100 0 (y << 5, 3 iter, q >> 3)
// -2 significant int pairs: 000.0000 1 -> 000.0010 1 (y << 7, 2 iter, q >> 4)
// General: y << 3 - 2sip, 4 + sip iter, q >> 2 - sip
//
// frac_nbits == 6:
// sip = 1 - leading / 2
// 1 significant int pairs: 01.0000 00 -> 01.0000 00 (y << 0, 6 iter, q >> 0)
// 0 significant int pairs: 00.0100 00 -> 00.1000 00 (y << 2, 5 iter, q >> 1)
// -1 significant int pairs: 00.0001 00 -> 00.0100 00 (y << 4, 4 iter, q >> 2)
// -2 significant int pairs: 00.0000 01 -> 00.0010 00 (y << 6, 3 iter, q >> 3)
// General: y << 2 - 2sip, 5 + sip iter, q >> 1 - sip
//
// frac_nbits == 7:
// sip = 1 - (leading + 1) / 2
// 1 significant int pairs: 1.0000 000 -> 1.0000 000 (y >> 1, 7 iter, q << 1)
// 0 significant int pairs: 0.0100 000 -> 0.1000 000 (y << 1, 6 iter, q >> 0)
// -1 significant int pairs: 0.0001 000 -> 0.0100 000 (y << 3, 5 iter, q >> 1)
// -2 significant int pairs: 0.0000 010 -> 0.0010 000 (y << 5, 4 iter, q >> 2)
// -3 significant int pairs: 0.0000 001 -> 0.0001 011 (y << 7, 3 iter, q >> 3)
// General: y << 1 - 2sip, 6 + sip iter, q >> -sip
//
// frac_nbits == 8:
// sip = 0 - leading / 2
// 0 significant int pairs: .0100 0000 -> .1000 0000 (y << 0, 7 iter, q << 1)
// -1 significant int pairs: .0001 0000 -> .0100 0000 (y << 2, 6 iter, q >> 0)
// -2 significant int pairs: .0000 0100 -> .0010 0000 (y << 4, 5 iter, q >> 1)
// -3 significant int pairs: .0000 0001 -> .0001 0000 (y << 6, 4 iter, q >> 2)
// General: y << -2sip, 7 + sip iter, q >> -1 - sip
//
// General:
// Even frac_nbits:
// sip = int_nbits / 2 - leading / 2
// Odd frac_nbits:
// sip = (int_nbits + 1) / 2 - (leading + 1) / 2
// Then:
// y << int_nbits - 2sip, frac_nbits - 1 + sip iter, q >> int_nbits - 1 - sip
macro_rules! impl_sqrt {
($u:ident) => {
pub const fn $u(val: NonZero<$u>, frac_nbits: u32) -> $u {
let int_nbits = $u::BITS - frac_nbits;
let odd_frac_nbits = frac_nbits % 2 != 0;
let leading = val.leading_zeros();
let sig_int_pairs = if odd_frac_nbits {
int_nbits.div_ceil(2) as i32 - leading.div_ceil(2) as i32
} else {
(int_nbits / 2) as i32 - (leading / 2) as i32
};
let mut i = 1;
let mut q_i = 1 << ($u::BITS - 2);
let mut next_bit = q_i >> 1;
let mut y_i = val.get();
let input_shl = int_nbits as i32 - sig_int_pairs * 2;
if input_shl < 0 {
// This can only happen when we have odd frac_nbits and the most
// significant bit is set. We would need to shift right by 1.
debug_assert!(input_shl == -1);
// Do one iteration here as this is a special case.
// In this special case, y is in the range [1, 2) instead of [1, 4),
// and q is in the range [1, √2) instead of [1, 2).
// Therefore, q_1 is always 0b1.0, and never 0b1.1.
// Since q_0 = q_1 = 1, y_1 = 2 × (y - q_1^2) = 2 × y - 2 × q_i.
// Since input_shl is -1, its effect is cancelled out by 2 × y,
// and we only need to subtract 2 × q_i from y_i.
y_i -= 2 * q_i;
next_bit >>= 1;
i += 1;
} else {
y_i <<= input_shl;
y_i -= q_i;
};
let iters = (frac_nbits as i32 - 1 + sig_int_pairs) as u32;
while i <= iters {
let d = next_bit >> 1;
if d == 0 {
if i == iters {
// Here result_shr must be 0, otherwise we wouldn't have
// room to potentially insert one extra bit.
debug_assert!(int_nbits as i32 - 1 - sig_int_pairs == 0);
// d == 0.5, so we really need q_i + 0.5 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
q_i += 1;
}
return q_i;
}
debug_assert!(i == iters - 1);
// Here result_shr must be -1, otherwise we wouldn't have
// room to potentially insert two extra bits.
debug_assert!(int_nbits as i32 - 1 - sig_int_pairs == -1);
// d == 0.5, so we really need q_i + 0.5 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
// We cannot subtract d == 0.5 from y_i immediately, so
// we subtract 1 from y_i before the multiplication by 2
// and then add 1 back. (There may be a potential overflow
// if we multiply y_i by 2 and then subtract 1.)
y_i -= q_i + 1;
y_i *= 2;
y_i += 1;
q_i += 1;
} else {
y_i *= 2;
}
// d == 0.25, so we really need q_i + 0.25 <= y_i,
// which can be obtained with q_i < y_i
if q_i < y_i {
// We cannot add next_bit == 0.5 to q_i immediately, so
// we add 1 to q_i after the left shift.
q_i = (q_i << 1) + 1;
} else {
q_i <<= 1;
}
return q_i;
}
if q_i + d <= y_i {
y_i -= q_i + d;
q_i += next_bit;
}
y_i *= 2;
next_bit = d;
i += 1;
}
let result_shr = int_nbits as i32 - 1 - sig_int_pairs;
q_i >> result_shr
}
};
}
impl_sqrt! { u8 }
impl_sqrt! { u16 }
impl_sqrt! { u32 }
impl_sqrt! { u64 }
impl_sqrt! { u128 }
#[cfg(test)]
mod tests {
use crate::types::extra::{
U0, U1, U3, U4, U5, U6, U7, U8, U9, U13, U14, U15, U16, U17, U29, U30, U31, U32, U33, U61,
U62, U63, U64, U65, U125, U126, U127, U128,
};
use crate::{
FixedI8, FixedI16, FixedI32, FixedI64, FixedI128, FixedU8, FixedU16, FixedU32, FixedU64,
FixedU128,
};
macro_rules! check_sqrt {
($val:expr) => {{
let sqrt = $val.sqrt();
assert!(sqrt * sqrt <= $val);
let delta = $val.wrapping_neg().wrapping_sub(!$val);
if let Some(sqrt_delta) = sqrt.checked_add(delta) {
if let Some(prod) = sqrt_delta.checked_mul(sqrt_delta) {
assert!(prod >= $val);
}
}
}};
}
#[test]
fn check_max_8() {
check_sqrt!(FixedU8::<U0>::MAX);
check_sqrt!(FixedU8::<U1>::MAX);
check_sqrt!(FixedU8::<U3>::MAX);
check_sqrt!(FixedU8::<U4>::MAX);
check_sqrt!(FixedU8::<U5>::MAX);
check_sqrt!(FixedU8::<U7>::MAX);
check_sqrt!(FixedU8::<U8>::MAX);
assert_eq!(FixedU8::<U8>::MAX.sqrt(), FixedU8::<U8>::MAX);
check_sqrt!(FixedI8::<U0>::MAX);
check_sqrt!(FixedI8::<U1>::MAX);
check_sqrt!(FixedI8::<U3>::MAX);
check_sqrt!(FixedI8::<U4>::MAX);
check_sqrt!(FixedI8::<U5>::MAX);
check_sqrt!(FixedI8::<U7>::MAX);
assert!(FixedI8::<U8>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_16() {
check_sqrt!(FixedU16::<U0>::MAX);
check_sqrt!(FixedU16::<U1>::MAX);
check_sqrt!(FixedU16::<U7>::MAX);
check_sqrt!(FixedU16::<U8>::MAX);
check_sqrt!(FixedU16::<U9>::MAX);
check_sqrt!(FixedU16::<U15>::MAX);
check_sqrt!(FixedU16::<U16>::MAX);
assert_eq!(FixedU16::<U16>::MAX.sqrt(), FixedU16::<U16>::MAX);
check_sqrt!(FixedI16::<U0>::MAX);
check_sqrt!(FixedI16::<U1>::MAX);
check_sqrt!(FixedI16::<U7>::MAX);
check_sqrt!(FixedI16::<U8>::MAX);
check_sqrt!(FixedI16::<U9>::MAX);
check_sqrt!(FixedI16::<U15>::MAX);
assert!(FixedI16::<U16>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_32() {
check_sqrt!(FixedU32::<U0>::MAX);
check_sqrt!(FixedU32::<U1>::MAX);
check_sqrt!(FixedU32::<U15>::MAX);
check_sqrt!(FixedU32::<U16>::MAX);
check_sqrt!(FixedU32::<U17>::MAX);
check_sqrt!(FixedU32::<U31>::MAX);
check_sqrt!(FixedU32::<U32>::MAX);
assert_eq!(FixedU32::<U32>::MAX.sqrt(), FixedU32::<U32>::MAX);
check_sqrt!(FixedI32::<U0>::MAX);
check_sqrt!(FixedI32::<U1>::MAX);
check_sqrt!(FixedI32::<U15>::MAX);
check_sqrt!(FixedI32::<U16>::MAX);
check_sqrt!(FixedI32::<U17>::MAX);
check_sqrt!(FixedI32::<U31>::MAX);
assert!(FixedI32::<U32>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_64() {
check_sqrt!(FixedU64::<U0>::MAX);
check_sqrt!(FixedU64::<U1>::MAX);
check_sqrt!(FixedU64::<U31>::MAX);
check_sqrt!(FixedU64::<U32>::MAX);
check_sqrt!(FixedU64::<U33>::MAX);
check_sqrt!(FixedU64::<U63>::MAX);
check_sqrt!(FixedU64::<U64>::MAX);
assert_eq!(FixedU64::<U64>::MAX.sqrt(), FixedU64::<U64>::MAX);
check_sqrt!(FixedI64::<U0>::MAX);
check_sqrt!(FixedI64::<U1>::MAX);
check_sqrt!(FixedI64::<U31>::MAX);
check_sqrt!(FixedI64::<U32>::MAX);
check_sqrt!(FixedI64::<U33>::MAX);
check_sqrt!(FixedI64::<U63>::MAX);
assert!(FixedI64::<U64>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_max_128() {
check_sqrt!(FixedU128::<U0>::MAX);
check_sqrt!(FixedU128::<U1>::MAX);
check_sqrt!(FixedU128::<U63>::MAX);
check_sqrt!(FixedU128::<U64>::MAX);
check_sqrt!(FixedU128::<U65>::MAX);
check_sqrt!(FixedU128::<U127>::MAX);
check_sqrt!(FixedU128::<U128>::MAX);
assert_eq!(FixedU128::<U128>::MAX.sqrt(), FixedU128::<U128>::MAX);
check_sqrt!(FixedI128::<U0>::MAX);
check_sqrt!(FixedI128::<U1>::MAX);
check_sqrt!(FixedI128::<U63>::MAX);
check_sqrt!(FixedI128::<U64>::MAX);
check_sqrt!(FixedI128::<U65>::MAX);
check_sqrt!(FixedI128::<U127>::MAX);
assert!(FixedI128::<U128>::MAX.checked_sqrt().is_none());
}
#[test]
fn check_two_8() {
assert_eq!(FixedU8::<U0>::from_num(2).sqrt(), FixedU8::<U0>::SQRT_2);
assert_eq!(FixedU8::<U1>::from_num(2).sqrt(), FixedU8::<U1>::SQRT_2);
assert_eq!(FixedU8::<U3>::from_num(2).sqrt(), FixedU8::<U3>::SQRT_2);
assert_eq!(FixedU8::<U4>::from_num(2).sqrt(), FixedU8::<U4>::SQRT_2);
assert_eq!(FixedU8::<U5>::from_num(2).sqrt(), FixedU8::<U5>::SQRT_2);
assert_eq!(FixedU8::<U6>::from_num(2).sqrt(), FixedU8::<U6>::SQRT_2);
assert!(
FixedU8::<U7>::MAX.sqrt() == FixedU8::<U7>::SQRT_2 - FixedU8::<U7>::DELTA
|| FixedU8::<U7>::MAX.sqrt() == FixedU8::<U7>::SQRT_2
);
assert_eq!(FixedI8::<U0>::from_num(2).sqrt(), FixedI8::<U0>::SQRT_2);
assert_eq!(FixedI8::<U1>::from_num(2).sqrt(), FixedI8::<U1>::SQRT_2);
assert_eq!(FixedI8::<U3>::from_num(2).sqrt(), FixedI8::<U3>::SQRT_2);
assert_eq!(FixedI8::<U4>::from_num(2).sqrt(), FixedI8::<U4>::SQRT_2);
assert_eq!(FixedI8::<U5>::from_num(2).sqrt(), FixedI8::<U5>::SQRT_2);
assert!(
FixedI8::<U6>::MAX.sqrt() == FixedI8::<U6>::SQRT_2 - FixedI8::<U6>::DELTA
|| FixedI8::<U6>::MAX.sqrt() == FixedI8::<U6>::SQRT_2
);
}
#[test]
fn check_two_16() {
assert_eq!(FixedU16::<U0>::from_num(2).sqrt(), FixedU16::<U0>::SQRT_2);
assert_eq!(FixedU16::<U1>::from_num(2).sqrt(), FixedU16::<U1>::SQRT_2);
assert_eq!(FixedU16::<U7>::from_num(2).sqrt(), FixedU16::<U7>::SQRT_2);
assert_eq!(FixedU16::<U8>::from_num(2).sqrt(), FixedU16::<U8>::SQRT_2);
assert_eq!(FixedU16::<U9>::from_num(2).sqrt(), FixedU16::<U9>::SQRT_2);
assert_eq!(FixedU16::<U13>::from_num(2).sqrt(), FixedU16::<U13>::SQRT_2);
assert_eq!(FixedU16::<U14>::from_num(2).sqrt(), FixedU16::<U14>::SQRT_2);
assert!(
FixedU16::<U15>::MAX.sqrt() == FixedU16::<U15>::SQRT_2 - FixedU16::<U15>::DELTA
|| FixedU16::<U15>::MAX.sqrt() == FixedU16::<U15>::SQRT_2
);
assert_eq!(FixedI16::<U0>::from_num(2).sqrt(), FixedI16::<U0>::SQRT_2);
assert_eq!(FixedI16::<U1>::from_num(2).sqrt(), FixedI16::<U1>::SQRT_2);
assert_eq!(FixedI16::<U7>::from_num(2).sqrt(), FixedI16::<U7>::SQRT_2);
assert_eq!(FixedI16::<U8>::from_num(2).sqrt(), FixedI16::<U8>::SQRT_2);
assert_eq!(FixedI16::<U9>::from_num(2).sqrt(), FixedI16::<U9>::SQRT_2);
assert_eq!(FixedI16::<U13>::from_num(2).sqrt(), FixedI16::<U13>::SQRT_2);
assert!(
FixedI16::<U14>::MAX.sqrt() == FixedI16::<U14>::SQRT_2 - FixedI16::<U14>::DELTA
|| FixedI16::<U14>::MAX.sqrt() == FixedI16::<U14>::SQRT_2
);
}
#[test]
fn check_two_32() {
assert_eq!(FixedU32::<U0>::from_num(2).sqrt(), FixedU32::<U0>::SQRT_2);
assert_eq!(FixedU32::<U1>::from_num(2).sqrt(), FixedU32::<U1>::SQRT_2);
assert_eq!(FixedU32::<U15>::from_num(2).sqrt(), FixedU32::<U15>::SQRT_2);
assert_eq!(FixedU32::<U16>::from_num(2).sqrt(), FixedU32::<U16>::SQRT_2);
assert_eq!(FixedU32::<U17>::from_num(2).sqrt(), FixedU32::<U17>::SQRT_2);
assert_eq!(FixedU32::<U29>::from_num(2).sqrt(), FixedU32::<U29>::SQRT_2);
assert_eq!(FixedU32::<U30>::from_num(2).sqrt(), FixedU32::<U30>::SQRT_2);
assert!(
FixedU32::<U31>::MAX.sqrt() == FixedU32::<U31>::SQRT_2 - FixedU32::<U31>::DELTA
|| FixedU32::<U31>::MAX.sqrt() == FixedU32::<U31>::SQRT_2
);
assert_eq!(FixedI32::<U0>::from_num(2).sqrt(), FixedI32::<U0>::SQRT_2);
assert_eq!(FixedI32::<U1>::from_num(2).sqrt(), FixedI32::<U1>::SQRT_2);
assert_eq!(FixedI32::<U15>::from_num(2).sqrt(), FixedI32::<U15>::SQRT_2);
assert_eq!(FixedI32::<U16>::from_num(2).sqrt(), FixedI32::<U16>::SQRT_2);
assert_eq!(FixedI32::<U17>::from_num(2).sqrt(), FixedI32::<U17>::SQRT_2);
assert_eq!(FixedI32::<U29>::from_num(2).sqrt(), FixedI32::<U29>::SQRT_2);
assert!(
FixedI32::<U30>::MAX.sqrt() == FixedI32::<U30>::SQRT_2 - FixedI32::<U30>::DELTA
|| FixedI32::<U30>::MAX.sqrt() == FixedI32::<U30>::SQRT_2
);
}
#[test]
fn check_two_64() {
assert_eq!(FixedU64::<U0>::from_num(2).sqrt(), FixedU64::<U0>::SQRT_2);
assert_eq!(FixedU64::<U1>::from_num(2).sqrt(), FixedU64::<U1>::SQRT_2);
assert_eq!(FixedU64::<U31>::from_num(2).sqrt(), FixedU64::<U31>::SQRT_2);
assert_eq!(FixedU64::<U32>::from_num(2).sqrt(), FixedU64::<U32>::SQRT_2);
assert_eq!(FixedU64::<U33>::from_num(2).sqrt(), FixedU64::<U33>::SQRT_2);
assert_eq!(FixedU64::<U61>::from_num(2).sqrt(), FixedU64::<U61>::SQRT_2);
assert_eq!(FixedU64::<U62>::from_num(2).sqrt(), FixedU64::<U62>::SQRT_2);
assert!(
FixedU64::<U63>::MAX.sqrt() == FixedU64::<U63>::SQRT_2 - FixedU64::<U63>::DELTA
|| FixedU64::<U63>::MAX.sqrt() == FixedU64::<U63>::SQRT_2
);
assert_eq!(FixedI64::<U0>::from_num(2).sqrt(), FixedI64::<U0>::SQRT_2);
assert_eq!(FixedI64::<U1>::from_num(2).sqrt(), FixedI64::<U1>::SQRT_2);
assert_eq!(FixedI64::<U31>::from_num(2).sqrt(), FixedI64::<U31>::SQRT_2);
assert_eq!(FixedI64::<U32>::from_num(2).sqrt(), FixedI64::<U32>::SQRT_2);
assert_eq!(FixedI64::<U33>::from_num(2).sqrt(), FixedI64::<U33>::SQRT_2);
assert_eq!(FixedI64::<U61>::from_num(2).sqrt(), FixedI64::<U61>::SQRT_2);
assert!(
FixedI64::<U62>::MAX.sqrt() == FixedI64::<U62>::SQRT_2 - FixedI64::<U62>::DELTA
|| FixedI64::<U62>::MAX.sqrt() == FixedI64::<U62>::SQRT_2
);
}
#[test]
fn check_two_128() {
assert_eq!(FixedU128::<U0>::from_num(2).sqrt(), FixedU128::<U0>::SQRT_2);
assert_eq!(FixedU128::<U1>::from_num(2).sqrt(), FixedU128::<U1>::SQRT_2);
assert_eq!(
FixedU128::<U63>::from_num(2).sqrt(),
FixedU128::<U63>::SQRT_2
);
assert_eq!(
FixedU128::<U64>::from_num(2).sqrt(),
FixedU128::<U64>::SQRT_2
);
assert_eq!(
FixedU128::<U65>::from_num(2).sqrt(),
FixedU128::<U65>::SQRT_2
);
assert_eq!(
FixedU128::<U125>::from_num(2).sqrt(),
FixedU128::<U125>::SQRT_2
);
assert_eq!(
FixedU128::<U126>::from_num(2).sqrt(),
FixedU128::<U126>::SQRT_2
);
assert!(
FixedU128::<U127>::MAX.sqrt() == FixedU128::<U127>::SQRT_2 - FixedU128::<U127>::DELTA
|| FixedU128::<U127>::MAX.sqrt() == FixedU128::<U127>::SQRT_2
);
assert_eq!(FixedI128::<U0>::from_num(2).sqrt(), FixedI128::<U0>::SQRT_2);
assert_eq!(FixedI128::<U1>::from_num(2).sqrt(), FixedI128::<U1>::SQRT_2);
assert_eq!(
FixedI128::<U63>::from_num(2).sqrt(),
FixedI128::<U63>::SQRT_2
);
assert_eq!(
FixedI128::<U64>::from_num(2).sqrt(),
FixedI128::<U64>::SQRT_2
);
assert_eq!(
FixedI128::<U65>::from_num(2).sqrt(),
FixedI128::<U65>::SQRT_2
);
assert_eq!(
FixedI128::<U125>::from_num(2).sqrt(),
FixedI128::<U125>::SQRT_2
);
assert!(
FixedI128::<U126>::MAX.sqrt() == FixedI128::<U126>::SQRT_2 - FixedI128::<U126>::DELTA
|| FixedI128::<U126>::MAX.sqrt() == FixedI128::<U126>::SQRT_2
);
}
}
+4926
View File
File diff suppressed because it is too large. Load diff
+322
View File
@@ -0,0 +1,322 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
use crate::traits::FixedEquiv;
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
use az::{
Cast, CastFrom, CheckedCast, CheckedCastFrom, OverflowingCast, OverflowingCastFrom,
SaturatingCast, SaturatingCastFrom, UnwrappedCast, UnwrappedCastFrom, WrappingCast,
WrappingCastFrom,
};
#[cfg(feature = "borsh")]
use borsh::{BorshDeserialize, BorshSerialize};
use bytemuck::{Contiguous, Pod};
use core::fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
use core::hash::Hash;
use core::iter::{Product, Sum};
use core::num::ParseIntError;
use core::ops::{
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div, DivAssign,
Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub, SubAssign,
};
use core::str::FromStr;
#[cfg(feature = "num-traits")]
use num_traits::{
Num, NumAssignRef, NumRef,
cast::{AsPrimitive, FromPrimitive},
int::PrimInt,
ops::{
checked::{CheckedNeg, CheckedRem, CheckedShl, CheckedShr},
euclid::{CheckedEuclid, Euclid},
mul_add::{MulAdd, MulAddAssign},
overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub},
saturating::{SaturatingAdd, SaturatingMul, SaturatingSub},
wrapping::{WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub},
},
};
#[cfg(feature = "serde")]
use serde::{de::Deserialize, ser::Serialize};
macro_rules! impl_bits {
($Bits:ident) => {
impl FixedBits for $Bits {
const MIN: $Bits = $Bits::MIN;
const MAX: $Bits = $Bits::MAX;
const IS_SIGNED: bool = $Bits::MIN != 0;
const BITS: u32 = $Bits::BITS;
}
impl Sealed for $Bits {}
impl FixedBitsCast<i8> for $Bits {}
impl FixedBitsCast<i16> for $Bits {}
impl FixedBitsCast<i32> for $Bits {}
impl FixedBitsCast<i64> for $Bits {}
impl FixedBitsCast<i128> for $Bits {}
impl FixedBitsCast<isize> for $Bits {}
impl FixedBitsCast<u8> for $Bits {}
impl FixedBitsCast<u16> for $Bits {}
impl FixedBitsCast<u32> for $Bits {}
impl FixedBitsCast<u64> for $Bits {}
impl FixedBitsCast<u128> for $Bits {}
impl FixedBitsCast<usize> for $Bits {}
impl FixedBitsOptionalArbitrary for $Bits {}
impl FixedBitsOptionalBorsh for $Bits {}
impl FixedBitsOptionalNum for $Bits {}
impl FixedBitsOptionalSerde for $Bits {}
};
}
/// This trait is implemented for <code>[Fixed]::[Bits]</code> for all
/// fixed-point numbers.
///
/// This provides some facilities to manipulate bits in generic functions.
///
/// This trait is sealed and cannot be implemented for more types; it is
/// implemented for [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`u8`],
/// [`u16`], [`u32`], [`u64`], and [`u128`].
///
/// # Examples
///
/// ```rust
/// use az::OverflowingAs;
/// use fixed::traits::Fixed;
/// use fixed::types::*;
/// fn limited_positive_bits<F: Fixed>(fixed: F) -> Option<u32> {
/// let bits = fixed.to_bits();
/// match bits.overflowing_as::<u32>() {
/// (wrapped, false) => Some(wrapped),
/// (_, true) => None,
/// }
/// }
/// assert_eq!(limited_positive_bits(I16F16::from_bits(100)), Some(100));
/// assert_eq!(limited_positive_bits(I16F16::from_bits(-100)), None);
/// ```
///
/// [Bits]: crate::traits::Fixed::Bits
/// [Fixed]: crate::traits::Fixed
pub trait FixedBits
where
Self: Default + Hash + Ord,
Self: Contiguous + Pod,
Self: Debug + Display + LowerExp + UpperExp,
Self: Binary + Octal + LowerHex + UpperHex,
Self: FromStr<Err = ParseIntError>,
Self: Add<Output = Self> + AddAssign,
Self: Sub<Output = Self> + SubAssign,
Self: Mul<Output = Self> + MulAssign,
Self: Div<Output = Self> + DivAssign,
Self: Rem<Output = Self> + RemAssign,
Self: Not<Output = Self>,
Self: BitAnd<Output = Self> + BitAndAssign,
Self: BitOr<Output = Self> + BitOrAssign,
Self: BitXor<Output = Self> + BitXorAssign,
Self: Shl<u32, Output = Self> + ShlAssign<u32>,
Self: Shr<u32, Output = Self> + ShrAssign<u32>,
Self: Sum + Product,
Self: FixedEquiv,
Self: FixedBitsCast<i8> + FixedBitsCast<i16> + FixedBitsCast<i32>,
Self: FixedBitsCast<i64> + FixedBitsCast<i128> + FixedBitsCast<isize>,
Self: FixedBitsCast<u8> + FixedBitsCast<u16> + FixedBitsCast<u32>,
Self: FixedBitsCast<u64> + FixedBitsCast<u128> + FixedBitsCast<usize>,
Self: FixedBitsOptionalArbitrary,
Self: FixedBitsOptionalBorsh,
Self: FixedBitsOptionalNum,
Self: FixedBitsOptionalSerde,
Self: Sealed,
{
/// The smallest value that can be represented by this integer type.
const MIN: Self;
/// The largest value that can be represented by this integer type.
const MAX: Self;
/// [`true`] if this integer type is signed.
const IS_SIGNED: bool;
/// The size of this integer type in bits.
const BITS: u32;
}
/// This trait is used to provide supertraits to the [`FixedBits`] trait, and
/// should not be used directly.
pub trait FixedBitsCast<Prim>
where
Self: TryInto<Prim> + TryFrom<Prim>,
Self: Cast<Prim> + CastFrom<Prim>,
Self: CheckedCast<Prim> + CheckedCastFrom<Prim>,
Self: SaturatingCast<Prim> + SaturatingCastFrom<Prim>,
Self: WrappingCast<Prim> + WrappingCastFrom<Prim>,
Self: UnwrappedCast<Prim> + UnwrappedCastFrom<Prim>,
Self: OverflowingCast<Prim> + OverflowingCastFrom<Prim>,
Self: Sealed,
{
}
pub trait Sealed {}
impl_bits! { i8 }
impl_bits! { i16 }
impl_bits! { i32 }
impl_bits! { i64 }
impl_bits! { i128 }
impl_bits! { u8 }
impl_bits! { u16 }
impl_bits! { u32 }
impl_bits! { u64 }
impl_bits! { u128 }
#[cfg(not(feature = "arbitrary"))]
/// This trait is used to provide supertrait to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `arbitrary` feature is enabled, [`Arbitrary`] is a supertrait of
/// [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalArbitrary: Sealed {}
#[cfg(feature = "arbitrary")]
/// This trait is used to provide supertrait to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `arbitrary` feature is enabled, [`Arbitrary`] is a supertrait of
/// [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalArbitrary: Sealed
where
Self: for<'a> Arbitrary<'a>,
{
}
#[cfg(not(feature = "borsh"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `borsh` experimental feature is enabled, [`BorshSerialize`] and
/// [`BorshDeserialize`] are supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalBorsh: Sealed {}
#[cfg(feature = "borsh")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `borsh` experimental feature is enabled, [`BorshSerialize`] and
/// [`BorshDeserialize`] are supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalBorsh: Sealed
where
Self: BorshSerialize + BorshDeserialize,
{
}
#[cfg(not(feature = "num-traits"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the [`num-traits` experimental feature][experimental features] is
/// enabled, the following are supertraits of [`FixedBits`]:
///
/// * [`PrimInt`], [`FromPrimitive`]
/// * <code>[AsPrimitive][`AsPrimitive`]&lt;T></code> where `T` can be [`i8`],
/// [`i16`], [`i32`], [`i64`], [`i128`], [`isize`], [`u8`], [`u16`],
/// [`u32`], [`u64`], [`u128`], [`usize`], [`f32`] or [`f64`]
/// * [`CheckedNeg`], [`CheckedRem`], [`CheckedShl`], [`CheckedShr`]
/// * [`SaturatingAdd`], [`SaturatingSub`], [`SaturatingMul`]
/// * [`WrappingAdd`], [`WrappingSub`], [`WrappingNeg`], [`WrappingMul`],
/// [`WrappingShl`], [`WrappingShr`]
/// * [`OverflowingAdd`], [`OverflowingSub`], [`OverflowingMul`]
/// * [`Euclid`], [`CheckedEuclid`]
/// * [`MulAdd`], [`MulAddAssign`]
///
/// [experimental features]: crate#experimental-optional-features
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalNum: Sealed {}
#[cfg(feature = "num-traits")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the [`num-traits` experimental feature][experimental features] is
/// enabled, the following are supertraits of [`FixedBits`]:
///
/// * [`Num`], [`NumRef`], [`NumAssignRef`]
/// * [`PrimInt`], [`FromPrimitive`]
/// * <code>[AsPrimitive][`AsPrimitive`]&lt;T></code> where `T` can be [`i8`],
/// [`i16`], [`i32`], [`i64`], [`i128`], [`isize`], [`u8`], [`u16`],
/// [`u32`], [`u64`], [`u128`], [`usize`], [`f32`] or [`f64`]
/// * [`CheckedNeg`], [`CheckedRem`], [`CheckedShl`], [`CheckedShr`]
/// * [`SaturatingAdd`], [`SaturatingSub`], [`SaturatingMul`]
/// * [`WrappingAdd`], [`WrappingSub`], [`WrappingNeg`], [`WrappingMul`],
/// [`WrappingShl`], [`WrappingShr`]
/// * [`OverflowingAdd`], [`OverflowingSub`], [`OverflowingMul`]
/// * [`Euclid`], [`CheckedEuclid`]
/// * [`MulAdd`], [`MulAddAssign`]
///
/// [experimental features]: crate#experimental-optional-features
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalNum: Sealed
where
Self: Num<FromStrRadixErr = ParseIntError> + NumRef + NumAssignRef,
Self: PrimInt + FromPrimitive,
Self: AsPrimitive<i8> + AsPrimitive<i16> + AsPrimitive<i32>,
Self: AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<isize>,
Self: AsPrimitive<u8> + AsPrimitive<u16> + AsPrimitive<u32>,
Self: AsPrimitive<u64> + AsPrimitive<u128> + AsPrimitive<usize>,
Self: AsPrimitive<f32> + AsPrimitive<f64>,
Self: CheckedNeg + CheckedRem + CheckedShl + CheckedShr,
Self: SaturatingAdd + SaturatingSub + SaturatingMul,
Self: WrappingAdd + WrappingSub + WrappingNeg + WrappingMul,
Self: WrappingShl + WrappingShr,
Self: OverflowingAdd + OverflowingSub + OverflowingMul,
Self: Euclid + CheckedEuclid,
Self: MulAdd + MulAddAssign,
{
}
#[cfg(not(feature = "serde"))]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `serde` feature is enabled, [`Serialize`] and [`Deserialize`] are
/// supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalSerde: Sealed {}
#[cfg(feature = "serde")]
/// This trait is used to provide supertraits to the [`FixedBits`] trait
/// depending on the crates’s [optional features], and should not be used
/// directly.
///
/// If the `serde` feature is enabled, [`Serialize`] and [`Deserialize`] are
/// supertraits of [`FixedBits`].
///
/// [optional features]: crate#optional-features
pub trait FixedBitsOptionalSerde: Sealed
where
Self: Serialize + for<'de> Deserialize<'de>,
{
}
+49
View File
@@ -0,0 +1,49 @@
// Copyright © 2018–2025 Trevor Spiteri
// This library is free software: you can redistribute it and/or
// modify it under the terms of either
//
// * the Apache License, Version 2.0 or
// * the MIT License
//
// at your option.
//
// You should have recieved copies of the Apache License and the MIT
// License along with the library. If not, see
// <https://www.apache.org/licenses/LICENSE-2.0> and
// <https://opensource.org/licenses/MIT>.
/*!
Extra types that do not need to be handled directly.
These types are mostly reexports from the [*typenum* crate].
[*typenum* crate]: https://crates.io/crates/typenum
*/
pub use typenum::{
Diff, IsLessOrEqual, Sum, True, U0, U1, U2, U3, U4, U5, U6, U7, U8, U9, U10, U11, U12, U13,
U14, U15, U16, U17, U18, U19, U20, U21, U22, U23, U24, U25, U26, U27, U28, U29, U30, U31, U32,
U33, U34, U35, U36, U37, U38, U39, U40, U41, U42, U43, U44, U45, U46, U47, U48, U49, U50, U51,
U52, U53, U54, U55, U56, U57, U58, U59, U60, U61, U62, U63, U64, U65, U66, U67, U68, U69, U70,
U71, U72, U73, U74, U75, U76, U77, U78, U79, U80, U81, U82, U83, U84, U85, U86, U87, U88, U89,
U90, U91, U92, U93, U94, U95, U96, U97, U98, U99, U100, U101, U102, U103, U104, U105, U106,
U107, U108, U109, U110, U111, U112, U113, U114, U115, U116, U117, U118, U119, U120, U121, U122,
U123, U124, U125, U126, U127, U128, Unsigned,
};
/// Implemented for all [`Unsigned`] integers ≤ 8.
pub trait LeEqU8: Unsigned + IsLessOrEqual<U8, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U8, Output = True>> LeEqU8 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 16.
pub trait LeEqU16: Unsigned + IsLessOrEqual<U16, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U16, Output = True>> LeEqU16 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 32.
pub trait LeEqU32: Unsigned + IsLessOrEqual<U32, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U32, Output = True>> LeEqU32 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 64.
pub trait LeEqU64: Unsigned + IsLessOrEqual<U64, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U64, Output = True>> LeEqU64 for T {}
/// Implemented for all [`Unsigned`] integers ≤ 128.
pub trait LeEqU128: Unsigned + IsLessOrEqual<U128, Output = True> {}
impl<T: Unsigned + IsLessOrEqual<U128, Output = True>> LeEqU128 for T {}
+1084
View File
File diff suppressed because it is too large. Load diff
+2833
View File
File diff suppressed because it is too large. Load diff
+2367
View File
File diff suppressed because it is too large. Load diff