├── .github └── workflows │ ├── ci.yaml │ ├── master.yaml │ └── pr.yaml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── ci ├── benchmarks │ ├── Cargo.toml │ └── src │ │ ├── main.rs │ │ └── rng.rs ├── rustup.sh └── test_full.sh └── src ├── lib.rs └── pow.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: merge_group 3 | 4 | jobs: 5 | 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | rust: [ 12 | 1.60.0, # MSRV 13 | stable, 14 | beta, 15 | nightly 16 | ] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/cache@v4 20 | if: startsWith(matrix.rust, '1') 21 | with: 22 | path: ~/.cargo/registry/index 23 | key: cargo-${{ matrix.rust }}-git-index 24 | - uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: ${{ matrix.rust }} 27 | - run: cargo build 28 | - run: ./ci/test_full.sh 29 | 30 | # try a target that doesn't have std at all, but does have alloc 31 | no_std: 32 | name: No Std (stable) 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v4 36 | - uses: dtolnay/rust-toolchain@stable 37 | with: 38 | target: thumbv6m-none-eabi 39 | - run: cargo build --target thumbv6m-none-eabi --no-default-features --features "num-bigint serde" 40 | 41 | fmt: 42 | name: Format 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v4 46 | - uses: dtolnay/rust-toolchain@1.62.0 47 | with: 48 | components: rustfmt 49 | - run: cargo fmt --all --check 50 | 51 | # One job that "summarizes" the success state of this pipeline. This can then be added to branch 52 | # protection, rather than having to add each job separately. 53 | success: 54 | name: Success 55 | runs-on: ubuntu-latest 56 | needs: [test, no_std, fmt] 57 | # Github branch protection is exceedingly silly and treats "jobs skipped because a dependency 58 | # failed" as success. So we have to do some contortions to ensure the job fails if any of its 59 | # dependencies fails. 60 | if: always() # make sure this is never "skipped" 61 | steps: 62 | # Manually check the status of all dependencies. `if: failure()` does not work. 63 | - name: check if any dependency failed 64 | run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' 65 | -------------------------------------------------------------------------------- /.github/workflows/master.yaml: -------------------------------------------------------------------------------- 1 | name: master 2 | on: 3 | push: 4 | branches: 5 | - master 6 | schedule: 7 | - cron: '0 0 * * 0' # 00:00 Sunday 8 | 9 | jobs: 10 | 11 | test: 12 | name: Test 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | rust: [1.60.0, stable] 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/cache@v4 20 | if: startsWith(matrix.rust, '1') 21 | with: 22 | path: ~/.cargo/registry/index 23 | key: cargo-${{ matrix.rust }}-git-index 24 | - uses: dtolnay/rust-toolchain@master 25 | with: 26 | toolchain: ${{ matrix.rust }} 27 | - run: cargo build 28 | - run: ./ci/test_full.sh 29 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: 3 | pull_request: 4 | 5 | jobs: 6 | 7 | test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | rust: [1.60.0, stable] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/cache@v4 16 | if: startsWith(matrix.rust, '1') 17 | with: 18 | path: ~/.cargo/registry/index 19 | key: cargo-${{ matrix.rust }}-git-index 20 | - uses: dtolnay/rust-toolchain@master 21 | with: 22 | toolchain: ${{ matrix.rust }} 23 | - run: cargo build 24 | - run: ./ci/test_full.sh 25 | 26 | fmt: 27 | name: Format 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@1.62.0 32 | with: 33 | components: rustfmt 34 | - run: cargo fmt --all --check 35 | 36 | # One job that "summarizes" the success state of this pipeline. This can then be added to branch 37 | # protection, rather than having to add each job separately. 38 | success: 39 | name: Success 40 | runs-on: ubuntu-latest 41 | needs: [test, fmt] 42 | # Github branch protection is exceedingly silly and treats "jobs skipped because a dependency 43 | # failed" as success. So we have to do some contortions to ensure the job fails if any of its 44 | # dependencies fails. 45 | if: always() # make sure this is never "skipped" 46 | steps: 47 | # Manually check the status of all dependencies. `if: failure()` does not work. 48 | - name: check if any dependency failed 49 | run: jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["The Rust Project Developers"] 3 | description = "Rational numbers implementation for Rust" 4 | documentation = "https://docs.rs/num-rational" 5 | homepage = "https://github.com/rust-num/num-rational" 6 | keywords = ["mathematics", "numerics", "fractions"] 7 | categories = ["algorithms", "data-structures", "science", "no-std"] 8 | license = "MIT OR Apache-2.0" 9 | name = "num-rational" 10 | repository = "https://github.com/rust-num/num-rational" 11 | version = "0.4.2" 12 | readme = "README.md" 13 | exclude = ["/ci/*", "/.github/*"] 14 | edition = "2021" 15 | rust-version = "1.60" 16 | 17 | [package.metadata.docs.rs] 18 | features = ["std", "num-bigint-std", "serde"] 19 | 20 | [dependencies] 21 | 22 | [dependencies.num-bigint] 23 | optional = true 24 | version = "0.4.0" 25 | default-features = false 26 | 27 | [dependencies.num-integer] 28 | version = "0.1.42" 29 | default-features = false 30 | features = ["i128"] 31 | 32 | [dependencies.num-traits] 33 | version = "0.2.18" 34 | default-features = false 35 | features = ["i128"] 36 | 37 | [dependencies.serde] 38 | optional = true 39 | version = "1.0.0" 40 | default-features = false 41 | 42 | [features] 43 | default = ["num-bigint", "std"] 44 | std = ["num-bigint?/std", "num-integer/std", "num-traits/std"] 45 | num-bigint-std = ["num-bigint/std"] 46 | num-bigint = ["dep:num-bigint"] 47 | serde = ["dep:serde"] 48 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # num-rational 2 | 3 | [![crate](https://img.shields.io/crates/v/num-rational.svg)](https://crates.io/crates/num-rational) 4 | [![documentation](https://docs.rs/num-rational/badge.svg)](https://docs.rs/num-rational) 5 | [![minimum rustc 1.60](https://img.shields.io/badge/rustc-1.60+-red.svg)](https://rust-lang.github.io/rfcs/2495-min-rust-version.html) 6 | [![build status](https://github.com/rust-num/num-rational/workflows/master/badge.svg)](https://github.com/rust-num/num-rational/actions) 7 | 8 | Generic `Rational` numbers (aka fractions) for Rust. 9 | 10 | ## Usage 11 | 12 | Add this to your `Cargo.toml`: 13 | 14 | ```toml 15 | [dependencies] 16 | num-rational = "0.4" 17 | ``` 18 | 19 | ## Features 20 | 21 | This crate can be used without the standard library (`#![no_std]`) by disabling 22 | the default `std` feature. Use this in `Cargo.toml`: 23 | 24 | ```toml 25 | [dependencies.num-rational] 26 | version = "0.4" 27 | default-features = false 28 | ``` 29 | 30 | ## Releases 31 | 32 | Release notes are available in [RELEASES.md](RELEASES.md). 33 | 34 | ## Compatibility 35 | 36 | The `num-rational` crate is tested for rustc 1.60 and greater. 37 | 38 | ## License 39 | 40 | Licensed under either of 41 | 42 | * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 43 | * [MIT license](http://opensource.org/licenses/MIT) 44 | 45 | at your option. 46 | 47 | ### Contribution 48 | 49 | Unless you explicitly state otherwise, any contribution intentionally submitted 50 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 51 | dual licensed as above, without any additional terms or conditions. 52 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Release 0.4.2 (2024-05-07) 2 | 3 | - [Upgrade to 2021 edition, **MSRV 1.60**][126] 4 | - [Add `Ratio::approximate_float_unsigned` to convert `FloatCore` types to unsigned][109] 5 | - [Add `const ZERO` and `ONE`, and implement `num_traits::ConstZero` and `ConstOne`][128] 6 | - [Add `Ratio::into_raw` to deconstruct the numerator and denominator][129] 7 | 8 | **Contributors**: @cuviper, @Enyium, @flavioroth, @waywardmonkeys 9 | 10 | [109]: https://github.com/rust-num/num-rational/pull/109 11 | [126]: https://github.com/rust-num/num-rational/pull/126 12 | [128]: https://github.com/rust-num/num-rational/pull/128 13 | [129]: https://github.com/rust-num/num-rational/pull/129 14 | 15 | # Release 0.4.1 (2022-06-23) 16 | 17 | - [Fewer `clone` calls are used when reducing a new `Ratio`][98]. 18 | - [Conversions to floating point are better at avoiding underflow][104]. 19 | - [`Ratio` now implements `Default`][105], returning a zero value. 20 | 21 | **Contributors**: @cuviper, @lemmih, @MattX 22 | 23 | [98]: https://github.com/rust-num/num-rational/pull/98 24 | [104]: https://github.com/rust-num/num-rational/pull/104 25 | [105]: https://github.com/rust-num/num-rational/pull/105 26 | 27 | # Release 0.4.0 (2021-03-05) 28 | 29 | - The optional `num-bigint` dependency is now 0.4. 30 | - [The `Rational` alias for `Ratio` is now deprecated][92]. It is 31 | recommended to use specific type sizes for numeric computation, like 32 | `Rational32` and `Rational64`. 33 | 34 | **Contributors**: @cuviper, @vks 35 | 36 | [92]: https://github.com/rust-num/num-rational/pull/92 37 | 38 | # Release 0.3.2 (2020-11-06) 39 | 40 | - [Fix always rebuilding with --remap-path-prefix][88] 41 | 42 | **Contributors**: @Nemo157 43 | 44 | [88]: https://github.com/rust-num/num-rational/pull/88 45 | 46 | # Release 0.3.1 (2020-10-29) 47 | 48 | - [Handle to_f64() with raw division by zero][83]. 49 | - [Better document panic behaviour][84]. 50 | - Clarify the license specification as "MIT OR Apache-2.0". 51 | 52 | **Contributors**: @cuviper, @zetok 53 | 54 | [83]: https://github.com/rust-num/num-rational/pull/83 55 | [84]: https://github.com/rust-num/num-rational/pull/84 56 | 57 | # Release 0.3.0 (2020-06-13) 58 | 59 | ### Enhancements 60 | 61 | - [`Ratio` now implements `ToPrimitive`][52]. 62 | - [`Ratio` now implements additional formatting traits][56]: 63 | - `Binary`, `Octal`, `LowerHex`, `UpperHex`, `LowerExp`, `UpperExp` 64 | - [The `Pow` implementations have been expanded][70]. 65 | - `Pow` and `Pow` are now implemented. 66 | - `Pow<_> for &Ratio` now uses `&T: Pow`. 67 | - The inherent `pow` method now uses `&T: Pow`. 68 | 69 | ### Breaking Changes 70 | 71 | - [`num-rational` now requires Rust 1.31 or greater][66]. 72 | - The "i128" opt-in feature was removed, now always available. 73 | - [The "num-bigint-std" feature replaces "bigint" with `std` enabled][80]. 74 | - The "num-bigint" feature without `std` uses `alloc` on Rust 1.36+. 75 | 76 | **Contributors**: @cuviper, @MattX, @maxbla 77 | 78 | [52]: https://github.com/rust-num/num-rational/pull/52 79 | [56]: https://github.com/rust-num/num-rational/pull/56 80 | [66]: https://github.com/rust-num/num-rational/pull/66 81 | [70]: https://github.com/rust-num/num-rational/pull/70 82 | [80]: https://github.com/rust-num/num-rational/pull/80 83 | 84 | # Release 0.2.4 (2020-03-17) 85 | 86 | - [Fixed `CheckedDiv` when both dividend and divisor are 0][74]. 87 | - [Fixed `CheckedDiv` with `min_value()` numerators][76]. 88 | 89 | [74]: https://github.com/rust-num/num-rational/pull/74 90 | [76]: https://github.com/rust-num/num-rational/pull/76 91 | 92 | # Release 0.2.3 (2020-01-09) 93 | 94 | - [`Ratio` now performs earlier reductions to avoid overflow with `+-*/%` operators][42]. 95 | - [`Ratio::{new_raw, numer, denom}` are now `const fn` for Rust 1.31 and later][48]. 96 | - [Updated the `autocfg` build dependency to 1.0][63]. 97 | 98 | **Contributors**: @cuviper, @dingelish, @jimbo1qaz, @maxbla 99 | 100 | [42]: https://github.com/rust-num/num-rational/pull/42 101 | [48]: https://github.com/rust-num/num-rational/pull/48 102 | [63]: https://github.com/rust-num/num-rational/pull/63 103 | 104 | # Release 0.2.2 (2019-06-10) 105 | 106 | - [`Ratio` now implements `Zero::set_zero` and `One::set_one`][47]. 107 | 108 | **Contributors**: @cuviper, @ignatenkobrain, @vks 109 | 110 | [47]: https://github.com/rust-num/num-rational/pull/47 111 | 112 | # Release 0.2.1 (2018-06-22) 113 | 114 | - Maintenance release to fix `html_root_url`. 115 | 116 | # Release 0.2.0 (2018-06-19) 117 | 118 | ### Enhancements 119 | 120 | - [`Ratio` now implements `One::is_one` and the `Inv` trait][19]. 121 | - [`Ratio` now implements `Sum` and `Product`][25]. 122 | - [`Ratio` now supports `i128` and `u128` components][29] with Rust 1.26+. 123 | - [`Ratio` now implements the `Pow` trait][21]. 124 | 125 | ### Breaking Changes 126 | 127 | - [`num-rational` now requires rustc 1.15 or greater][18]. 128 | - [There is now a `std` feature][23], enabled by default, along with the 129 | implication that building *without* this feature makes this a `#![no_std]` 130 | crate. A few methods now require `FloatCore` instead of `Float`. 131 | - [The `serde` dependency has been updated to 1.0][24], and `rustc-serialize` 132 | is no longer supported by `num-rational`. 133 | - The optional `num-bigint` dependency has been updated to 0.2, and should be 134 | enabled using the `bigint-std` feature. In the future, it may be possible 135 | to use the `bigint` feature with `no_std`. 136 | 137 | **Contributors**: @clarcharr, @cuviper, @Emerentius, @robomancer-or, @vks 138 | 139 | [18]: https://github.com/rust-num/num-rational/pull/18 140 | [19]: https://github.com/rust-num/num-rational/pull/19 141 | [21]: https://github.com/rust-num/num-rational/pull/21 142 | [23]: https://github.com/rust-num/num-rational/pull/23 143 | [24]: https://github.com/rust-num/num-rational/pull/24 144 | [25]: https://github.com/rust-num/num-rational/pull/25 145 | [29]: https://github.com/rust-num/num-rational/pull/29 146 | 147 | 148 | # Release 0.1.42 (2018-02-08) 149 | 150 | - Maintenance release to update dependencies. 151 | 152 | 153 | # Release 0.1.41 (2018-01-26) 154 | 155 | - [num-rational now has its own source repository][num-356] at [rust-num/num-rational][home]. 156 | - [`Ratio` now implements `CheckedAdd`, `CheckedSub`, `CheckedMul`, and `CheckedDiv`][11]. 157 | - [`Ratio` now implements `AddAssign`, `SubAssign`, `MulAssign`, `DivAssign`, and `RemAssign`][12] 158 | with either `Ratio` or an integer on the right side. The non-assignment operators now also 159 | accept integers as an operand. 160 | - [`Ratio` operators now make fewer `clone()` calls][14]. 161 | 162 | Thanks to @c410-f3r, @cuviper, and @psimonyi for their contributions! 163 | 164 | [home]: https://github.com/rust-num/num-rational 165 | [num-356]: https://github.com/rust-num/num/pull/356 166 | [11]: https://github.com/rust-num/num-rational/pull/11 167 | [12]: https://github.com/rust-num/num-rational/pull/12 168 | [14]: https://github.com/rust-num/num-rational/pull/14 169 | 170 | 171 | # Prior releases 172 | 173 | No prior release notes were kept. Thanks all the same to the many 174 | contributors that have made this crate what it is! 175 | -------------------------------------------------------------------------------- /ci/benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarks" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | 10 | [dependencies.num-rational] 11 | path = "../.." 12 | default-features = false 13 | features = ["num-bigint"] 14 | 15 | [dependencies.num-bigint] 16 | version = "0.4.0" 17 | default-features = false 18 | 19 | [dependencies.rand] 20 | version = "0.8" 21 | default-features = false 22 | -------------------------------------------------------------------------------- /ci/benchmarks/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | 5 | use num_bigint::BigInt; 6 | use num_rational::{BigRational, Ratio}; 7 | use test::Bencher; 8 | 9 | mod rng; 10 | use rng::get_rng; 11 | 12 | #[bench] 13 | fn alloc_ratio_bigint_bench(b: &mut Bencher) { 14 | use rand::RngCore; 15 | let mut rng = get_rng(); 16 | b.iter(|| { 17 | let a = BigInt::from(rng.next_u64()); 18 | let b = BigInt::from(rng.next_u64()); 19 | BigRational::new(a, b) 20 | }); 21 | } 22 | 23 | #[bench] 24 | fn alloc_ratio_u64_bench(b: &mut Bencher) { 25 | use rand::RngCore; 26 | let mut rng = get_rng(); 27 | b.iter(|| { 28 | let a = rng.next_u64(); 29 | let b = rng.next_u64(); 30 | Ratio::new(a, b) 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /ci/benchmarks/src/rng.rs: -------------------------------------------------------------------------------- 1 | use rand::RngCore; 2 | 3 | pub(crate) fn get_rng() -> impl RngCore { 4 | XorShiftStar { 5 | a: 0x0123_4567_89AB_CDEF, 6 | } 7 | } 8 | 9 | /// Simple `Rng` for benchmarking without additional dependencies 10 | struct XorShiftStar { 11 | a: u64, 12 | } 13 | 14 | impl RngCore for XorShiftStar { 15 | fn next_u32(&mut self) -> u32 { 16 | self.next_u64() as u32 17 | } 18 | 19 | fn next_u64(&mut self) -> u64 { 20 | // https://en.wikipedia.org/wiki/Xorshift#xorshift* 21 | self.a ^= self.a >> 12; 22 | self.a ^= self.a << 25; 23 | self.a ^= self.a >> 27; 24 | self.a.wrapping_mul(0x2545_F491_4F6C_DD1D) 25 | } 26 | 27 | fn fill_bytes(&mut self, dest: &mut [u8]) { 28 | for chunk in dest.chunks_mut(8) { 29 | let bytes = self.next_u64().to_le_bytes(); 30 | let slice = &bytes[..chunk.len()]; 31 | chunk.copy_from_slice(slice) 32 | } 33 | } 34 | 35 | fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { 36 | Ok(self.fill_bytes(dest)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ci/rustup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Use rustup to locally run the same suite of tests as .github/workflows/ 3 | # (You should first install/update all of the versions below.) 4 | 5 | set -ex 6 | 7 | ci=$(dirname $0) 8 | for version in 1.60.0 stable beta nightly; do 9 | rustup run "$version" "$ci/test_full.sh" 10 | done 11 | -------------------------------------------------------------------------------- /ci/test_full.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | CRATE=num-rational 6 | MSRV=1.60 7 | 8 | get_rust_version() { 9 | local array=($(rustc --version)); 10 | echo "${array[1]}"; 11 | return 0; 12 | } 13 | RUST_VERSION=$(get_rust_version) 14 | 15 | check_version() { 16 | IFS=. read -ra rust <<< "$RUST_VERSION" 17 | IFS=. read -ra want <<< "$1" 18 | [[ "${rust[0]}" -gt "${want[0]}" || 19 | ( "${rust[0]}" -eq "${want[0]}" && 20 | "${rust[1]}" -ge "${want[1]}" ) 21 | ]] 22 | } 23 | 24 | echo "Testing $CRATE on rustc $RUST_VERSION" 25 | if ! check_version $MSRV ; then 26 | echo "The minimum for $CRATE is rustc $MSRV" 27 | exit 1 28 | fi 29 | 30 | STD_FEATURES=(num-bigint-std serde) 31 | NO_STD_FEATURES=(num-bigint serde) 32 | echo "Testing supported features: ${STD_FEATURES[*]}" 33 | echo " no_std supported features: ${NO_STD_FEATURES[*]}" 34 | 35 | set -x 36 | 37 | # test the default with std 38 | cargo build 39 | cargo test 40 | 41 | # test each isolated feature with std 42 | for feature in ${STD_FEATURES[*]}; do 43 | cargo build --no-default-features --features="std $feature" 44 | cargo test --no-default-features --features="std $feature" 45 | done 46 | 47 | # test all supported features with std 48 | cargo build --no-default-features --features="std ${STD_FEATURES[*]}" 49 | cargo test --no-default-features --features="std ${STD_FEATURES[*]}" 50 | 51 | 52 | # test minimal `no_std` 53 | cargo build --no-default-features 54 | cargo test --no-default-features 55 | 56 | # test each isolated feature without std 57 | for feature in ${NO_STD_FEATURES[*]}; do 58 | cargo build --no-default-features --features="$feature" 59 | cargo test --no-default-features --features="$feature" 60 | done 61 | 62 | # test all supported features without std 63 | cargo build --no-default-features --features="${NO_STD_FEATURES[*]}" 64 | cargo test --no-default-features --features="${NO_STD_FEATURES[*]}" 65 | 66 | # make sure benchmarks can be built and sanity-tested 67 | if rustc --version | grep -q nightly; then 68 | cargo test --manifest-path ci/benchmarks/Cargo.toml 69 | fi 70 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT 2 | // file at the top-level directory of this distribution and at 3 | // http://rust-lang.org/COPYRIGHT. 4 | // 5 | // Licensed under the Apache License, Version 2.0 or the MIT license 7 | // , at your 8 | // option. This file may not be copied, modified, or distributed 9 | // except according to those terms. 10 | 11 | //! Rational numbers 12 | //! 13 | //! ## Compatibility 14 | //! 15 | //! The `num-rational` crate is tested for rustc 1.60 and greater. 16 | 17 | #![doc(html_root_url = "https://docs.rs/num-rational/0.4")] 18 | #![no_std] 19 | // Ratio ops often use other "suspicious" ops 20 | #![allow(clippy::suspicious_arithmetic_impl)] 21 | #![allow(clippy::suspicious_op_assign_impl)] 22 | 23 | #[cfg(feature = "std")] 24 | #[macro_use] 25 | extern crate std; 26 | 27 | use core::cmp; 28 | use core::fmt; 29 | use core::fmt::{Binary, Display, Formatter, LowerExp, LowerHex, Octal, UpperExp, UpperHex}; 30 | use core::hash::{Hash, Hasher}; 31 | use core::ops::{Add, Div, Mul, Neg, Rem, ShlAssign, Sub}; 32 | use core::str::FromStr; 33 | #[cfg(feature = "std")] 34 | use std::error::Error; 35 | 36 | #[cfg(feature = "num-bigint")] 37 | use num_bigint::{BigInt, BigUint, Sign, ToBigInt}; 38 | 39 | use num_integer::Integer; 40 | use num_traits::float::FloatCore; 41 | use num_traits::{ 42 | Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, ConstOne, ConstZero, FromPrimitive, 43 | Inv, Num, NumCast, One, Pow, Signed, ToPrimitive, Unsigned, Zero, 44 | }; 45 | 46 | mod pow; 47 | 48 | /// Represents the ratio between two numbers. 49 | #[derive(Copy, Clone, Debug)] 50 | #[allow(missing_docs)] 51 | pub struct Ratio { 52 | /// Numerator. 53 | numer: T, 54 | /// Denominator. 55 | denom: T, 56 | } 57 | 58 | /// Alias for a `Ratio` of machine-sized integers. 59 | #[deprecated( 60 | since = "0.4.0", 61 | note = "it's better to use a specific size, like `Rational32` or `Rational64`" 62 | )] 63 | pub type Rational = Ratio; 64 | /// Alias for a `Ratio` of 32-bit-sized integers. 65 | pub type Rational32 = Ratio; 66 | /// Alias for a `Ratio` of 64-bit-sized integers. 67 | pub type Rational64 = Ratio; 68 | 69 | #[cfg(feature = "num-bigint")] 70 | /// Alias for arbitrary precision rationals. 71 | pub type BigRational = Ratio; 72 | 73 | /// These method are `const`. 74 | impl Ratio { 75 | /// Creates a `Ratio` without checking for `denom == 0` or reducing. 76 | /// 77 | /// **There are several methods that will panic if used on a `Ratio` with 78 | /// `denom == 0`.** 79 | #[inline] 80 | pub const fn new_raw(numer: T, denom: T) -> Ratio { 81 | Ratio { numer, denom } 82 | } 83 | 84 | /// Deconstructs a `Ratio` into its numerator and denominator. 85 | #[inline] 86 | pub fn into_raw(self) -> (T, T) { 87 | (self.numer, self.denom) 88 | } 89 | 90 | /// Gets an immutable reference to the numerator. 91 | #[inline] 92 | pub const fn numer(&self) -> &T { 93 | &self.numer 94 | } 95 | 96 | /// Gets an immutable reference to the denominator. 97 | #[inline] 98 | pub const fn denom(&self) -> &T { 99 | &self.denom 100 | } 101 | } 102 | 103 | impl Ratio { 104 | /// Creates a new `Ratio`. 105 | /// 106 | /// **Panics if `denom` is zero.** 107 | #[inline] 108 | pub fn new(numer: T, denom: T) -> Ratio { 109 | let mut ret = Ratio::new_raw(numer, denom); 110 | ret.reduce(); 111 | ret 112 | } 113 | 114 | /// Creates a `Ratio` representing the integer `t`. 115 | #[inline] 116 | pub fn from_integer(t: T) -> Ratio { 117 | Ratio::new_raw(t, One::one()) 118 | } 119 | 120 | /// Converts to an integer, rounding towards zero. 121 | #[inline] 122 | pub fn to_integer(&self) -> T { 123 | self.trunc().numer 124 | } 125 | 126 | /// Returns true if the rational number is an integer (denominator is 1). 127 | #[inline] 128 | pub fn is_integer(&self) -> bool { 129 | self.denom.is_one() 130 | } 131 | 132 | /// Puts self into lowest terms, with `denom` > 0. 133 | /// 134 | /// **Panics if `denom` is zero.** 135 | fn reduce(&mut self) { 136 | if self.denom.is_zero() { 137 | panic!("denominator == 0"); 138 | } 139 | if self.numer.is_zero() { 140 | self.denom.set_one(); 141 | return; 142 | } 143 | if self.numer == self.denom { 144 | self.set_one(); 145 | return; 146 | } 147 | let g: T = self.numer.gcd(&self.denom); 148 | 149 | // FIXME(#5992): assignment operator overloads 150 | // T: Clone + Integer != T: Clone + NumAssign 151 | 152 | #[inline] 153 | fn replace_with(x: &mut T, f: impl FnOnce(T) -> T) { 154 | let y = core::mem::replace(x, T::zero()); 155 | *x = f(y); 156 | } 157 | 158 | // self.numer /= g; 159 | replace_with(&mut self.numer, |x| x / g.clone()); 160 | 161 | // self.denom /= g; 162 | replace_with(&mut self.denom, |x| x / g); 163 | 164 | // keep denom positive! 165 | if self.denom < T::zero() { 166 | replace_with(&mut self.numer, |x| T::zero() - x); 167 | replace_with(&mut self.denom, |x| T::zero() - x); 168 | } 169 | } 170 | 171 | /// Returns a reduced copy of self. 172 | /// 173 | /// In general, it is not necessary to use this method, as the only 174 | /// method of procuring a non-reduced fraction is through `new_raw`. 175 | /// 176 | /// **Panics if `denom` is zero.** 177 | pub fn reduced(&self) -> Ratio { 178 | let mut ret = self.clone(); 179 | ret.reduce(); 180 | ret 181 | } 182 | 183 | /// Returns the reciprocal. 184 | /// 185 | /// **Panics if the `Ratio` is zero.** 186 | #[inline] 187 | pub fn recip(&self) -> Ratio { 188 | self.clone().into_recip() 189 | } 190 | 191 | #[inline] 192 | fn into_recip(self) -> Ratio { 193 | match self.numer.cmp(&T::zero()) { 194 | cmp::Ordering::Equal => panic!("division by zero"), 195 | cmp::Ordering::Greater => Ratio::new_raw(self.denom, self.numer), 196 | cmp::Ordering::Less => Ratio::new_raw(T::zero() - self.denom, T::zero() - self.numer), 197 | } 198 | } 199 | 200 | /// Rounds towards minus infinity. 201 | #[inline] 202 | pub fn floor(&self) -> Ratio { 203 | if *self < Zero::zero() { 204 | let one: T = One::one(); 205 | Ratio::from_integer( 206 | (self.numer.clone() - self.denom.clone() + one) / self.denom.clone(), 207 | ) 208 | } else { 209 | Ratio::from_integer(self.numer.clone() / self.denom.clone()) 210 | } 211 | } 212 | 213 | /// Rounds towards plus infinity. 214 | #[inline] 215 | pub fn ceil(&self) -> Ratio { 216 | if *self < Zero::zero() { 217 | Ratio::from_integer(self.numer.clone() / self.denom.clone()) 218 | } else { 219 | let one: T = One::one(); 220 | Ratio::from_integer( 221 | (self.numer.clone() + self.denom.clone() - one) / self.denom.clone(), 222 | ) 223 | } 224 | } 225 | 226 | /// Rounds to the nearest integer. Rounds half-way cases away from zero. 227 | #[inline] 228 | pub fn round(&self) -> Ratio { 229 | let zero: Ratio = Zero::zero(); 230 | let one: T = One::one(); 231 | let two: T = one.clone() + one.clone(); 232 | 233 | // Find unsigned fractional part of rational number 234 | let mut fractional = self.fract(); 235 | if fractional < zero { 236 | fractional = zero - fractional 237 | }; 238 | 239 | // The algorithm compares the unsigned fractional part with 1/2, that 240 | // is, a/b >= 1/2, or a >= b/2. For odd denominators, we use 241 | // a >= (b/2)+1. This avoids overflow issues. 242 | let half_or_larger = if fractional.denom.is_even() { 243 | fractional.numer >= fractional.denom / two 244 | } else { 245 | fractional.numer >= (fractional.denom / two) + one 246 | }; 247 | 248 | if half_or_larger { 249 | let one: Ratio = One::one(); 250 | if *self >= Zero::zero() { 251 | self.trunc() + one 252 | } else { 253 | self.trunc() - one 254 | } 255 | } else { 256 | self.trunc() 257 | } 258 | } 259 | 260 | /// Rounds towards zero. 261 | #[inline] 262 | pub fn trunc(&self) -> Ratio { 263 | Ratio::from_integer(self.numer.clone() / self.denom.clone()) 264 | } 265 | 266 | /// Returns the fractional part of a number, with division rounded towards zero. 267 | /// 268 | /// Satisfies `self == self.trunc() + self.fract()`. 269 | #[inline] 270 | pub fn fract(&self) -> Ratio { 271 | Ratio::new_raw(self.numer.clone() % self.denom.clone(), self.denom.clone()) 272 | } 273 | 274 | /// Raises the `Ratio` to the power of an exponent. 275 | #[inline] 276 | pub fn pow(&self, expon: i32) -> Ratio 277 | where 278 | for<'a> &'a T: Pow, 279 | { 280 | Pow::pow(self, expon) 281 | } 282 | } 283 | 284 | #[cfg(feature = "num-bigint")] 285 | impl Ratio { 286 | /// Converts a float into a rational number. 287 | pub fn from_float(f: T) -> Option { 288 | if !f.is_finite() { 289 | return None; 290 | } 291 | let (mantissa, exponent, sign) = f.integer_decode(); 292 | let bigint_sign = if sign == 1 { Sign::Plus } else { Sign::Minus }; 293 | if exponent < 0 { 294 | let one: BigInt = One::one(); 295 | let denom: BigInt = one << ((-exponent) as usize); 296 | let numer: BigUint = FromPrimitive::from_u64(mantissa).unwrap(); 297 | Some(Ratio::new(BigInt::from_biguint(bigint_sign, numer), denom)) 298 | } else { 299 | let mut numer: BigUint = FromPrimitive::from_u64(mantissa).unwrap(); 300 | numer <<= exponent as usize; 301 | Some(Ratio::from_integer(BigInt::from_biguint( 302 | bigint_sign, 303 | numer, 304 | ))) 305 | } 306 | } 307 | } 308 | 309 | impl Default for Ratio { 310 | /// Returns zero 311 | fn default() -> Self { 312 | Ratio::zero() 313 | } 314 | } 315 | 316 | // From integer 317 | impl From for Ratio 318 | where 319 | T: Clone + Integer, 320 | { 321 | fn from(x: T) -> Ratio { 322 | Ratio::from_integer(x) 323 | } 324 | } 325 | 326 | // From pair (through the `new` constructor) 327 | impl From<(T, T)> for Ratio 328 | where 329 | T: Clone + Integer, 330 | { 331 | fn from(pair: (T, T)) -> Ratio { 332 | Ratio::new(pair.0, pair.1) 333 | } 334 | } 335 | 336 | // Comparisons 337 | 338 | // Mathematically, comparing a/b and c/d is the same as comparing a*d and b*c, but it's very easy 339 | // for those multiplications to overflow fixed-size integers, so we need to take care. 340 | 341 | impl Ord for Ratio { 342 | #[inline] 343 | fn cmp(&self, other: &Self) -> cmp::Ordering { 344 | // With equal denominators, the numerators can be directly compared 345 | if self.denom == other.denom { 346 | let ord = self.numer.cmp(&other.numer); 347 | return if self.denom < T::zero() { 348 | ord.reverse() 349 | } else { 350 | ord 351 | }; 352 | } 353 | 354 | // With equal numerators, the denominators can be inversely compared 355 | if self.numer == other.numer { 356 | if self.numer.is_zero() { 357 | return cmp::Ordering::Equal; 358 | } 359 | let ord = self.denom.cmp(&other.denom); 360 | return if self.numer < T::zero() { 361 | ord 362 | } else { 363 | ord.reverse() 364 | }; 365 | } 366 | 367 | // Unfortunately, we don't have CheckedMul to try. That could sometimes avoid all the 368 | // division below, or even always avoid it for BigInt and BigUint. 369 | // FIXME- future breaking change to add Checked* to Integer? 370 | 371 | // Compare as floored integers and remainders 372 | let (self_int, self_rem) = self.numer.div_mod_floor(&self.denom); 373 | let (other_int, other_rem) = other.numer.div_mod_floor(&other.denom); 374 | match self_int.cmp(&other_int) { 375 | cmp::Ordering::Greater => cmp::Ordering::Greater, 376 | cmp::Ordering::Less => cmp::Ordering::Less, 377 | cmp::Ordering::Equal => { 378 | match (self_rem.is_zero(), other_rem.is_zero()) { 379 | (true, true) => cmp::Ordering::Equal, 380 | (true, false) => cmp::Ordering::Less, 381 | (false, true) => cmp::Ordering::Greater, 382 | (false, false) => { 383 | // Compare the reciprocals of the remaining fractions in reverse 384 | let self_recip = Ratio::new_raw(self.denom.clone(), self_rem); 385 | let other_recip = Ratio::new_raw(other.denom.clone(), other_rem); 386 | self_recip.cmp(&other_recip).reverse() 387 | } 388 | } 389 | } 390 | } 391 | } 392 | } 393 | 394 | impl PartialOrd for Ratio { 395 | #[inline] 396 | fn partial_cmp(&self, other: &Self) -> Option { 397 | Some(self.cmp(other)) 398 | } 399 | } 400 | 401 | impl PartialEq for Ratio { 402 | #[inline] 403 | fn eq(&self, other: &Self) -> bool { 404 | self.cmp(other) == cmp::Ordering::Equal 405 | } 406 | } 407 | 408 | impl Eq for Ratio {} 409 | 410 | // NB: We can't just `#[derive(Hash)]`, because it needs to agree 411 | // with `Eq` even for non-reduced ratios. 412 | impl Hash for Ratio { 413 | fn hash(&self, state: &mut H) { 414 | recurse(&self.numer, &self.denom, state); 415 | 416 | fn recurse(numer: &T, denom: &T, state: &mut H) { 417 | if !denom.is_zero() { 418 | let (int, rem) = numer.div_mod_floor(denom); 419 | int.hash(state); 420 | recurse(denom, &rem, state); 421 | } else { 422 | denom.hash(state); 423 | } 424 | } 425 | } 426 | } 427 | 428 | mod iter_sum_product { 429 | use crate::Ratio; 430 | use core::iter::{Product, Sum}; 431 | use num_integer::Integer; 432 | use num_traits::{One, Zero}; 433 | 434 | impl Sum for Ratio { 435 | fn sum(iter: I) -> Self 436 | where 437 | I: Iterator>, 438 | { 439 | iter.fold(Self::zero(), |sum, num| sum + num) 440 | } 441 | } 442 | 443 | impl<'a, T: Integer + Clone> Sum<&'a Ratio> for Ratio { 444 | fn sum(iter: I) -> Self 445 | where 446 | I: Iterator>, 447 | { 448 | iter.fold(Self::zero(), |sum, num| sum + num) 449 | } 450 | } 451 | 452 | impl Product for Ratio { 453 | fn product(iter: I) -> Self 454 | where 455 | I: Iterator>, 456 | { 457 | iter.fold(Self::one(), |prod, num| prod * num) 458 | } 459 | } 460 | 461 | impl<'a, T: Integer + Clone> Product<&'a Ratio> for Ratio { 462 | fn product(iter: I) -> Self 463 | where 464 | I: Iterator>, 465 | { 466 | iter.fold(Self::one(), |prod, num| prod * num) 467 | } 468 | } 469 | } 470 | 471 | mod opassign { 472 | use core::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign}; 473 | 474 | use crate::Ratio; 475 | use num_integer::Integer; 476 | use num_traits::NumAssign; 477 | 478 | impl AddAssign for Ratio { 479 | fn add_assign(&mut self, other: Ratio) { 480 | if self.denom == other.denom { 481 | self.numer += other.numer 482 | } else { 483 | let lcm = self.denom.lcm(&other.denom); 484 | let lhs_numer = self.numer.clone() * (lcm.clone() / self.denom.clone()); 485 | let rhs_numer = other.numer * (lcm.clone() / other.denom); 486 | self.numer = lhs_numer + rhs_numer; 487 | self.denom = lcm; 488 | } 489 | self.reduce(); 490 | } 491 | } 492 | 493 | // (a/b) / (c/d) = (a/gcd_ac)*(d/gcd_bd) / ((c/gcd_ac)*(b/gcd_bd)) 494 | impl DivAssign for Ratio { 495 | fn div_assign(&mut self, other: Ratio) { 496 | let gcd_ac = self.numer.gcd(&other.numer); 497 | let gcd_bd = self.denom.gcd(&other.denom); 498 | self.numer /= gcd_ac.clone(); 499 | self.numer *= other.denom / gcd_bd.clone(); 500 | self.denom /= gcd_bd; 501 | self.denom *= other.numer / gcd_ac; 502 | self.reduce(); // TODO: remove this line. see #8. 503 | } 504 | } 505 | 506 | // a/b * c/d = (a/gcd_ad)*(c/gcd_bc) / ((d/gcd_ad)*(b/gcd_bc)) 507 | impl MulAssign for Ratio { 508 | fn mul_assign(&mut self, other: Ratio) { 509 | let gcd_ad = self.numer.gcd(&other.denom); 510 | let gcd_bc = self.denom.gcd(&other.numer); 511 | self.numer /= gcd_ad.clone(); 512 | self.numer *= other.numer / gcd_bc.clone(); 513 | self.denom /= gcd_bc; 514 | self.denom *= other.denom / gcd_ad; 515 | self.reduce(); // TODO: remove this line. see #8. 516 | } 517 | } 518 | 519 | impl RemAssign for Ratio { 520 | fn rem_assign(&mut self, other: Ratio) { 521 | if self.denom == other.denom { 522 | self.numer %= other.numer 523 | } else { 524 | let lcm = self.denom.lcm(&other.denom); 525 | let lhs_numer = self.numer.clone() * (lcm.clone() / self.denom.clone()); 526 | let rhs_numer = other.numer * (lcm.clone() / other.denom); 527 | self.numer = lhs_numer % rhs_numer; 528 | self.denom = lcm; 529 | } 530 | self.reduce(); 531 | } 532 | } 533 | 534 | impl SubAssign for Ratio { 535 | fn sub_assign(&mut self, other: Ratio) { 536 | if self.denom == other.denom { 537 | self.numer -= other.numer 538 | } else { 539 | let lcm = self.denom.lcm(&other.denom); 540 | let lhs_numer = self.numer.clone() * (lcm.clone() / self.denom.clone()); 541 | let rhs_numer = other.numer * (lcm.clone() / other.denom); 542 | self.numer = lhs_numer - rhs_numer; 543 | self.denom = lcm; 544 | } 545 | self.reduce(); 546 | } 547 | } 548 | 549 | // a/b + c/1 = (a*1 + b*c) / (b*1) = (a + b*c) / b 550 | impl AddAssign for Ratio { 551 | fn add_assign(&mut self, other: T) { 552 | self.numer += self.denom.clone() * other; 553 | self.reduce(); 554 | } 555 | } 556 | 557 | impl DivAssign for Ratio { 558 | fn div_assign(&mut self, other: T) { 559 | let gcd = self.numer.gcd(&other); 560 | self.numer /= gcd.clone(); 561 | self.denom *= other / gcd; 562 | self.reduce(); // TODO: remove this line. see #8. 563 | } 564 | } 565 | 566 | impl MulAssign for Ratio { 567 | fn mul_assign(&mut self, other: T) { 568 | let gcd = self.denom.gcd(&other); 569 | self.denom /= gcd.clone(); 570 | self.numer *= other / gcd; 571 | self.reduce(); // TODO: remove this line. see #8. 572 | } 573 | } 574 | 575 | // a/b % c/1 = (a*1 % b*c) / (b*1) = (a % b*c) / b 576 | impl RemAssign for Ratio { 577 | fn rem_assign(&mut self, other: T) { 578 | self.numer %= self.denom.clone() * other; 579 | self.reduce(); 580 | } 581 | } 582 | 583 | // a/b - c/1 = (a*1 - b*c) / (b*1) = (a - b*c) / b 584 | impl SubAssign for Ratio { 585 | fn sub_assign(&mut self, other: T) { 586 | self.numer -= self.denom.clone() * other; 587 | self.reduce(); 588 | } 589 | } 590 | 591 | macro_rules! forward_op_assign { 592 | (impl $imp:ident, $method:ident) => { 593 | impl<'a, T: Clone + Integer + NumAssign> $imp<&'a Ratio> for Ratio { 594 | #[inline] 595 | fn $method(&mut self, other: &Ratio) { 596 | self.$method(other.clone()) 597 | } 598 | } 599 | impl<'a, T: Clone + Integer + NumAssign> $imp<&'a T> for Ratio { 600 | #[inline] 601 | fn $method(&mut self, other: &T) { 602 | self.$method(other.clone()) 603 | } 604 | } 605 | }; 606 | } 607 | 608 | forward_op_assign!(impl AddAssign, add_assign); 609 | forward_op_assign!(impl DivAssign, div_assign); 610 | forward_op_assign!(impl MulAssign, mul_assign); 611 | forward_op_assign!(impl RemAssign, rem_assign); 612 | forward_op_assign!(impl SubAssign, sub_assign); 613 | } 614 | 615 | macro_rules! forward_ref_ref_binop { 616 | (impl $imp:ident, $method:ident) => { 617 | impl<'a, 'b, T: Clone + Integer> $imp<&'b Ratio> for &'a Ratio { 618 | type Output = Ratio; 619 | 620 | #[inline] 621 | fn $method(self, other: &'b Ratio) -> Ratio { 622 | self.clone().$method(other.clone()) 623 | } 624 | } 625 | impl<'a, 'b, T: Clone + Integer> $imp<&'b T> for &'a Ratio { 626 | type Output = Ratio; 627 | 628 | #[inline] 629 | fn $method(self, other: &'b T) -> Ratio { 630 | self.clone().$method(other.clone()) 631 | } 632 | } 633 | }; 634 | } 635 | 636 | macro_rules! forward_ref_val_binop { 637 | (impl $imp:ident, $method:ident) => { 638 | impl<'a, T> $imp> for &'a Ratio 639 | where 640 | T: Clone + Integer, 641 | { 642 | type Output = Ratio; 643 | 644 | #[inline] 645 | fn $method(self, other: Ratio) -> Ratio { 646 | self.clone().$method(other) 647 | } 648 | } 649 | impl<'a, T> $imp for &'a Ratio 650 | where 651 | T: Clone + Integer, 652 | { 653 | type Output = Ratio; 654 | 655 | #[inline] 656 | fn $method(self, other: T) -> Ratio { 657 | self.clone().$method(other) 658 | } 659 | } 660 | }; 661 | } 662 | 663 | macro_rules! forward_val_ref_binop { 664 | (impl $imp:ident, $method:ident) => { 665 | impl<'a, T> $imp<&'a Ratio> for Ratio 666 | where 667 | T: Clone + Integer, 668 | { 669 | type Output = Ratio; 670 | 671 | #[inline] 672 | fn $method(self, other: &Ratio) -> Ratio { 673 | self.$method(other.clone()) 674 | } 675 | } 676 | impl<'a, T> $imp<&'a T> for Ratio 677 | where 678 | T: Clone + Integer, 679 | { 680 | type Output = Ratio; 681 | 682 | #[inline] 683 | fn $method(self, other: &T) -> Ratio { 684 | self.$method(other.clone()) 685 | } 686 | } 687 | }; 688 | } 689 | 690 | macro_rules! forward_all_binop { 691 | (impl $imp:ident, $method:ident) => { 692 | forward_ref_ref_binop!(impl $imp, $method); 693 | forward_ref_val_binop!(impl $imp, $method); 694 | forward_val_ref_binop!(impl $imp, $method); 695 | }; 696 | } 697 | 698 | // Arithmetic 699 | forward_all_binop!(impl Mul, mul); 700 | // a/b * c/d = (a/gcd_ad)*(c/gcd_bc) / ((d/gcd_ad)*(b/gcd_bc)) 701 | impl Mul> for Ratio 702 | where 703 | T: Clone + Integer, 704 | { 705 | type Output = Ratio; 706 | #[inline] 707 | fn mul(self, rhs: Ratio) -> Ratio { 708 | let gcd_ad = self.numer.gcd(&rhs.denom); 709 | let gcd_bc = self.denom.gcd(&rhs.numer); 710 | Ratio::new( 711 | self.numer / gcd_ad.clone() * (rhs.numer / gcd_bc.clone()), 712 | self.denom / gcd_bc * (rhs.denom / gcd_ad), 713 | ) 714 | } 715 | } 716 | // a/b * c/1 = (a*c) / (b*1) = (a*c) / b 717 | impl Mul for Ratio 718 | where 719 | T: Clone + Integer, 720 | { 721 | type Output = Ratio; 722 | #[inline] 723 | fn mul(self, rhs: T) -> Ratio { 724 | let gcd = self.denom.gcd(&rhs); 725 | Ratio::new(self.numer * (rhs / gcd.clone()), self.denom / gcd) 726 | } 727 | } 728 | 729 | forward_all_binop!(impl Div, div); 730 | // (a/b) / (c/d) = (a/gcd_ac)*(d/gcd_bd) / ((c/gcd_ac)*(b/gcd_bd)) 731 | impl Div> for Ratio 732 | where 733 | T: Clone + Integer, 734 | { 735 | type Output = Ratio; 736 | 737 | #[inline] 738 | fn div(self, rhs: Ratio) -> Ratio { 739 | let gcd_ac = self.numer.gcd(&rhs.numer); 740 | let gcd_bd = self.denom.gcd(&rhs.denom); 741 | Ratio::new( 742 | self.numer / gcd_ac.clone() * (rhs.denom / gcd_bd.clone()), 743 | self.denom / gcd_bd * (rhs.numer / gcd_ac), 744 | ) 745 | } 746 | } 747 | // (a/b) / (c/1) = (a*1) / (b*c) = a / (b*c) 748 | impl Div for Ratio 749 | where 750 | T: Clone + Integer, 751 | { 752 | type Output = Ratio; 753 | 754 | #[inline] 755 | fn div(self, rhs: T) -> Ratio { 756 | let gcd = self.numer.gcd(&rhs); 757 | Ratio::new(self.numer / gcd.clone(), self.denom * (rhs / gcd)) 758 | } 759 | } 760 | 761 | macro_rules! arith_impl { 762 | (impl $imp:ident, $method:ident) => { 763 | forward_all_binop!(impl $imp, $method); 764 | // Abstracts a/b `op` c/d = (a*lcm/b `op` c*lcm/d)/lcm where lcm = lcm(b,d) 765 | impl $imp> for Ratio { 766 | type Output = Ratio; 767 | #[inline] 768 | fn $method(self, rhs: Ratio) -> Ratio { 769 | if self.denom == rhs.denom { 770 | return Ratio::new(self.numer.$method(rhs.numer), rhs.denom); 771 | } 772 | let lcm = self.denom.lcm(&rhs.denom); 773 | let lhs_numer = self.numer * (lcm.clone() / self.denom); 774 | let rhs_numer = rhs.numer * (lcm.clone() / rhs.denom); 775 | Ratio::new(lhs_numer.$method(rhs_numer), lcm) 776 | } 777 | } 778 | // Abstracts the a/b `op` c/1 = (a*1 `op` b*c) / (b*1) = (a `op` b*c) / b pattern 779 | impl $imp for Ratio { 780 | type Output = Ratio; 781 | #[inline] 782 | fn $method(self, rhs: T) -> Ratio { 783 | Ratio::new(self.numer.$method(self.denom.clone() * rhs), self.denom) 784 | } 785 | } 786 | }; 787 | } 788 | 789 | arith_impl!(impl Add, add); 790 | arith_impl!(impl Sub, sub); 791 | arith_impl!(impl Rem, rem); 792 | 793 | // a/b * c/d = (a*c)/(b*d) 794 | impl CheckedMul for Ratio 795 | where 796 | T: Clone + Integer + CheckedMul, 797 | { 798 | #[inline] 799 | fn checked_mul(&self, rhs: &Ratio) -> Option> { 800 | let gcd_ad = self.numer.gcd(&rhs.denom); 801 | let gcd_bc = self.denom.gcd(&rhs.numer); 802 | Some(Ratio::new( 803 | (self.numer.clone() / gcd_ad.clone()) 804 | .checked_mul(&(rhs.numer.clone() / gcd_bc.clone()))?, 805 | (self.denom.clone() / gcd_bc).checked_mul(&(rhs.denom.clone() / gcd_ad))?, 806 | )) 807 | } 808 | } 809 | 810 | // (a/b) / (c/d) = (a*d)/(b*c) 811 | impl CheckedDiv for Ratio 812 | where 813 | T: Clone + Integer + CheckedMul, 814 | { 815 | #[inline] 816 | fn checked_div(&self, rhs: &Ratio) -> Option> { 817 | if rhs.is_zero() { 818 | return None; 819 | } 820 | let (numer, denom) = if self.denom == rhs.denom { 821 | (self.numer.clone(), rhs.numer.clone()) 822 | } else if self.numer == rhs.numer { 823 | (rhs.denom.clone(), self.denom.clone()) 824 | } else { 825 | let gcd_ac = self.numer.gcd(&rhs.numer); 826 | let gcd_bd = self.denom.gcd(&rhs.denom); 827 | ( 828 | (self.numer.clone() / gcd_ac.clone()) 829 | .checked_mul(&(rhs.denom.clone() / gcd_bd.clone()))?, 830 | (self.denom.clone() / gcd_bd).checked_mul(&(rhs.numer.clone() / gcd_ac))?, 831 | ) 832 | }; 833 | // Manual `reduce()`, avoiding sharp edges 834 | if denom.is_zero() { 835 | None 836 | } else if numer.is_zero() { 837 | Some(Self::zero()) 838 | } else if numer == denom { 839 | Some(Self::one()) 840 | } else { 841 | let g = numer.gcd(&denom); 842 | let numer = numer / g.clone(); 843 | let denom = denom / g; 844 | let raw = if denom < T::zero() { 845 | // We need to keep denom positive, but 2's-complement MIN may 846 | // overflow negation -- instead we can check multiplying -1. 847 | let n1 = T::zero() - T::one(); 848 | Ratio::new_raw(numer.checked_mul(&n1)?, denom.checked_mul(&n1)?) 849 | } else { 850 | Ratio::new_raw(numer, denom) 851 | }; 852 | Some(raw) 853 | } 854 | } 855 | } 856 | 857 | // As arith_impl! but for Checked{Add,Sub} traits 858 | macro_rules! checked_arith_impl { 859 | (impl $imp:ident, $method:ident) => { 860 | impl $imp for Ratio { 861 | #[inline] 862 | fn $method(&self, rhs: &Ratio) -> Option> { 863 | let gcd = self.denom.clone().gcd(&rhs.denom); 864 | let lcm = (self.denom.clone() / gcd.clone()).checked_mul(&rhs.denom)?; 865 | let lhs_numer = (lcm.clone() / self.denom.clone()).checked_mul(&self.numer)?; 866 | let rhs_numer = (lcm.clone() / rhs.denom.clone()).checked_mul(&rhs.numer)?; 867 | Some(Ratio::new(lhs_numer.$method(&rhs_numer)?, lcm)) 868 | } 869 | } 870 | }; 871 | } 872 | 873 | // a/b + c/d = (lcm/b*a + lcm/d*c)/lcm, where lcm = lcm(b,d) 874 | checked_arith_impl!(impl CheckedAdd, checked_add); 875 | 876 | // a/b - c/d = (lcm/b*a - lcm/d*c)/lcm, where lcm = lcm(b,d) 877 | checked_arith_impl!(impl CheckedSub, checked_sub); 878 | 879 | impl Neg for Ratio 880 | where 881 | T: Clone + Integer + Neg, 882 | { 883 | type Output = Ratio; 884 | 885 | #[inline] 886 | fn neg(self) -> Ratio { 887 | Ratio::new_raw(-self.numer, self.denom) 888 | } 889 | } 890 | 891 | impl<'a, T> Neg for &'a Ratio 892 | where 893 | T: Clone + Integer + Neg, 894 | { 895 | type Output = Ratio; 896 | 897 | #[inline] 898 | fn neg(self) -> Ratio { 899 | -self.clone() 900 | } 901 | } 902 | 903 | impl Inv for Ratio 904 | where 905 | T: Clone + Integer, 906 | { 907 | type Output = Ratio; 908 | 909 | #[inline] 910 | fn inv(self) -> Ratio { 911 | self.recip() 912 | } 913 | } 914 | 915 | impl<'a, T> Inv for &'a Ratio 916 | where 917 | T: Clone + Integer, 918 | { 919 | type Output = Ratio; 920 | 921 | #[inline] 922 | fn inv(self) -> Ratio { 923 | self.recip() 924 | } 925 | } 926 | 927 | // Constants 928 | impl Ratio { 929 | /// A constant `Ratio` 0/1. 930 | pub const ZERO: Self = Self::new_raw(T::ZERO, T::ONE); 931 | } 932 | 933 | impl ConstZero for Ratio { 934 | const ZERO: Self = Self::ZERO; 935 | } 936 | 937 | impl Zero for Ratio { 938 | #[inline] 939 | fn zero() -> Ratio { 940 | Ratio::new_raw(Zero::zero(), One::one()) 941 | } 942 | 943 | #[inline] 944 | fn is_zero(&self) -> bool { 945 | self.numer.is_zero() 946 | } 947 | 948 | #[inline] 949 | fn set_zero(&mut self) { 950 | self.numer.set_zero(); 951 | self.denom.set_one(); 952 | } 953 | } 954 | 955 | impl Ratio { 956 | /// A constant `Ratio` 1/1. 957 | pub const ONE: Self = Self::new_raw(T::ONE, T::ONE); 958 | } 959 | 960 | impl ConstOne for Ratio { 961 | const ONE: Self = Self::ONE; 962 | } 963 | 964 | impl One for Ratio { 965 | #[inline] 966 | fn one() -> Ratio { 967 | Ratio::new_raw(One::one(), One::one()) 968 | } 969 | 970 | #[inline] 971 | fn is_one(&self) -> bool { 972 | self.numer == self.denom 973 | } 974 | 975 | #[inline] 976 | fn set_one(&mut self) { 977 | self.numer.set_one(); 978 | self.denom.set_one(); 979 | } 980 | } 981 | 982 | impl Num for Ratio { 983 | type FromStrRadixErr = ParseRatioError; 984 | 985 | /// Parses `numer/denom` where the numbers are in base `radix`. 986 | fn from_str_radix(s: &str, radix: u32) -> Result, ParseRatioError> { 987 | if s.splitn(2, '/').count() == 2 { 988 | let mut parts = s.splitn(2, '/').map(|ss| { 989 | T::from_str_radix(ss, radix).map_err(|_| ParseRatioError { 990 | kind: RatioErrorKind::ParseError, 991 | }) 992 | }); 993 | let numer: T = parts.next().unwrap()?; 994 | let denom: T = parts.next().unwrap()?; 995 | if denom.is_zero() { 996 | Err(ParseRatioError { 997 | kind: RatioErrorKind::ZeroDenominator, 998 | }) 999 | } else { 1000 | Ok(Ratio::new(numer, denom)) 1001 | } 1002 | } else { 1003 | Err(ParseRatioError { 1004 | kind: RatioErrorKind::ParseError, 1005 | }) 1006 | } 1007 | } 1008 | } 1009 | 1010 | impl Signed for Ratio { 1011 | #[inline] 1012 | fn abs(&self) -> Ratio { 1013 | if self.is_negative() { 1014 | -self.clone() 1015 | } else { 1016 | self.clone() 1017 | } 1018 | } 1019 | 1020 | #[inline] 1021 | fn abs_sub(&self, other: &Ratio) -> Ratio { 1022 | if *self <= *other { 1023 | Zero::zero() 1024 | } else { 1025 | self - other 1026 | } 1027 | } 1028 | 1029 | #[inline] 1030 | fn signum(&self) -> Ratio { 1031 | if self.is_positive() { 1032 | Self::one() 1033 | } else if self.is_zero() { 1034 | Self::zero() 1035 | } else { 1036 | -Self::one() 1037 | } 1038 | } 1039 | 1040 | #[inline] 1041 | fn is_positive(&self) -> bool { 1042 | (self.numer.is_positive() && self.denom.is_positive()) 1043 | || (self.numer.is_negative() && self.denom.is_negative()) 1044 | } 1045 | 1046 | #[inline] 1047 | fn is_negative(&self) -> bool { 1048 | (self.numer.is_negative() && self.denom.is_positive()) 1049 | || (self.numer.is_positive() && self.denom.is_negative()) 1050 | } 1051 | } 1052 | 1053 | // String conversions 1054 | macro_rules! impl_formatting { 1055 | ($fmt_trait:ident, $prefix:expr, $fmt_str:expr, $fmt_alt:expr) => { 1056 | impl $fmt_trait for Ratio { 1057 | #[cfg(feature = "std")] 1058 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1059 | let pre_pad = if self.denom.is_one() { 1060 | format!($fmt_str, self.numer) 1061 | } else { 1062 | if f.alternate() { 1063 | format!(concat!($fmt_str, "/", $fmt_alt), self.numer, self.denom) 1064 | } else { 1065 | format!(concat!($fmt_str, "/", $fmt_str), self.numer, self.denom) 1066 | } 1067 | }; 1068 | if let Some(pre_pad) = pre_pad.strip_prefix("-") { 1069 | f.pad_integral(false, $prefix, pre_pad) 1070 | } else { 1071 | f.pad_integral(true, $prefix, &pre_pad) 1072 | } 1073 | } 1074 | #[cfg(not(feature = "std"))] 1075 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1076 | let plus = if f.sign_plus() && self.numer >= T::zero() { 1077 | "+" 1078 | } else { 1079 | "" 1080 | }; 1081 | if self.denom.is_one() { 1082 | if f.alternate() { 1083 | write!(f, concat!("{}", $fmt_alt), plus, self.numer) 1084 | } else { 1085 | write!(f, concat!("{}", $fmt_str), plus, self.numer) 1086 | } 1087 | } else { 1088 | if f.alternate() { 1089 | write!( 1090 | f, 1091 | concat!("{}", $fmt_alt, "/", $fmt_alt), 1092 | plus, self.numer, self.denom 1093 | ) 1094 | } else { 1095 | write!( 1096 | f, 1097 | concat!("{}", $fmt_str, "/", $fmt_str), 1098 | plus, self.numer, self.denom 1099 | ) 1100 | } 1101 | } 1102 | } 1103 | } 1104 | }; 1105 | } 1106 | 1107 | impl_formatting!(Display, "", "{}", "{:#}"); 1108 | impl_formatting!(Octal, "0o", "{:o}", "{:#o}"); 1109 | impl_formatting!(Binary, "0b", "{:b}", "{:#b}"); 1110 | impl_formatting!(LowerHex, "0x", "{:x}", "{:#x}"); 1111 | impl_formatting!(UpperHex, "0x", "{:X}", "{:#X}"); 1112 | impl_formatting!(LowerExp, "", "{:e}", "{:#e}"); 1113 | impl_formatting!(UpperExp, "", "{:E}", "{:#E}"); 1114 | 1115 | impl FromStr for Ratio { 1116 | type Err = ParseRatioError; 1117 | 1118 | /// Parses `numer/denom` or just `numer`. 1119 | fn from_str(s: &str) -> Result, ParseRatioError> { 1120 | let mut split = s.splitn(2, '/'); 1121 | 1122 | let n = split.next().ok_or(ParseRatioError { 1123 | kind: RatioErrorKind::ParseError, 1124 | })?; 1125 | let num = FromStr::from_str(n).map_err(|_| ParseRatioError { 1126 | kind: RatioErrorKind::ParseError, 1127 | })?; 1128 | 1129 | let d = split.next().unwrap_or("1"); 1130 | let den = FromStr::from_str(d).map_err(|_| ParseRatioError { 1131 | kind: RatioErrorKind::ParseError, 1132 | })?; 1133 | 1134 | if Zero::is_zero(&den) { 1135 | Err(ParseRatioError { 1136 | kind: RatioErrorKind::ZeroDenominator, 1137 | }) 1138 | } else { 1139 | Ok(Ratio::new(num, den)) 1140 | } 1141 | } 1142 | } 1143 | 1144 | impl From> for (T, T) { 1145 | fn from(val: Ratio) -> Self { 1146 | (val.numer, val.denom) 1147 | } 1148 | } 1149 | 1150 | #[cfg(feature = "serde")] 1151 | impl serde::Serialize for Ratio 1152 | where 1153 | T: serde::Serialize + Clone + Integer + PartialOrd, 1154 | { 1155 | fn serialize(&self, serializer: S) -> Result 1156 | where 1157 | S: serde::Serializer, 1158 | { 1159 | (self.numer(), self.denom()).serialize(serializer) 1160 | } 1161 | } 1162 | 1163 | #[cfg(feature = "serde")] 1164 | impl<'de, T> serde::Deserialize<'de> for Ratio 1165 | where 1166 | T: serde::Deserialize<'de> + Clone + Integer + PartialOrd, 1167 | { 1168 | fn deserialize(deserializer: D) -> Result 1169 | where 1170 | D: serde::Deserializer<'de>, 1171 | { 1172 | use serde::de::Error; 1173 | use serde::de::Unexpected; 1174 | let (numer, denom): (T, T) = serde::Deserialize::deserialize(deserializer)?; 1175 | if denom.is_zero() { 1176 | Err(Error::invalid_value( 1177 | Unexpected::Signed(0), 1178 | &"a ratio with non-zero denominator", 1179 | )) 1180 | } else { 1181 | Ok(Ratio::new_raw(numer, denom)) 1182 | } 1183 | } 1184 | } 1185 | 1186 | // FIXME: Bubble up specific errors 1187 | #[derive(Copy, Clone, Debug, PartialEq)] 1188 | pub struct ParseRatioError { 1189 | kind: RatioErrorKind, 1190 | } 1191 | 1192 | #[derive(Copy, Clone, Debug, PartialEq)] 1193 | enum RatioErrorKind { 1194 | ParseError, 1195 | ZeroDenominator, 1196 | } 1197 | 1198 | impl fmt::Display for ParseRatioError { 1199 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 1200 | self.kind.description().fmt(f) 1201 | } 1202 | } 1203 | 1204 | #[cfg(feature = "std")] 1205 | impl Error for ParseRatioError { 1206 | #[allow(deprecated)] 1207 | fn description(&self) -> &str { 1208 | self.kind.description() 1209 | } 1210 | } 1211 | 1212 | impl RatioErrorKind { 1213 | fn description(&self) -> &'static str { 1214 | match *self { 1215 | RatioErrorKind::ParseError => "failed to parse integer", 1216 | RatioErrorKind::ZeroDenominator => "zero value denominator", 1217 | } 1218 | } 1219 | } 1220 | 1221 | #[cfg(feature = "num-bigint")] 1222 | impl FromPrimitive for Ratio { 1223 | fn from_i64(n: i64) -> Option { 1224 | Some(Ratio::from_integer(n.into())) 1225 | } 1226 | 1227 | fn from_i128(n: i128) -> Option { 1228 | Some(Ratio::from_integer(n.into())) 1229 | } 1230 | 1231 | fn from_u64(n: u64) -> Option { 1232 | Some(Ratio::from_integer(n.into())) 1233 | } 1234 | 1235 | fn from_u128(n: u128) -> Option { 1236 | Some(Ratio::from_integer(n.into())) 1237 | } 1238 | 1239 | fn from_f32(n: f32) -> Option { 1240 | Ratio::from_float(n) 1241 | } 1242 | 1243 | fn from_f64(n: f64) -> Option { 1244 | Ratio::from_float(n) 1245 | } 1246 | } 1247 | 1248 | macro_rules! from_primitive_integer { 1249 | ($typ:ty, $approx:ident) => { 1250 | impl FromPrimitive for Ratio<$typ> { 1251 | fn from_i64(n: i64) -> Option { 1252 | <$typ as FromPrimitive>::from_i64(n).map(Ratio::from_integer) 1253 | } 1254 | 1255 | fn from_i128(n: i128) -> Option { 1256 | <$typ as FromPrimitive>::from_i128(n).map(Ratio::from_integer) 1257 | } 1258 | 1259 | fn from_u64(n: u64) -> Option { 1260 | <$typ as FromPrimitive>::from_u64(n).map(Ratio::from_integer) 1261 | } 1262 | 1263 | fn from_u128(n: u128) -> Option { 1264 | <$typ as FromPrimitive>::from_u128(n).map(Ratio::from_integer) 1265 | } 1266 | 1267 | fn from_f32(n: f32) -> Option { 1268 | $approx(n, 10e-20, 30) 1269 | } 1270 | 1271 | fn from_f64(n: f64) -> Option { 1272 | $approx(n, 10e-20, 30) 1273 | } 1274 | } 1275 | }; 1276 | } 1277 | 1278 | from_primitive_integer!(i8, approximate_float); 1279 | from_primitive_integer!(i16, approximate_float); 1280 | from_primitive_integer!(i32, approximate_float); 1281 | from_primitive_integer!(i64, approximate_float); 1282 | from_primitive_integer!(i128, approximate_float); 1283 | from_primitive_integer!(isize, approximate_float); 1284 | 1285 | from_primitive_integer!(u8, approximate_float_unsigned); 1286 | from_primitive_integer!(u16, approximate_float_unsigned); 1287 | from_primitive_integer!(u32, approximate_float_unsigned); 1288 | from_primitive_integer!(u64, approximate_float_unsigned); 1289 | from_primitive_integer!(u128, approximate_float_unsigned); 1290 | from_primitive_integer!(usize, approximate_float_unsigned); 1291 | 1292 | impl Ratio { 1293 | pub fn approximate_float(f: F) -> Option> { 1294 | // 1/10e-20 < 1/2**32 which seems like a good default, and 30 seems 1295 | // to work well. Might want to choose something based on the types in the future, e.g. 1296 | // T::max().recip() and T::bits() or something similar. 1297 | let epsilon = ::from(10e-20).expect("Can't convert 10e-20"); 1298 | approximate_float(f, epsilon, 30) 1299 | } 1300 | } 1301 | 1302 | impl Ratio { 1303 | pub fn approximate_float_unsigned(f: F) -> Option> { 1304 | // 1/10e-20 < 1/2**32 which seems like a good default, and 30 seems 1305 | // to work well. Might want to choose something based on the types in the future, e.g. 1306 | // T::max().recip() and T::bits() or something similar. 1307 | let epsilon = ::from(10e-20).expect("Can't convert 10e-20"); 1308 | approximate_float_unsigned(f, epsilon, 30) 1309 | } 1310 | } 1311 | 1312 | fn approximate_float(val: F, max_error: F, max_iterations: usize) -> Option> 1313 | where 1314 | T: Integer + Signed + Bounded + NumCast + Clone, 1315 | F: FloatCore + NumCast, 1316 | { 1317 | let negative = val.is_sign_negative(); 1318 | let abs_val = val.abs(); 1319 | 1320 | let r = approximate_float_unsigned(abs_val, max_error, max_iterations)?; 1321 | 1322 | // Make negative again if needed 1323 | Some(if negative { r.neg() } else { r }) 1324 | } 1325 | 1326 | // No Unsigned constraint because this also works on positive integers and is called 1327 | // like that, see above 1328 | fn approximate_float_unsigned(val: F, max_error: F, max_iterations: usize) -> Option> 1329 | where 1330 | T: Integer + Bounded + NumCast + Clone, 1331 | F: FloatCore + NumCast, 1332 | { 1333 | // Continued fractions algorithm 1334 | // https://web.archive.org/web/20200629111319/http://mathforum.org:80/dr.math/faq/faq.fractions.html#decfrac 1335 | 1336 | if val < F::zero() || val.is_nan() { 1337 | return None; 1338 | } 1339 | 1340 | let mut q = val; 1341 | let mut n0 = T::zero(); 1342 | let mut d0 = T::one(); 1343 | let mut n1 = T::one(); 1344 | let mut d1 = T::zero(); 1345 | 1346 | let t_max = T::max_value(); 1347 | let t_max_f = ::from(t_max.clone())?; 1348 | 1349 | // 1/epsilon > T::MAX 1350 | let epsilon = t_max_f.recip(); 1351 | 1352 | // Overflow 1353 | if q > t_max_f { 1354 | return None; 1355 | } 1356 | 1357 | for _ in 0..max_iterations { 1358 | let a = match ::from(q) { 1359 | None => break, 1360 | Some(a) => a, 1361 | }; 1362 | 1363 | let a_f = match ::from(a.clone()) { 1364 | None => break, 1365 | Some(a_f) => a_f, 1366 | }; 1367 | let f = q - a_f; 1368 | 1369 | // Prevent overflow 1370 | if !a.is_zero() 1371 | && (n1 > t_max.clone() / a.clone() 1372 | || d1 > t_max.clone() / a.clone() 1373 | || a.clone() * n1.clone() > t_max.clone() - n0.clone() 1374 | || a.clone() * d1.clone() > t_max.clone() - d0.clone()) 1375 | { 1376 | break; 1377 | } 1378 | 1379 | let n = a.clone() * n1.clone() + n0.clone(); 1380 | let d = a.clone() * d1.clone() + d0.clone(); 1381 | 1382 | n0 = n1; 1383 | d0 = d1; 1384 | n1 = n.clone(); 1385 | d1 = d.clone(); 1386 | 1387 | // Simplify fraction. Doing so here instead of at the end 1388 | // allows us to get closer to the target value without overflows 1389 | let g = Integer::gcd(&n1, &d1); 1390 | if !g.is_zero() { 1391 | n1 = n1 / g.clone(); 1392 | d1 = d1 / g.clone(); 1393 | } 1394 | 1395 | // Close enough? 1396 | let (n_f, d_f) = match (::from(n), ::from(d)) { 1397 | (Some(n_f), Some(d_f)) => (n_f, d_f), 1398 | _ => break, 1399 | }; 1400 | if (n_f / d_f - val).abs() < max_error { 1401 | break; 1402 | } 1403 | 1404 | // Prevent division by ~0 1405 | if f < epsilon { 1406 | break; 1407 | } 1408 | q = f.recip(); 1409 | } 1410 | 1411 | // Overflow 1412 | if d1.is_zero() { 1413 | return None; 1414 | } 1415 | 1416 | Some(Ratio::new(n1, d1)) 1417 | } 1418 | 1419 | #[cfg(not(feature = "num-bigint"))] 1420 | macro_rules! to_primitive_small { 1421 | ($($type_name:ty)*) => ($( 1422 | impl ToPrimitive for Ratio<$type_name> { 1423 | fn to_i64(&self) -> Option { 1424 | self.to_integer().to_i64() 1425 | } 1426 | 1427 | fn to_i128(&self) -> Option { 1428 | self.to_integer().to_i128() 1429 | } 1430 | 1431 | fn to_u64(&self) -> Option { 1432 | self.to_integer().to_u64() 1433 | } 1434 | 1435 | fn to_u128(&self) -> Option { 1436 | self.to_integer().to_u128() 1437 | } 1438 | 1439 | fn to_f64(&self) -> Option { 1440 | let float = self.numer.to_f64().unwrap() / self.denom.to_f64().unwrap(); 1441 | if float.is_nan() { 1442 | None 1443 | } else { 1444 | Some(float) 1445 | } 1446 | } 1447 | } 1448 | )*) 1449 | } 1450 | 1451 | #[cfg(not(feature = "num-bigint"))] 1452 | to_primitive_small!(u8 i8 u16 i16 u32 i32); 1453 | 1454 | #[cfg(all(target_pointer_width = "32", not(feature = "num-bigint")))] 1455 | to_primitive_small!(usize isize); 1456 | 1457 | #[cfg(not(feature = "num-bigint"))] 1458 | macro_rules! to_primitive_64 { 1459 | ($($type_name:ty)*) => ($( 1460 | impl ToPrimitive for Ratio<$type_name> { 1461 | fn to_i64(&self) -> Option { 1462 | self.to_integer().to_i64() 1463 | } 1464 | 1465 | fn to_i128(&self) -> Option { 1466 | self.to_integer().to_i128() 1467 | } 1468 | 1469 | fn to_u64(&self) -> Option { 1470 | self.to_integer().to_u64() 1471 | } 1472 | 1473 | fn to_u128(&self) -> Option { 1474 | self.to_integer().to_u128() 1475 | } 1476 | 1477 | fn to_f64(&self) -> Option { 1478 | let float = ratio_to_f64( 1479 | self.numer as i128, 1480 | self.denom as i128 1481 | ); 1482 | if float.is_nan() { 1483 | None 1484 | } else { 1485 | Some(float) 1486 | } 1487 | } 1488 | } 1489 | )*) 1490 | } 1491 | 1492 | #[cfg(not(feature = "num-bigint"))] 1493 | to_primitive_64!(u64 i64); 1494 | 1495 | #[cfg(all(target_pointer_width = "64", not(feature = "num-bigint")))] 1496 | to_primitive_64!(usize isize); 1497 | 1498 | #[cfg(feature = "num-bigint")] 1499 | impl ToPrimitive for Ratio { 1500 | fn to_i64(&self) -> Option { 1501 | self.to_integer().to_i64() 1502 | } 1503 | 1504 | fn to_i128(&self) -> Option { 1505 | self.to_integer().to_i128() 1506 | } 1507 | 1508 | fn to_u64(&self) -> Option { 1509 | self.to_integer().to_u64() 1510 | } 1511 | 1512 | fn to_u128(&self) -> Option { 1513 | self.to_integer().to_u128() 1514 | } 1515 | 1516 | fn to_f64(&self) -> Option { 1517 | let float = match (self.numer.to_i64(), self.denom.to_i64()) { 1518 | (Some(numer), Some(denom)) => ratio_to_f64( 1519 | >::from(numer), 1520 | >::from(denom), 1521 | ), 1522 | _ => { 1523 | let numer: BigInt = self.numer.to_bigint()?; 1524 | let denom: BigInt = self.denom.to_bigint()?; 1525 | ratio_to_f64(numer, denom) 1526 | } 1527 | }; 1528 | if float.is_nan() { 1529 | None 1530 | } else { 1531 | Some(float) 1532 | } 1533 | } 1534 | } 1535 | 1536 | trait Bits { 1537 | fn bits(&self) -> u64; 1538 | } 1539 | 1540 | #[cfg(feature = "num-bigint")] 1541 | impl Bits for BigInt { 1542 | fn bits(&self) -> u64 { 1543 | self.bits() 1544 | } 1545 | } 1546 | 1547 | impl Bits for i128 { 1548 | fn bits(&self) -> u64 { 1549 | (128 - self.wrapping_abs().leading_zeros()).into() 1550 | } 1551 | } 1552 | 1553 | /// Converts a ratio of `T` to an f64. 1554 | /// 1555 | /// In addition to stated trait bounds, `T` must be able to hold numbers 56 bits larger than 1556 | /// the largest of `numer` and `denom`. This is automatically true if `T` is `BigInt`. 1557 | fn ratio_to_f64 + ToPrimitive>( 1558 | numer: T, 1559 | denom: T, 1560 | ) -> f64 { 1561 | use core::f64::{INFINITY, MANTISSA_DIGITS, MAX_EXP, MIN_EXP, RADIX}; 1562 | 1563 | assert_eq!( 1564 | RADIX, 2, 1565 | "only floating point implementations with radix 2 are supported" 1566 | ); 1567 | 1568 | // Inclusive upper and lower bounds to the range of exactly-representable ints in an f64. 1569 | const MAX_EXACT_INT: i64 = 1i64 << MANTISSA_DIGITS; 1570 | const MIN_EXACT_INT: i64 = -MAX_EXACT_INT; 1571 | 1572 | let flo_sign = numer.signum().to_f64().unwrap() / denom.signum().to_f64().unwrap(); 1573 | if !flo_sign.is_normal() { 1574 | return flo_sign; 1575 | } 1576 | 1577 | // Fast track: both sides can losslessly be converted to f64s. In this case, letting the 1578 | // FPU do the job is faster and easier. In any other case, converting to f64s may lead 1579 | // to an inexact result: https://stackoverflow.com/questions/56641441/. 1580 | if let (Some(n), Some(d)) = (numer.to_i64(), denom.to_i64()) { 1581 | let exact = MIN_EXACT_INT..=MAX_EXACT_INT; 1582 | if exact.contains(&n) && exact.contains(&d) { 1583 | return n.to_f64().unwrap() / d.to_f64().unwrap(); 1584 | } 1585 | } 1586 | 1587 | // Otherwise, the goal is to obtain a quotient with at least 55 bits. 53 of these bits will 1588 | // be used as the mantissa of the resulting float, and the remaining two are for rounding. 1589 | // There's an error of up to 1 on the number of resulting bits, so we may get either 55 or 1590 | // 56 bits. 1591 | let mut numer = numer.abs(); 1592 | let mut denom = denom.abs(); 1593 | let (is_diff_positive, absolute_diff) = match numer.bits().checked_sub(denom.bits()) { 1594 | Some(diff) => (true, diff), 1595 | None => (false, denom.bits() - numer.bits()), 1596 | }; 1597 | 1598 | // Filter out overflows and underflows. After this step, the signed difference fits in an 1599 | // isize. 1600 | if is_diff_positive && absolute_diff > MAX_EXP as u64 { 1601 | return INFINITY * flo_sign; 1602 | } 1603 | if !is_diff_positive && absolute_diff > -MIN_EXP as u64 + MANTISSA_DIGITS as u64 + 1 { 1604 | return 0.0 * flo_sign; 1605 | } 1606 | let diff = if is_diff_positive { 1607 | absolute_diff.to_isize().unwrap() 1608 | } else { 1609 | -absolute_diff.to_isize().unwrap() 1610 | }; 1611 | 1612 | // Shift is chosen so that the quotient will have 55 or 56 bits. The exception is if the 1613 | // quotient is going to be subnormal, in which case it may have fewer bits. 1614 | let shift: isize = diff.max(MIN_EXP as isize) - MANTISSA_DIGITS as isize - 2; 1615 | if shift >= 0 { 1616 | denom <<= shift as usize 1617 | } else { 1618 | numer <<= -shift as usize 1619 | }; 1620 | 1621 | let (quotient, remainder) = numer.div_rem(&denom); 1622 | 1623 | // This is guaranteed to fit since we've set up quotient to be at most 56 bits. 1624 | let mut quotient = quotient.to_u64().unwrap(); 1625 | let n_rounding_bits = { 1626 | let quotient_bits = 64 - quotient.leading_zeros() as isize; 1627 | let subnormal_bits = MIN_EXP as isize - shift; 1628 | quotient_bits.max(subnormal_bits) - MANTISSA_DIGITS as isize 1629 | } as usize; 1630 | debug_assert!(n_rounding_bits == 2 || n_rounding_bits == 3); 1631 | let rounding_bit_mask = (1u64 << n_rounding_bits) - 1; 1632 | 1633 | // Round to 53 bits with round-to-even. For rounding, we need to take into account both 1634 | // our rounding bits and the division's remainder. 1635 | let ls_bit = quotient & (1u64 << n_rounding_bits) != 0; 1636 | let ms_rounding_bit = quotient & (1u64 << (n_rounding_bits - 1)) != 0; 1637 | let ls_rounding_bits = quotient & (rounding_bit_mask >> 1) != 0; 1638 | if ms_rounding_bit && (ls_bit || ls_rounding_bits || !remainder.is_zero()) { 1639 | quotient += 1u64 << n_rounding_bits; 1640 | } 1641 | quotient &= !rounding_bit_mask; 1642 | 1643 | // The quotient is guaranteed to be exactly representable as it's now 53 bits + 2 or 3 1644 | // trailing zeros, so there is no risk of a rounding error here. 1645 | let q_float = quotient as f64 * flo_sign; 1646 | ldexp(q_float, shift as i32) 1647 | } 1648 | 1649 | /// Multiply `x` by 2 to the power of `exp`. Returns an accurate result even if `2^exp` is not 1650 | /// representable. 1651 | fn ldexp(x: f64, exp: i32) -> f64 { 1652 | use core::f64::{INFINITY, MANTISSA_DIGITS, MAX_EXP, RADIX}; 1653 | 1654 | assert_eq!( 1655 | RADIX, 2, 1656 | "only floating point implementations with radix 2 are supported" 1657 | ); 1658 | 1659 | const EXPONENT_MASK: u64 = 0x7ff << 52; 1660 | const MAX_UNSIGNED_EXPONENT: i32 = 0x7fe; 1661 | const MIN_SUBNORMAL_POWER: i32 = MANTISSA_DIGITS as i32; 1662 | 1663 | if x.is_zero() || x.is_infinite() || x.is_nan() { 1664 | return x; 1665 | } 1666 | 1667 | // Filter out obvious over / underflows to make sure the resulting exponent fits in an isize. 1668 | if exp > 3 * MAX_EXP { 1669 | return INFINITY * x.signum(); 1670 | } else if exp < -3 * MAX_EXP { 1671 | return 0.0 * x.signum(); 1672 | } 1673 | 1674 | // curr_exp is the x's *biased* exponent, and is in the [-54, MAX_UNSIGNED_EXPONENT] range. 1675 | let (bits, curr_exp) = if !x.is_normal() { 1676 | // If x is subnormal, we make it normal by multiplying by 2^53. This causes no loss of 1677 | // precision or rounding. 1678 | let normal_x = x * 2f64.powi(MIN_SUBNORMAL_POWER); 1679 | let bits = normal_x.to_bits(); 1680 | // This cast is safe because the exponent is at most 0x7fe, which fits in an i32. 1681 | ( 1682 | bits, 1683 | ((bits & EXPONENT_MASK) >> 52) as i32 - MIN_SUBNORMAL_POWER, 1684 | ) 1685 | } else { 1686 | let bits = x.to_bits(); 1687 | let curr_exp = (bits & EXPONENT_MASK) >> 52; 1688 | // This cast is safe because the exponent is at most 0x7fe, which fits in an i32. 1689 | (bits, curr_exp as i32) 1690 | }; 1691 | 1692 | // The addition can't overflow because exponent is between 0 and 0x7fe, and exp is between 1693 | // -2*MAX_EXP and 2*MAX_EXP. 1694 | let new_exp = curr_exp + exp; 1695 | 1696 | if new_exp > MAX_UNSIGNED_EXPONENT { 1697 | INFINITY * x.signum() 1698 | } else if new_exp > 0 { 1699 | // Normal case: exponent is not too large nor subnormal. 1700 | let new_bits = (bits & !EXPONENT_MASK) | ((new_exp as u64) << 52); 1701 | f64::from_bits(new_bits) 1702 | } else if new_exp >= -(MANTISSA_DIGITS as i32) { 1703 | // Result is subnormal but may not be zero. 1704 | // In this case, we increase the exponent by 54 to make it normal, then multiply the end 1705 | // result by 2^-53. This results in a single multiplication with no prior rounding error, 1706 | // so there is no risk of double rounding. 1707 | let new_exp = new_exp + MIN_SUBNORMAL_POWER; 1708 | debug_assert!(new_exp >= 0); 1709 | let new_bits = (bits & !EXPONENT_MASK) | ((new_exp as u64) << 52); 1710 | f64::from_bits(new_bits) * 2f64.powi(-MIN_SUBNORMAL_POWER) 1711 | } else { 1712 | // Result is zero. 1713 | return 0.0 * x.signum(); 1714 | } 1715 | } 1716 | 1717 | #[cfg(test)] 1718 | #[cfg(feature = "std")] 1719 | fn hash(x: &T) -> u64 { 1720 | use std::collections::hash_map::RandomState; 1721 | use std::hash::BuildHasher; 1722 | let mut hasher = ::Hasher::new(); 1723 | x.hash(&mut hasher); 1724 | hasher.finish() 1725 | } 1726 | 1727 | #[cfg(test)] 1728 | mod test { 1729 | use super::ldexp; 1730 | #[cfg(feature = "num-bigint")] 1731 | use super::{BigInt, BigRational}; 1732 | use super::{Ratio, Rational64}; 1733 | 1734 | use core::f64; 1735 | use core::i32; 1736 | use core::i64; 1737 | use core::str::FromStr; 1738 | use num_integer::Integer; 1739 | use num_traits::ToPrimitive; 1740 | use num_traits::{FromPrimitive, One, Pow, Signed, Zero}; 1741 | 1742 | pub const _0: Rational64 = Ratio { numer: 0, denom: 1 }; 1743 | pub const _1: Rational64 = Ratio { numer: 1, denom: 1 }; 1744 | pub const _2: Rational64 = Ratio { numer: 2, denom: 1 }; 1745 | pub const _NEG2: Rational64 = Ratio { 1746 | numer: -2, 1747 | denom: 1, 1748 | }; 1749 | pub const _8: Rational64 = Ratio { numer: 8, denom: 1 }; 1750 | pub const _15: Rational64 = Ratio { 1751 | numer: 15, 1752 | denom: 1, 1753 | }; 1754 | pub const _16: Rational64 = Ratio { 1755 | numer: 16, 1756 | denom: 1, 1757 | }; 1758 | 1759 | pub const _1_2: Rational64 = Ratio { numer: 1, denom: 2 }; 1760 | pub const _1_8: Rational64 = Ratio { numer: 1, denom: 8 }; 1761 | pub const _1_15: Rational64 = Ratio { 1762 | numer: 1, 1763 | denom: 15, 1764 | }; 1765 | pub const _1_16: Rational64 = Ratio { 1766 | numer: 1, 1767 | denom: 16, 1768 | }; 1769 | pub const _3_2: Rational64 = Ratio { numer: 3, denom: 2 }; 1770 | pub const _5_2: Rational64 = Ratio { numer: 5, denom: 2 }; 1771 | pub const _NEG1_2: Rational64 = Ratio { 1772 | numer: -1, 1773 | denom: 2, 1774 | }; 1775 | pub const _1_NEG2: Rational64 = Ratio { 1776 | numer: 1, 1777 | denom: -2, 1778 | }; 1779 | pub const _NEG1_NEG2: Rational64 = Ratio { 1780 | numer: -1, 1781 | denom: -2, 1782 | }; 1783 | pub const _1_3: Rational64 = Ratio { numer: 1, denom: 3 }; 1784 | pub const _NEG1_3: Rational64 = Ratio { 1785 | numer: -1, 1786 | denom: 3, 1787 | }; 1788 | pub const _2_3: Rational64 = Ratio { numer: 2, denom: 3 }; 1789 | pub const _NEG2_3: Rational64 = Ratio { 1790 | numer: -2, 1791 | denom: 3, 1792 | }; 1793 | pub const _MIN: Rational64 = Ratio { 1794 | numer: i64::MIN, 1795 | denom: 1, 1796 | }; 1797 | pub const _MIN_P1: Rational64 = Ratio { 1798 | numer: i64::MIN + 1, 1799 | denom: 1, 1800 | }; 1801 | pub const _MAX: Rational64 = Ratio { 1802 | numer: i64::MAX, 1803 | denom: 1, 1804 | }; 1805 | pub const _MAX_M1: Rational64 = Ratio { 1806 | numer: i64::MAX - 1, 1807 | denom: 1, 1808 | }; 1809 | pub const _BILLION: Rational64 = Ratio { 1810 | numer: 1_000_000_000, 1811 | denom: 1, 1812 | }; 1813 | 1814 | #[cfg(feature = "num-bigint")] 1815 | pub fn to_big(n: Rational64) -> BigRational { 1816 | Ratio::new( 1817 | FromPrimitive::from_i64(n.numer).unwrap(), 1818 | FromPrimitive::from_i64(n.denom).unwrap(), 1819 | ) 1820 | } 1821 | #[cfg(not(feature = "num-bigint"))] 1822 | pub fn to_big(n: Rational64) -> Rational64 { 1823 | Ratio::new( 1824 | FromPrimitive::from_i64(n.numer).unwrap(), 1825 | FromPrimitive::from_i64(n.denom).unwrap(), 1826 | ) 1827 | } 1828 | 1829 | #[test] 1830 | fn test_test_constants() { 1831 | // check our constants are what Ratio::new etc. would make. 1832 | assert_eq!(_0, Zero::zero()); 1833 | assert_eq!(_1, One::one()); 1834 | assert_eq!(_2, Ratio::from_integer(2)); 1835 | assert_eq!(_1_2, Ratio::new(1, 2)); 1836 | assert_eq!(_3_2, Ratio::new(3, 2)); 1837 | assert_eq!(_NEG1_2, Ratio::new(-1, 2)); 1838 | assert_eq!(_2, From::from(2)); 1839 | } 1840 | 1841 | #[test] 1842 | fn test_new_reduce() { 1843 | assert_eq!(Ratio::new(2, 2), One::one()); 1844 | assert_eq!(Ratio::new(0, i32::MIN), Zero::zero()); 1845 | assert_eq!(Ratio::new(i32::MIN, i32::MIN), One::one()); 1846 | } 1847 | #[test] 1848 | #[should_panic] 1849 | fn test_new_zero() { 1850 | let _a = Ratio::new(1, 0); 1851 | } 1852 | 1853 | #[test] 1854 | fn test_approximate_float() { 1855 | assert_eq!(Ratio::from_f32(0.5f32), Some(Ratio::new(1i64, 2))); 1856 | assert_eq!(Ratio::from_f64(0.5f64), Some(Ratio::new(1i32, 2))); 1857 | assert_eq!(Ratio::from_f32(5f32), Some(Ratio::new(5i64, 1))); 1858 | assert_eq!(Ratio::from_f64(5f64), Some(Ratio::new(5i32, 1))); 1859 | assert_eq!(Ratio::from_f32(29.97f32), Some(Ratio::new(2997i64, 100))); 1860 | assert_eq!(Ratio::from_f32(-29.97f32), Some(Ratio::new(-2997i64, 100))); 1861 | 1862 | assert_eq!(Ratio::::from_f32(63.5f32), Some(Ratio::new(127i8, 2))); 1863 | assert_eq!(Ratio::::from_f32(126.5f32), Some(Ratio::new(126i8, 1))); 1864 | assert_eq!(Ratio::::from_f32(127.0f32), Some(Ratio::new(127i8, 1))); 1865 | assert_eq!(Ratio::::from_f32(127.5f32), None); 1866 | assert_eq!(Ratio::::from_f32(-63.5f32), Some(Ratio::new(-127i8, 2))); 1867 | assert_eq!( 1868 | Ratio::::from_f32(-126.5f32), 1869 | Some(Ratio::new(-126i8, 1)) 1870 | ); 1871 | assert_eq!( 1872 | Ratio::::from_f32(-127.0f32), 1873 | Some(Ratio::new(-127i8, 1)) 1874 | ); 1875 | assert_eq!(Ratio::::from_f32(-127.5f32), None); 1876 | 1877 | assert_eq!(Ratio::::from_f32(-127f32), None); 1878 | assert_eq!(Ratio::::from_f32(127f32), Some(Ratio::new(127u8, 1))); 1879 | assert_eq!(Ratio::::from_f32(127.5f32), Some(Ratio::new(255u8, 2))); 1880 | assert_eq!(Ratio::::from_f32(256f32), None); 1881 | 1882 | assert_eq!(Ratio::::from_f64(-10e200), None); 1883 | assert_eq!(Ratio::::from_f64(10e200), None); 1884 | assert_eq!(Ratio::::from_f64(f64::INFINITY), None); 1885 | assert_eq!(Ratio::::from_f64(f64::NEG_INFINITY), None); 1886 | assert_eq!(Ratio::::from_f64(f64::NAN), None); 1887 | assert_eq!( 1888 | Ratio::::from_f64(f64::EPSILON), 1889 | Some(Ratio::new(1, 4503599627370496)) 1890 | ); 1891 | assert_eq!(Ratio::::from_f64(0.0), Some(Ratio::new(0, 1))); 1892 | assert_eq!(Ratio::::from_f64(-0.0), Some(Ratio::new(0, 1))); 1893 | } 1894 | 1895 | #[test] 1896 | #[allow(clippy::eq_op)] 1897 | fn test_cmp() { 1898 | assert!(_0 == _0 && _1 == _1); 1899 | assert!(_0 != _1 && _1 != _0); 1900 | assert!(_0 < _1 && !(_1 < _0)); 1901 | assert!(_1 > _0 && !(_0 > _1)); 1902 | 1903 | assert!(_0 <= _0 && _1 <= _1); 1904 | assert!(_0 <= _1 && !(_1 <= _0)); 1905 | 1906 | assert!(_0 >= _0 && _1 >= _1); 1907 | assert!(_1 >= _0 && !(_0 >= _1)); 1908 | 1909 | let _0_2: Rational64 = Ratio::new_raw(0, 2); 1910 | assert_eq!(_0, _0_2); 1911 | } 1912 | 1913 | #[test] 1914 | fn test_cmp_overflow() { 1915 | use core::cmp::Ordering; 1916 | 1917 | // issue #7 example: 1918 | let big = Ratio::new(128u8, 1); 1919 | let small = big.recip(); 1920 | assert!(big > small); 1921 | 1922 | // try a few that are closer together 1923 | // (some matching numer, some matching denom, some neither) 1924 | let ratios = [ 1925 | Ratio::new(125_i8, 127_i8), 1926 | Ratio::new(63_i8, 64_i8), 1927 | Ratio::new(124_i8, 125_i8), 1928 | Ratio::new(125_i8, 126_i8), 1929 | Ratio::new(126_i8, 127_i8), 1930 | Ratio::new(127_i8, 126_i8), 1931 | ]; 1932 | 1933 | fn check_cmp(a: Ratio, b: Ratio, ord: Ordering) { 1934 | #[cfg(feature = "std")] 1935 | println!("comparing {} and {}", a, b); 1936 | assert_eq!(a.cmp(&b), ord); 1937 | assert_eq!(b.cmp(&a), ord.reverse()); 1938 | } 1939 | 1940 | for (i, &a) in ratios.iter().enumerate() { 1941 | check_cmp(a, a, Ordering::Equal); 1942 | check_cmp(-a, a, Ordering::Less); 1943 | for &b in &ratios[i + 1..] { 1944 | check_cmp(a, b, Ordering::Less); 1945 | check_cmp(-a, -b, Ordering::Greater); 1946 | check_cmp(a.recip(), b.recip(), Ordering::Greater); 1947 | check_cmp(-a.recip(), -b.recip(), Ordering::Less); 1948 | } 1949 | } 1950 | } 1951 | 1952 | #[test] 1953 | fn test_to_integer() { 1954 | assert_eq!(_0.to_integer(), 0); 1955 | assert_eq!(_1.to_integer(), 1); 1956 | assert_eq!(_2.to_integer(), 2); 1957 | assert_eq!(_1_2.to_integer(), 0); 1958 | assert_eq!(_3_2.to_integer(), 1); 1959 | assert_eq!(_NEG1_2.to_integer(), 0); 1960 | } 1961 | 1962 | #[test] 1963 | fn test_numer() { 1964 | assert_eq!(_0.numer(), &0); 1965 | assert_eq!(_1.numer(), &1); 1966 | assert_eq!(_2.numer(), &2); 1967 | assert_eq!(_1_2.numer(), &1); 1968 | assert_eq!(_3_2.numer(), &3); 1969 | assert_eq!(_NEG1_2.numer(), &(-1)); 1970 | } 1971 | #[test] 1972 | fn test_denom() { 1973 | assert_eq!(_0.denom(), &1); 1974 | assert_eq!(_1.denom(), &1); 1975 | assert_eq!(_2.denom(), &1); 1976 | assert_eq!(_1_2.denom(), &2); 1977 | assert_eq!(_3_2.denom(), &2); 1978 | assert_eq!(_NEG1_2.denom(), &2); 1979 | } 1980 | 1981 | #[test] 1982 | fn test_is_integer() { 1983 | assert!(_0.is_integer()); 1984 | assert!(_1.is_integer()); 1985 | assert!(_2.is_integer()); 1986 | assert!(!_1_2.is_integer()); 1987 | assert!(!_3_2.is_integer()); 1988 | assert!(!_NEG1_2.is_integer()); 1989 | } 1990 | 1991 | #[cfg(not(feature = "std"))] 1992 | use core::fmt::{self, Write}; 1993 | #[cfg(not(feature = "std"))] 1994 | #[derive(Debug)] 1995 | struct NoStdTester { 1996 | cursor: usize, 1997 | buf: [u8; NoStdTester::BUF_SIZE], 1998 | } 1999 | 2000 | #[cfg(not(feature = "std"))] 2001 | impl NoStdTester { 2002 | fn new() -> NoStdTester { 2003 | NoStdTester { 2004 | buf: [0; Self::BUF_SIZE], 2005 | cursor: 0, 2006 | } 2007 | } 2008 | 2009 | fn clear(&mut self) { 2010 | self.buf = [0; Self::BUF_SIZE]; 2011 | self.cursor = 0; 2012 | } 2013 | 2014 | const WRITE_ERR: &'static str = "Formatted output too long"; 2015 | const BUF_SIZE: usize = 32; 2016 | } 2017 | 2018 | #[cfg(not(feature = "std"))] 2019 | impl Write for NoStdTester { 2020 | fn write_str(&mut self, s: &str) -> fmt::Result { 2021 | for byte in s.bytes() { 2022 | self.buf[self.cursor] = byte; 2023 | self.cursor += 1; 2024 | if self.cursor >= self.buf.len() { 2025 | return Err(fmt::Error {}); 2026 | } 2027 | } 2028 | Ok(()) 2029 | } 2030 | } 2031 | 2032 | #[cfg(not(feature = "std"))] 2033 | impl PartialEq for NoStdTester { 2034 | fn eq(&self, other: &str) -> bool { 2035 | let other = other.as_bytes(); 2036 | for index in 0..self.cursor { 2037 | if self.buf.get(index) != other.get(index) { 2038 | return false; 2039 | } 2040 | } 2041 | true 2042 | } 2043 | } 2044 | 2045 | macro_rules! assert_fmt_eq { 2046 | ($fmt_args:expr, $string:expr) => { 2047 | #[cfg(not(feature = "std"))] 2048 | { 2049 | let mut tester = NoStdTester::new(); 2050 | write!(tester, "{}", $fmt_args).expect(NoStdTester::WRITE_ERR); 2051 | assert_eq!(tester, *$string); 2052 | tester.clear(); 2053 | } 2054 | #[cfg(feature = "std")] 2055 | { 2056 | assert_eq!(std::fmt::format($fmt_args), $string); 2057 | } 2058 | }; 2059 | } 2060 | 2061 | #[test] 2062 | fn test_show() { 2063 | // Test: 2064 | // :b :o :x, :X, :? 2065 | // alternate or not (#) 2066 | // positive and negative 2067 | // padding 2068 | // does not test precision (i.e. truncation) 2069 | assert_fmt_eq!(format_args!("{}", _2), "2"); 2070 | assert_fmt_eq!(format_args!("{:+}", _2), "+2"); 2071 | assert_fmt_eq!(format_args!("{:-}", _2), "2"); 2072 | assert_fmt_eq!(format_args!("{}", _1_2), "1/2"); 2073 | assert_fmt_eq!(format_args!("{}", -_1_2), "-1/2"); // test negatives 2074 | assert_fmt_eq!(format_args!("{}", _0), "0"); 2075 | assert_fmt_eq!(format_args!("{}", -_2), "-2"); 2076 | assert_fmt_eq!(format_args!("{:+}", -_2), "-2"); 2077 | assert_fmt_eq!(format_args!("{:b}", _2), "10"); 2078 | assert_fmt_eq!(format_args!("{:#b}", _2), "0b10"); 2079 | assert_fmt_eq!(format_args!("{:b}", _1_2), "1/10"); 2080 | assert_fmt_eq!(format_args!("{:+b}", _1_2), "+1/10"); 2081 | assert_fmt_eq!(format_args!("{:-b}", _1_2), "1/10"); 2082 | assert_fmt_eq!(format_args!("{:b}", _0), "0"); 2083 | assert_fmt_eq!(format_args!("{:#b}", _1_2), "0b1/0b10"); 2084 | // no std does not support padding 2085 | #[cfg(feature = "std")] 2086 | assert_eq!(&format!("{:010b}", _1_2), "0000001/10"); 2087 | #[cfg(feature = "std")] 2088 | assert_eq!(&format!("{:#010b}", _1_2), "0b001/0b10"); 2089 | let half_i8: Ratio = Ratio::new(1_i8, 2_i8); 2090 | assert_fmt_eq!(format_args!("{:b}", -half_i8), "11111111/10"); 2091 | assert_fmt_eq!(format_args!("{:#b}", -half_i8), "0b11111111/0b10"); 2092 | #[cfg(feature = "std")] 2093 | assert_eq!(&format!("{:05}", Ratio::new(-1_i8, 1_i8)), "-0001"); 2094 | 2095 | assert_fmt_eq!(format_args!("{:o}", _8), "10"); 2096 | assert_fmt_eq!(format_args!("{:o}", _1_8), "1/10"); 2097 | assert_fmt_eq!(format_args!("{:o}", _0), "0"); 2098 | assert_fmt_eq!(format_args!("{:#o}", _1_8), "0o1/0o10"); 2099 | #[cfg(feature = "std")] 2100 | assert_eq!(&format!("{:010o}", _1_8), "0000001/10"); 2101 | #[cfg(feature = "std")] 2102 | assert_eq!(&format!("{:#010o}", _1_8), "0o001/0o10"); 2103 | assert_fmt_eq!(format_args!("{:o}", -half_i8), "377/2"); 2104 | assert_fmt_eq!(format_args!("{:#o}", -half_i8), "0o377/0o2"); 2105 | 2106 | assert_fmt_eq!(format_args!("{:x}", _16), "10"); 2107 | assert_fmt_eq!(format_args!("{:x}", _15), "f"); 2108 | assert_fmt_eq!(format_args!("{:x}", _1_16), "1/10"); 2109 | assert_fmt_eq!(format_args!("{:x}", _1_15), "1/f"); 2110 | assert_fmt_eq!(format_args!("{:x}", _0), "0"); 2111 | assert_fmt_eq!(format_args!("{:#x}", _1_16), "0x1/0x10"); 2112 | #[cfg(feature = "std")] 2113 | assert_eq!(&format!("{:010x}", _1_16), "0000001/10"); 2114 | #[cfg(feature = "std")] 2115 | assert_eq!(&format!("{:#010x}", _1_16), "0x001/0x10"); 2116 | assert_fmt_eq!(format_args!("{:x}", -half_i8), "ff/2"); 2117 | assert_fmt_eq!(format_args!("{:#x}", -half_i8), "0xff/0x2"); 2118 | 2119 | assert_fmt_eq!(format_args!("{:X}", _16), "10"); 2120 | assert_fmt_eq!(format_args!("{:X}", _15), "F"); 2121 | assert_fmt_eq!(format_args!("{:X}", _1_16), "1/10"); 2122 | assert_fmt_eq!(format_args!("{:X}", _1_15), "1/F"); 2123 | assert_fmt_eq!(format_args!("{:X}", _0), "0"); 2124 | assert_fmt_eq!(format_args!("{:#X}", _1_16), "0x1/0x10"); 2125 | #[cfg(feature = "std")] 2126 | assert_eq!(format!("{:010X}", _1_16), "0000001/10"); 2127 | #[cfg(feature = "std")] 2128 | assert_eq!(format!("{:#010X}", _1_16), "0x001/0x10"); 2129 | assert_fmt_eq!(format_args!("{:X}", -half_i8), "FF/2"); 2130 | assert_fmt_eq!(format_args!("{:#X}", -half_i8), "0xFF/0x2"); 2131 | 2132 | assert_fmt_eq!(format_args!("{:e}", -_2), "-2e0"); 2133 | assert_fmt_eq!(format_args!("{:#e}", -_2), "-2e0"); 2134 | assert_fmt_eq!(format_args!("{:+e}", -_2), "-2e0"); 2135 | assert_fmt_eq!(format_args!("{:e}", _BILLION), "1e9"); 2136 | assert_fmt_eq!(format_args!("{:+e}", _BILLION), "+1e9"); 2137 | assert_fmt_eq!(format_args!("{:e}", _BILLION.recip()), "1e0/1e9"); 2138 | assert_fmt_eq!(format_args!("{:+e}", _BILLION.recip()), "+1e0/1e9"); 2139 | 2140 | assert_fmt_eq!(format_args!("{:E}", -_2), "-2E0"); 2141 | assert_fmt_eq!(format_args!("{:#E}", -_2), "-2E0"); 2142 | assert_fmt_eq!(format_args!("{:+E}", -_2), "-2E0"); 2143 | assert_fmt_eq!(format_args!("{:E}", _BILLION), "1E9"); 2144 | assert_fmt_eq!(format_args!("{:+E}", _BILLION), "+1E9"); 2145 | assert_fmt_eq!(format_args!("{:E}", _BILLION.recip()), "1E0/1E9"); 2146 | assert_fmt_eq!(format_args!("{:+E}", _BILLION.recip()), "+1E0/1E9"); 2147 | } 2148 | 2149 | mod arith { 2150 | use super::super::{Ratio, Rational64}; 2151 | use super::{to_big, _0, _1, _1_2, _2, _3_2, _5_2, _MAX, _MAX_M1, _MIN, _MIN_P1, _NEG1_2}; 2152 | use core::fmt::Debug; 2153 | use num_integer::Integer; 2154 | use num_traits::{Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, NumAssign}; 2155 | 2156 | #[test] 2157 | fn test_add() { 2158 | fn test(a: Rational64, b: Rational64, c: Rational64) { 2159 | assert_eq!(a + b, c); 2160 | assert_eq!( 2161 | { 2162 | let mut x = a; 2163 | x += b; 2164 | x 2165 | }, 2166 | c 2167 | ); 2168 | assert_eq!(to_big(a) + to_big(b), to_big(c)); 2169 | assert_eq!(a.checked_add(&b), Some(c)); 2170 | assert_eq!(to_big(a).checked_add(&to_big(b)), Some(to_big(c))); 2171 | } 2172 | fn test_assign(a: Rational64, b: i64, c: Rational64) { 2173 | assert_eq!(a + b, c); 2174 | assert_eq!( 2175 | { 2176 | let mut x = a; 2177 | x += b; 2178 | x 2179 | }, 2180 | c 2181 | ); 2182 | } 2183 | 2184 | test(_1, _1_2, _3_2); 2185 | test(_1, _1, _2); 2186 | test(_1_2, _3_2, _2); 2187 | test(_1_2, _NEG1_2, _0); 2188 | test_assign(_1_2, 1, _3_2); 2189 | } 2190 | 2191 | #[test] 2192 | fn test_add_overflow() { 2193 | // compares Ratio(1, T::max_value()) + Ratio(1, T::max_value()) 2194 | // to Ratio(1+1, T::max_value()) for each integer type. 2195 | // Previously, this calculation would overflow. 2196 | fn test_add_typed_overflow() 2197 | where 2198 | T: Integer + Bounded + Clone + Debug + NumAssign, 2199 | { 2200 | let _1_max = Ratio::new(T::one(), T::max_value()); 2201 | let _2_max = Ratio::new(T::one() + T::one(), T::max_value()); 2202 | assert_eq!(_1_max.clone() + _1_max.clone(), _2_max); 2203 | assert_eq!( 2204 | { 2205 | let mut tmp = _1_max.clone(); 2206 | tmp += _1_max; 2207 | tmp 2208 | }, 2209 | _2_max 2210 | ); 2211 | } 2212 | test_add_typed_overflow::(); 2213 | test_add_typed_overflow::(); 2214 | test_add_typed_overflow::(); 2215 | test_add_typed_overflow::(); 2216 | test_add_typed_overflow::(); 2217 | test_add_typed_overflow::(); 2218 | 2219 | test_add_typed_overflow::(); 2220 | test_add_typed_overflow::(); 2221 | test_add_typed_overflow::(); 2222 | test_add_typed_overflow::(); 2223 | test_add_typed_overflow::(); 2224 | test_add_typed_overflow::(); 2225 | } 2226 | 2227 | #[test] 2228 | fn test_sub() { 2229 | fn test(a: Rational64, b: Rational64, c: Rational64) { 2230 | assert_eq!(a - b, c); 2231 | assert_eq!( 2232 | { 2233 | let mut x = a; 2234 | x -= b; 2235 | x 2236 | }, 2237 | c 2238 | ); 2239 | assert_eq!(to_big(a) - to_big(b), to_big(c)); 2240 | assert_eq!(a.checked_sub(&b), Some(c)); 2241 | assert_eq!(to_big(a).checked_sub(&to_big(b)), Some(to_big(c))); 2242 | } 2243 | fn test_assign(a: Rational64, b: i64, c: Rational64) { 2244 | assert_eq!(a - b, c); 2245 | assert_eq!( 2246 | { 2247 | let mut x = a; 2248 | x -= b; 2249 | x 2250 | }, 2251 | c 2252 | ); 2253 | } 2254 | 2255 | test(_1, _1_2, _1_2); 2256 | test(_3_2, _1_2, _1); 2257 | test(_1, _NEG1_2, _3_2); 2258 | test_assign(_1_2, 1, _NEG1_2); 2259 | } 2260 | 2261 | #[test] 2262 | fn test_sub_overflow() { 2263 | // compares Ratio(1, T::max_value()) - Ratio(1, T::max_value()) to T::zero() 2264 | // for each integer type. Previously, this calculation would overflow. 2265 | fn test_sub_typed_overflow() 2266 | where 2267 | T: Integer + Bounded + Clone + Debug + NumAssign, 2268 | { 2269 | let _1_max: Ratio = Ratio::new(T::one(), T::max_value()); 2270 | assert!(T::is_zero(&(_1_max.clone() - _1_max.clone()).numer)); 2271 | { 2272 | let mut tmp: Ratio = _1_max.clone(); 2273 | tmp -= _1_max; 2274 | assert!(T::is_zero(&tmp.numer)); 2275 | } 2276 | } 2277 | test_sub_typed_overflow::(); 2278 | test_sub_typed_overflow::(); 2279 | test_sub_typed_overflow::(); 2280 | test_sub_typed_overflow::(); 2281 | test_sub_typed_overflow::(); 2282 | test_sub_typed_overflow::(); 2283 | 2284 | test_sub_typed_overflow::(); 2285 | test_sub_typed_overflow::(); 2286 | test_sub_typed_overflow::(); 2287 | test_sub_typed_overflow::(); 2288 | test_sub_typed_overflow::(); 2289 | test_sub_typed_overflow::(); 2290 | } 2291 | 2292 | #[test] 2293 | fn test_mul() { 2294 | fn test(a: Rational64, b: Rational64, c: Rational64) { 2295 | assert_eq!(a * b, c); 2296 | assert_eq!( 2297 | { 2298 | let mut x = a; 2299 | x *= b; 2300 | x 2301 | }, 2302 | c 2303 | ); 2304 | assert_eq!(to_big(a) * to_big(b), to_big(c)); 2305 | assert_eq!(a.checked_mul(&b), Some(c)); 2306 | assert_eq!(to_big(a).checked_mul(&to_big(b)), Some(to_big(c))); 2307 | } 2308 | fn test_assign(a: Rational64, b: i64, c: Rational64) { 2309 | assert_eq!(a * b, c); 2310 | assert_eq!( 2311 | { 2312 | let mut x = a; 2313 | x *= b; 2314 | x 2315 | }, 2316 | c 2317 | ); 2318 | } 2319 | 2320 | test(_1, _1_2, _1_2); 2321 | test(_1_2, _3_2, Ratio::new(3, 4)); 2322 | test(_1_2, _NEG1_2, Ratio::new(-1, 4)); 2323 | test_assign(_1_2, 2, _1); 2324 | } 2325 | 2326 | #[test] 2327 | fn test_mul_overflow() { 2328 | fn test_mul_typed_overflow() 2329 | where 2330 | T: Integer + Bounded + Clone + Debug + NumAssign + CheckedMul, 2331 | { 2332 | let two = T::one() + T::one(); 2333 | let _3 = T::one() + T::one() + T::one(); 2334 | 2335 | // 1/big * 2/3 = 1/(max/4*3), where big is max/2 2336 | // make big = max/2, but also divisible by 2 2337 | let big = T::max_value() / two.clone() / two.clone() * two.clone(); 2338 | let _1_big: Ratio = Ratio::new(T::one(), big.clone()); 2339 | let _2_3: Ratio = Ratio::new(two.clone(), _3.clone()); 2340 | assert_eq!(None, big.clone().checked_mul(&_3.clone())); 2341 | let expected = Ratio::new(T::one(), big / two.clone() * _3.clone()); 2342 | assert_eq!(expected.clone(), _1_big.clone() * _2_3.clone()); 2343 | assert_eq!( 2344 | Some(expected.clone()), 2345 | _1_big.clone().checked_mul(&_2_3.clone()) 2346 | ); 2347 | assert_eq!(expected, { 2348 | let mut tmp = _1_big; 2349 | tmp *= _2_3; 2350 | tmp 2351 | }); 2352 | 2353 | // big/3 * 3 = big/1 2354 | // make big = max/2, but make it indivisible by 3 2355 | let big = T::max_value() / two / _3.clone() * _3.clone() + T::one(); 2356 | assert_eq!(None, big.clone().checked_mul(&_3.clone())); 2357 | let big_3 = Ratio::new(big.clone(), _3.clone()); 2358 | let expected = Ratio::new(big, T::one()); 2359 | assert_eq!(expected, big_3.clone() * _3.clone()); 2360 | assert_eq!(expected, { 2361 | let mut tmp = big_3; 2362 | tmp *= _3; 2363 | tmp 2364 | }); 2365 | } 2366 | test_mul_typed_overflow::(); 2367 | test_mul_typed_overflow::(); 2368 | test_mul_typed_overflow::(); 2369 | test_mul_typed_overflow::(); 2370 | test_mul_typed_overflow::(); 2371 | test_mul_typed_overflow::(); 2372 | 2373 | test_mul_typed_overflow::(); 2374 | test_mul_typed_overflow::(); 2375 | test_mul_typed_overflow::(); 2376 | test_mul_typed_overflow::(); 2377 | test_mul_typed_overflow::(); 2378 | test_mul_typed_overflow::(); 2379 | } 2380 | 2381 | #[test] 2382 | fn test_div() { 2383 | fn test(a: Rational64, b: Rational64, c: Rational64) { 2384 | assert_eq!(a / b, c); 2385 | assert_eq!( 2386 | { 2387 | let mut x = a; 2388 | x /= b; 2389 | x 2390 | }, 2391 | c 2392 | ); 2393 | assert_eq!(to_big(a) / to_big(b), to_big(c)); 2394 | assert_eq!(a.checked_div(&b), Some(c)); 2395 | assert_eq!(to_big(a).checked_div(&to_big(b)), Some(to_big(c))); 2396 | } 2397 | fn test_assign(a: Rational64, b: i64, c: Rational64) { 2398 | assert_eq!(a / b, c); 2399 | assert_eq!( 2400 | { 2401 | let mut x = a; 2402 | x /= b; 2403 | x 2404 | }, 2405 | c 2406 | ); 2407 | } 2408 | 2409 | test(_1, _1_2, _2); 2410 | test(_3_2, _1_2, _1 + _2); 2411 | test(_1, _NEG1_2, _NEG1_2 + _NEG1_2 + _NEG1_2 + _NEG1_2); 2412 | test_assign(_1, 2, _1_2); 2413 | } 2414 | 2415 | #[test] 2416 | fn test_div_overflow() { 2417 | fn test_div_typed_overflow() 2418 | where 2419 | T: Integer + Bounded + Clone + Debug + NumAssign + CheckedMul, 2420 | { 2421 | let two = T::one() + T::one(); 2422 | let _3 = T::one() + T::one() + T::one(); 2423 | 2424 | // 1/big / 3/2 = 1/(max/4*3), where big is max/2 2425 | // big ~ max/2, and big is divisible by 2 2426 | let big = T::max_value() / two.clone() / two.clone() * two.clone(); 2427 | assert_eq!(None, big.clone().checked_mul(&_3.clone())); 2428 | let _1_big: Ratio = Ratio::new(T::one(), big.clone()); 2429 | let _3_two: Ratio = Ratio::new(_3.clone(), two.clone()); 2430 | let expected = Ratio::new(T::one(), big / two.clone() * _3.clone()); 2431 | assert_eq!(expected.clone(), _1_big.clone() / _3_two.clone()); 2432 | assert_eq!( 2433 | Some(expected.clone()), 2434 | _1_big.clone().checked_div(&_3_two.clone()) 2435 | ); 2436 | assert_eq!(expected, { 2437 | let mut tmp = _1_big; 2438 | tmp /= _3_two; 2439 | tmp 2440 | }); 2441 | 2442 | // 3/big / 3 = 1/big where big is max/2 2443 | // big ~ max/2, and big is not divisible by 3 2444 | let big = T::max_value() / two / _3.clone() * _3.clone() + T::one(); 2445 | assert_eq!(None, big.clone().checked_mul(&_3.clone())); 2446 | let _3_big = Ratio::new(_3.clone(), big.clone()); 2447 | let expected = Ratio::new(T::one(), big); 2448 | assert_eq!(expected, _3_big.clone() / _3.clone()); 2449 | assert_eq!(expected, { 2450 | let mut tmp = _3_big; 2451 | tmp /= _3; 2452 | tmp 2453 | }); 2454 | } 2455 | test_div_typed_overflow::(); 2456 | test_div_typed_overflow::(); 2457 | test_div_typed_overflow::(); 2458 | test_div_typed_overflow::(); 2459 | test_div_typed_overflow::(); 2460 | test_div_typed_overflow::(); 2461 | 2462 | test_div_typed_overflow::(); 2463 | test_div_typed_overflow::(); 2464 | test_div_typed_overflow::(); 2465 | test_div_typed_overflow::(); 2466 | test_div_typed_overflow::(); 2467 | test_div_typed_overflow::(); 2468 | } 2469 | 2470 | #[test] 2471 | fn test_rem() { 2472 | fn test(a: Rational64, b: Rational64, c: Rational64) { 2473 | assert_eq!(a % b, c); 2474 | assert_eq!( 2475 | { 2476 | let mut x = a; 2477 | x %= b; 2478 | x 2479 | }, 2480 | c 2481 | ); 2482 | assert_eq!(to_big(a) % to_big(b), to_big(c)) 2483 | } 2484 | fn test_assign(a: Rational64, b: i64, c: Rational64) { 2485 | assert_eq!(a % b, c); 2486 | assert_eq!( 2487 | { 2488 | let mut x = a; 2489 | x %= b; 2490 | x 2491 | }, 2492 | c 2493 | ); 2494 | } 2495 | 2496 | test(_3_2, _1, _1_2); 2497 | test(_3_2, _1_2, _0); 2498 | test(_5_2, _3_2, _1); 2499 | test(_2, _NEG1_2, _0); 2500 | test(_1_2, _2, _1_2); 2501 | test_assign(_3_2, 1, _1_2); 2502 | } 2503 | 2504 | #[test] 2505 | fn test_rem_overflow() { 2506 | // tests that Ratio(1,2) % Ratio(1, T::max_value()) equals 0 2507 | // for each integer type. Previously, this calculation would overflow. 2508 | fn test_rem_typed_overflow() 2509 | where 2510 | T: Integer + Bounded + Clone + Debug + NumAssign, 2511 | { 2512 | let two = T::one() + T::one(); 2513 | // value near to maximum, but divisible by two 2514 | let max_div2 = T::max_value() / two.clone() * two.clone(); 2515 | let _1_max: Ratio = Ratio::new(T::one(), max_div2); 2516 | let _1_two: Ratio = Ratio::new(T::one(), two); 2517 | assert!(T::is_zero(&(_1_two.clone() % _1_max.clone()).numer)); 2518 | { 2519 | let mut tmp: Ratio = _1_two; 2520 | tmp %= _1_max; 2521 | assert!(T::is_zero(&tmp.numer)); 2522 | } 2523 | } 2524 | test_rem_typed_overflow::(); 2525 | test_rem_typed_overflow::(); 2526 | test_rem_typed_overflow::(); 2527 | test_rem_typed_overflow::(); 2528 | test_rem_typed_overflow::(); 2529 | test_rem_typed_overflow::(); 2530 | 2531 | test_rem_typed_overflow::(); 2532 | test_rem_typed_overflow::(); 2533 | test_rem_typed_overflow::(); 2534 | test_rem_typed_overflow::(); 2535 | test_rem_typed_overflow::(); 2536 | test_rem_typed_overflow::(); 2537 | } 2538 | 2539 | #[test] 2540 | fn test_neg() { 2541 | fn test(a: Rational64, b: Rational64) { 2542 | assert_eq!(-a, b); 2543 | assert_eq!(-to_big(a), to_big(b)) 2544 | } 2545 | 2546 | test(_0, _0); 2547 | test(_1_2, _NEG1_2); 2548 | test(-_1, _1); 2549 | } 2550 | #[test] 2551 | #[allow(clippy::eq_op)] 2552 | fn test_zero() { 2553 | assert_eq!(_0 + _0, _0); 2554 | assert_eq!(_0 * _0, _0); 2555 | assert_eq!(_0 * _1, _0); 2556 | assert_eq!(_0 / _NEG1_2, _0); 2557 | assert_eq!(_0 - _0, _0); 2558 | } 2559 | #[test] 2560 | #[should_panic] 2561 | fn test_div_0() { 2562 | let _a = _1 / _0; 2563 | } 2564 | 2565 | #[test] 2566 | fn test_checked_failures() { 2567 | let big = Ratio::new(128u8, 1); 2568 | let small = Ratio::new(1, 128u8); 2569 | assert_eq!(big.checked_add(&big), None); 2570 | assert_eq!(small.checked_sub(&big), None); 2571 | assert_eq!(big.checked_mul(&big), None); 2572 | assert_eq!(small.checked_div(&big), None); 2573 | assert_eq!(_1.checked_div(&_0), None); 2574 | } 2575 | 2576 | #[test] 2577 | fn test_checked_zeros() { 2578 | assert_eq!(_0.checked_add(&_0), Some(_0)); 2579 | assert_eq!(_0.checked_sub(&_0), Some(_0)); 2580 | assert_eq!(_0.checked_mul(&_0), Some(_0)); 2581 | assert_eq!(_0.checked_div(&_0), None); 2582 | } 2583 | 2584 | #[test] 2585 | fn test_checked_min() { 2586 | assert_eq!(_MIN.checked_add(&_MIN), None); 2587 | assert_eq!(_MIN.checked_sub(&_MIN), Some(_0)); 2588 | assert_eq!(_MIN.checked_mul(&_MIN), None); 2589 | assert_eq!(_MIN.checked_div(&_MIN), Some(_1)); 2590 | assert_eq!(_0.checked_add(&_MIN), Some(_MIN)); 2591 | assert_eq!(_0.checked_sub(&_MIN), None); 2592 | assert_eq!(_0.checked_mul(&_MIN), Some(_0)); 2593 | assert_eq!(_0.checked_div(&_MIN), Some(_0)); 2594 | assert_eq!(_1.checked_add(&_MIN), Some(_MIN_P1)); 2595 | assert_eq!(_1.checked_sub(&_MIN), None); 2596 | assert_eq!(_1.checked_mul(&_MIN), Some(_MIN)); 2597 | assert_eq!(_1.checked_div(&_MIN), None); 2598 | assert_eq!(_MIN.checked_add(&_0), Some(_MIN)); 2599 | assert_eq!(_MIN.checked_sub(&_0), Some(_MIN)); 2600 | assert_eq!(_MIN.checked_mul(&_0), Some(_0)); 2601 | assert_eq!(_MIN.checked_div(&_0), None); 2602 | assert_eq!(_MIN.checked_add(&_1), Some(_MIN_P1)); 2603 | assert_eq!(_MIN.checked_sub(&_1), None); 2604 | assert_eq!(_MIN.checked_mul(&_1), Some(_MIN)); 2605 | assert_eq!(_MIN.checked_div(&_1), Some(_MIN)); 2606 | } 2607 | 2608 | #[test] 2609 | fn test_checked_max() { 2610 | assert_eq!(_MAX.checked_add(&_MAX), None); 2611 | assert_eq!(_MAX.checked_sub(&_MAX), Some(_0)); 2612 | assert_eq!(_MAX.checked_mul(&_MAX), None); 2613 | assert_eq!(_MAX.checked_div(&_MAX), Some(_1)); 2614 | assert_eq!(_0.checked_add(&_MAX), Some(_MAX)); 2615 | assert_eq!(_0.checked_sub(&_MAX), Some(_MIN_P1)); 2616 | assert_eq!(_0.checked_mul(&_MAX), Some(_0)); 2617 | assert_eq!(_0.checked_div(&_MAX), Some(_0)); 2618 | assert_eq!(_1.checked_add(&_MAX), None); 2619 | assert_eq!(_1.checked_sub(&_MAX), Some(-_MAX_M1)); 2620 | assert_eq!(_1.checked_mul(&_MAX), Some(_MAX)); 2621 | assert_eq!(_1.checked_div(&_MAX), Some(_MAX.recip())); 2622 | assert_eq!(_MAX.checked_add(&_0), Some(_MAX)); 2623 | assert_eq!(_MAX.checked_sub(&_0), Some(_MAX)); 2624 | assert_eq!(_MAX.checked_mul(&_0), Some(_0)); 2625 | assert_eq!(_MAX.checked_div(&_0), None); 2626 | assert_eq!(_MAX.checked_add(&_1), None); 2627 | assert_eq!(_MAX.checked_sub(&_1), Some(_MAX_M1)); 2628 | assert_eq!(_MAX.checked_mul(&_1), Some(_MAX)); 2629 | assert_eq!(_MAX.checked_div(&_1), Some(_MAX)); 2630 | } 2631 | 2632 | #[test] 2633 | fn test_checked_min_max() { 2634 | assert_eq!(_MIN.checked_add(&_MAX), Some(-_1)); 2635 | assert_eq!(_MIN.checked_sub(&_MAX), None); 2636 | assert_eq!(_MIN.checked_mul(&_MAX), None); 2637 | assert_eq!( 2638 | _MIN.checked_div(&_MAX), 2639 | Some(Ratio::new(_MIN.numer, _MAX.numer)) 2640 | ); 2641 | assert_eq!(_MAX.checked_add(&_MIN), Some(-_1)); 2642 | assert_eq!(_MAX.checked_sub(&_MIN), None); 2643 | assert_eq!(_MAX.checked_mul(&_MIN), None); 2644 | assert_eq!(_MAX.checked_div(&_MIN), None); 2645 | } 2646 | } 2647 | 2648 | #[test] 2649 | fn test_round() { 2650 | assert_eq!(_1_3.ceil(), _1); 2651 | assert_eq!(_1_3.floor(), _0); 2652 | assert_eq!(_1_3.round(), _0); 2653 | assert_eq!(_1_3.trunc(), _0); 2654 | 2655 | assert_eq!(_NEG1_3.ceil(), _0); 2656 | assert_eq!(_NEG1_3.floor(), -_1); 2657 | assert_eq!(_NEG1_3.round(), _0); 2658 | assert_eq!(_NEG1_3.trunc(), _0); 2659 | 2660 | assert_eq!(_2_3.ceil(), _1); 2661 | assert_eq!(_2_3.floor(), _0); 2662 | assert_eq!(_2_3.round(), _1); 2663 | assert_eq!(_2_3.trunc(), _0); 2664 | 2665 | assert_eq!(_NEG2_3.ceil(), _0); 2666 | assert_eq!(_NEG2_3.floor(), -_1); 2667 | assert_eq!(_NEG2_3.round(), -_1); 2668 | assert_eq!(_NEG2_3.trunc(), _0); 2669 | 2670 | assert_eq!(_1_2.ceil(), _1); 2671 | assert_eq!(_1_2.floor(), _0); 2672 | assert_eq!(_1_2.round(), _1); 2673 | assert_eq!(_1_2.trunc(), _0); 2674 | 2675 | assert_eq!(_NEG1_2.ceil(), _0); 2676 | assert_eq!(_NEG1_2.floor(), -_1); 2677 | assert_eq!(_NEG1_2.round(), -_1); 2678 | assert_eq!(_NEG1_2.trunc(), _0); 2679 | 2680 | assert_eq!(_1.ceil(), _1); 2681 | assert_eq!(_1.floor(), _1); 2682 | assert_eq!(_1.round(), _1); 2683 | assert_eq!(_1.trunc(), _1); 2684 | 2685 | // Overflow checks 2686 | 2687 | let _neg1 = Ratio::from_integer(-1); 2688 | let _large_rat1 = Ratio::new(i32::MAX, i32::MAX - 1); 2689 | let _large_rat2 = Ratio::new(i32::MAX - 1, i32::MAX); 2690 | let _large_rat3 = Ratio::new(i32::MIN + 2, i32::MIN + 1); 2691 | let _large_rat4 = Ratio::new(i32::MIN + 1, i32::MIN + 2); 2692 | let _large_rat5 = Ratio::new(i32::MIN + 2, i32::MAX); 2693 | let _large_rat6 = Ratio::new(i32::MAX, i32::MIN + 2); 2694 | let _large_rat7 = Ratio::new(1, i32::MIN + 1); 2695 | let _large_rat8 = Ratio::new(1, i32::MAX); 2696 | 2697 | assert_eq!(_large_rat1.round(), One::one()); 2698 | assert_eq!(_large_rat2.round(), One::one()); 2699 | assert_eq!(_large_rat3.round(), One::one()); 2700 | assert_eq!(_large_rat4.round(), One::one()); 2701 | assert_eq!(_large_rat5.round(), _neg1); 2702 | assert_eq!(_large_rat6.round(), _neg1); 2703 | assert_eq!(_large_rat7.round(), Zero::zero()); 2704 | assert_eq!(_large_rat8.round(), Zero::zero()); 2705 | } 2706 | 2707 | #[test] 2708 | fn test_fract() { 2709 | assert_eq!(_1.fract(), _0); 2710 | assert_eq!(_NEG1_2.fract(), _NEG1_2); 2711 | assert_eq!(_1_2.fract(), _1_2); 2712 | assert_eq!(_3_2.fract(), _1_2); 2713 | } 2714 | 2715 | #[test] 2716 | fn test_recip() { 2717 | assert_eq!(_1 * _1.recip(), _1); 2718 | assert_eq!(_2 * _2.recip(), _1); 2719 | assert_eq!(_1_2 * _1_2.recip(), _1); 2720 | assert_eq!(_3_2 * _3_2.recip(), _1); 2721 | assert_eq!(_NEG1_2 * _NEG1_2.recip(), _1); 2722 | 2723 | assert_eq!(_3_2.recip(), _2_3); 2724 | assert_eq!(_NEG1_2.recip(), _NEG2); 2725 | assert_eq!(_NEG1_2.recip().denom(), &1); 2726 | } 2727 | 2728 | #[test] 2729 | #[should_panic(expected = "division by zero")] 2730 | fn test_recip_fail() { 2731 | let _a = Ratio::new(0, 1).recip(); 2732 | } 2733 | 2734 | #[test] 2735 | fn test_pow() { 2736 | fn test(r: Rational64, e: i32, expected: Rational64) { 2737 | assert_eq!(r.pow(e), expected); 2738 | assert_eq!(Pow::pow(r, e), expected); 2739 | assert_eq!(Pow::pow(r, &e), expected); 2740 | assert_eq!(Pow::pow(&r, e), expected); 2741 | assert_eq!(Pow::pow(&r, &e), expected); 2742 | #[cfg(feature = "num-bigint")] 2743 | test_big(r, e, expected); 2744 | } 2745 | 2746 | #[cfg(feature = "num-bigint")] 2747 | fn test_big(r: Rational64, e: i32, expected: Rational64) { 2748 | let r = BigRational::new_raw(r.numer.into(), r.denom.into()); 2749 | let expected = BigRational::new_raw(expected.numer.into(), expected.denom.into()); 2750 | assert_eq!((&r).pow(e), expected); 2751 | assert_eq!(Pow::pow(r.clone(), e), expected); 2752 | assert_eq!(Pow::pow(r.clone(), &e), expected); 2753 | assert_eq!(Pow::pow(&r, e), expected); 2754 | assert_eq!(Pow::pow(&r, &e), expected); 2755 | } 2756 | 2757 | test(_1_2, 2, Ratio::new(1, 4)); 2758 | test(_1_2, -2, Ratio::new(4, 1)); 2759 | test(_1, 1, _1); 2760 | test(_1, i32::MAX, _1); 2761 | test(_1, i32::MIN, _1); 2762 | test(_NEG1_2, 2, _1_2.pow(2i32)); 2763 | test(_NEG1_2, 3, -_1_2.pow(3i32)); 2764 | test(_3_2, 0, _1); 2765 | test(_3_2, -1, _3_2.recip()); 2766 | test(_3_2, 3, Ratio::new(27, 8)); 2767 | } 2768 | 2769 | #[test] 2770 | #[cfg(feature = "std")] 2771 | fn test_to_from_str() { 2772 | use std::string::{String, ToString}; 2773 | fn test(r: Rational64, s: String) { 2774 | assert_eq!(FromStr::from_str(&s), Ok(r)); 2775 | assert_eq!(r.to_string(), s); 2776 | } 2777 | test(_1, "1".to_string()); 2778 | test(_0, "0".to_string()); 2779 | test(_1_2, "1/2".to_string()); 2780 | test(_3_2, "3/2".to_string()); 2781 | test(_2, "2".to_string()); 2782 | test(_NEG1_2, "-1/2".to_string()); 2783 | } 2784 | #[test] 2785 | fn test_from_str_fail() { 2786 | fn test(s: &str) { 2787 | let rational: Result = FromStr::from_str(s); 2788 | assert!(rational.is_err()); 2789 | } 2790 | 2791 | let xs = ["0 /1", "abc", "", "1/", "--1/2", "3/2/1", "1/0"]; 2792 | for &s in xs.iter() { 2793 | test(s); 2794 | } 2795 | } 2796 | 2797 | #[cfg(feature = "num-bigint")] 2798 | #[test] 2799 | fn test_from_float() { 2800 | use num_traits::float::FloatCore; 2801 | fn test(given: T, (numer, denom): (&str, &str)) { 2802 | let ratio: BigRational = Ratio::from_float(given).unwrap(); 2803 | assert_eq!( 2804 | ratio, 2805 | Ratio::new( 2806 | FromStr::from_str(numer).unwrap(), 2807 | FromStr::from_str(denom).unwrap() 2808 | ) 2809 | ); 2810 | } 2811 | 2812 | // f32 2813 | test(core::f32::consts::PI, ("13176795", "4194304")); 2814 | test(2f32.powf(100.), ("1267650600228229401496703205376", "1")); 2815 | test( 2816 | -(2f32.powf(100.)), 2817 | ("-1267650600228229401496703205376", "1"), 2818 | ); 2819 | test( 2820 | 1.0 / 2f32.powf(100.), 2821 | ("1", "1267650600228229401496703205376"), 2822 | ); 2823 | test(684729.48391f32, ("1369459", "2")); 2824 | test(-8573.5918555f32, ("-4389679", "512")); 2825 | 2826 | // f64 2827 | test( 2828 | core::f64::consts::PI, 2829 | ("884279719003555", "281474976710656"), 2830 | ); 2831 | test(2f64.powf(100.), ("1267650600228229401496703205376", "1")); 2832 | test( 2833 | -(2f64.powf(100.)), 2834 | ("-1267650600228229401496703205376", "1"), 2835 | ); 2836 | test(684729.48391f64, ("367611342500051", "536870912")); 2837 | test(-8573.5918555f64, ("-4713381968463931", "549755813888")); 2838 | test( 2839 | 1.0 / 2f64.powf(100.), 2840 | ("1", "1267650600228229401496703205376"), 2841 | ); 2842 | } 2843 | 2844 | #[cfg(feature = "num-bigint")] 2845 | #[test] 2846 | fn test_from_float_fail() { 2847 | use core::{f32, f64}; 2848 | 2849 | assert_eq!(Ratio::from_float(f32::NAN), None); 2850 | assert_eq!(Ratio::from_float(f32::INFINITY), None); 2851 | assert_eq!(Ratio::from_float(f32::NEG_INFINITY), None); 2852 | assert_eq!(Ratio::from_float(f64::NAN), None); 2853 | assert_eq!(Ratio::from_float(f64::INFINITY), None); 2854 | assert_eq!(Ratio::from_float(f64::NEG_INFINITY), None); 2855 | } 2856 | 2857 | #[test] 2858 | fn test_signed() { 2859 | assert_eq!(_NEG1_2.abs(), _1_2); 2860 | assert_eq!(_3_2.abs_sub(&_1_2), _1); 2861 | assert_eq!(_1_2.abs_sub(&_3_2), Zero::zero()); 2862 | assert_eq!(_1_2.signum(), One::one()); 2863 | assert_eq!(_NEG1_2.signum(), ->::one()); 2864 | assert_eq!(_0.signum(), Zero::zero()); 2865 | assert!(_NEG1_2.is_negative()); 2866 | assert!(_1_NEG2.is_negative()); 2867 | assert!(!_NEG1_2.is_positive()); 2868 | assert!(!_1_NEG2.is_positive()); 2869 | assert!(_1_2.is_positive()); 2870 | assert!(_NEG1_NEG2.is_positive()); 2871 | assert!(!_1_2.is_negative()); 2872 | assert!(!_NEG1_NEG2.is_negative()); 2873 | assert!(!_0.is_positive()); 2874 | assert!(!_0.is_negative()); 2875 | } 2876 | 2877 | #[test] 2878 | #[cfg(feature = "std")] 2879 | fn test_hash() { 2880 | assert!(crate::hash(&_0) != crate::hash(&_1)); 2881 | assert!(crate::hash(&_0) != crate::hash(&_3_2)); 2882 | 2883 | // a == b -> hash(a) == hash(b) 2884 | let a = Rational64::new_raw(4, 2); 2885 | let b = Rational64::new_raw(6, 3); 2886 | assert_eq!(a, b); 2887 | assert_eq!(crate::hash(&a), crate::hash(&b)); 2888 | 2889 | let a = Rational64::new_raw(123456789, 1000); 2890 | let b = Rational64::new_raw(123456789 * 5, 5000); 2891 | assert_eq!(a, b); 2892 | assert_eq!(crate::hash(&a), crate::hash(&b)); 2893 | } 2894 | 2895 | #[test] 2896 | fn test_into_pair() { 2897 | assert_eq!((0, 1), _0.into()); 2898 | assert_eq!((-2, 1), _NEG2.into()); 2899 | assert_eq!((1, -2), _1_NEG2.into()); 2900 | } 2901 | 2902 | #[test] 2903 | fn test_from_pair() { 2904 | assert_eq!(_0, Ratio::from((0, 1))); 2905 | assert_eq!(_1, Ratio::from((1, 1))); 2906 | assert_eq!(_NEG2, Ratio::from((-2, 1))); 2907 | assert_eq!(_1_NEG2, Ratio::from((1, -2))); 2908 | } 2909 | 2910 | #[test] 2911 | fn ratio_iter_sum() { 2912 | // generic function to assure the iter method can be called 2913 | // for any Iterator with Item = Ratio or Ratio<&impl Integer> 2914 | fn iter_sums(slice: &[Ratio]) -> [Ratio; 3] { 2915 | let mut manual_sum = Ratio::new(T::zero(), T::one()); 2916 | for ratio in slice { 2917 | manual_sum = manual_sum + ratio; 2918 | } 2919 | [manual_sum, slice.iter().sum(), slice.iter().cloned().sum()] 2920 | } 2921 | // collect into array so test works on no_std 2922 | let mut nums = [Ratio::new(0, 1); 1000]; 2923 | for (i, r) in (0..1000).map(|n| Ratio::new(n, 500)).enumerate() { 2924 | nums[i] = r; 2925 | } 2926 | let sums = iter_sums(&nums[..]); 2927 | assert_eq!(sums[0], sums[1]); 2928 | assert_eq!(sums[0], sums[2]); 2929 | } 2930 | 2931 | #[test] 2932 | fn ratio_iter_product() { 2933 | // generic function to assure the iter method can be called 2934 | // for any Iterator with Item = Ratio or Ratio<&impl Integer> 2935 | fn iter_products(slice: &[Ratio]) -> [Ratio; 3] { 2936 | let mut manual_prod = Ratio::new(T::one(), T::one()); 2937 | for ratio in slice { 2938 | manual_prod = manual_prod * ratio; 2939 | } 2940 | [ 2941 | manual_prod, 2942 | slice.iter().product(), 2943 | slice.iter().cloned().product(), 2944 | ] 2945 | } 2946 | 2947 | // collect into array so test works on no_std 2948 | let mut nums = [Ratio::new(0, 1); 1000]; 2949 | for (i, r) in (0..1000).map(|n| Ratio::new(n, 500)).enumerate() { 2950 | nums[i] = r; 2951 | } 2952 | let products = iter_products(&nums[..]); 2953 | assert_eq!(products[0], products[1]); 2954 | assert_eq!(products[0], products[2]); 2955 | } 2956 | 2957 | #[test] 2958 | fn test_num_zero() { 2959 | let zero = Rational64::zero(); 2960 | assert!(zero.is_zero()); 2961 | 2962 | let mut r = Rational64::new(123, 456); 2963 | assert!(!r.is_zero()); 2964 | assert_eq!(r + zero, r); 2965 | 2966 | r.set_zero(); 2967 | assert!(r.is_zero()); 2968 | } 2969 | 2970 | #[test] 2971 | fn test_num_one() { 2972 | let one = Rational64::one(); 2973 | assert!(one.is_one()); 2974 | 2975 | let mut r = Rational64::new(123, 456); 2976 | assert!(!r.is_one()); 2977 | assert_eq!(r * one, r); 2978 | 2979 | r.set_one(); 2980 | assert!(r.is_one()); 2981 | } 2982 | 2983 | #[test] 2984 | fn test_const() { 2985 | const N: Ratio = Ratio::new_raw(123, 456); 2986 | const N_NUMER: &i32 = N.numer(); 2987 | const N_DENOM: &i32 = N.denom(); 2988 | 2989 | assert_eq!(N_NUMER, &123); 2990 | assert_eq!(N_DENOM, &456); 2991 | 2992 | let r = N.reduced(); 2993 | assert_eq!(r.numer(), &(123 / 3)); 2994 | assert_eq!(r.denom(), &(456 / 3)); 2995 | } 2996 | 2997 | #[test] 2998 | fn test_ratio_to_i64() { 2999 | assert_eq!(5, Rational64::new(70, 14).to_u64().unwrap()); 3000 | assert_eq!(-3, Rational64::new(-31, 8).to_i64().unwrap()); 3001 | assert_eq!(None, Rational64::new(-31, 8).to_u64()); 3002 | } 3003 | 3004 | #[test] 3005 | #[cfg(feature = "num-bigint")] 3006 | fn test_ratio_to_i128() { 3007 | assert_eq!( 3008 | 1i128 << 70, 3009 | Ratio::::new(1i128 << 77, 1i128 << 7) 3010 | .to_i128() 3011 | .unwrap() 3012 | ); 3013 | } 3014 | 3015 | #[test] 3016 | #[cfg(feature = "num-bigint")] 3017 | fn test_big_ratio_to_f64() { 3018 | assert_eq!( 3019 | BigRational::new( 3020 | "1234567890987654321234567890987654321234567890" 3021 | .parse() 3022 | .unwrap(), 3023 | "3".parse().unwrap() 3024 | ) 3025 | .to_f64(), 3026 | Some(411522630329218100000000000000000000000000000f64) 3027 | ); 3028 | assert_eq!(Ratio::from_float(5e-324).unwrap().to_f64(), Some(5e-324)); 3029 | assert_eq!( 3030 | // subnormal 3031 | BigRational::new(BigInt::one(), BigInt::one() << 1050).to_f64(), 3032 | Some(2.0f64.powi(-50).powi(21)) 3033 | ); 3034 | assert_eq!( 3035 | // definite underflow 3036 | BigRational::new(BigInt::one(), BigInt::one() << 1100).to_f64(), 3037 | Some(0.0) 3038 | ); 3039 | assert_eq!( 3040 | BigRational::from(BigInt::one() << 1050).to_f64(), 3041 | Some(core::f64::INFINITY) 3042 | ); 3043 | assert_eq!( 3044 | BigRational::from((-BigInt::one()) << 1050).to_f64(), 3045 | Some(core::f64::NEG_INFINITY) 3046 | ); 3047 | assert_eq!( 3048 | BigRational::new( 3049 | "1234567890987654321234567890".parse().unwrap(), 3050 | "987654321234567890987654321".parse().unwrap() 3051 | ) 3052 | .to_f64(), 3053 | Some(1.2499999893125f64) 3054 | ); 3055 | assert_eq!( 3056 | BigRational::new_raw(BigInt::one(), BigInt::zero()).to_f64(), 3057 | Some(core::f64::INFINITY) 3058 | ); 3059 | assert_eq!( 3060 | BigRational::new_raw(-BigInt::one(), BigInt::zero()).to_f64(), 3061 | Some(core::f64::NEG_INFINITY) 3062 | ); 3063 | assert_eq!( 3064 | BigRational::new_raw(BigInt::zero(), BigInt::zero()).to_f64(), 3065 | None 3066 | ); 3067 | } 3068 | 3069 | #[test] 3070 | fn test_ratio_to_f64() { 3071 | assert_eq!(Ratio::::new(1, 2).to_f64(), Some(0.5f64)); 3072 | assert_eq!(Rational64::new(1, 2).to_f64(), Some(0.5f64)); 3073 | assert_eq!(Rational64::new(1, -2).to_f64(), Some(-0.5f64)); 3074 | assert_eq!(Rational64::new(0, 2).to_f64(), Some(0.0f64)); 3075 | assert_eq!(Rational64::new(0, -2).to_f64(), Some(-0.0f64)); 3076 | assert_eq!(Rational64::new((1 << 57) + 1, 1 << 54).to_f64(), Some(8f64)); 3077 | assert_eq!( 3078 | Rational64::new((1 << 52) + 1, 1 << 52).to_f64(), 3079 | Some(1.0000000000000002f64), 3080 | ); 3081 | assert_eq!( 3082 | Rational64::new((1 << 60) + (1 << 8), 1 << 60).to_f64(), 3083 | Some(1.0000000000000002f64), 3084 | ); 3085 | assert_eq!( 3086 | Ratio::::new_raw(1, 0).to_f64(), 3087 | Some(core::f64::INFINITY) 3088 | ); 3089 | assert_eq!( 3090 | Ratio::::new_raw(-1, 0).to_f64(), 3091 | Some(core::f64::NEG_INFINITY) 3092 | ); 3093 | assert_eq!(Ratio::::new_raw(0, 0).to_f64(), None); 3094 | } 3095 | 3096 | #[test] 3097 | fn test_ldexp() { 3098 | use core::f64::{INFINITY, MAX_EXP, MIN_EXP, NAN, NEG_INFINITY}; 3099 | assert_eq!(ldexp(1.0, 0), 1.0); 3100 | assert_eq!(ldexp(1.0, 1), 2.0); 3101 | assert_eq!(ldexp(0.0, 1), 0.0); 3102 | assert_eq!(ldexp(-0.0, 1), -0.0); 3103 | 3104 | // Cases where ldexp is equivalent to multiplying by 2^exp because there's no over- or 3105 | // underflow. 3106 | assert_eq!(ldexp(3.5, 5), 3.5 * 2f64.powi(5)); 3107 | assert_eq!(ldexp(1.0, MAX_EXP - 1), 2f64.powi(MAX_EXP - 1)); 3108 | assert_eq!(ldexp(2.77, MIN_EXP + 3), 2.77 * 2f64.powi(MIN_EXP + 3)); 3109 | 3110 | // Case where initial value is subnormal 3111 | assert_eq!(ldexp(5e-324, 4), 5e-324 * 2f64.powi(4)); 3112 | assert_eq!(ldexp(5e-324, 200), 5e-324 * 2f64.powi(200)); 3113 | 3114 | // Near underflow (2^exp is too small to represent, but not x*2^exp) 3115 | assert_eq!(ldexp(4.0, MIN_EXP - 3), 2f64.powi(MIN_EXP - 1)); 3116 | 3117 | // Near overflow 3118 | assert_eq!(ldexp(0.125, MAX_EXP + 3), 2f64.powi(MAX_EXP)); 3119 | 3120 | // Overflow and underflow cases 3121 | assert_eq!(ldexp(1.0, MIN_EXP - 54), 0.0); 3122 | assert_eq!(ldexp(-1.0, MIN_EXP - 54), -0.0); 3123 | assert_eq!(ldexp(1.0, MAX_EXP), INFINITY); 3124 | assert_eq!(ldexp(-1.0, MAX_EXP), NEG_INFINITY); 3125 | 3126 | // Special values 3127 | assert_eq!(ldexp(INFINITY, 1), INFINITY); 3128 | assert_eq!(ldexp(NEG_INFINITY, 1), NEG_INFINITY); 3129 | assert!(ldexp(NAN, 1).is_nan()); 3130 | } 3131 | } 3132 | -------------------------------------------------------------------------------- /src/pow.rs: -------------------------------------------------------------------------------- 1 | use crate::Ratio; 2 | 3 | use core::cmp; 4 | use num_integer::Integer; 5 | use num_traits::{One, Pow}; 6 | 7 | macro_rules! pow_unsigned_impl { 8 | (@ $exp:ty) => { 9 | type Output = Ratio; 10 | #[inline] 11 | fn pow(self, expon: $exp) -> Ratio { 12 | Ratio::new_raw(self.numer.pow(expon), self.denom.pow(expon)) 13 | } 14 | }; 15 | ($exp:ty) => { 16 | impl> Pow<$exp> for Ratio { 17 | pow_unsigned_impl!(@ $exp); 18 | } 19 | impl<'a, T: Clone + Integer> Pow<$exp> for &'a Ratio 20 | where 21 | &'a T: Pow<$exp, Output = T>, 22 | { 23 | pow_unsigned_impl!(@ $exp); 24 | } 25 | impl<'b, T: Clone + Integer + Pow<$exp, Output = T>> Pow<&'b $exp> for Ratio { 26 | type Output = Ratio; 27 | #[inline] 28 | fn pow(self, expon: &'b $exp) -> Ratio { 29 | Pow::pow(self, *expon) 30 | } 31 | } 32 | impl<'a, 'b, T: Clone + Integer> Pow<&'b $exp> for &'a Ratio 33 | where 34 | &'a T: Pow<$exp, Output = T>, 35 | { 36 | type Output = Ratio; 37 | #[inline] 38 | fn pow(self, expon: &'b $exp) -> Ratio { 39 | Pow::pow(self, *expon) 40 | } 41 | } 42 | }; 43 | } 44 | pow_unsigned_impl!(u8); 45 | pow_unsigned_impl!(u16); 46 | pow_unsigned_impl!(u32); 47 | pow_unsigned_impl!(u64); 48 | pow_unsigned_impl!(u128); 49 | pow_unsigned_impl!(usize); 50 | 51 | macro_rules! pow_signed_impl { 52 | (@ &'b BigInt, BigUint) => { 53 | type Output = Ratio; 54 | #[inline] 55 | fn pow(self, expon: &'b BigInt) -> Ratio { 56 | match expon.sign() { 57 | Sign::NoSign => One::one(), 58 | Sign::Minus => { 59 | Pow::pow(self, expon.magnitude()).into_recip() 60 | } 61 | Sign::Plus => Pow::pow(self, expon.magnitude()), 62 | } 63 | } 64 | }; 65 | (@ $exp:ty, $unsigned:ty) => { 66 | type Output = Ratio; 67 | #[inline] 68 | fn pow(self, expon: $exp) -> Ratio { 69 | match expon.cmp(&0) { 70 | cmp::Ordering::Equal => One::one(), 71 | cmp::Ordering::Less => { 72 | let expon = expon.wrapping_abs() as $unsigned; 73 | Pow::pow(self, expon).into_recip() 74 | } 75 | cmp::Ordering::Greater => Pow::pow(self, expon as $unsigned), 76 | } 77 | } 78 | }; 79 | ($exp:ty, $unsigned:ty) => { 80 | impl> Pow<$exp> for Ratio { 81 | pow_signed_impl!(@ $exp, $unsigned); 82 | } 83 | impl<'a, T: Clone + Integer> Pow<$exp> for &'a Ratio 84 | where 85 | &'a T: Pow<$unsigned, Output = T>, 86 | { 87 | pow_signed_impl!(@ $exp, $unsigned); 88 | } 89 | impl<'b, T: Clone + Integer + Pow<$unsigned, Output = T>> Pow<&'b $exp> for Ratio { 90 | type Output = Ratio; 91 | #[inline] 92 | fn pow(self, expon: &'b $exp) -> Ratio { 93 | Pow::pow(self, *expon) 94 | } 95 | } 96 | impl<'a, 'b, T: Clone + Integer> Pow<&'b $exp> for &'a Ratio 97 | where 98 | &'a T: Pow<$unsigned, Output = T>, 99 | { 100 | type Output = Ratio; 101 | #[inline] 102 | fn pow(self, expon: &'b $exp) -> Ratio { 103 | Pow::pow(self, *expon) 104 | } 105 | } 106 | }; 107 | } 108 | pow_signed_impl!(i8, u8); 109 | pow_signed_impl!(i16, u16); 110 | pow_signed_impl!(i32, u32); 111 | pow_signed_impl!(i64, u64); 112 | pow_signed_impl!(i128, u128); 113 | pow_signed_impl!(isize, usize); 114 | 115 | #[cfg(feature = "num-bigint")] 116 | mod bigint { 117 | use super::*; 118 | use num_bigint::{BigInt, BigUint, Sign}; 119 | 120 | impl Pow<&'b BigUint, Output = T>> Pow for Ratio { 121 | type Output = Ratio; 122 | #[inline] 123 | fn pow(self, expon: BigUint) -> Ratio { 124 | Pow::pow(self, &expon) 125 | } 126 | } 127 | impl<'a, T: Clone + Integer> Pow for &'a Ratio 128 | where 129 | &'a T: for<'b> Pow<&'b BigUint, Output = T>, 130 | { 131 | type Output = Ratio; 132 | #[inline] 133 | fn pow(self, expon: BigUint) -> Ratio { 134 | Pow::pow(self, &expon) 135 | } 136 | } 137 | impl<'b, T: Clone + Integer + Pow<&'b BigUint, Output = T>> Pow<&'b BigUint> for Ratio { 138 | pow_unsigned_impl!(@ &'b BigUint); 139 | } 140 | impl<'a, 'b, T: Clone + Integer> Pow<&'b BigUint> for &'a Ratio 141 | where 142 | &'a T: Pow<&'b BigUint, Output = T>, 143 | { 144 | pow_unsigned_impl!(@ &'b BigUint); 145 | } 146 | 147 | impl Pow<&'b BigUint, Output = T>> Pow for Ratio { 148 | type Output = Ratio; 149 | #[inline] 150 | fn pow(self, expon: BigInt) -> Ratio { 151 | Pow::pow(self, &expon) 152 | } 153 | } 154 | impl<'a, T: Clone + Integer> Pow for &'a Ratio 155 | where 156 | &'a T: for<'b> Pow<&'b BigUint, Output = T>, 157 | { 158 | type Output = Ratio; 159 | #[inline] 160 | fn pow(self, expon: BigInt) -> Ratio { 161 | Pow::pow(self, &expon) 162 | } 163 | } 164 | impl<'b, T: Clone + Integer + Pow<&'b BigUint, Output = T>> Pow<&'b BigInt> for Ratio { 165 | pow_signed_impl!(@ &'b BigInt, BigUint); 166 | } 167 | impl<'a, 'b, T: Clone + Integer> Pow<&'b BigInt> for &'a Ratio 168 | where 169 | &'a T: Pow<&'b BigUint, Output = T>, 170 | { 171 | pow_signed_impl!(@ &'b BigInt, BigUint); 172 | } 173 | } 174 | --------------------------------------------------------------------------------