├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-BOOST ├── README.md ├── benches └── bench.rs ├── build.rs ├── chart ├── .gitignore └── performance.tex ├── examples └── upstream_benchmark.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz_ryu.rs ├── performance.png ├── src ├── buffer │ └── mod.rs ├── common.rs ├── d2s.rs ├── d2s_full_table.rs ├── d2s_intrinsics.rs ├── d2s_small_table.rs ├── digit_table.rs ├── f2s.rs ├── f2s_intrinsics.rs ├── lib.rs ├── parse.rs ├── pretty │ ├── exponent.rs │ ├── mantissa.rs │ └── mod.rs ├── s2d.rs └── s2f.rs └── tests ├── common_test.rs ├── d2s_intrinsics_test.rs ├── d2s_table_test.rs ├── d2s_test.rs ├── exhaustive.rs ├── f2s_test.rs ├── macros └── mod.rs ├── s2d_test.rs └── s2f_test.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | pre_ci: 17 | uses: dtolnay/.github/.github/workflows/pre_ci.yml@master 18 | 19 | test: 20 | name: Rust ${{matrix.rust}} 21 | needs: pre_ci 22 | if: needs.pre_ci.outputs.continue 23 | runs-on: ubuntu-latest 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | rust: [nightly, beta, stable] 28 | timeout-minutes: 45 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: ${{matrix.rust}} 34 | - name: Enable type layout randomization 35 | run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV 36 | if: matrix.rust == 'nightly' 37 | - run: cargo test 38 | - run: cargo test --features small 39 | - run: cargo build --tests --features no-panic --release 40 | if: matrix.rust == 'nightly' 41 | - uses: actions/upload-artifact@v4 42 | if: matrix.rust == 'nightly' && always() 43 | with: 44 | name: Cargo.lock 45 | path: Cargo.lock 46 | continue-on-error: true 47 | 48 | msrv: 49 | name: Rust 1.36.0 50 | needs: pre_ci 51 | if: needs.pre_ci.outputs.continue 52 | runs-on: ubuntu-latest 53 | timeout-minutes: 45 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: dtolnay/rust-toolchain@1.36.0 57 | - run: cargo build 58 | - run: cargo build --features small 59 | 60 | doc: 61 | name: Documentation 62 | needs: pre_ci 63 | if: needs.pre_ci.outputs.continue 64 | runs-on: ubuntu-latest 65 | timeout-minutes: 45 66 | env: 67 | RUSTDOCFLAGS: -Dwarnings 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: dtolnay/rust-toolchain@nightly 71 | - uses: dtolnay/install@cargo-docs-rs 72 | - run: cargo docs-rs 73 | 74 | miri: 75 | name: Miri 76 | needs: pre_ci 77 | if: needs.pre_ci.outputs.continue 78 | runs-on: ubuntu-latest 79 | timeout-minutes: 45 80 | steps: 81 | - uses: actions/checkout@v4 82 | - uses: dtolnay/rust-toolchain@miri 83 | with: 84 | toolchain: nightly-2025-05-16 # https://github.com/rust-lang/miri/issues/4323 85 | - run: cargo miri setup 86 | - run: cargo miri test 87 | env: 88 | MIRIFLAGS: -Zmiri-strict-provenance 89 | 90 | clippy: 91 | name: Clippy 92 | runs-on: ubuntu-latest 93 | if: github.event_name != 'pull_request' 94 | timeout-minutes: 45 95 | steps: 96 | - uses: actions/checkout@v4 97 | - uses: dtolnay/rust-toolchain@clippy 98 | - run: cargo clippy --tests --benches -- -Dclippy::all -Dclippy::pedantic 99 | 100 | outdated: 101 | name: Outdated 102 | runs-on: ubuntu-latest 103 | if: github.event_name != 'pull_request' 104 | timeout-minutes: 45 105 | steps: 106 | - uses: actions/checkout@v4 107 | - uses: dtolnay/rust-toolchain@stable 108 | - uses: dtolnay/install@cargo-outdated 109 | - run: cargo outdated --workspace --exit-code 1 110 | - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1 111 | 112 | fuzz: 113 | name: Fuzz 114 | needs: pre_ci 115 | if: needs.pre_ci.outputs.continue 116 | runs-on: ubuntu-latest 117 | timeout-minutes: 45 118 | steps: 119 | - uses: actions/checkout@v4 120 | - uses: dtolnay/rust-toolchain@nightly 121 | - uses: dtolnay/install@cargo-fuzz 122 | - run: cargo fuzz check 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /upstream 3 | /Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ryu" 3 | version = "1.0.20" 4 | authors = ["David Tolnay "] 5 | categories = ["value-formatting", "no-std", "no-std::no-alloc"] 6 | description = "Fast floating point to string conversion" 7 | documentation = "https://docs.rs/ryu" 8 | edition = "2018" 9 | exclude = ["build.rs", "performance.png", "chart/**"] 10 | keywords = ["float"] 11 | license = "Apache-2.0 OR BSL-1.0" 12 | repository = "https://github.com/dtolnay/ryu" 13 | rust-version = "1.36" 14 | 15 | [features] 16 | # Use smaller lookup tables. Instead of storing every required power of 17 | # 5, only store every 26th entry, and compute intermediate values with a 18 | # multiplication. This reduces the lookup table size by about 10x (only 19 | # one case, and only f64) at the cost of some performance. 20 | small = [] 21 | 22 | [dependencies] 23 | no-panic = { version = "0.1", optional = true } 24 | 25 | [dev-dependencies] 26 | num_cpus = "1.8" 27 | rand = "0.9" 28 | rand_xorshift = "0.4" 29 | 30 | [package.metadata.docs.rs] 31 | targets = ["x86_64-unknown-linux-gnu"] 32 | rustdoc-args = [ 33 | "--generate-link-to-definition", 34 | "--extern-html-root-url=core=https://doc.rust-lang.org", 35 | "--extern-html-root-url=alloc=https://doc.rust-lang.org", 36 | "--extern-html-root-url=std=https://doc.rust-lang.org", 37 | ] 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LICENSE-BOOST: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ryū 2 | 3 | [github](https://github.com/dtolnay/ryu) 4 | [crates.io](https://crates.io/crates/ryu) 5 | [docs.rs](https://docs.rs/ryu) 6 | [build status](https://github.com/dtolnay/ryu/actions?query=branch%3Amaster) 7 | 8 | Pure Rust implementation of Ryū, an algorithm to quickly convert floating point 9 | numbers to decimal strings. 10 | 11 | The PLDI'18 paper [*Ryū: fast float-to-string conversion*][paper] by Ulf Adams 12 | includes a complete correctness proof of the algorithm. The paper is available 13 | under the creative commons CC-BY-SA license. 14 | 15 | This Rust implementation is a line-by-line port of Ulf Adams' implementation in 16 | C, [https://github.com/ulfjack/ryu][upstream]. 17 | 18 | *Requirements: this crate supports any compiler version back to rustc 1.36; it 19 | uses nothing from the Rust standard library so is usable from no_std crates.* 20 | 21 | [paper]: https://dl.acm.org/citation.cfm?id=3192369 22 | [upstream]: https://github.com/ulfjack/ryu/tree/77e767f5e056bab96e895072fc21618ecff2f44b 23 | 24 | ```toml 25 | [dependencies] 26 | ryu = "1.0" 27 | ``` 28 | 29 |
30 | 31 | ## Example 32 | 33 | ```rust 34 | fn main() { 35 | let mut buffer = ryu::Buffer::new(); 36 | let printed = buffer.format(1.234); 37 | assert_eq!(printed, "1.234"); 38 | } 39 | ``` 40 | 41 |
42 | 43 | ## Performance (lower is better) 44 | 45 | ![performance](https://raw.githubusercontent.com/dtolnay/ryu/master/performance.png) 46 | 47 | You can run upstream's benchmarks with: 48 | 49 | ```console 50 | $ git clone https://github.com/ulfjack/ryu c-ryu 51 | $ cd c-ryu 52 | $ bazel run -c opt //ryu/benchmark:ryu_benchmark 53 | ``` 54 | 55 | And the same benchmark against our implementation with: 56 | 57 | ```console 58 | $ git clone https://github.com/dtolnay/ryu rust-ryu 59 | $ cd rust-ryu 60 | $ cargo run --example upstream_benchmark --release 61 | ``` 62 | 63 | These benchmarks measure the average time to print a 32-bit float and average 64 | time to print a 64-bit float, where the inputs are distributed as uniform random 65 | bit patterns 32 and 64 bits wide. 66 | 67 | The upstream C code, the unsafe direct Rust port, and the safe pretty Rust API 68 | all perform the same, taking around 21 nanoseconds to format a 32-bit float and 69 | 31 nanoseconds to format a 64-bit float. 70 | 71 | There is also a Rust-specific benchmark comparing this implementation to the 72 | standard library which you can run with: 73 | 74 | ```console 75 | $ cargo bench 76 | ``` 77 | 78 | The benchmark shows Ryū approximately 2-5x faster than the standard library 79 | across a range of f32 and f64 inputs. Measurements are in nanoseconds per 80 | iteration; smaller is better. 81 | 82 | ## Formatting 83 | 84 | This library tends to produce more human-readable output than the standard 85 | library's to\_string, which never uses scientific notation. Here are two 86 | examples: 87 | 88 | - *ryu:* 1.23e40, *std:* 12300000000000000000000000000000000000000 89 | - *ryu:* 1.23e-40, *std:* 0.000000000000000000000000000000000000000123 90 | 91 | Both libraries print short decimals such as 0.0000123 without scientific 92 | notation. 93 | 94 |
95 | 96 | #### License 97 | 98 | 99 | Licensed under either of Apache License, Version 100 | 2.0 or Boost Software License 1.0 at your 101 | option. 102 | 103 | 104 |
105 | 106 | 107 | Unless you explicitly state otherwise, any contribution intentionally submitted 108 | for inclusion in this crate by you, as defined in the Apache-2.0 license, shall 109 | be dual licensed as above, without any additional terms or conditions. 110 | 111 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | // cargo bench 2 | 3 | #![feature(test)] 4 | #![allow( 5 | clippy::approx_constant, 6 | clippy::excessive_precision, 7 | clippy::unreadable_literal 8 | )] 9 | 10 | extern crate test; 11 | 12 | use std::io::Write; 13 | use std::{f32, f64}; 14 | use test::{black_box, Bencher}; 15 | 16 | macro_rules! benches { 17 | ($($name:ident($value:expr),)*) => { 18 | mod bench_ryu { 19 | use super::*; 20 | $( 21 | #[bench] 22 | fn $name(b: &mut Bencher) { 23 | let mut buf = ryu::Buffer::new(); 24 | 25 | b.iter(move || { 26 | let value = black_box($value); 27 | let formatted = buf.format_finite(value); 28 | black_box(formatted); 29 | }); 30 | } 31 | )* 32 | } 33 | 34 | mod bench_std_fmt { 35 | use super::*; 36 | $( 37 | #[bench] 38 | fn $name(b: &mut Bencher) { 39 | let mut buf = Vec::with_capacity(20); 40 | 41 | b.iter(|| { 42 | buf.clear(); 43 | let value = black_box($value); 44 | write!(&mut buf, "{}", value).unwrap(); 45 | black_box(buf.as_slice()); 46 | }); 47 | } 48 | )* 49 | } 50 | }; 51 | } 52 | 53 | benches! { 54 | bench_0_f64(0f64), 55 | bench_short_f64(0.1234f64), 56 | bench_e_f64(2.718281828459045f64), 57 | bench_max_f64(f64::MAX), 58 | bench_0_f32(0f32), 59 | bench_short_f32(0.1234f32), 60 | bench_e_f32(2.718281828459045f32), 61 | bench_max_f32(f32::MAX), 62 | } 63 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // Warning: build.rs is not published to crates.io. 3 | 4 | println!("cargo:rerun-if-changed=build.rs"); 5 | println!("cargo:rustc-cfg=check_cfg"); 6 | println!("cargo:rustc-check-cfg=cfg(check_cfg)"); 7 | println!("cargo:rustc-check-cfg=cfg(exhaustive)"); 8 | } 9 | -------------------------------------------------------------------------------- /chart/.gitignore: -------------------------------------------------------------------------------- 1 | /*.aux 2 | /*.fdb_latexmk 3 | /*.fls 4 | /*.log 5 | /*.pdf 6 | /*.png 7 | /*.svg 8 | -------------------------------------------------------------------------------- /chart/performance.tex: -------------------------------------------------------------------------------- 1 | \documentclass{standalone} 2 | \usepackage{pgfplots} 3 | \usepackage{sansmath} 4 | \pgfplotsset{compat=1.16} 5 | \definecolor{ryu}{HTML}{3366FF} 6 | \definecolor{std}{HTML}{949494} 7 | \definecolor{bg}{HTML}{CFCFCF} 8 | \begin{document} 9 | \pagecolor{white} 10 | \begin{tikzpicture} 11 | \edef\entries{ 12 | "$0.0$", 13 | "$0.1234$", 14 | "$2.718281828459045$", 15 | "$1.7976931348623157e308$", 16 | } 17 | \begin{axis}[ 18 | width=5in, 19 | height=3.5in, 20 | ybar, 21 | ymin=0, 22 | bar width=24pt, 23 | enlarge x limits={abs=39pt}, 24 | ylabel={nanos for one call to write}, 25 | legend style={ 26 | anchor=north west, 27 | at={(0.025,0.975)}, 28 | legend columns=1, 29 | draw=none, 30 | fill=none, 31 | }, 32 | legend entries={ 33 | ryu::Buffer::new().format\_finite(value)\\ 34 | std::write!(\&mut buf, ``\{\}'', value)\\ 35 | }, 36 | legend cell align=left, 37 | xtick={-0.5,0.5,1.5,2.5,3.5,4.5}, 38 | xticklabels={}, 39 | xtick pos=left, 40 | visualization depends on={y \as \rawy}, 41 | every node near coord/.append style={ 42 | shift={(axis direction cs:0,-\rawy/2)}, 43 | rotate=90, 44 | anchor=center, 45 | font=\sansmath\sffamily, 46 | }, 47 | axis background/.style={fill=bg}, 48 | tick label style={font=\sansmath\sffamily}, 49 | every axis label={font=\sansmath\sffamily}, 50 | legend style={font=\sansmath\sffamily}, 51 | label style={font=\sansmath\sffamily}, 52 | ] 53 | \addplot[ 54 | black, 55 | fill=ryu, 56 | area legend, 57 | nodes near coords={}, 58 | ] coordinates { 59 | (0, 3) 60 | (1, 40) 61 | (2, 27) 62 | (3, 28) 63 | }; 64 | \addplot[ 65 | black, 66 | fill=std, 67 | area legend, 68 | nodes near coords=\pgfmathsetmacro{\input}{{\entries}[\coordindex]}\input, 69 | ] coordinates { 70 | (0, 20) 71 | (1, 66) 72 | (2, 88) 73 | (3, 114) 74 | }; 75 | \end{axis} 76 | \pgfresetboundingbox\path 77 | (current axis.south west) -- ++(-0.44in,-0.09in) 78 | rectangle (current axis.north east) -- ++(0.05in,0.05in); 79 | \end{tikzpicture} 80 | \end{document} 81 | -------------------------------------------------------------------------------- /examples/upstream_benchmark.rs: -------------------------------------------------------------------------------- 1 | // cargo run --example upstream_benchmark --release 2 | 3 | use rand::{Rng, SeedableRng}; 4 | 5 | const SAMPLES: usize = 10000; 6 | const ITERATIONS: usize = 1000; 7 | 8 | struct MeanAndVariance { 9 | n: i64, 10 | mean: f64, 11 | m2: f64, 12 | } 13 | 14 | impl MeanAndVariance { 15 | fn new() -> Self { 16 | MeanAndVariance { 17 | n: 0, 18 | mean: 0.0, 19 | m2: 0.0, 20 | } 21 | } 22 | 23 | fn update(&mut self, x: f64) { 24 | self.n += 1; 25 | let d = x - self.mean; 26 | self.mean += d / self.n as f64; 27 | let d2 = x - self.mean; 28 | self.m2 += d * d2; 29 | } 30 | 31 | fn variance(&self) -> f64 { 32 | self.m2 / (self.n - 1) as f64 33 | } 34 | 35 | fn stddev(&self) -> f64 { 36 | self.variance().sqrt() 37 | } 38 | } 39 | 40 | macro_rules! benchmark { 41 | ($name:ident, $ty:ident) => { 42 | fn $name() -> usize { 43 | let mut rng = rand_xorshift::XorShiftRng::from_seed([123u8; 16]); 44 | let mut mv = MeanAndVariance::new(); 45 | let mut throwaway = 0; 46 | for _ in 0..SAMPLES { 47 | let f = loop { 48 | let f = $ty::from_bits(rng.random()); 49 | if f.is_finite() { 50 | break f; 51 | } 52 | }; 53 | 54 | let t1 = std::time::SystemTime::now(); 55 | for _ in 0..ITERATIONS { 56 | throwaway += ryu::Buffer::new().format_finite(f).len(); 57 | } 58 | let duration = t1.elapsed().unwrap(); 59 | let nanos = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64; 60 | mv.update(nanos as f64 / ITERATIONS as f64); 61 | } 62 | println!( 63 | "{:12} {:8.3} {:8.3}", 64 | concat!(stringify!($name), ":"), 65 | mv.mean, 66 | mv.stddev(), 67 | ); 68 | throwaway 69 | } 70 | }; 71 | } 72 | 73 | benchmark!(pretty32, f32); 74 | benchmark!(pretty64, f64); 75 | 76 | fn main() { 77 | println!("{:>20}{:>9}", "Average", "Stddev"); 78 | let mut throwaway = 0; 79 | throwaway += pretty32(); 80 | throwaway += pretty64(); 81 | if std::env::var_os("ryu-benchmark").is_some() { 82 | // Prevent the compiler from optimizing the code away. 83 | println!("{}", throwaway); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | /artifacts/ 2 | /corpus/ 3 | /coverage/ 4 | /target/ 5 | /Cargo.lock 6 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ryu-fuzz" 3 | version = "0.0.0" 4 | authors = ["David Tolnay "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | arbitrary = { version = "1", features = ["derive"] } 13 | libfuzzer-sys = "0.4" 14 | ryu = { path = ".." } 15 | 16 | [features] 17 | small = ["ryu/small"] 18 | 19 | [[bin]] 20 | name = "fuzz_ryu" 21 | path = "fuzz_targets/fuzz_ryu.rs" 22 | test = false 23 | doc = false 24 | 25 | [workspace] 26 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_ryu.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | 3 | use arbitrary::Arbitrary; 4 | use libfuzzer_sys::fuzz_target; 5 | use std::mem; 6 | 7 | #[derive(Arbitrary, Debug)] 8 | enum FloatInput { 9 | F32(f32), 10 | F64(f64), 11 | } 12 | 13 | macro_rules! ryu_test { 14 | ($val:expr, $method:ident) => { 15 | match $val { 16 | val => { 17 | let mut buffer = ryu::Buffer::new(); 18 | let string = buffer.$method(val); 19 | assert!(string.len() <= mem::size_of::()); 20 | if val.is_finite() { 21 | assert_eq!(val, string.parse().unwrap()); 22 | } 23 | } 24 | } 25 | }; 26 | } 27 | 28 | fuzz_target!(|inputs: (FloatInput, bool)| { 29 | let (input, finite) = inputs; 30 | match (input, finite) { 31 | (FloatInput::F32(val), false) => ryu_test!(val, format), 32 | (FloatInput::F32(val), true) => ryu_test!(val, format_finite), 33 | (FloatInput::F64(val), false) => ryu_test!(val, format), 34 | (FloatInput::F64(val), true) => ryu_test!(val, format_finite), 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dtolnay/ryu/5402d270591e9eccce21956fc25dacf2e95d83fb/performance.png -------------------------------------------------------------------------------- /src/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::raw; 2 | use core::mem::MaybeUninit; 3 | use core::{slice, str}; 4 | #[cfg(feature = "no-panic")] 5 | use no_panic::no_panic; 6 | 7 | const NAN: &str = "NaN"; 8 | const INFINITY: &str = "inf"; 9 | const NEG_INFINITY: &str = "-inf"; 10 | 11 | /// Safe API for formatting floating point numbers to text. 12 | /// 13 | /// ## Example 14 | /// 15 | /// ``` 16 | /// let mut buffer = ryu::Buffer::new(); 17 | /// let printed = buffer.format_finite(1.234); 18 | /// assert_eq!(printed, "1.234"); 19 | /// ``` 20 | pub struct Buffer { 21 | bytes: [MaybeUninit; 24], 22 | } 23 | 24 | impl Buffer { 25 | /// This is a cheap operation; you don't need to worry about reusing buffers 26 | /// for efficiency. 27 | #[inline] 28 | #[cfg_attr(feature = "no-panic", no_panic)] 29 | pub fn new() -> Self { 30 | let bytes = [MaybeUninit::::uninit(); 24]; 31 | Buffer { bytes } 32 | } 33 | 34 | /// Print a floating point number into this buffer and return a reference to 35 | /// its string representation within the buffer. 36 | /// 37 | /// # Special cases 38 | /// 39 | /// This function formats NaN as the string "NaN", positive infinity as 40 | /// "inf", and negative infinity as "-inf" to match std::fmt. 41 | /// 42 | /// If your input is known to be finite, you may get better performance by 43 | /// calling the `format_finite` method instead of `format` to avoid the 44 | /// checks for special cases. 45 | #[cfg_attr(feature = "no-panic", inline)] 46 | #[cfg_attr(feature = "no-panic", no_panic)] 47 | pub fn format(&mut self, f: F) -> &str { 48 | if f.is_nonfinite() { 49 | f.format_nonfinite() 50 | } else { 51 | self.format_finite(f) 52 | } 53 | } 54 | 55 | /// Print a floating point number into this buffer and return a reference to 56 | /// its string representation within the buffer. 57 | /// 58 | /// # Special cases 59 | /// 60 | /// This function **does not** check for NaN or infinity. If the input 61 | /// number is not a finite float, the printed representation will be some 62 | /// correctly formatted but unspecified numerical value. 63 | /// 64 | /// Please check [`is_finite`] yourself before calling this function, or 65 | /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. 66 | /// 67 | /// [`is_finite`]: f64::is_finite 68 | /// [`is_nan`]: f64::is_nan 69 | /// [`is_infinite`]: f64::is_infinite 70 | #[inline] 71 | #[cfg_attr(feature = "no-panic", no_panic)] 72 | pub fn format_finite(&mut self, f: F) -> &str { 73 | unsafe { 74 | let n = f.write_to_ryu_buffer(self.bytes.as_mut_ptr() as *mut u8); 75 | debug_assert!(n <= self.bytes.len()); 76 | let slice = slice::from_raw_parts(self.bytes.as_ptr() as *const u8, n); 77 | str::from_utf8_unchecked(slice) 78 | } 79 | } 80 | } 81 | 82 | impl Copy for Buffer {} 83 | 84 | impl Clone for Buffer { 85 | #[inline] 86 | #[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072 87 | fn clone(&self) -> Self { 88 | Buffer::new() 89 | } 90 | } 91 | 92 | impl Default for Buffer { 93 | #[inline] 94 | #[cfg_attr(feature = "no-panic", no_panic)] 95 | fn default() -> Self { 96 | Buffer::new() 97 | } 98 | } 99 | 100 | /// A floating point number, f32 or f64, that can be written into a 101 | /// [`ryu::Buffer`][Buffer]. 102 | /// 103 | /// This trait is sealed and cannot be implemented for types outside of the 104 | /// `ryu` crate. 105 | pub trait Float: Sealed {} 106 | impl Float for f32 {} 107 | impl Float for f64 {} 108 | 109 | pub trait Sealed: Copy { 110 | fn is_nonfinite(self) -> bool; 111 | fn format_nonfinite(self) -> &'static str; 112 | unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize; 113 | } 114 | 115 | impl Sealed for f32 { 116 | #[inline] 117 | fn is_nonfinite(self) -> bool { 118 | const EXP_MASK: u32 = 0x7f800000; 119 | let bits = self.to_bits(); 120 | bits & EXP_MASK == EXP_MASK 121 | } 122 | 123 | #[cold] 124 | #[cfg_attr(feature = "no-panic", inline)] 125 | fn format_nonfinite(self) -> &'static str { 126 | const MANTISSA_MASK: u32 = 0x007fffff; 127 | const SIGN_MASK: u32 = 0x80000000; 128 | let bits = self.to_bits(); 129 | if bits & MANTISSA_MASK != 0 { 130 | NAN 131 | } else if bits & SIGN_MASK != 0 { 132 | NEG_INFINITY 133 | } else { 134 | INFINITY 135 | } 136 | } 137 | 138 | #[inline] 139 | unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { 140 | raw::format32(self, result) 141 | } 142 | } 143 | 144 | impl Sealed for f64 { 145 | #[inline] 146 | fn is_nonfinite(self) -> bool { 147 | const EXP_MASK: u64 = 0x7ff0000000000000; 148 | let bits = self.to_bits(); 149 | bits & EXP_MASK == EXP_MASK 150 | } 151 | 152 | #[cold] 153 | #[cfg_attr(feature = "no-panic", inline)] 154 | fn format_nonfinite(self) -> &'static str { 155 | const MANTISSA_MASK: u64 = 0x000fffffffffffff; 156 | const SIGN_MASK: u64 = 0x8000000000000000; 157 | let bits = self.to_bits(); 158 | if bits & MANTISSA_MASK != 0 { 159 | NAN 160 | } else if bits & SIGN_MASK != 0 { 161 | NEG_INFINITY 162 | } else { 163 | INFINITY 164 | } 165 | } 166 | 167 | #[inline] 168 | unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { 169 | raw::format64(self, result) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | // Returns the number of decimal digits in v, which must not contain more than 9 22 | // digits. 23 | #[cfg_attr(feature = "no-panic", inline)] 24 | pub fn decimal_length9(v: u32) -> u32 { 25 | // Function precondition: v is not a 10-digit number. 26 | // (f2s: 9 digits are sufficient for round-tripping.) 27 | debug_assert!(v < 1000000000); 28 | 29 | if v >= 100000000 { 30 | 9 31 | } else if v >= 10000000 { 32 | 8 33 | } else if v >= 1000000 { 34 | 7 35 | } else if v >= 100000 { 36 | 6 37 | } else if v >= 10000 { 38 | 5 39 | } else if v >= 1000 { 40 | 4 41 | } else if v >= 100 { 42 | 3 43 | } else if v >= 10 { 44 | 2 45 | } else { 46 | 1 47 | } 48 | } 49 | 50 | // Returns e == 0 ? 1 : [log_2(5^e)]; requires 0 <= e <= 3528. 51 | #[cfg_attr(feature = "no-panic", inline)] 52 | #[allow(dead_code)] 53 | pub fn log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ { 54 | // This approximation works up to the point that the multiplication 55 | // overflows at e = 3529. If the multiplication were done in 64 bits, it 56 | // would fail at 5^4004 which is just greater than 2^9297. 57 | debug_assert!(e >= 0); 58 | debug_assert!(e <= 3528); 59 | ((e as u32 * 1217359) >> 19) as i32 60 | } 61 | 62 | // Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. 63 | #[cfg_attr(feature = "no-panic", inline)] 64 | pub fn pow5bits(e: i32) -> i32 /* or u32 -> u32 */ { 65 | // This approximation works up to the point that the multiplication 66 | // overflows at e = 3529. If the multiplication were done in 64 bits, it 67 | // would fail at 5^4004 which is just greater than 2^9297. 68 | debug_assert!(e >= 0); 69 | debug_assert!(e <= 3528); 70 | (((e as u32 * 1217359) >> 19) + 1) as i32 71 | } 72 | 73 | #[cfg_attr(feature = "no-panic", inline)] 74 | #[allow(dead_code)] 75 | pub fn ceil_log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ { 76 | log2_pow5(e) + 1 77 | } 78 | 79 | // Returns floor(log_10(2^e)); requires 0 <= e <= 1650. 80 | #[cfg_attr(feature = "no-panic", inline)] 81 | pub fn log10_pow2(e: i32) -> u32 /* or u32 -> u32 */ { 82 | // The first value this approximation fails for is 2^1651 which is just greater than 10^297. 83 | debug_assert!(e >= 0); 84 | debug_assert!(e <= 1650); 85 | (e as u32 * 78913) >> 18 86 | } 87 | 88 | // Returns floor(log_10(5^e)); requires 0 <= e <= 2620. 89 | #[cfg_attr(feature = "no-panic", inline)] 90 | pub fn log10_pow5(e: i32) -> u32 /* or u32 -> u32 */ { 91 | // The first value this approximation fails for is 5^2621 which is just greater than 10^1832. 92 | debug_assert!(e >= 0); 93 | debug_assert!(e <= 2620); 94 | (e as u32 * 732923) >> 20 95 | } 96 | -------------------------------------------------------------------------------- /src/d2s.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | use crate::common::{log10_pow2, log10_pow5, pow5bits}; 22 | #[cfg(not(feature = "small"))] 23 | pub use crate::d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT}; 24 | use crate::d2s_intrinsics::{ 25 | div10, div100, div5, mul_shift_all_64, multiple_of_power_of_2, multiple_of_power_of_5, 26 | }; 27 | #[cfg(feature = "small")] 28 | pub use crate::d2s_small_table::{compute_inv_pow5, compute_pow5}; 29 | use core::mem::MaybeUninit; 30 | 31 | pub const DOUBLE_MANTISSA_BITS: u32 = 52; 32 | pub const DOUBLE_EXPONENT_BITS: u32 = 11; 33 | pub const DOUBLE_BIAS: i32 = 1023; 34 | pub const DOUBLE_POW5_INV_BITCOUNT: i32 = 125; 35 | pub const DOUBLE_POW5_BITCOUNT: i32 = 125; 36 | 37 | #[cfg_attr(feature = "no-panic", inline)] 38 | pub fn decimal_length17(v: u64) -> u32 { 39 | // This is slightly faster than a loop. 40 | // The average output length is 16.38 digits, so we check high-to-low. 41 | // Function precondition: v is not an 18, 19, or 20-digit number. 42 | // (17 digits are sufficient for round-tripping.) 43 | debug_assert!(v < 100000000000000000); 44 | 45 | if v >= 10000000000000000 { 46 | 17 47 | } else if v >= 1000000000000000 { 48 | 16 49 | } else if v >= 100000000000000 { 50 | 15 51 | } else if v >= 10000000000000 { 52 | 14 53 | } else if v >= 1000000000000 { 54 | 13 55 | } else if v >= 100000000000 { 56 | 12 57 | } else if v >= 10000000000 { 58 | 11 59 | } else if v >= 1000000000 { 60 | 10 61 | } else if v >= 100000000 { 62 | 9 63 | } else if v >= 10000000 { 64 | 8 65 | } else if v >= 1000000 { 66 | 7 67 | } else if v >= 100000 { 68 | 6 69 | } else if v >= 10000 { 70 | 5 71 | } else if v >= 1000 { 72 | 4 73 | } else if v >= 100 { 74 | 3 75 | } else if v >= 10 { 76 | 2 77 | } else { 78 | 1 79 | } 80 | } 81 | 82 | // A floating decimal representing m * 10^e. 83 | pub struct FloatingDecimal64 { 84 | pub mantissa: u64, 85 | // Decimal exponent's range is -324 to 308 86 | // inclusive, and can fit in i16 if needed. 87 | pub exponent: i32, 88 | } 89 | 90 | #[cfg_attr(feature = "no-panic", inline)] 91 | pub fn d2d(ieee_mantissa: u64, ieee_exponent: u32) -> FloatingDecimal64 { 92 | let (e2, m2) = if ieee_exponent == 0 { 93 | ( 94 | // We subtract 2 so that the bounds computation has 2 additional bits. 95 | 1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2, 96 | ieee_mantissa, 97 | ) 98 | } else { 99 | ( 100 | ieee_exponent as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2, 101 | (1u64 << DOUBLE_MANTISSA_BITS) | ieee_mantissa, 102 | ) 103 | }; 104 | let even = (m2 & 1) == 0; 105 | let accept_bounds = even; 106 | 107 | // Step 2: Determine the interval of valid decimal representations. 108 | let mv = 4 * m2; 109 | // Implicit bool -> int conversion. True is 1, false is 0. 110 | let mm_shift = (ieee_mantissa != 0 || ieee_exponent <= 1) as u32; 111 | // We would compute mp and mm like this: 112 | // uint64_t mp = 4 * m2 + 2; 113 | // uint64_t mm = mv - 1 - mm_shift; 114 | 115 | // Step 3: Convert to a decimal power base using 128-bit arithmetic. 116 | let mut vr: u64; 117 | let mut vp: u64; 118 | let mut vm: u64; 119 | let mut vp_uninit: MaybeUninit = MaybeUninit::uninit(); 120 | let mut vm_uninit: MaybeUninit = MaybeUninit::uninit(); 121 | let e10: i32; 122 | let mut vm_is_trailing_zeros = false; 123 | let mut vr_is_trailing_zeros = false; 124 | if e2 >= 0 { 125 | // I tried special-casing q == 0, but there was no effect on performance. 126 | // This expression is slightly faster than max(0, log10_pow2(e2) - 1). 127 | let q = log10_pow2(e2) - (e2 > 3) as u32; 128 | e10 = q as i32; 129 | let k = DOUBLE_POW5_INV_BITCOUNT + pow5bits(q as i32) - 1; 130 | let i = -e2 + q as i32 + k; 131 | vr = unsafe { 132 | mul_shift_all_64( 133 | m2, 134 | #[cfg(feature = "small")] 135 | &compute_inv_pow5(q), 136 | #[cfg(not(feature = "small"))] 137 | { 138 | debug_assert!(q < DOUBLE_POW5_INV_SPLIT.len() as u32); 139 | DOUBLE_POW5_INV_SPLIT.get_unchecked(q as usize) 140 | }, 141 | i as u32, 142 | vp_uninit.as_mut_ptr(), 143 | vm_uninit.as_mut_ptr(), 144 | mm_shift, 145 | ) 146 | }; 147 | vp = unsafe { vp_uninit.assume_init() }; 148 | vm = unsafe { vm_uninit.assume_init() }; 149 | if q <= 21 { 150 | // This should use q <= 22, but I think 21 is also safe. Smaller values 151 | // may still be safe, but it's more difficult to reason about them. 152 | // Only one of mp, mv, and mm can be a multiple of 5, if any. 153 | let mv_mod5 = (mv as u32).wrapping_sub(5u32.wrapping_mul(div5(mv) as u32)); 154 | if mv_mod5 == 0 { 155 | vr_is_trailing_zeros = multiple_of_power_of_5(mv, q); 156 | } else if accept_bounds { 157 | // Same as min(e2 + (~mm & 1), pow5_factor(mm)) >= q 158 | // <=> e2 + (~mm & 1) >= q && pow5_factor(mm) >= q 159 | // <=> true && pow5_factor(mm) >= q, since e2 >= q. 160 | vm_is_trailing_zeros = multiple_of_power_of_5(mv - 1 - mm_shift as u64, q); 161 | } else { 162 | // Same as min(e2 + 1, pow5_factor(mp)) >= q. 163 | vp -= multiple_of_power_of_5(mv + 2, q) as u64; 164 | } 165 | } 166 | } else { 167 | // This expression is slightly faster than max(0, log10_pow5(-e2) - 1). 168 | let q = log10_pow5(-e2) - (-e2 > 1) as u32; 169 | e10 = q as i32 + e2; 170 | let i = -e2 - q as i32; 171 | let k = pow5bits(i) - DOUBLE_POW5_BITCOUNT; 172 | let j = q as i32 - k; 173 | vr = unsafe { 174 | mul_shift_all_64( 175 | m2, 176 | #[cfg(feature = "small")] 177 | &compute_pow5(i as u32), 178 | #[cfg(not(feature = "small"))] 179 | { 180 | debug_assert!(i < DOUBLE_POW5_SPLIT.len() as i32); 181 | DOUBLE_POW5_SPLIT.get_unchecked(i as usize) 182 | }, 183 | j as u32, 184 | vp_uninit.as_mut_ptr(), 185 | vm_uninit.as_mut_ptr(), 186 | mm_shift, 187 | ) 188 | }; 189 | vp = unsafe { vp_uninit.assume_init() }; 190 | vm = unsafe { vm_uninit.assume_init() }; 191 | if q <= 1 { 192 | // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. 193 | // mv = 4 * m2, so it always has at least two trailing 0 bits. 194 | vr_is_trailing_zeros = true; 195 | if accept_bounds { 196 | // mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit iff mm_shift == 1. 197 | vm_is_trailing_zeros = mm_shift == 1; 198 | } else { 199 | // mp = mv + 2, so it always has at least one trailing 0 bit. 200 | vp -= 1; 201 | } 202 | } else if q < 63 { 203 | // TODO(ulfjack): Use a tighter bound here. 204 | // We want to know if the full product has at least q trailing zeros. 205 | // We need to compute min(p2(mv), p5(mv) - e2) >= q 206 | // <=> p2(mv) >= q && p5(mv) - e2 >= q 207 | // <=> p2(mv) >= q (because -e2 >= q) 208 | vr_is_trailing_zeros = multiple_of_power_of_2(mv, q); 209 | } 210 | } 211 | 212 | // Step 4: Find the shortest decimal representation in the interval of valid representations. 213 | let mut removed = 0i32; 214 | let mut last_removed_digit = 0u8; 215 | // On average, we remove ~2 digits. 216 | let output = if vm_is_trailing_zeros || vr_is_trailing_zeros { 217 | // General case, which happens rarely (~0.7%). 218 | loop { 219 | let vp_div10 = div10(vp); 220 | let vm_div10 = div10(vm); 221 | if vp_div10 <= vm_div10 { 222 | break; 223 | } 224 | let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32)); 225 | let vr_div10 = div10(vr); 226 | let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); 227 | vm_is_trailing_zeros &= vm_mod10 == 0; 228 | vr_is_trailing_zeros &= last_removed_digit == 0; 229 | last_removed_digit = vr_mod10 as u8; 230 | vr = vr_div10; 231 | vp = vp_div10; 232 | vm = vm_div10; 233 | removed += 1; 234 | } 235 | if vm_is_trailing_zeros { 236 | loop { 237 | let vm_div10 = div10(vm); 238 | let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32)); 239 | if vm_mod10 != 0 { 240 | break; 241 | } 242 | let vp_div10 = div10(vp); 243 | let vr_div10 = div10(vr); 244 | let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); 245 | vr_is_trailing_zeros &= last_removed_digit == 0; 246 | last_removed_digit = vr_mod10 as u8; 247 | vr = vr_div10; 248 | vp = vp_div10; 249 | vm = vm_div10; 250 | removed += 1; 251 | } 252 | } 253 | if vr_is_trailing_zeros && last_removed_digit == 5 && vr % 2 == 0 { 254 | // Round even if the exact number is .....50..0. 255 | last_removed_digit = 4; 256 | } 257 | // We need to take vr + 1 if vr is outside bounds or we need to round up. 258 | vr + ((vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5) 259 | as u64 260 | } else { 261 | // Specialized for the common case (~99.3%). Percentages below are relative to this. 262 | let mut round_up = false; 263 | let vp_div100 = div100(vp); 264 | let vm_div100 = div100(vm); 265 | // Optimization: remove two digits at a time (~86.2%). 266 | if vp_div100 > vm_div100 { 267 | let vr_div100 = div100(vr); 268 | let vr_mod100 = (vr as u32).wrapping_sub(100u32.wrapping_mul(vr_div100 as u32)); 269 | round_up = vr_mod100 >= 50; 270 | vr = vr_div100; 271 | vp = vp_div100; 272 | vm = vm_div100; 273 | removed += 2; 274 | } 275 | // Loop iterations below (approximately), without optimization above: 276 | // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% 277 | // Loop iterations below (approximately), with optimization above: 278 | // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% 279 | loop { 280 | let vp_div10 = div10(vp); 281 | let vm_div10 = div10(vm); 282 | if vp_div10 <= vm_div10 { 283 | break; 284 | } 285 | let vr_div10 = div10(vr); 286 | let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); 287 | round_up = vr_mod10 >= 5; 288 | vr = vr_div10; 289 | vp = vp_div10; 290 | vm = vm_div10; 291 | removed += 1; 292 | } 293 | // We need to take vr + 1 if vr is outside bounds or we need to round up. 294 | vr + (vr == vm || round_up) as u64 295 | }; 296 | let exp = e10 + removed; 297 | 298 | FloatingDecimal64 { 299 | exponent: exp, 300 | mantissa: output, 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/d2s_full_table.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | const DOUBLE_POW5_INV_TABLE_SIZE: usize = 342; 22 | const DOUBLE_POW5_TABLE_SIZE: usize = 326; 23 | 24 | pub static DOUBLE_POW5_INV_SPLIT: [(u64, u64); DOUBLE_POW5_INV_TABLE_SIZE] = [ 25 | (1, 2305843009213693952), 26 | (11068046444225730970, 1844674407370955161), 27 | (5165088340638674453, 1475739525896764129), 28 | (7821419487252849886, 1180591620717411303), 29 | (8824922364862649494, 1888946593147858085), 30 | (7059937891890119595, 1511157274518286468), 31 | (13026647942995916322, 1208925819614629174), 32 | (9774590264567735146, 1934281311383406679), 33 | (11509021026396098440, 1547425049106725343), 34 | (16585914450600699399, 1237940039285380274), 35 | (15469416676735388068, 1980704062856608439), 36 | (16064882156130220778, 1584563250285286751), 37 | (9162556910162266299, 1267650600228229401), 38 | (7281393426775805432, 2028240960365167042), 39 | (16893161185646375315, 1622592768292133633), 40 | (2446482504291369283, 1298074214633706907), 41 | (7603720821608101175, 2076918743413931051), 42 | (2393627842544570617, 1661534994731144841), 43 | (16672297533003297786, 1329227995784915872), 44 | (11918280793837635165, 2126764793255865396), 45 | (5845275820328197809, 1701411834604692317), 46 | (15744267100488289217, 1361129467683753853), 47 | (3054734472329800808, 2177807148294006166), 48 | (17201182836831481939, 1742245718635204932), 49 | (6382248639981364905, 1393796574908163946), 50 | (2832900194486363201, 2230074519853062314), 51 | (5955668970331000884, 1784059615882449851), 52 | (1075186361522890384, 1427247692705959881), 53 | (12788344622662355584, 2283596308329535809), 54 | (13920024512871794791, 1826877046663628647), 55 | (3757321980813615186, 1461501637330902918), 56 | (10384555214134712795, 1169201309864722334), 57 | (5547241898389809503, 1870722095783555735), 58 | (4437793518711847602, 1496577676626844588), 59 | (10928932444453298728, 1197262141301475670), 60 | (17486291911125277965, 1915619426082361072), 61 | (6610335899416401726, 1532495540865888858), 62 | (12666966349016942027, 1225996432692711086), 63 | (12888448528943286597, 1961594292308337738), 64 | (17689456452638449924, 1569275433846670190), 65 | (14151565162110759939, 1255420347077336152), 66 | (7885109000409574610, 2008672555323737844), 67 | (9997436015069570011, 1606938044258990275), 68 | (7997948812055656009, 1285550435407192220), 69 | (12796718099289049614, 2056880696651507552), 70 | (2858676849947419045, 1645504557321206042), 71 | (13354987924183666206, 1316403645856964833), 72 | (17678631863951955605, 2106245833371143733), 73 | (3074859046935833515, 1684996666696914987), 74 | (13527933681774397782, 1347997333357531989), 75 | (10576647446613305481, 2156795733372051183), 76 | (15840015586774465031, 1725436586697640946), 77 | (8982663654677661702, 1380349269358112757), 78 | (18061610662226169046, 2208558830972980411), 79 | (10759939715039024913, 1766847064778384329), 80 | (12297300586773130254, 1413477651822707463), 81 | (15986332124095098083, 2261564242916331941), 82 | (9099716884534168143, 1809251394333065553), 83 | (14658471137111155161, 1447401115466452442), 84 | (4348079280205103483, 1157920892373161954), 85 | (14335624477811986218, 1852673427797059126), 86 | (7779150767507678651, 1482138742237647301), 87 | (2533971799264232598, 1185710993790117841), 88 | (15122401323048503126, 1897137590064188545), 89 | (12097921058438802501, 1517710072051350836), 90 | (5988988032009131678, 1214168057641080669), 91 | (16961078480698431330, 1942668892225729070), 92 | (13568862784558745064, 1554135113780583256), 93 | (7165741412905085728, 1243308091024466605), 94 | (11465186260648137165, 1989292945639146568), 95 | (16550846638002330379, 1591434356511317254), 96 | (16930026125143774626, 1273147485209053803), 97 | (4951948911778577463, 2037035976334486086), 98 | (272210314680951647, 1629628781067588869), 99 | (3907117066486671641, 1303703024854071095), 100 | (6251387306378674625, 2085924839766513752), 101 | (16069156289328670670, 1668739871813211001), 102 | (9165976216721026213, 1334991897450568801), 103 | (7286864317269821294, 2135987035920910082), 104 | (16897537898041588005, 1708789628736728065), 105 | (13518030318433270404, 1367031702989382452), 106 | (6871453250525591353, 2187250724783011924), 107 | (9186511415162383406, 1749800579826409539), 108 | (11038557946871817048, 1399840463861127631), 109 | (10282995085511086630, 2239744742177804210), 110 | (8226396068408869304, 1791795793742243368), 111 | (13959814484210916090, 1433436634993794694), 112 | (11267656730511734774, 2293498615990071511), 113 | (5324776569667477496, 1834798892792057209), 114 | (7949170070475892320, 1467839114233645767), 115 | (17427382500606444826, 1174271291386916613), 116 | (5747719112518849781, 1878834066219066582), 117 | (15666221734240810795, 1503067252975253265), 118 | (12532977387392648636, 1202453802380202612), 119 | (5295368560860596524, 1923926083808324180), 120 | (4236294848688477220, 1539140867046659344), 121 | (7078384693692692099, 1231312693637327475), 122 | (11325415509908307358, 1970100309819723960), 123 | (9060332407926645887, 1576080247855779168), 124 | (14626963555825137356, 1260864198284623334), 125 | (12335095245094488799, 2017382717255397335), 126 | (9868076196075591040, 1613906173804317868), 127 | (15273158586344293478, 1291124939043454294), 128 | (13369007293925138595, 2065799902469526871), 129 | (7005857020398200553, 1652639921975621497), 130 | (16672732060544291412, 1322111937580497197), 131 | (11918976037903224966, 2115379100128795516), 132 | (5845832015580669650, 1692303280103036413), 133 | (12055363241948356366, 1353842624082429130), 134 | (841837113407818570, 2166148198531886609), 135 | (4362818505468165179, 1732918558825509287), 136 | (14558301248600263113, 1386334847060407429), 137 | (12225235553534690011, 2218135755296651887), 138 | (2401490813343931363, 1774508604237321510), 139 | (1921192650675145090, 1419606883389857208), 140 | (17831303500047873437, 2271371013423771532), 141 | (6886345170554478103, 1817096810739017226), 142 | (1819727321701672159, 1453677448591213781), 143 | (16213177116328979020, 1162941958872971024), 144 | (14873036941900635463, 1860707134196753639), 145 | (15587778368262418694, 1488565707357402911), 146 | (8780873879868024632, 1190852565885922329), 147 | (2981351763563108441, 1905364105417475727), 148 | (13453127855076217722, 1524291284333980581), 149 | (7073153469319063855, 1219433027467184465), 150 | (11317045550910502167, 1951092843947495144), 151 | (12742985255470312057, 1560874275157996115), 152 | (10194388204376249646, 1248699420126396892), 153 | (1553625868034358140, 1997919072202235028), 154 | (8621598323911307159, 1598335257761788022), 155 | (17965325103354776697, 1278668206209430417), 156 | (13987124906400001422, 2045869129935088668), 157 | (121653480894270168, 1636695303948070935), 158 | (97322784715416134, 1309356243158456748), 159 | (14913111714512307107, 2094969989053530796), 160 | (8241140556867935363, 1675975991242824637), 161 | (17660958889720079260, 1340780792994259709), 162 | (17189487779326395846, 2145249268790815535), 163 | (13751590223461116677, 1716199415032652428), 164 | (18379969808252713988, 1372959532026121942), 165 | (14650556434236701088, 2196735251241795108), 166 | (652398703163629901, 1757388200993436087), 167 | (11589965406756634890, 1405910560794748869), 168 | (7475898206584884855, 2249456897271598191), 169 | (2291369750525997561, 1799565517817278553), 170 | (9211793429904618695, 1439652414253822842), 171 | (18428218302589300235, 2303443862806116547), 172 | (7363877012587619542, 1842755090244893238), 173 | (13269799239553916280, 1474204072195914590), 174 | (10615839391643133024, 1179363257756731672), 175 | (2227947767661371545, 1886981212410770676), 176 | (16539753473096738529, 1509584969928616540), 177 | (13231802778477390823, 1207667975942893232), 178 | (6413489186596184024, 1932268761508629172), 179 | (16198837793502678189, 1545815009206903337), 180 | (5580372605318321905, 1236652007365522670), 181 | (8928596168509315048, 1978643211784836272), 182 | (18210923379033183008, 1582914569427869017), 183 | (7190041073742725760, 1266331655542295214), 184 | (436019273762630246, 2026130648867672343), 185 | (7727513048493924843, 1620904519094137874), 186 | (9871359253537050198, 1296723615275310299), 187 | (4726128361433549347, 2074757784440496479), 188 | (7470251503888749801, 1659806227552397183), 189 | (13354898832594820487, 1327844982041917746), 190 | (13989140502667892133, 2124551971267068394), 191 | (14880661216876224029, 1699641577013654715), 192 | (11904528973500979224, 1359713261610923772), 193 | (4289851098633925465, 2175541218577478036), 194 | (18189276137874781665, 1740432974861982428), 195 | (3483374466074094362, 1392346379889585943), 196 | (1884050330976640656, 2227754207823337509), 197 | (5196589079523222848, 1782203366258670007), 198 | (15225317707844309248, 1425762693006936005), 199 | (5913764258841343181, 2281220308811097609), 200 | (8420360221814984868, 1824976247048878087), 201 | (17804334621677718864, 1459980997639102469), 202 | (17932816512084085415, 1167984798111281975), 203 | (10245762345624985047, 1868775676978051161), 204 | (4507261061758077715, 1495020541582440929), 205 | (7295157664148372495, 1196016433265952743), 206 | (7982903447895485668, 1913626293225524389), 207 | (10075671573058298858, 1530901034580419511), 208 | (4371188443704728763, 1224720827664335609), 209 | (14372599139411386667, 1959553324262936974), 210 | (15187428126271019657, 1567642659410349579), 211 | (15839291315758726049, 1254114127528279663), 212 | (3206773216762499739, 2006582604045247462), 213 | (13633465017635730761, 1605266083236197969), 214 | (14596120828850494932, 1284212866588958375), 215 | (4907049252451240275, 2054740586542333401), 216 | (236290587219081897, 1643792469233866721), 217 | (14946427728742906810, 1315033975387093376), 218 | (16535586736504830250, 2104054360619349402), 219 | (5849771759720043554, 1683243488495479522), 220 | (15747863852001765813, 1346594790796383617), 221 | (10439186904235184007, 2154551665274213788), 222 | (15730047152871967852, 1723641332219371030), 223 | (12584037722297574282, 1378913065775496824), 224 | (9066413911450387881, 2206260905240794919), 225 | (10942479943902220628, 1765008724192635935), 226 | (8753983955121776503, 1412006979354108748), 227 | (10317025513452932081, 2259211166966573997), 228 | (874922781278525018, 1807368933573259198), 229 | (8078635854506640661, 1445895146858607358), 230 | (13841606313089133175, 1156716117486885886), 231 | (14767872471458792434, 1850745787979017418), 232 | (746251532941302978, 1480596630383213935), 233 | (597001226353042382, 1184477304306571148), 234 | (15712597221132509104, 1895163686890513836), 235 | (8880728962164096960, 1516130949512411069), 236 | (10793931984473187891, 1212904759609928855), 237 | (17270291175157100626, 1940647615375886168), 238 | (2748186495899949531, 1552518092300708935), 239 | (2198549196719959625, 1242014473840567148), 240 | (18275073973719576693, 1987223158144907436), 241 | (10930710364233751031, 1589778526515925949), 242 | (12433917106128911148, 1271822821212740759), 243 | (8826220925580526867, 2034916513940385215), 244 | (7060976740464421494, 1627933211152308172), 245 | (16716827836597268165, 1302346568921846537), 246 | (11989529279587987770, 2083754510274954460), 247 | (9591623423670390216, 1667003608219963568), 248 | (15051996368420132820, 1333602886575970854), 249 | (13015147745246481542, 2133764618521553367), 250 | (3033420566713364587, 1707011694817242694), 251 | (6116085268112601993, 1365609355853794155), 252 | (9785736428980163188, 2184974969366070648), 253 | (15207286772667951197, 1747979975492856518), 254 | (1097782973908629988, 1398383980394285215), 255 | (1756452758253807981, 2237414368630856344), 256 | (5094511021344956708, 1789931494904685075), 257 | (4075608817075965366, 1431945195923748060), 258 | (6520974107321544586, 2291112313477996896), 259 | (1527430471115325346, 1832889850782397517), 260 | (12289990821117991246, 1466311880625918013), 261 | (17210690286378213644, 1173049504500734410), 262 | (9090360384495590213, 1876879207201175057), 263 | (18340334751822203140, 1501503365760940045), 264 | (14672267801457762512, 1201202692608752036), 265 | (16096930852848599373, 1921924308174003258), 266 | (1809498238053148529, 1537539446539202607), 267 | (12515645034668249793, 1230031557231362085), 268 | (1578287981759648052, 1968050491570179337), 269 | (12330676829633449412, 1574440393256143469), 270 | (13553890278448669853, 1259552314604914775), 271 | (3239480371808320148, 2015283703367863641), 272 | (17348979556414297411, 1612226962694290912), 273 | (6500486015647617283, 1289781570155432730), 274 | (10400777625036187652, 2063650512248692368), 275 | (15699319729512770768, 1650920409798953894), 276 | (16248804598352126938, 1320736327839163115), 277 | (7551343283653851484, 2113178124542660985), 278 | (6041074626923081187, 1690542499634128788), 279 | (12211557331022285596, 1352433999707303030), 280 | (1091747655926105338, 2163894399531684849), 281 | (4562746939482794594, 1731115519625347879), 282 | (7339546366328145998, 1384892415700278303), 283 | (8053925371383123274, 2215827865120445285), 284 | (6443140297106498619, 1772662292096356228), 285 | (12533209867169019542, 1418129833677084982), 286 | (5295740528502789974, 2269007733883335972), 287 | (15304638867027962949, 1815206187106668777), 288 | (4865013464138549713, 1452164949685335022), 289 | (14960057215536570740, 1161731959748268017), 290 | (9178696285890871890, 1858771135597228828), 291 | (14721654658196518159, 1487016908477783062), 292 | (4398626097073393881, 1189613526782226450), 293 | (7037801755317430209, 1903381642851562320), 294 | (5630241404253944167, 1522705314281249856), 295 | (814844308661245011, 1218164251424999885), 296 | (1303750893857992017, 1949062802279999816), 297 | (15800395974054034906, 1559250241823999852), 298 | (5261619149759407279, 1247400193459199882), 299 | (12107939454356961969, 1995840309534719811), 300 | (5997002748743659252, 1596672247627775849), 301 | (8486951013736837725, 1277337798102220679), 302 | (2511075177753209390, 2043740476963553087), 303 | (13076906586428298482, 1634992381570842469), 304 | (14150874083884549109, 1307993905256673975), 305 | (4194654460505726958, 2092790248410678361), 306 | (18113118827372222859, 1674232198728542688), 307 | (3422448617672047318, 1339385758982834151), 308 | (16543964232501006678, 2143017214372534641), 309 | (9545822571258895019, 1714413771498027713), 310 | (15015355686490936662, 1371531017198422170), 311 | (5577825024675947042, 2194449627517475473), 312 | (11840957649224578280, 1755559702013980378), 313 | (16851463748863483271, 1404447761611184302), 314 | (12204946739213931940, 2247116418577894884), 315 | (13453306206113055875, 1797693134862315907), 316 | (3383947335406624054, 1438154507889852726), 317 | (16482362180876329456, 2301047212623764361), 318 | (9496540929959153242, 1840837770099011489), 319 | (11286581558709232917, 1472670216079209191), 320 | (5339916432225476010, 1178136172863367353), 321 | (4854517476818851293, 1885017876581387765), 322 | (3883613981455081034, 1508014301265110212), 323 | (14174937629389795797, 1206411441012088169), 324 | (11611853762797942306, 1930258305619341071), 325 | (5600134195496443521, 1544206644495472857), 326 | (15548153800622885787, 1235365315596378285), 327 | (6430302007287065643, 1976584504954205257), 328 | (16212288050055383484, 1581267603963364205), 329 | (12969830440044306787, 1265014083170691364), 330 | (9683682259845159889, 2024022533073106183), 331 | (15125643437359948558, 1619218026458484946), 332 | (8411165935146048523, 1295374421166787957), 333 | (17147214310975587960, 2072599073866860731), 334 | (10028422634038560045, 1658079259093488585), 335 | (8022738107230848036, 1326463407274790868), 336 | (9147032156827446534, 2122341451639665389), 337 | (11006974540203867551, 1697873161311732311), 338 | (5116230817421183718, 1358298529049385849), 339 | (15564666937357714594, 2173277646479017358), 340 | (1383687105660440706, 1738622117183213887), 341 | (12174996128754083534, 1390897693746571109), 342 | (8411947361780802685, 2225436309994513775), 343 | (6729557889424642148, 1780349047995611020), 344 | (5383646311539713719, 1424279238396488816), 345 | (1235136468979721303, 2278846781434382106), 346 | (15745504434151418335, 1823077425147505684), 347 | (16285752362063044992, 1458461940118004547), 348 | (5649904260166615347, 1166769552094403638), 349 | (5350498001524674232, 1866831283351045821), 350 | (591049586477829062, 1493465026680836657), 351 | (11540886113407994219, 1194772021344669325), 352 | (18673707743239135, 1911635234151470921), 353 | (14772334225162232601, 1529308187321176736), 354 | (8128518565387875758, 1223446549856941389), 355 | (1937583260394870242, 1957514479771106223), 356 | (8928764237799716840, 1566011583816884978), 357 | (14521709019723594119, 1252809267053507982), 358 | (8477339172590109297, 2004494827285612772), 359 | (17849917782297818407, 1603595861828490217), 360 | (6901236596354434079, 1282876689462792174), 361 | (18420676183650915173, 2052602703140467478), 362 | (3668494502695001169, 1642082162512373983), 363 | (10313493231639821582, 1313665730009899186), 364 | (9122891541139893884, 2101865168015838698), 365 | (14677010862395735754, 1681492134412670958), 366 | (673562245690857633, 1345193707530136767), 367 | ]; 368 | 369 | pub static DOUBLE_POW5_SPLIT: [(u64, u64); DOUBLE_POW5_TABLE_SIZE] = [ 370 | (0, 1152921504606846976), 371 | (0, 1441151880758558720), 372 | (0, 1801439850948198400), 373 | (0, 2251799813685248000), 374 | (0, 1407374883553280000), 375 | (0, 1759218604441600000), 376 | (0, 2199023255552000000), 377 | (0, 1374389534720000000), 378 | (0, 1717986918400000000), 379 | (0, 2147483648000000000), 380 | (0, 1342177280000000000), 381 | (0, 1677721600000000000), 382 | (0, 2097152000000000000), 383 | (0, 1310720000000000000), 384 | (0, 1638400000000000000), 385 | (0, 2048000000000000000), 386 | (0, 1280000000000000000), 387 | (0, 1600000000000000000), 388 | (0, 2000000000000000000), 389 | (0, 1250000000000000000), 390 | (0, 1562500000000000000), 391 | (0, 1953125000000000000), 392 | (0, 1220703125000000000), 393 | (0, 1525878906250000000), 394 | (0, 1907348632812500000), 395 | (0, 1192092895507812500), 396 | (0, 1490116119384765625), 397 | (4611686018427387904, 1862645149230957031), 398 | (9799832789158199296, 1164153218269348144), 399 | (12249790986447749120, 1455191522836685180), 400 | (15312238733059686400, 1818989403545856475), 401 | (14528612397897220096, 2273736754432320594), 402 | (13692068767113150464, 1421085471520200371), 403 | (12503399940464050176, 1776356839400250464), 404 | (15629249925580062720, 2220446049250313080), 405 | (9768281203487539200, 1387778780781445675), 406 | (7598665485932036096, 1734723475976807094), 407 | (274959820560269312, 2168404344971008868), 408 | (9395221924704944128, 1355252715606880542), 409 | (2520655369026404352, 1694065894508600678), 410 | (12374191248137781248, 2117582368135750847), 411 | (14651398557727195136, 1323488980084844279), 412 | (13702562178731606016, 1654361225106055349), 413 | (3293144668132343808, 2067951531382569187), 414 | (18199116482078572544, 1292469707114105741), 415 | (8913837547316051968, 1615587133892632177), 416 | (15753982952572452864, 2019483917365790221), 417 | (12152082354571476992, 1262177448353618888), 418 | (15190102943214346240, 1577721810442023610), 419 | (9764256642163156992, 1972152263052529513), 420 | (17631875447420442880, 1232595164407830945), 421 | (8204786253993389888, 1540743955509788682), 422 | (1032610780636961552, 1925929944387235853), 423 | (2951224747111794922, 1203706215242022408), 424 | (3689030933889743652, 1504632769052528010), 425 | (13834660704216955373, 1880790961315660012), 426 | (17870034976990372916, 1175494350822287507), 427 | (17725857702810578241, 1469367938527859384), 428 | (3710578054803671186, 1836709923159824231), 429 | (26536550077201078, 2295887403949780289), 430 | (11545800389866720434, 1434929627468612680), 431 | (14432250487333400542, 1793662034335765850), 432 | (8816941072311974870, 2242077542919707313), 433 | (17039803216263454053, 1401298464324817070), 434 | (12076381983474541759, 1751623080406021338), 435 | (5872105442488401391, 2189528850507526673), 436 | (15199280947623720629, 1368455531567204170), 437 | (9775729147674874978, 1710569414459005213), 438 | (16831347453020981627, 2138211768073756516), 439 | (1296220121283337709, 1336382355046097823), 440 | (15455333206886335848, 1670477943807622278), 441 | (10095794471753144002, 2088097429759527848), 442 | (6309871544845715001, 1305060893599704905), 443 | (12499025449484531656, 1631326116999631131), 444 | (11012095793428276666, 2039157646249538914), 445 | (11494245889320060820, 1274473528905961821), 446 | (532749306367912313, 1593091911132452277), 447 | (5277622651387278295, 1991364888915565346), 448 | (7910200175544436838, 1244603055572228341), 449 | (14499436237857933952, 1555753819465285426), 450 | (8900923260467641632, 1944692274331606783), 451 | (12480606065433357876, 1215432671457254239), 452 | (10989071563364309441, 1519290839321567799), 453 | (9124653435777998898, 1899113549151959749), 454 | (8008751406574943263, 1186945968219974843), 455 | (5399253239791291175, 1483682460274968554), 456 | (15972438586593889776, 1854603075343710692), 457 | (759402079766405302, 1159126922089819183), 458 | (14784310654990170340, 1448908652612273978), 459 | (9257016281882937117, 1811135815765342473), 460 | (16182956370781059300, 2263919769706678091), 461 | (7808504722524468110, 1414949856066673807), 462 | (5148944884728197234, 1768687320083342259), 463 | (1824495087482858639, 2210859150104177824), 464 | (1140309429676786649, 1381786968815111140), 465 | (1425386787095983311, 1727233711018888925), 466 | (6393419502297367043, 2159042138773611156), 467 | (13219259225790630210, 1349401336733506972), 468 | (16524074032238287762, 1686751670916883715), 469 | (16043406521870471799, 2108439588646104644), 470 | (803757039314269066, 1317774742903815403), 471 | (14839754354425000045, 1647218428629769253), 472 | (4714634887749086344, 2059023035787211567), 473 | (9864175832484260821, 1286889397367007229), 474 | (16941905809032713930, 1608611746708759036), 475 | (2730638187581340797, 2010764683385948796), 476 | (10930020904093113806, 1256727927116217997), 477 | (18274212148543780162, 1570909908895272496), 478 | (4396021111970173586, 1963637386119090621), 479 | (5053356204195052443, 1227273366324431638), 480 | (15540067292098591362, 1534091707905539547), 481 | (14813398096695851299, 1917614634881924434), 482 | (13870059828862294966, 1198509146801202771), 483 | (12725888767650480803, 1498136433501503464), 484 | (15907360959563101004, 1872670541876879330), 485 | (14553786618154326031, 1170419088673049581), 486 | (4357175217410743827, 1463023860841311977), 487 | (10058155040190817688, 1828779826051639971), 488 | (7961007781811134206, 2285974782564549964), 489 | (14199001900486734687, 1428734239102843727), 490 | (13137066357181030455, 1785917798878554659), 491 | (11809646928048900164, 2232397248598193324), 492 | (16604401366885338411, 1395248280373870827), 493 | (16143815690179285109, 1744060350467338534), 494 | (10956397575869330579, 2180075438084173168), 495 | (6847748484918331612, 1362547148802608230), 496 | (17783057643002690323, 1703183936003260287), 497 | (17617136035325974999, 2128979920004075359), 498 | (17928239049719816230, 1330612450002547099), 499 | (17798612793722382384, 1663265562503183874), 500 | (13024893955298202172, 2079081953128979843), 501 | (5834715712847682405, 1299426220705612402), 502 | (16516766677914378815, 1624282775882015502), 503 | (11422586310538197711, 2030353469852519378), 504 | (11750802462513761473, 1268970918657824611), 505 | (10076817059714813937, 1586213648322280764), 506 | (12596021324643517422, 1982767060402850955), 507 | (5566670318688504437, 1239229412751781847), 508 | (2346651879933242642, 1549036765939727309), 509 | (7545000868343941206, 1936295957424659136), 510 | (4715625542714963254, 1210184973390411960), 511 | (5894531928393704067, 1512731216738014950), 512 | (16591536947346905892, 1890914020922518687), 513 | (17287239619732898039, 1181821263076574179), 514 | (16997363506238734644, 1477276578845717724), 515 | (2799960309088866689, 1846595723557147156), 516 | (10973347230035317489, 1154122327223216972), 517 | (13716684037544146861, 1442652909029021215), 518 | (12534169028502795672, 1803316136286276519), 519 | (11056025267201106687, 2254145170357845649), 520 | (18439230838069161439, 1408840731473653530), 521 | (13825666510731675991, 1761050914342066913), 522 | (3447025083132431277, 2201313642927583642), 523 | (6766076695385157452, 1375821026829739776), 524 | (8457595869231446815, 1719776283537174720), 525 | (10571994836539308519, 2149720354421468400), 526 | (6607496772837067824, 1343575221513417750), 527 | (17482743002901110588, 1679469026891772187), 528 | (17241742735199000331, 2099336283614715234), 529 | (15387775227926763111, 1312085177259197021), 530 | (5399660979626290177, 1640106471573996277), 531 | (11361262242960250625, 2050133089467495346), 532 | (11712474920277544544, 1281333180917184591), 533 | (10028907631919542777, 1601666476146480739), 534 | (7924448521472040567, 2002083095183100924), 535 | (14176152362774801162, 1251301934489438077), 536 | (3885132398186337741, 1564127418111797597), 537 | (9468101516160310080, 1955159272639746996), 538 | (15140935484454969608, 1221974545399841872), 539 | (479425281859160394, 1527468181749802341), 540 | (5210967620751338397, 1909335227187252926), 541 | (17091912818251750210, 1193334516992033078), 542 | (12141518985959911954, 1491668146240041348), 543 | (15176898732449889943, 1864585182800051685), 544 | (11791404716994875166, 1165365739250032303), 545 | (10127569877816206054, 1456707174062540379), 546 | (8047776328842869663, 1820883967578175474), 547 | (836348374198811271, 2276104959472719343), 548 | (7440246761515338900, 1422565599670449589), 549 | (13911994470321561530, 1778206999588061986), 550 | (8166621051047176104, 2222758749485077483), 551 | (2798295147690791113, 1389224218428173427), 552 | (17332926989895652603, 1736530273035216783), 553 | (17054472718942177850, 2170662841294020979), 554 | (8353202440125167204, 1356664275808763112), 555 | (10441503050156459005, 1695830344760953890), 556 | (3828506775840797949, 2119787930951192363), 557 | (86973725686804766, 1324867456844495227), 558 | (13943775212390669669, 1656084321055619033), 559 | (3594660960206173375, 2070105401319523792), 560 | (2246663100128858359, 1293815875824702370), 561 | (12031700912015848757, 1617269844780877962), 562 | (5816254103165035138, 2021587305976097453), 563 | (5941001823691840913, 1263492066235060908), 564 | (7426252279614801142, 1579365082793826135), 565 | (4671129331091113523, 1974206353492282669), 566 | (5225298841145639904, 1233878970932676668), 567 | (6531623551432049880, 1542348713665845835), 568 | (3552843420862674446, 1927935892082307294), 569 | (16055585193321335241, 1204959932551442058), 570 | (10846109454796893243, 1506199915689302573), 571 | (18169322836923504458, 1882749894611628216), 572 | (11355826773077190286, 1176718684132267635), 573 | (9583097447919099954, 1470898355165334544), 574 | (11978871809898874942, 1838622943956668180), 575 | (14973589762373593678, 2298278679945835225), 576 | (2440964573842414192, 1436424174966147016), 577 | (3051205717303017741, 1795530218707683770), 578 | (13037379183483547984, 2244412773384604712), 579 | (8148361989677217490, 1402757983365377945), 580 | (14797138505523909766, 1753447479206722431), 581 | (13884737113477499304, 2191809349008403039), 582 | (15595489723564518921, 1369880843130251899), 583 | (14882676136028260747, 1712351053912814874), 584 | (9379973133180550126, 2140438817391018593), 585 | (17391698254306313589, 1337774260869386620), 586 | (3292878744173340370, 1672217826086733276), 587 | (4116098430216675462, 2090272282608416595), 588 | (266718509671728212, 1306420176630260372), 589 | (333398137089660265, 1633025220787825465), 590 | (5028433689789463235, 2041281525984781831), 591 | (10060300083759496378, 1275800953740488644), 592 | (12575375104699370472, 1594751192175610805), 593 | (1884160825592049379, 1993438990219513507), 594 | (17318501580490888525, 1245899368887195941), 595 | (7813068920331446945, 1557374211108994927), 596 | (5154650131986920777, 1946717763886243659), 597 | (915813323278131534, 1216698602428902287), 598 | (14979824709379828129, 1520873253036127858), 599 | (9501408849870009354, 1901091566295159823), 600 | (12855909558809837702, 1188182228934474889), 601 | (2234828893230133415, 1485227786168093612), 602 | (2793536116537666769, 1856534732710117015), 603 | (8663489100477123587, 1160334207943823134), 604 | (1605989338741628675, 1450417759929778918), 605 | (11230858710281811652, 1813022199912223647), 606 | (9426887369424876662, 2266277749890279559), 607 | (12809333633531629769, 1416423593681424724), 608 | (16011667041914537212, 1770529492101780905), 609 | (6179525747111007803, 2213161865127226132), 610 | (13085575628799155685, 1383226165704516332), 611 | (16356969535998944606, 1729032707130645415), 612 | (15834525901571292854, 2161290883913306769), 613 | (2979049660840976177, 1350806802445816731), 614 | (17558870131333383934, 1688508503057270913), 615 | (8113529608884566205, 2110635628821588642), 616 | (9682642023980241782, 1319147268013492901), 617 | (16714988548402690132, 1648934085016866126), 618 | (11670363648648586857, 2061167606271082658), 619 | (11905663298832754689, 1288229753919426661), 620 | (1047021068258779650, 1610287192399283327), 621 | (15143834390605638274, 2012858990499104158), 622 | (4853210475701136017, 1258036869061940099), 623 | (1454827076199032118, 1572546086327425124), 624 | (1818533845248790147, 1965682607909281405), 625 | (3442426662494187794, 1228551629943300878), 626 | (13526405364972510550, 1535689537429126097), 627 | (3072948650933474476, 1919611921786407622), 628 | (15755650962115585259, 1199757451116504763), 629 | (15082877684217093670, 1499696813895630954), 630 | (9630225068416591280, 1874621017369538693), 631 | (8324733676974063502, 1171638135855961683), 632 | (5794231077790191473, 1464547669819952104), 633 | (7242788847237739342, 1830684587274940130), 634 | (18276858095901949986, 2288355734093675162), 635 | (16034722328366106645, 1430222333808546976), 636 | (1596658836748081690, 1787777917260683721), 637 | (6607509564362490017, 2234722396575854651), 638 | (1823850468512862308, 1396701497859909157), 639 | (6891499104068465790, 1745876872324886446), 640 | (17837745916940358045, 2182346090406108057), 641 | (4231062170446641922, 1363966306503817536), 642 | (5288827713058302403, 1704957883129771920), 643 | (6611034641322878003, 2131197353912214900), 644 | (13355268687681574560, 1331998346195134312), 645 | (16694085859601968200, 1664997932743917890), 646 | (11644235287647684442, 2081247415929897363), 647 | (4971804045566108824, 1300779634956185852), 648 | (6214755056957636030, 1625974543695232315), 649 | (3156757802769657134, 2032468179619040394), 650 | (6584659645158423613, 1270292612261900246), 651 | (17454196593302805324, 1587865765327375307), 652 | (17206059723201118751, 1984832206659219134), 653 | (6142101308573311315, 1240520129162011959), 654 | (3065940617289251240, 1550650161452514949), 655 | (8444111790038951954, 1938312701815643686), 656 | (665883850346957067, 1211445438634777304), 657 | (832354812933696334, 1514306798293471630), 658 | (10263815553021896226, 1892883497866839537), 659 | (17944099766707154901, 1183052186166774710), 660 | (13206752671529167818, 1478815232708468388), 661 | (16508440839411459773, 1848519040885585485), 662 | (12623618533845856310, 1155324400553490928), 663 | (15779523167307320387, 1444155500691863660), 664 | (1277659885424598868, 1805194375864829576), 665 | (1597074856780748586, 2256492969831036970), 666 | (5609857803915355770, 1410308106144398106), 667 | (16235694291748970521, 1762885132680497632), 668 | (1847873790976661535, 2203606415850622041), 669 | (12684136165428883219, 1377254009906638775), 670 | (11243484188358716120, 1721567512383298469), 671 | (219297180166231438, 2151959390479123087), 672 | (7054589765244976505, 1344974619049451929), 673 | (13429923224983608535, 1681218273811814911), 674 | (12175718012802122765, 2101522842264768639), 675 | (14527352785642408584, 1313451776415480399), 676 | (13547504963625622826, 1641814720519350499), 677 | (12322695186104640628, 2052268400649188124), 678 | (16925056528170176201, 1282667750405742577), 679 | (7321262604930556539, 1603334688007178222), 680 | (18374950293017971482, 2004168360008972777), 681 | (4566814905495150320, 1252605225005607986), 682 | (14931890668723713708, 1565756531257009982), 683 | (9441491299049866327, 1957195664071262478), 684 | (1289246043478778550, 1223247290044539049), 685 | (6223243572775861092, 1529059112555673811), 686 | (3167368447542438461, 1911323890694592264), 687 | (1979605279714024038, 1194577431684120165), 688 | (7086192618069917952, 1493221789605150206), 689 | (18081112809442173248, 1866527237006437757), 690 | (13606538515115052232, 1166579523129023598), 691 | (7784801107039039482, 1458224403911279498), 692 | (507629346944023544, 1822780504889099373), 693 | (5246222702107417334, 2278475631111374216), 694 | (3278889188817135834, 1424047269444608885), 695 | (8710297504448807696, 1780059086805761106), 696 | ]; 697 | -------------------------------------------------------------------------------- /src/d2s_intrinsics.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | use core::ptr; 22 | 23 | #[cfg_attr(feature = "no-panic", inline)] 24 | pub fn div5(x: u64) -> u64 { 25 | x / 5 26 | } 27 | 28 | #[cfg_attr(feature = "no-panic", inline)] 29 | pub fn div10(x: u64) -> u64 { 30 | x / 10 31 | } 32 | 33 | #[cfg_attr(feature = "no-panic", inline)] 34 | pub fn div100(x: u64) -> u64 { 35 | x / 100 36 | } 37 | 38 | #[cfg_attr(feature = "no-panic", inline)] 39 | pub(crate) fn pow5_factor(mut value: u64) -> u32 { 40 | const M_INV_5: u64 = 14757395258967641293; // 5 * m_inv_5 = 1 (mod 2^64) 41 | const N_DIV_5: u64 = 3689348814741910323; // #{ n | n = 0 (mod 2^64) } = 2^64 / 5 42 | let mut count = 0u32; 43 | loop { 44 | debug_assert!(value != 0); 45 | value = value.wrapping_mul(M_INV_5); 46 | if value > N_DIV_5 { 47 | break; 48 | } 49 | count += 1; 50 | } 51 | count 52 | } 53 | 54 | // Returns true if value is divisible by 5^p. 55 | #[cfg_attr(feature = "no-panic", inline)] 56 | pub fn multiple_of_power_of_5(value: u64, p: u32) -> bool { 57 | // I tried a case distinction on p, but there was no performance difference. 58 | pow5_factor(value) >= p 59 | } 60 | 61 | // Returns true if value is divisible by 2^p. 62 | #[cfg_attr(feature = "no-panic", inline)] 63 | pub fn multiple_of_power_of_2(value: u64, p: u32) -> bool { 64 | debug_assert!(value != 0); 65 | debug_assert!(p < 64); 66 | // __builtin_ctzll doesn't appear to be faster here. 67 | (value & ((1u64 << p) - 1)) == 0 68 | } 69 | 70 | #[cfg_attr(feature = "no-panic", inline)] 71 | pub fn mul_shift_64(m: u64, mul: &(u64, u64), j: u32) -> u64 { 72 | let b0 = m as u128 * mul.0 as u128; 73 | let b2 = m as u128 * mul.1 as u128; 74 | (((b0 >> 64) + b2) >> (j - 64)) as u64 75 | } 76 | 77 | #[cfg_attr(feature = "no-panic", inline)] 78 | pub unsafe fn mul_shift_all_64( 79 | m: u64, 80 | mul: &(u64, u64), 81 | j: u32, 82 | vp: *mut u64, 83 | vm: *mut u64, 84 | mm_shift: u32, 85 | ) -> u64 { 86 | ptr::write(vp, mul_shift_64(4 * m + 2, mul, j)); 87 | ptr::write(vm, mul_shift_64(4 * m - 1 - mm_shift as u64, mul, j)); 88 | mul_shift_64(4 * m, mul, j) 89 | } 90 | -------------------------------------------------------------------------------- /src/d2s_small_table.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | use crate::common::pow5bits; 22 | 23 | pub static DOUBLE_POW5_INV_SPLIT2: [(u64, u64); 15] = [ 24 | (1, 2305843009213693952), 25 | (5955668970331000884, 1784059615882449851), 26 | (8982663654677661702, 1380349269358112757), 27 | (7286864317269821294, 2135987035920910082), 28 | (7005857020398200553, 1652639921975621497), 29 | (17965325103354776697, 1278668206209430417), 30 | (8928596168509315048, 1978643211784836272), 31 | (10075671573058298858, 1530901034580419511), 32 | (597001226353042382, 1184477304306571148), 33 | (1527430471115325346, 1832889850782397517), 34 | (12533209867169019542, 1418129833677084982), 35 | (5577825024675947042, 2194449627517475473), 36 | (11006974540203867551, 1697873161311732311), 37 | (10313493231639821582, 1313665730009899186), 38 | (12701016819766672773, 2032799256770390445), 39 | ]; 40 | 41 | pub static POW5_INV_OFFSETS: [u32; 19] = [ 42 | 0x54544554, 0x04055545, 0x10041000, 0x00400414, 0x40010000, 0x41155555, 0x00000454, 0x00010044, 43 | 0x40000000, 0x44000041, 0x50454450, 0x55550054, 0x51655554, 0x40004000, 0x01000001, 0x00010500, 44 | 0x51515411, 0x05555554, 0x00000000, 45 | ]; 46 | 47 | pub static DOUBLE_POW5_SPLIT2: [(u64, u64); 13] = [ 48 | (0, 1152921504606846976), 49 | (0, 1490116119384765625), 50 | (1032610780636961552, 1925929944387235853), 51 | (7910200175544436838, 1244603055572228341), 52 | (16941905809032713930, 1608611746708759036), 53 | (13024893955298202172, 2079081953128979843), 54 | (6607496772837067824, 1343575221513417750), 55 | (17332926989895652603, 1736530273035216783), 56 | (13037379183483547984, 2244412773384604712), 57 | (1605989338741628675, 1450417759929778918), 58 | (9630225068416591280, 1874621017369538693), 59 | (665883850346957067, 1211445438634777304), 60 | (14931890668723713708, 1565756531257009982), 61 | ]; 62 | 63 | pub static POW5_OFFSETS: [u32; 21] = [ 64 | 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x40000000, 0x59695995, 0x55545555, 0x56555515, 65 | 0x41150504, 0x40555410, 0x44555145, 0x44504540, 0x45555550, 0x40004000, 0x96440440, 0x55565565, 66 | 0x54454045, 0x40154151, 0x55559155, 0x51405555, 0x00000105, 67 | ]; 68 | 69 | pub static DOUBLE_POW5_TABLE: [u64; 26] = [ 70 | 1, 71 | 5, 72 | 25, 73 | 125, 74 | 625, 75 | 3125, 76 | 15625, 77 | 78125, 78 | 390625, 79 | 1953125, 80 | 9765625, 81 | 48828125, 82 | 244140625, 83 | 1220703125, 84 | 6103515625, 85 | 30517578125, 86 | 152587890625, 87 | 762939453125, 88 | 3814697265625, 89 | 19073486328125, 90 | 95367431640625, 91 | 476837158203125, 92 | 2384185791015625, 93 | 11920928955078125, 94 | 59604644775390625, 95 | 298023223876953125, 96 | ]; 97 | 98 | // Computes 5^i in the form required by Ryū. 99 | #[cfg_attr(feature = "no-panic", inline)] 100 | pub unsafe fn compute_pow5(i: u32) -> (u64, u64) { 101 | let base = i / DOUBLE_POW5_TABLE.len() as u32; 102 | let base2 = base * DOUBLE_POW5_TABLE.len() as u32; 103 | let offset = i - base2; 104 | debug_assert!(base < DOUBLE_POW5_SPLIT2.len() as u32); 105 | let mul = *DOUBLE_POW5_SPLIT2.get_unchecked(base as usize); 106 | if offset == 0 { 107 | return mul; 108 | } 109 | debug_assert!(offset < DOUBLE_POW5_TABLE.len() as u32); 110 | let m = *DOUBLE_POW5_TABLE.get_unchecked(offset as usize); 111 | let b0 = m as u128 * mul.0 as u128; 112 | let b2 = m as u128 * mul.1 as u128; 113 | let delta = pow5bits(i as i32) - pow5bits(base2 as i32); 114 | debug_assert!(i / 16 < POW5_OFFSETS.len() as u32); 115 | let shifted_sum = (b0 >> delta) 116 | + (b2 << (64 - delta)) 117 | + ((*POW5_OFFSETS.get_unchecked((i / 16) as usize) >> ((i % 16) << 1)) & 3) as u128; 118 | (shifted_sum as u64, (shifted_sum >> 64) as u64) 119 | } 120 | 121 | // Computes 5^-i in the form required by Ryū. 122 | #[cfg_attr(feature = "no-panic", inline)] 123 | pub unsafe fn compute_inv_pow5(i: u32) -> (u64, u64) { 124 | let base = (i + DOUBLE_POW5_TABLE.len() as u32 - 1) / DOUBLE_POW5_TABLE.len() as u32; 125 | let base2 = base * DOUBLE_POW5_TABLE.len() as u32; 126 | let offset = base2 - i; 127 | debug_assert!(base < DOUBLE_POW5_INV_SPLIT2.len() as u32); 128 | let mul = *DOUBLE_POW5_INV_SPLIT2.get_unchecked(base as usize); // 1/5^base2 129 | if offset == 0 { 130 | return mul; 131 | } 132 | debug_assert!(offset < DOUBLE_POW5_TABLE.len() as u32); 133 | let m = *DOUBLE_POW5_TABLE.get_unchecked(offset as usize); // 5^offset 134 | let b0 = m as u128 * (mul.0 - 1) as u128; 135 | let b2 = m as u128 * mul.1 as u128; // 1/5^base2 * 5^offset = 1/5^(base2-offset) = 1/5^i 136 | let delta = pow5bits(base2 as i32) - pow5bits(i as i32); 137 | debug_assert!(base < POW5_INV_OFFSETS.len() as u32); 138 | let shifted_sum = ((b0 >> delta) + (b2 << (64 - delta))) 139 | + 1 140 | + ((*POW5_INV_OFFSETS.get_unchecked((i / 16) as usize) >> ((i % 16) << 1)) & 3) as u128; 141 | (shifted_sum as u64, (shifted_sum >> 64) as u64) 142 | } 143 | -------------------------------------------------------------------------------- /src/digit_table.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | // A table of all two-digit numbers. This is used to speed up decimal digit 22 | // generation by copying pairs of digits into the final output. 23 | pub static DIGIT_TABLE: [u8; 200] = *b"\ 24 | 0001020304050607080910111213141516171819\ 25 | 2021222324252627282930313233343536373839\ 26 | 4041424344454647484950515253545556575859\ 27 | 6061626364656667686970717273747576777879\ 28 | 8081828384858687888990919293949596979899"; 29 | -------------------------------------------------------------------------------- /src/f2s.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | use crate::common::{log10_pow2, log10_pow5, pow5bits}; 22 | use crate::f2s_intrinsics::{ 23 | mul_pow5_div_pow2, mul_pow5_inv_div_pow2, multiple_of_power_of_2_32, multiple_of_power_of_5_32, 24 | }; 25 | 26 | pub const FLOAT_MANTISSA_BITS: u32 = 23; 27 | pub const FLOAT_EXPONENT_BITS: u32 = 8; 28 | const FLOAT_BIAS: i32 = 127; 29 | pub use crate::f2s_intrinsics::{FLOAT_POW5_BITCOUNT, FLOAT_POW5_INV_BITCOUNT}; 30 | 31 | // A floating decimal representing m * 10^e. 32 | pub struct FloatingDecimal32 { 33 | pub mantissa: u32, 34 | // Decimal exponent's range is -45 to 38 35 | // inclusive, and can fit in i16 if needed. 36 | pub exponent: i32, 37 | } 38 | 39 | #[cfg_attr(feature = "no-panic", inline)] 40 | pub fn f2d(ieee_mantissa: u32, ieee_exponent: u32) -> FloatingDecimal32 { 41 | let (e2, m2) = if ieee_exponent == 0 { 42 | ( 43 | // We subtract 2 so that the bounds computation has 2 additional bits. 44 | 1 - FLOAT_BIAS - FLOAT_MANTISSA_BITS as i32 - 2, 45 | ieee_mantissa, 46 | ) 47 | } else { 48 | ( 49 | ieee_exponent as i32 - FLOAT_BIAS - FLOAT_MANTISSA_BITS as i32 - 2, 50 | (1u32 << FLOAT_MANTISSA_BITS) | ieee_mantissa, 51 | ) 52 | }; 53 | let even = (m2 & 1) == 0; 54 | let accept_bounds = even; 55 | 56 | // Step 2: Determine the interval of valid decimal representations. 57 | let mv = 4 * m2; 58 | let mp = 4 * m2 + 2; 59 | // Implicit bool -> int conversion. True is 1, false is 0. 60 | let mm_shift = (ieee_mantissa != 0 || ieee_exponent <= 1) as u32; 61 | let mm = 4 * m2 - 1 - mm_shift; 62 | 63 | // Step 3: Convert to a decimal power base using 64-bit arithmetic. 64 | let mut vr: u32; 65 | let mut vp: u32; 66 | let mut vm: u32; 67 | let e10: i32; 68 | let mut vm_is_trailing_zeros = false; 69 | let mut vr_is_trailing_zeros = false; 70 | let mut last_removed_digit = 0u8; 71 | if e2 >= 0 { 72 | let q = log10_pow2(e2); 73 | e10 = q as i32; 74 | let k = FLOAT_POW5_INV_BITCOUNT + pow5bits(q as i32) - 1; 75 | let i = -e2 + q as i32 + k; 76 | vr = mul_pow5_inv_div_pow2(mv, q, i); 77 | vp = mul_pow5_inv_div_pow2(mp, q, i); 78 | vm = mul_pow5_inv_div_pow2(mm, q, i); 79 | if q != 0 && (vp - 1) / 10 <= vm / 10 { 80 | // We need to know one removed digit even if we are not going to loop below. We could use 81 | // q = X - 1 above, except that would require 33 bits for the result, and we've found that 82 | // 32-bit arithmetic is faster even on 64-bit machines. 83 | let l = FLOAT_POW5_INV_BITCOUNT + pow5bits(q as i32 - 1) - 1; 84 | last_removed_digit = 85 | (mul_pow5_inv_div_pow2(mv, q - 1, -e2 + q as i32 - 1 + l) % 10) as u8; 86 | } 87 | if q <= 9 { 88 | // The largest power of 5 that fits in 24 bits is 5^10, but q <= 9 seems to be safe as well. 89 | // Only one of mp, mv, and mm can be a multiple of 5, if any. 90 | if mv % 5 == 0 { 91 | vr_is_trailing_zeros = multiple_of_power_of_5_32(mv, q); 92 | } else if accept_bounds { 93 | vm_is_trailing_zeros = multiple_of_power_of_5_32(mm, q); 94 | } else { 95 | vp -= multiple_of_power_of_5_32(mp, q) as u32; 96 | } 97 | } 98 | } else { 99 | let q = log10_pow5(-e2); 100 | e10 = q as i32 + e2; 101 | let i = -e2 - q as i32; 102 | let k = pow5bits(i) - FLOAT_POW5_BITCOUNT; 103 | let mut j = q as i32 - k; 104 | vr = mul_pow5_div_pow2(mv, i as u32, j); 105 | vp = mul_pow5_div_pow2(mp, i as u32, j); 106 | vm = mul_pow5_div_pow2(mm, i as u32, j); 107 | if q != 0 && (vp - 1) / 10 <= vm / 10 { 108 | j = q as i32 - 1 - (pow5bits(i + 1) - FLOAT_POW5_BITCOUNT); 109 | last_removed_digit = (mul_pow5_div_pow2(mv, (i + 1) as u32, j) % 10) as u8; 110 | } 111 | if q <= 1 { 112 | // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. 113 | // mv = 4 * m2, so it always has at least two trailing 0 bits. 114 | vr_is_trailing_zeros = true; 115 | if accept_bounds { 116 | // mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit iff mm_shift == 1. 117 | vm_is_trailing_zeros = mm_shift == 1; 118 | } else { 119 | // mp = mv + 2, so it always has at least one trailing 0 bit. 120 | vp -= 1; 121 | } 122 | } else if q < 31 { 123 | // TODO(ulfjack): Use a tighter bound here. 124 | vr_is_trailing_zeros = multiple_of_power_of_2_32(mv, q - 1); 125 | } 126 | } 127 | 128 | // Step 4: Find the shortest decimal representation in the interval of valid representations. 129 | let mut removed = 0i32; 130 | let output = if vm_is_trailing_zeros || vr_is_trailing_zeros { 131 | // General case, which happens rarely (~4.0%). 132 | while vp / 10 > vm / 10 { 133 | vm_is_trailing_zeros &= vm - (vm / 10) * 10 == 0; 134 | vr_is_trailing_zeros &= last_removed_digit == 0; 135 | last_removed_digit = (vr % 10) as u8; 136 | vr /= 10; 137 | vp /= 10; 138 | vm /= 10; 139 | removed += 1; 140 | } 141 | if vm_is_trailing_zeros { 142 | while vm % 10 == 0 { 143 | vr_is_trailing_zeros &= last_removed_digit == 0; 144 | last_removed_digit = (vr % 10) as u8; 145 | vr /= 10; 146 | vp /= 10; 147 | vm /= 10; 148 | removed += 1; 149 | } 150 | } 151 | if vr_is_trailing_zeros && last_removed_digit == 5 && vr % 2 == 0 { 152 | // Round even if the exact number is .....50..0. 153 | last_removed_digit = 4; 154 | } 155 | // We need to take vr + 1 if vr is outside bounds or we need to round up. 156 | vr + ((vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5) 157 | as u32 158 | } else { 159 | // Specialized for the common case (~96.0%). Percentages below are relative to this. 160 | // Loop iterations below (approximately): 161 | // 0: 13.6%, 1: 70.7%, 2: 14.1%, 3: 1.39%, 4: 0.14%, 5+: 0.01% 162 | while vp / 10 > vm / 10 { 163 | last_removed_digit = (vr % 10) as u8; 164 | vr /= 10; 165 | vp /= 10; 166 | vm /= 10; 167 | removed += 1; 168 | } 169 | // We need to take vr + 1 if vr is outside bounds or we need to round up. 170 | vr + (vr == vm || last_removed_digit >= 5) as u32 171 | }; 172 | let exp = e10 + removed; 173 | 174 | FloatingDecimal32 { 175 | exponent: exp, 176 | mantissa: output, 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/f2s_intrinsics.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | use crate::d2s; 22 | 23 | pub const FLOAT_POW5_INV_BITCOUNT: i32 = d2s::DOUBLE_POW5_INV_BITCOUNT - 64; 24 | pub const FLOAT_POW5_BITCOUNT: i32 = d2s::DOUBLE_POW5_BITCOUNT - 64; 25 | 26 | #[cfg_attr(feature = "no-panic", inline)] 27 | fn pow5factor_32(mut value: u32) -> u32 { 28 | let mut count = 0u32; 29 | loop { 30 | debug_assert!(value != 0); 31 | let q = value / 5; 32 | let r = value % 5; 33 | if r != 0 { 34 | break; 35 | } 36 | value = q; 37 | count += 1; 38 | } 39 | count 40 | } 41 | 42 | // Returns true if value is divisible by 5^p. 43 | #[cfg_attr(feature = "no-panic", inline)] 44 | pub fn multiple_of_power_of_5_32(value: u32, p: u32) -> bool { 45 | pow5factor_32(value) >= p 46 | } 47 | 48 | // Returns true if value is divisible by 2^p. 49 | #[cfg_attr(feature = "no-panic", inline)] 50 | pub fn multiple_of_power_of_2_32(value: u32, p: u32) -> bool { 51 | // __builtin_ctz doesn't appear to be faster here. 52 | (value & ((1u32 << p) - 1)) == 0 53 | } 54 | 55 | // It seems to be slightly faster to avoid uint128_t here, although the 56 | // generated code for uint128_t looks slightly nicer. 57 | #[cfg_attr(feature = "no-panic", inline)] 58 | fn mul_shift_32(m: u32, factor: u64, shift: i32) -> u32 { 59 | debug_assert!(shift > 32); 60 | 61 | // The casts here help MSVC to avoid calls to the __allmul library 62 | // function. 63 | let factor_lo = factor as u32; 64 | let factor_hi = (factor >> 32) as u32; 65 | let bits0 = m as u64 * factor_lo as u64; 66 | let bits1 = m as u64 * factor_hi as u64; 67 | 68 | let sum = (bits0 >> 32) + bits1; 69 | let shifted_sum = sum >> (shift - 32); 70 | debug_assert!(shifted_sum <= u32::max_value() as u64); 71 | shifted_sum as u32 72 | } 73 | 74 | #[cfg_attr(feature = "no-panic", inline)] 75 | pub fn mul_pow5_inv_div_pow2(m: u32, q: u32, j: i32) -> u32 { 76 | #[cfg(feature = "small")] 77 | { 78 | // The inverse multipliers are defined as [2^x / 5^y] + 1; the upper 64 79 | // bits from the double lookup table are the correct bits for [2^x / 80 | // 5^y], so we have to add 1 here. Note that we rely on the fact that 81 | // the added 1 that's already stored in the table never overflows into 82 | // the upper 64 bits. 83 | let pow5 = unsafe { d2s::compute_inv_pow5(q) }; 84 | mul_shift_32(m, pow5.1 + 1, j) 85 | } 86 | 87 | #[cfg(not(feature = "small"))] 88 | { 89 | debug_assert!(q < d2s::DOUBLE_POW5_INV_SPLIT.len() as u32); 90 | unsafe { 91 | mul_shift_32( 92 | m, 93 | d2s::DOUBLE_POW5_INV_SPLIT.get_unchecked(q as usize).1 + 1, 94 | j, 95 | ) 96 | } 97 | } 98 | } 99 | 100 | #[cfg_attr(feature = "no-panic", inline)] 101 | pub fn mul_pow5_div_pow2(m: u32, i: u32, j: i32) -> u32 { 102 | #[cfg(feature = "small")] 103 | { 104 | let pow5 = unsafe { d2s::compute_pow5(i) }; 105 | mul_shift_32(m, pow5.1, j) 106 | } 107 | 108 | #[cfg(not(feature = "small"))] 109 | { 110 | debug_assert!(i < d2s::DOUBLE_POW5_SPLIT.len() as u32); 111 | unsafe { mul_shift_32(m, d2s::DOUBLE_POW5_SPLIT.get_unchecked(i as usize).1, j) } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [![github]](https://github.com/dtolnay/ryu) [![crates-io]](https://crates.io/crates/ryu) [![docs-rs]](https://docs.rs/ryu) 2 | //! 3 | //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github 4 | //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust 5 | //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs 6 | //! 7 | //!
8 | //! 9 | //! Pure Rust implementation of Ryū, an algorithm to quickly convert floating 10 | //! point numbers to decimal strings. 11 | //! 12 | //! The PLDI'18 paper [*Ryū: fast float-to-string conversion*][paper] by Ulf 13 | //! Adams includes a complete correctness proof of the algorithm. The paper is 14 | //! available under the creative commons CC-BY-SA license. 15 | //! 16 | //! This Rust implementation is a line-by-line port of Ulf Adams' implementation 17 | //! in C, [https://github.com/ulfjack/ryu][upstream]. 18 | //! 19 | //! [paper]: https://dl.acm.org/citation.cfm?id=3192369 20 | //! [upstream]: https://github.com/ulfjack/ryu 21 | //! 22 | //! # Example 23 | //! 24 | //! ``` 25 | //! fn main() { 26 | //! let mut buffer = ryu::Buffer::new(); 27 | //! let printed = buffer.format(1.234); 28 | //! assert_eq!(printed, "1.234"); 29 | //! } 30 | //! ``` 31 | //! 32 | //! ## Performance (lower is better) 33 | //! 34 | //! ![performance](https://raw.githubusercontent.com/dtolnay/ryu/master/performance.png) 35 | //! 36 | //! You can run upstream's benchmarks with: 37 | //! 38 | //! ```console 39 | //! $ git clone https://github.com/ulfjack/ryu c-ryu 40 | //! $ cd c-ryu 41 | //! $ bazel run -c opt //ryu/benchmark 42 | //! ``` 43 | //! 44 | //! And the same benchmark against our implementation with: 45 | //! 46 | //! ```console 47 | //! $ git clone https://github.com/dtolnay/ryu rust-ryu 48 | //! $ cd rust-ryu 49 | //! $ cargo run --example upstream_benchmark --release 50 | //! ``` 51 | //! 52 | //! These benchmarks measure the average time to print a 32-bit float and average 53 | //! time to print a 64-bit float, where the inputs are distributed as uniform random 54 | //! bit patterns 32 and 64 bits wide. 55 | //! 56 | //! The upstream C code, the unsafe direct Rust port, and the safe pretty Rust API 57 | //! all perform the same, taking around 21 nanoseconds to format a 32-bit float and 58 | //! 31 nanoseconds to format a 64-bit float. 59 | //! 60 | //! There is also a Rust-specific benchmark comparing this implementation to the 61 | //! standard library which you can run with: 62 | //! 63 | //! ```console 64 | //! $ cargo bench 65 | //! ``` 66 | //! 67 | //! The benchmark shows Ryū approximately 2-5x faster than the standard library 68 | //! across a range of f32 and f64 inputs. Measurements are in nanoseconds per 69 | //! iteration; smaller is better. 70 | //! 71 | //! ## Formatting 72 | //! 73 | //! This library tends to produce more human-readable output than the standard 74 | //! library's to\_string, which never uses scientific notation. Here are two 75 | //! examples: 76 | //! 77 | //! - *ryu:* 1.23e40, *std:* 12300000000000000000000000000000000000000 78 | //! - *ryu:* 1.23e-40, *std:* 0.000000000000000000000000000000000000000123 79 | //! 80 | //! Both libraries print short decimals such as 0.0000123 without scientific 81 | //! notation. 82 | 83 | #![no_std] 84 | #![doc(html_root_url = "https://docs.rs/ryu/1.0.20")] 85 | #![allow( 86 | clippy::cast_lossless, 87 | clippy::cast_possible_truncation, 88 | clippy::cast_possible_wrap, 89 | clippy::cast_sign_loss, 90 | clippy::checked_conversions, 91 | clippy::doc_markdown, 92 | clippy::expl_impl_clone_on_copy, 93 | clippy::if_not_else, 94 | clippy::many_single_char_names, 95 | clippy::missing_panics_doc, 96 | clippy::module_name_repetitions, 97 | clippy::must_use_candidate, 98 | clippy::needless_doctest_main, 99 | clippy::similar_names, 100 | clippy::too_many_lines, 101 | clippy::unreadable_literal, 102 | clippy::unseparated_literal_suffix, 103 | clippy::wildcard_imports 104 | )] 105 | 106 | mod buffer; 107 | mod common; 108 | mod d2s; 109 | #[cfg(not(feature = "small"))] 110 | mod d2s_full_table; 111 | mod d2s_intrinsics; 112 | #[cfg(feature = "small")] 113 | mod d2s_small_table; 114 | mod digit_table; 115 | mod f2s; 116 | mod f2s_intrinsics; 117 | mod pretty; 118 | 119 | pub use crate::buffer::{Buffer, Float}; 120 | 121 | /// Unsafe functions that mirror the API of the C implementation of Ryū. 122 | pub mod raw { 123 | pub use crate::pretty::{format32, format64}; 124 | } 125 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{self, Display}; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub enum Error { 5 | InputTooShort, 6 | InputTooLong, 7 | MalformedInput, 8 | } 9 | 10 | impl Display for Error { 11 | fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 12 | let msg = match self { 13 | Error::InputTooShort => "input too short", 14 | Error::InputTooLong => "input too long", 15 | Error::MalformedInput => "malformed input", 16 | }; 17 | formatter.write_str(msg) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/pretty/exponent.rs: -------------------------------------------------------------------------------- 1 | use crate::digit_table::DIGIT_TABLE; 2 | use core::ptr; 3 | 4 | #[cfg_attr(feature = "no-panic", inline)] 5 | pub unsafe fn write_exponent3(mut k: isize, mut result: *mut u8) -> usize { 6 | let sign = k < 0; 7 | if sign { 8 | *result = b'-'; 9 | result = result.offset(1); 10 | k = -k; 11 | } 12 | 13 | debug_assert!(k < 1000); 14 | if k >= 100 { 15 | *result = b'0' + (k / 100) as u8; 16 | k %= 100; 17 | let d = DIGIT_TABLE.as_ptr().offset(k * 2); 18 | ptr::copy_nonoverlapping(d, result.offset(1), 2); 19 | sign as usize + 3 20 | } else if k >= 10 { 21 | let d = DIGIT_TABLE.as_ptr().offset(k * 2); 22 | ptr::copy_nonoverlapping(d, result, 2); 23 | sign as usize + 2 24 | } else { 25 | *result = b'0' + k as u8; 26 | sign as usize + 1 27 | } 28 | } 29 | 30 | #[cfg_attr(feature = "no-panic", inline)] 31 | pub unsafe fn write_exponent2(mut k: isize, mut result: *mut u8) -> usize { 32 | let sign = k < 0; 33 | if sign { 34 | *result = b'-'; 35 | result = result.offset(1); 36 | k = -k; 37 | } 38 | 39 | debug_assert!(k < 100); 40 | if k >= 10 { 41 | let d = DIGIT_TABLE.as_ptr().offset(k * 2); 42 | ptr::copy_nonoverlapping(d, result, 2); 43 | sign as usize + 2 44 | } else { 45 | *result = b'0' + k as u8; 46 | sign as usize + 1 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/pretty/mantissa.rs: -------------------------------------------------------------------------------- 1 | use crate::digit_table::DIGIT_TABLE; 2 | use core::ptr; 3 | 4 | #[cfg_attr(feature = "no-panic", inline)] 5 | pub unsafe fn write_mantissa_long(mut output: u64, mut result: *mut u8) { 6 | if (output >> 32) != 0 { 7 | // One expensive 64-bit division. 8 | let mut output2 = (output - 100_000_000 * (output / 100_000_000)) as u32; 9 | output /= 100_000_000; 10 | 11 | let c = output2 % 10_000; 12 | output2 /= 10_000; 13 | let d = output2 % 10_000; 14 | let c0 = (c % 100) << 1; 15 | let c1 = (c / 100) << 1; 16 | let d0 = (d % 100) << 1; 17 | let d1 = (d / 100) << 1; 18 | ptr::copy_nonoverlapping( 19 | DIGIT_TABLE.as_ptr().offset(c0 as isize), 20 | result.offset(-2), 21 | 2, 22 | ); 23 | ptr::copy_nonoverlapping( 24 | DIGIT_TABLE.as_ptr().offset(c1 as isize), 25 | result.offset(-4), 26 | 2, 27 | ); 28 | ptr::copy_nonoverlapping( 29 | DIGIT_TABLE.as_ptr().offset(d0 as isize), 30 | result.offset(-6), 31 | 2, 32 | ); 33 | ptr::copy_nonoverlapping( 34 | DIGIT_TABLE.as_ptr().offset(d1 as isize), 35 | result.offset(-8), 36 | 2, 37 | ); 38 | result = result.offset(-8); 39 | } 40 | write_mantissa(output as u32, result); 41 | } 42 | 43 | #[cfg_attr(feature = "no-panic", inline)] 44 | pub unsafe fn write_mantissa(mut output: u32, mut result: *mut u8) { 45 | while output >= 10_000 { 46 | let c = output - 10_000 * (output / 10_000); 47 | output /= 10_000; 48 | let c0 = (c % 100) << 1; 49 | let c1 = (c / 100) << 1; 50 | ptr::copy_nonoverlapping( 51 | DIGIT_TABLE.as_ptr().offset(c0 as isize), 52 | result.offset(-2), 53 | 2, 54 | ); 55 | ptr::copy_nonoverlapping( 56 | DIGIT_TABLE.as_ptr().offset(c1 as isize), 57 | result.offset(-4), 58 | 2, 59 | ); 60 | result = result.offset(-4); 61 | } 62 | if output >= 100 { 63 | let c = (output % 100) << 1; 64 | output /= 100; 65 | ptr::copy_nonoverlapping( 66 | DIGIT_TABLE.as_ptr().offset(c as isize), 67 | result.offset(-2), 68 | 2, 69 | ); 70 | result = result.offset(-2); 71 | } 72 | if output >= 10 { 73 | let c = output << 1; 74 | ptr::copy_nonoverlapping( 75 | DIGIT_TABLE.as_ptr().offset(c as isize), 76 | result.offset(-2), 77 | 2, 78 | ); 79 | } else { 80 | *result.offset(-1) = b'0' + output as u8; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/pretty/mod.rs: -------------------------------------------------------------------------------- 1 | mod exponent; 2 | mod mantissa; 3 | 4 | use self::exponent::{write_exponent2, write_exponent3}; 5 | use self::mantissa::{write_mantissa, write_mantissa_long}; 6 | use crate::common; 7 | use crate::d2s::{self, d2d, DOUBLE_EXPONENT_BITS, DOUBLE_MANTISSA_BITS}; 8 | use crate::f2s::{f2d, FLOAT_EXPONENT_BITS, FLOAT_MANTISSA_BITS}; 9 | use core::ptr; 10 | #[cfg(feature = "no-panic")] 11 | use no_panic::no_panic; 12 | 13 | /// Print f64 to the given buffer and return number of bytes written. 14 | /// 15 | /// At most 24 bytes will be written. 16 | /// 17 | /// ## Special cases 18 | /// 19 | /// This function **does not** check for NaN or infinity. If the input 20 | /// number is not a finite float, the printed representation will be some 21 | /// correctly formatted but unspecified numerical value. 22 | /// 23 | /// Please check [`is_finite`] yourself before calling this function, or 24 | /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. 25 | /// 26 | /// [`is_finite`]: f64::is_finite 27 | /// [`is_nan`]: f64::is_nan 28 | /// [`is_infinite`]: f64::is_infinite 29 | /// 30 | /// ## Safety 31 | /// 32 | /// The `result` pointer argument must point to sufficiently many writable bytes 33 | /// to hold Ryū's representation of `f`. 34 | /// 35 | /// ## Example 36 | /// 37 | /// ``` 38 | /// use std::{mem::MaybeUninit, slice, str}; 39 | /// 40 | /// let f = 1.234f64; 41 | /// 42 | /// unsafe { 43 | /// let mut buffer = [MaybeUninit::::uninit(); 24]; 44 | /// let len = ryu::raw::format64(f, buffer.as_mut_ptr() as *mut u8); 45 | /// let slice = slice::from_raw_parts(buffer.as_ptr() as *const u8, len); 46 | /// let print = str::from_utf8_unchecked(slice); 47 | /// assert_eq!(print, "1.234"); 48 | /// } 49 | /// ``` 50 | #[must_use] 51 | #[cfg_attr(feature = "no-panic", no_panic)] 52 | pub unsafe fn format64(f: f64, result: *mut u8) -> usize { 53 | let bits = f.to_bits(); 54 | let sign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; 55 | let ieee_mantissa = bits & ((1u64 << DOUBLE_MANTISSA_BITS) - 1); 56 | let ieee_exponent = 57 | (bits >> DOUBLE_MANTISSA_BITS) as u32 & ((1u32 << DOUBLE_EXPONENT_BITS) - 1); 58 | 59 | let mut index = 0isize; 60 | if sign { 61 | *result = b'-'; 62 | index += 1; 63 | } 64 | 65 | if ieee_exponent == 0 && ieee_mantissa == 0 { 66 | ptr::copy_nonoverlapping(b"0.0".as_ptr(), result.offset(index), 3); 67 | return sign as usize + 3; 68 | } 69 | 70 | let v = d2d(ieee_mantissa, ieee_exponent); 71 | 72 | let length = d2s::decimal_length17(v.mantissa) as isize; 73 | let k = v.exponent as isize; 74 | let kk = length + k; // 10^(kk-1) <= v < 10^kk 75 | debug_assert!(k >= -324); 76 | 77 | if 0 <= k && kk <= 16 { 78 | // 1234e7 -> 12340000000.0 79 | write_mantissa_long(v.mantissa, result.offset(index + length)); 80 | for i in length..kk { 81 | *result.offset(index + i) = b'0'; 82 | } 83 | *result.offset(index + kk) = b'.'; 84 | *result.offset(index + kk + 1) = b'0'; 85 | index as usize + kk as usize + 2 86 | } else if 0 < kk && kk <= 16 { 87 | // 1234e-2 -> 12.34 88 | write_mantissa_long(v.mantissa, result.offset(index + length + 1)); 89 | ptr::copy(result.offset(index + 1), result.offset(index), kk as usize); 90 | *result.offset(index + kk) = b'.'; 91 | index as usize + length as usize + 1 92 | } else if -5 < kk && kk <= 0 { 93 | // 1234e-6 -> 0.001234 94 | *result.offset(index) = b'0'; 95 | *result.offset(index + 1) = b'.'; 96 | let offset = 2 - kk; 97 | for i in 2..offset { 98 | *result.offset(index + i) = b'0'; 99 | } 100 | write_mantissa_long(v.mantissa, result.offset(index + length + offset)); 101 | index as usize + length as usize + offset as usize 102 | } else if length == 1 { 103 | // 1e30 104 | *result.offset(index) = b'0' + v.mantissa as u8; 105 | *result.offset(index + 1) = b'e'; 106 | index as usize + 2 + write_exponent3(kk - 1, result.offset(index + 2)) 107 | } else { 108 | // 1234e30 -> 1.234e33 109 | write_mantissa_long(v.mantissa, result.offset(index + length + 1)); 110 | *result.offset(index) = *result.offset(index + 1); 111 | *result.offset(index + 1) = b'.'; 112 | *result.offset(index + length + 1) = b'e'; 113 | index as usize 114 | + length as usize 115 | + 2 116 | + write_exponent3(kk - 1, result.offset(index + length + 2)) 117 | } 118 | } 119 | 120 | /// Print f32 to the given buffer and return number of bytes written. 121 | /// 122 | /// At most 16 bytes will be written. 123 | /// 124 | /// ## Special cases 125 | /// 126 | /// This function **does not** check for NaN or infinity. If the input 127 | /// number is not a finite float, the printed representation will be some 128 | /// correctly formatted but unspecified numerical value. 129 | /// 130 | /// Please check [`is_finite`] yourself before calling this function, or 131 | /// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. 132 | /// 133 | /// [`is_finite`]: f32::is_finite 134 | /// [`is_nan`]: f32::is_nan 135 | /// [`is_infinite`]: f32::is_infinite 136 | /// 137 | /// ## Safety 138 | /// 139 | /// The `result` pointer argument must point to sufficiently many writable bytes 140 | /// to hold Ryū's representation of `f`. 141 | /// 142 | /// ## Example 143 | /// 144 | /// ``` 145 | /// use std::{mem::MaybeUninit, slice, str}; 146 | /// 147 | /// let f = 1.234f32; 148 | /// 149 | /// unsafe { 150 | /// let mut buffer = [MaybeUninit::::uninit(); 16]; 151 | /// let len = ryu::raw::format32(f, buffer.as_mut_ptr() as *mut u8); 152 | /// let slice = slice::from_raw_parts(buffer.as_ptr() as *const u8, len); 153 | /// let print = str::from_utf8_unchecked(slice); 154 | /// assert_eq!(print, "1.234"); 155 | /// } 156 | /// ``` 157 | #[must_use] 158 | #[cfg_attr(feature = "no-panic", no_panic)] 159 | pub unsafe fn format32(f: f32, result: *mut u8) -> usize { 160 | let bits = f.to_bits(); 161 | let sign = ((bits >> (FLOAT_MANTISSA_BITS + FLOAT_EXPONENT_BITS)) & 1) != 0; 162 | let ieee_mantissa = bits & ((1u32 << FLOAT_MANTISSA_BITS) - 1); 163 | let ieee_exponent = (bits >> FLOAT_MANTISSA_BITS) & ((1u32 << FLOAT_EXPONENT_BITS) - 1); 164 | 165 | let mut index = 0isize; 166 | if sign { 167 | *result = b'-'; 168 | index += 1; 169 | } 170 | 171 | if ieee_exponent == 0 && ieee_mantissa == 0 { 172 | ptr::copy_nonoverlapping(b"0.0".as_ptr(), result.offset(index), 3); 173 | return sign as usize + 3; 174 | } 175 | 176 | let v = f2d(ieee_mantissa, ieee_exponent); 177 | 178 | let length = common::decimal_length9(v.mantissa) as isize; 179 | let k = v.exponent as isize; 180 | let kk = length + k; // 10^(kk-1) <= v < 10^kk 181 | debug_assert!(k >= -45); 182 | 183 | if 0 <= k && kk <= 13 { 184 | // 1234e7 -> 12340000000.0 185 | write_mantissa(v.mantissa, result.offset(index + length)); 186 | for i in length..kk { 187 | *result.offset(index + i) = b'0'; 188 | } 189 | *result.offset(index + kk) = b'.'; 190 | *result.offset(index + kk + 1) = b'0'; 191 | index as usize + kk as usize + 2 192 | } else if 0 < kk && kk <= 13 { 193 | // 1234e-2 -> 12.34 194 | write_mantissa(v.mantissa, result.offset(index + length + 1)); 195 | ptr::copy(result.offset(index + 1), result.offset(index), kk as usize); 196 | *result.offset(index + kk) = b'.'; 197 | index as usize + length as usize + 1 198 | } else if -6 < kk && kk <= 0 { 199 | // 1234e-6 -> 0.001234 200 | *result.offset(index) = b'0'; 201 | *result.offset(index + 1) = b'.'; 202 | let offset = 2 - kk; 203 | for i in 2..offset { 204 | *result.offset(index + i) = b'0'; 205 | } 206 | write_mantissa(v.mantissa, result.offset(index + length + offset)); 207 | index as usize + length as usize + offset as usize 208 | } else if length == 1 { 209 | // 1e30 210 | *result.offset(index) = b'0' + v.mantissa as u8; 211 | *result.offset(index + 1) = b'e'; 212 | index as usize + 2 + write_exponent2(kk - 1, result.offset(index + 2)) 213 | } else { 214 | // 1234e30 -> 1.234e33 215 | write_mantissa(v.mantissa, result.offset(index + length + 1)); 216 | *result.offset(index) = *result.offset(index + 1); 217 | *result.offset(index + 1) = b'.'; 218 | *result.offset(index + length + 1) = b'e'; 219 | index as usize 220 | + length as usize 221 | + 2 222 | + write_exponent2(kk - 1, result.offset(index + length + 2)) 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/s2d.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ceil_log2_pow5, log2_pow5}; 2 | use crate::d2s; 3 | use crate::d2s_intrinsics::{mul_shift_64, multiple_of_power_of_2, multiple_of_power_of_5}; 4 | use crate::parse::Error; 5 | #[cfg(feature = "no-panic")] 6 | use no_panic::no_panic; 7 | 8 | const DOUBLE_EXPONENT_BIAS: usize = 1023; 9 | 10 | fn floor_log2(value: u64) -> u32 { 11 | 63_u32.wrapping_sub(value.leading_zeros()) 12 | } 13 | 14 | #[cfg_attr(feature = "no-panic", no_panic)] 15 | pub fn s2d(buffer: &[u8]) -> Result { 16 | let len = buffer.len(); 17 | if len == 0 { 18 | return Err(Error::InputTooShort); 19 | } 20 | 21 | let mut m10digits = 0; 22 | let mut e10digits = 0; 23 | let mut dot_index = len; 24 | let mut e_index = len; 25 | let mut m10 = 0u64; 26 | let mut e10 = 0i32; 27 | let mut signed_m = false; 28 | let mut signed_e = false; 29 | 30 | let mut i = 0; 31 | if unsafe { *buffer.get_unchecked(0) } == b'-' { 32 | signed_m = true; 33 | i += 1; 34 | } 35 | 36 | while let Some(c) = buffer.get(i).copied() { 37 | if c == b'.' { 38 | if dot_index != len { 39 | return Err(Error::MalformedInput); 40 | } 41 | dot_index = i; 42 | i += 1; 43 | continue; 44 | } 45 | if c < b'0' || c > b'9' { 46 | break; 47 | } 48 | if m10digits >= 17 { 49 | return Err(Error::InputTooLong); 50 | } 51 | m10 = 10 * m10 + (c - b'0') as u64; 52 | if m10 != 0 { 53 | m10digits += 1; 54 | } 55 | i += 1; 56 | } 57 | 58 | if let Some(b'e') | Some(b'E') = buffer.get(i) { 59 | e_index = i; 60 | i += 1; 61 | match buffer.get(i) { 62 | Some(b'-') => { 63 | signed_e = true; 64 | i += 1; 65 | } 66 | Some(b'+') => i += 1, 67 | _ => {} 68 | } 69 | while let Some(c) = buffer.get(i).copied() { 70 | if c < b'0' || c > b'9' { 71 | return Err(Error::MalformedInput); 72 | } 73 | if e10digits > 3 { 74 | // TODO: Be more lenient. Return +/-Infinity or +/-0 instead. 75 | return Err(Error::InputTooLong); 76 | } 77 | e10 = 10 * e10 + (c - b'0') as i32; 78 | if e10 != 0 { 79 | e10digits += 1; 80 | } 81 | i += 1; 82 | } 83 | } 84 | 85 | if i < len { 86 | return Err(Error::MalformedInput); 87 | } 88 | if signed_e { 89 | e10 = -e10; 90 | } 91 | e10 -= if dot_index < e_index { 92 | (e_index - dot_index - 1) as i32 93 | } else { 94 | 0 95 | }; 96 | if m10 == 0 { 97 | return Ok(if signed_m { -0.0 } else { 0.0 }); 98 | } 99 | 100 | if m10digits + e10 <= -324 || m10 == 0 { 101 | // Number is less than 1e-324, which should be rounded down to 0; return 102 | // +/-0.0. 103 | let ieee = (signed_m as u64) << (d2s::DOUBLE_EXPONENT_BITS + d2s::DOUBLE_MANTISSA_BITS); 104 | return Ok(f64::from_bits(ieee)); 105 | } 106 | if m10digits + e10 >= 310 { 107 | // Number is larger than 1e+309, which should be rounded to +/-Infinity. 108 | let ieee = ((signed_m as u64) << (d2s::DOUBLE_EXPONENT_BITS + d2s::DOUBLE_MANTISSA_BITS)) 109 | | (0x7ff_u64 << d2s::DOUBLE_MANTISSA_BITS); 110 | return Ok(f64::from_bits(ieee)); 111 | } 112 | 113 | // Convert to binary float m2 * 2^e2, while retaining information about 114 | // whether the conversion was exact (trailing_zeros). 115 | let e2: i32; 116 | let m2: u64; 117 | let mut trailing_zeros: bool; 118 | if e10 >= 0 { 119 | // The length of m * 10^e in bits is: 120 | // log2(m10 * 10^e10) = log2(m10) + e10 log2(10) = log2(m10) + e10 + e10 * log2(5) 121 | // 122 | // We want to compute the DOUBLE_MANTISSA_BITS + 1 top-most bits (+1 for 123 | // the implicit leading one in IEEE format). We therefore choose a 124 | // binary output exponent of 125 | // log2(m10 * 10^e10) - (DOUBLE_MANTISSA_BITS + 1). 126 | // 127 | // We use floor(log2(5^e10)) so that we get at least this many bits; 128 | // better to have an additional bit than to not have enough bits. 129 | e2 = floor_log2(m10) 130 | .wrapping_add(e10 as u32) 131 | .wrapping_add(log2_pow5(e10) as u32) 132 | .wrapping_sub(d2s::DOUBLE_MANTISSA_BITS + 1) as i32; 133 | 134 | // We now compute [m10 * 10^e10 / 2^e2] = [m10 * 5^e10 / 2^(e2-e10)]. 135 | // To that end, we use the DOUBLE_POW5_SPLIT table. 136 | let j = e2 137 | .wrapping_sub(e10) 138 | .wrapping_sub(ceil_log2_pow5(e10)) 139 | .wrapping_add(d2s::DOUBLE_POW5_BITCOUNT); 140 | debug_assert!(j >= 0); 141 | debug_assert!(e10 < d2s::DOUBLE_POW5_SPLIT.len() as i32); 142 | m2 = mul_shift_64( 143 | m10, 144 | unsafe { d2s::DOUBLE_POW5_SPLIT.get_unchecked(e10 as usize) }, 145 | j as u32, 146 | ); 147 | 148 | // We also compute if the result is exact, i.e., 149 | // [m10 * 10^e10 / 2^e2] == m10 * 10^e10 / 2^e2. 150 | // This can only be the case if 2^e2 divides m10 * 10^e10, which in turn 151 | // requires that the largest power of 2 that divides m10 + e10 is 152 | // greater than e2. If e2 is less than e10, then the result must be 153 | // exact. Otherwise we use the existing multiple_of_power_of_2 function. 154 | trailing_zeros = 155 | e2 < e10 || e2 - e10 < 64 && multiple_of_power_of_2(m10, (e2 - e10) as u32); 156 | } else { 157 | e2 = floor_log2(m10) 158 | .wrapping_add(e10 as u32) 159 | .wrapping_sub(ceil_log2_pow5(-e10) as u32) 160 | .wrapping_sub(d2s::DOUBLE_MANTISSA_BITS + 1) as i32; 161 | let j = e2 162 | .wrapping_sub(e10) 163 | .wrapping_add(ceil_log2_pow5(-e10)) 164 | .wrapping_sub(1) 165 | .wrapping_add(d2s::DOUBLE_POW5_INV_BITCOUNT); 166 | debug_assert!(-e10 < d2s::DOUBLE_POW5_INV_SPLIT.len() as i32); 167 | m2 = mul_shift_64( 168 | m10, 169 | unsafe { d2s::DOUBLE_POW5_INV_SPLIT.get_unchecked(-e10 as usize) }, 170 | j as u32, 171 | ); 172 | trailing_zeros = multiple_of_power_of_5(m10, -e10 as u32); 173 | } 174 | 175 | // Compute the final IEEE exponent. 176 | let mut ieee_e2 = i32::max(0, e2 + DOUBLE_EXPONENT_BIAS as i32 + floor_log2(m2) as i32) as u32; 177 | 178 | if ieee_e2 > 0x7fe { 179 | // Final IEEE exponent is larger than the maximum representable; return +/-Infinity. 180 | let ieee = ((signed_m as u64) << (d2s::DOUBLE_EXPONENT_BITS + d2s::DOUBLE_MANTISSA_BITS)) 181 | | (0x7ff_u64 << d2s::DOUBLE_MANTISSA_BITS); 182 | return Ok(f64::from_bits(ieee)); 183 | } 184 | 185 | // We need to figure out how much we need to shift m2. The tricky part is 186 | // that we need to take the final IEEE exponent into account, so we need to 187 | // reverse the bias and also special-case the value 0. 188 | let shift = if ieee_e2 == 0 { 1 } else { ieee_e2 as i32 } 189 | .wrapping_sub(e2) 190 | .wrapping_sub(DOUBLE_EXPONENT_BIAS as i32) 191 | .wrapping_sub(d2s::DOUBLE_MANTISSA_BITS as i32); 192 | debug_assert!(shift >= 0); 193 | 194 | // We need to round up if the exact value is more than 0.5 above the value 195 | // we computed. That's equivalent to checking if the last removed bit was 1 196 | // and either the value was not just trailing zeros or the result would 197 | // otherwise be odd. 198 | // 199 | // We need to update trailing_zeros given that we have the exact output 200 | // exponent ieee_e2 now. 201 | trailing_zeros &= (m2 & ((1_u64 << (shift - 1)) - 1)) == 0; 202 | let last_removed_bit = (m2 >> (shift - 1)) & 1; 203 | let round_up = last_removed_bit != 0 && (!trailing_zeros || ((m2 >> shift) & 1) != 0); 204 | 205 | let mut ieee_m2 = (m2 >> shift).wrapping_add(round_up as u64); 206 | debug_assert!(ieee_m2 <= 1_u64 << (d2s::DOUBLE_MANTISSA_BITS + 1)); 207 | ieee_m2 &= (1_u64 << d2s::DOUBLE_MANTISSA_BITS) - 1; 208 | if ieee_m2 == 0 && round_up { 209 | // Due to how the IEEE represents +/-Infinity, we don't need to check 210 | // for overflow here. 211 | ieee_e2 += 1; 212 | } 213 | let ieee = ((((signed_m as u64) << d2s::DOUBLE_EXPONENT_BITS) | ieee_e2 as u64) 214 | << d2s::DOUBLE_MANTISSA_BITS) 215 | | ieee_m2; 216 | Ok(f64::from_bits(ieee)) 217 | } 218 | -------------------------------------------------------------------------------- /src/s2f.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{ceil_log2_pow5, log2_pow5}; 2 | use crate::f2s; 3 | use crate::f2s_intrinsics::{ 4 | mul_pow5_div_pow2, mul_pow5_inv_div_pow2, multiple_of_power_of_2_32, multiple_of_power_of_5_32, 5 | }; 6 | use crate::parse::Error; 7 | #[cfg(feature = "no-panic")] 8 | use no_panic::no_panic; 9 | 10 | const FLOAT_EXPONENT_BIAS: usize = 127; 11 | 12 | fn floor_log2(value: u32) -> u32 { 13 | 31_u32.wrapping_sub(value.leading_zeros()) 14 | } 15 | 16 | #[cfg_attr(feature = "no-panic", no_panic)] 17 | pub fn s2f(buffer: &[u8]) -> Result { 18 | let len = buffer.len(); 19 | if len == 0 { 20 | return Err(Error::InputTooShort); 21 | } 22 | 23 | let mut m10digits = 0; 24 | let mut e10digits = 0; 25 | let mut dot_index = len; 26 | let mut e_index = len; 27 | let mut m10 = 0u32; 28 | let mut e10 = 0i32; 29 | let mut signed_m = false; 30 | let mut signed_e = false; 31 | 32 | let mut i = 0; 33 | if unsafe { *buffer.get_unchecked(0) } == b'-' { 34 | signed_m = true; 35 | i += 1; 36 | } 37 | 38 | while let Some(c) = buffer.get(i).copied() { 39 | if c == b'.' { 40 | if dot_index != len { 41 | return Err(Error::MalformedInput); 42 | } 43 | dot_index = i; 44 | i += 1; 45 | continue; 46 | } 47 | if c < b'0' || c > b'9' { 48 | break; 49 | } 50 | if m10digits >= 9 { 51 | return Err(Error::InputTooLong); 52 | } 53 | m10 = 10 * m10 + (c - b'0') as u32; 54 | if m10 != 0 { 55 | m10digits += 1; 56 | } 57 | i += 1; 58 | } 59 | 60 | if let Some(b'e') | Some(b'E') = buffer.get(i) { 61 | e_index = i; 62 | i += 1; 63 | match buffer.get(i) { 64 | Some(b'-') => { 65 | signed_e = true; 66 | i += 1; 67 | } 68 | Some(b'+') => i += 1, 69 | _ => {} 70 | } 71 | while let Some(c) = buffer.get(i).copied() { 72 | if c < b'0' || c > b'9' { 73 | return Err(Error::MalformedInput); 74 | } 75 | if e10digits > 3 { 76 | // TODO: Be more lenient. Return +/-Infinity or +/-0 instead. 77 | return Err(Error::InputTooLong); 78 | } 79 | e10 = 10 * e10 + (c - b'0') as i32; 80 | if e10 != 0 { 81 | e10digits += 1; 82 | } 83 | i += 1; 84 | } 85 | } 86 | 87 | if i < len { 88 | return Err(Error::MalformedInput); 89 | } 90 | if signed_e { 91 | e10 = -e10; 92 | } 93 | e10 -= if dot_index < e_index { 94 | (e_index - dot_index - 1) as i32 95 | } else { 96 | 0 97 | }; 98 | if m10 == 0 { 99 | return Ok(if signed_m { -0.0 } else { 0.0 }); 100 | } 101 | 102 | if m10digits + e10 <= -46 || m10 == 0 { 103 | // Number is less than 1e-46, which should be rounded down to 0; return 104 | // +/-0.0. 105 | let ieee = (signed_m as u32) << (f2s::FLOAT_EXPONENT_BITS + f2s::FLOAT_MANTISSA_BITS); 106 | return Ok(f32::from_bits(ieee)); 107 | } 108 | if m10digits + e10 >= 40 { 109 | // Number is larger than 1e+39, which should be rounded to +/-Infinity. 110 | let ieee = ((signed_m as u32) << (f2s::FLOAT_EXPONENT_BITS + f2s::FLOAT_MANTISSA_BITS)) 111 | | (0xff_u32 << f2s::FLOAT_MANTISSA_BITS); 112 | return Ok(f32::from_bits(ieee)); 113 | } 114 | 115 | // Convert to binary float m2 * 2^e2, while retaining information about 116 | // whether the conversion was exact (trailing_zeros). 117 | let e2: i32; 118 | let m2: u32; 119 | let mut trailing_zeros: bool; 120 | if e10 >= 0 { 121 | // The length of m * 10^e in bits is: 122 | // log2(m10 * 10^e10) = log2(m10) + e10 log2(10) = log2(m10) + e10 + e10 * log2(5) 123 | // 124 | // We want to compute the FLOAT_MANTISSA_BITS + 1 top-most bits (+1 for 125 | // the implicit leading one in IEEE format). We therefore choose a 126 | // binary output exponent of 127 | // log2(m10 * 10^e10) - (FLOAT_MANTISSA_BITS + 1). 128 | // 129 | // We use floor(log2(5^e10)) so that we get at least this many bits; better to 130 | // have an additional bit than to not have enough bits. 131 | e2 = floor_log2(m10) 132 | .wrapping_add(e10 as u32) 133 | .wrapping_add(log2_pow5(e10) as u32) 134 | .wrapping_sub(f2s::FLOAT_MANTISSA_BITS + 1) as i32; 135 | 136 | // We now compute [m10 * 10^e10 / 2^e2] = [m10 * 5^e10 / 2^(e2-e10)]. 137 | // To that end, we use the FLOAT_POW5_SPLIT table. 138 | let j = e2 139 | .wrapping_sub(e10) 140 | .wrapping_sub(ceil_log2_pow5(e10)) 141 | .wrapping_add(f2s::FLOAT_POW5_BITCOUNT); 142 | debug_assert!(j >= 0); 143 | m2 = mul_pow5_div_pow2(m10, e10 as u32, j); 144 | 145 | // We also compute if the result is exact, i.e., 146 | // [m10 * 10^e10 / 2^e2] == m10 * 10^e10 / 2^e2. 147 | // This can only be the case if 2^e2 divides m10 * 10^e10, which in turn 148 | // requires that the largest power of 2 that divides m10 + e10 is 149 | // greater than e2. If e2 is less than e10, then the result must be 150 | // exact. Otherwise we use the existing multiple_of_power_of_2 function. 151 | trailing_zeros = 152 | e2 < e10 || e2 - e10 < 32 && multiple_of_power_of_2_32(m10, (e2 - e10) as u32); 153 | } else { 154 | e2 = floor_log2(m10) 155 | .wrapping_add(e10 as u32) 156 | .wrapping_sub(ceil_log2_pow5(-e10) as u32) 157 | .wrapping_sub(f2s::FLOAT_MANTISSA_BITS + 1) as i32; 158 | 159 | // We now compute [m10 * 10^e10 / 2^e2] = [m10 / (5^(-e10) 2^(e2-e10))]. 160 | let j = e2 161 | .wrapping_sub(e10) 162 | .wrapping_add(ceil_log2_pow5(-e10)) 163 | .wrapping_sub(1) 164 | .wrapping_add(f2s::FLOAT_POW5_INV_BITCOUNT); 165 | m2 = mul_pow5_inv_div_pow2(m10, -e10 as u32, j); 166 | 167 | // We also compute if the result is exact, i.e., 168 | // [m10 / (5^(-e10) 2^(e2-e10))] == m10 / (5^(-e10) 2^(e2-e10)) 169 | // 170 | // If e2-e10 >= 0, we need to check whether (5^(-e10) 2^(e2-e10)) 171 | // divides m10, which is the case iff pow5(m10) >= -e10 AND pow2(m10) >= 172 | // e2-e10. 173 | // 174 | // If e2-e10 < 0, we have actually computed [m10 * 2^(e10 e2) / 175 | // 5^(-e10)] above, and we need to check whether 5^(-e10) divides (m10 * 176 | // 2^(e10-e2)), which is the case iff pow5(m10 * 2^(e10-e2)) = pow5(m10) 177 | // >= -e10. 178 | trailing_zeros = (e2 < e10 179 | || (e2 - e10 < 32 && multiple_of_power_of_2_32(m10, (e2 - e10) as u32))) 180 | && multiple_of_power_of_5_32(m10, -e10 as u32); 181 | } 182 | 183 | // Compute the final IEEE exponent. 184 | let mut ieee_e2 = i32::max(0, e2 + FLOAT_EXPONENT_BIAS as i32 + floor_log2(m2) as i32) as u32; 185 | 186 | if ieee_e2 > 0xfe { 187 | // Final IEEE exponent is larger than the maximum representable; return 188 | // +/-Infinity. 189 | let ieee = ((signed_m as u32) << (f2s::FLOAT_EXPONENT_BITS + f2s::FLOAT_MANTISSA_BITS)) 190 | | (0xff_u32 << f2s::FLOAT_MANTISSA_BITS); 191 | return Ok(f32::from_bits(ieee)); 192 | } 193 | 194 | // We need to figure out how much we need to shift m2. The tricky part is 195 | // that we need to take the final IEEE exponent into account, so we need to 196 | // reverse the bias and also special-case the value 0. 197 | let shift = if ieee_e2 == 0 { 1 } else { ieee_e2 as i32 } 198 | .wrapping_sub(e2) 199 | .wrapping_sub(FLOAT_EXPONENT_BIAS as i32) 200 | .wrapping_sub(f2s::FLOAT_MANTISSA_BITS as i32); 201 | debug_assert!(shift >= 0); 202 | 203 | // We need to round up if the exact value is more than 0.5 above the value 204 | // we computed. That's equivalent to checking if the last removed bit was 1 205 | // and either the value was not just trailing zeros or the result would 206 | // otherwise be odd. 207 | // 208 | // We need to update trailing_zeros given that we have the exact output 209 | // exponent ieee_e2 now. 210 | trailing_zeros &= (m2 & ((1_u32 << (shift - 1)) - 1)) == 0; 211 | let last_removed_bit = (m2 >> (shift - 1)) & 1; 212 | let round_up = last_removed_bit != 0 && (!trailing_zeros || ((m2 >> shift) & 1) != 0); 213 | 214 | let mut ieee_m2 = (m2 >> shift).wrapping_add(round_up as u32); 215 | debug_assert!(ieee_m2 <= 1_u32 << (f2s::FLOAT_MANTISSA_BITS + 1)); 216 | ieee_m2 &= (1_u32 << f2s::FLOAT_MANTISSA_BITS) - 1; 217 | if ieee_m2 == 0 && round_up { 218 | // Rounding up may overflow the mantissa. 219 | // In this case we move a trailing zero of the mantissa into the 220 | // exponent. 221 | // Due to how the IEEE represents +/-Infinity, we don't need to check 222 | // for overflow here. 223 | ieee_e2 += 1; 224 | } 225 | let ieee = ((((signed_m as u32) << f2s::FLOAT_EXPONENT_BITS) | ieee_e2) 226 | << f2s::FLOAT_MANTISSA_BITS) 227 | | ieee_m2; 228 | Ok(f32::from_bits(ieee)) 229 | } 230 | -------------------------------------------------------------------------------- /tests/common_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow(dead_code)] 22 | #![allow( 23 | clippy::approx_constant, 24 | clippy::cast_possible_wrap, 25 | clippy::cast_sign_loss, 26 | clippy::excessive_precision, 27 | clippy::unreadable_literal, 28 | clippy::wildcard_imports 29 | )] 30 | 31 | #[path = "../src/common.rs"] 32 | mod common; 33 | 34 | use common::{ceil_log2_pow5, decimal_length9, log10_pow2, log10_pow5}; 35 | 36 | #[test] 37 | fn test_decimal_length9() { 38 | assert_eq!(1, decimal_length9(0)); 39 | assert_eq!(1, decimal_length9(1)); 40 | assert_eq!(1, decimal_length9(9)); 41 | assert_eq!(2, decimal_length9(10)); 42 | assert_eq!(2, decimal_length9(99)); 43 | assert_eq!(3, decimal_length9(100)); 44 | assert_eq!(3, decimal_length9(999)); 45 | assert_eq!(9, decimal_length9(999999999)); 46 | } 47 | 48 | #[test] 49 | fn test_ceil_log2_pow5() { 50 | assert_eq!(1, ceil_log2_pow5(0)); 51 | assert_eq!(3, ceil_log2_pow5(1)); 52 | assert_eq!(5, ceil_log2_pow5(2)); 53 | assert_eq!(7, ceil_log2_pow5(3)); 54 | assert_eq!(10, ceil_log2_pow5(4)); 55 | assert_eq!(8192, ceil_log2_pow5(3528)); 56 | } 57 | 58 | #[test] 59 | fn test_log10_pow2() { 60 | assert_eq!(0, log10_pow2(0)); 61 | assert_eq!(0, log10_pow2(1)); 62 | assert_eq!(0, log10_pow2(2)); 63 | assert_eq!(0, log10_pow2(3)); 64 | assert_eq!(1, log10_pow2(4)); 65 | assert_eq!(496, log10_pow2(1650)); 66 | } 67 | 68 | #[test] 69 | fn test_log10_pow5() { 70 | assert_eq!(0, log10_pow5(0)); 71 | assert_eq!(0, log10_pow5(1)); 72 | assert_eq!(1, log10_pow5(2)); 73 | assert_eq!(2, log10_pow5(3)); 74 | assert_eq!(2, log10_pow5(4)); 75 | assert_eq!(1831, log10_pow5(2620)); 76 | } 77 | 78 | #[test] 79 | fn test_float_to_bits() { 80 | assert_eq!(0, 0.0_f32.to_bits()); 81 | assert_eq!(0x40490fda, 3.1415926_f32.to_bits()); 82 | } 83 | 84 | #[test] 85 | fn test_double_to_bits() { 86 | assert_eq!(0, 0.0_f64.to_bits()); 87 | assert_eq!( 88 | 0x400921FB54442D18, 89 | 3.1415926535897932384626433_f64.to_bits(), 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /tests/d2s_intrinsics_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow(dead_code)] 22 | #![allow( 23 | clippy::cast_lossless, 24 | clippy::cast_possible_truncation, 25 | clippy::unreadable_literal 26 | )] 27 | 28 | #[path = "../src/d2s_intrinsics.rs"] 29 | mod d2s_intrinsics; 30 | 31 | use d2s_intrinsics::pow5_factor; 32 | 33 | #[test] 34 | fn test_pow5_factor() { 35 | assert_eq!(0, pow5_factor(1)); 36 | assert_eq!(0, pow5_factor(2)); 37 | assert_eq!(0, pow5_factor(3)); 38 | assert_eq!(0, pow5_factor(4)); 39 | assert_eq!(1, pow5_factor(5)); 40 | assert_eq!(0, pow5_factor(6)); 41 | assert_eq!(0, pow5_factor(7)); 42 | assert_eq!(0, pow5_factor(8)); 43 | assert_eq!(0, pow5_factor(9)); 44 | assert_eq!(1, pow5_factor(10)); 45 | 46 | assert_eq!(0, pow5_factor(12)); 47 | assert_eq!(0, pow5_factor(14)); 48 | assert_eq!(0, pow5_factor(16)); 49 | assert_eq!(0, pow5_factor(18)); 50 | assert_eq!(1, pow5_factor(20)); 51 | 52 | assert_eq!(2, pow5_factor(5 * 5)); 53 | assert_eq!(3, pow5_factor(5 * 5 * 5)); 54 | assert_eq!(4, pow5_factor(5 * 5 * 5 * 5)); 55 | assert_eq!(5, pow5_factor(5 * 5 * 5 * 5 * 5)); 56 | assert_eq!(6, pow5_factor(5 * 5 * 5 * 5 * 5 * 5)); 57 | assert_eq!(7, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5)); 58 | assert_eq!(8, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); 59 | assert_eq!(9, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); 60 | assert_eq!(10, pow5_factor(5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5)); 61 | 62 | assert_eq!(0, pow5_factor(42)); 63 | assert_eq!(1, pow5_factor(42 * 5)); 64 | assert_eq!(2, pow5_factor(42 * 5 * 5)); 65 | assert_eq!(3, pow5_factor(42 * 5 * 5 * 5)); 66 | assert_eq!(4, pow5_factor(42 * 5 * 5 * 5 * 5)); 67 | assert_eq!(5, pow5_factor(42 * 5 * 5 * 5 * 5 * 5)); 68 | 69 | assert_eq!(27, pow5_factor(7450580596923828125)); // 5^27, largest power of 5 < 2^64. 70 | assert_eq!(1, pow5_factor(18446744073709551615)); // 2^64 - 1, largest multiple of 5 < 2^64. 71 | assert_eq!(0, pow5_factor(18446744073709551614)); // 2^64 - 2, largest non-multiple of 5 < 2^64. 72 | } 73 | -------------------------------------------------------------------------------- /tests/d2s_table_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow(dead_code)] 22 | #![allow( 23 | clippy::cast_lossless, 24 | clippy::cast_possible_truncation, 25 | clippy::cast_possible_wrap, 26 | clippy::cast_sign_loss, 27 | clippy::unreadable_literal, 28 | clippy::unseparated_literal_suffix, 29 | clippy::wildcard_imports 30 | )] 31 | 32 | #[path = "../src/common.rs"] 33 | mod common; 34 | 35 | #[path = "../src/d2s_full_table.rs"] 36 | mod d2s_full_table; 37 | 38 | #[path = "../src/d2s_intrinsics.rs"] 39 | mod d2s_intrinsics; 40 | 41 | #[path = "../src/d2s_small_table.rs"] 42 | mod d2s_small_table; 43 | 44 | use d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT}; 45 | use d2s_small_table::{compute_inv_pow5, compute_pow5}; 46 | 47 | #[test] 48 | fn test_compute_pow5() { 49 | for (i, entry) in DOUBLE_POW5_SPLIT.iter().enumerate() { 50 | assert_eq!(*entry, unsafe { compute_pow5(i as u32) }, "entry {}", i); 51 | } 52 | } 53 | 54 | #[test] 55 | fn test_compute_inv_pow5() { 56 | for (i, entry) in DOUBLE_POW5_INV_SPLIT[..292].iter().enumerate() { 57 | assert_eq!(*entry, unsafe { compute_inv_pow5(i as u32) }, "entry {}", i); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/d2s_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow( 22 | clippy::approx_constant, 23 | clippy::cast_lossless, 24 | clippy::float_cmp, 25 | clippy::int_plus_one, 26 | clippy::non_ascii_literal, 27 | clippy::unreadable_literal, 28 | clippy::unseparated_literal_suffix 29 | )] 30 | 31 | #[macro_use] 32 | mod macros; 33 | 34 | use std::f64; 35 | 36 | fn pretty(f: f64) -> String { 37 | ryu::Buffer::new().format(f).to_owned() 38 | } 39 | 40 | fn ieee_parts_to_double(sign: bool, ieee_exponent: u32, ieee_mantissa: u64) -> f64 { 41 | assert!(ieee_exponent <= 2047); 42 | assert!(ieee_mantissa <= (1u64 << 53) - 1); 43 | f64::from_bits(((sign as u64) << 63) | ((ieee_exponent as u64) << 52) | ieee_mantissa) 44 | } 45 | 46 | #[test] 47 | fn test_ryu() { 48 | check!(0.3); 49 | check!(1234000000000000.0); 50 | check!(1.234e16); 51 | check!(2.71828); 52 | check!(1.1e128); 53 | check!(1.1e-64); 54 | check!(2.718281828459045); 55 | check!(5e-324); 56 | check!(1.7976931348623157e308); 57 | } 58 | 59 | #[test] 60 | fn test_random() { 61 | let n = if cfg!(miri) { 100 } else { 1000000 }; 62 | let mut buffer = ryu::Buffer::new(); 63 | for _ in 0..n { 64 | let f: f64 = rand::random(); 65 | assert_eq!(f, buffer.format_finite(f).parse().unwrap()); 66 | } 67 | } 68 | 69 | #[test] 70 | #[cfg_attr(miri, ignore = "too slow for miri")] 71 | fn test_non_finite() { 72 | for i in 0u64..1 << 23 { 73 | let f = f64::from_bits((((1 << 11) - 1) << 52) + (i << 29)); 74 | assert!(!f.is_finite(), "f={}", f); 75 | ryu::Buffer::new().format_finite(f); 76 | } 77 | } 78 | 79 | #[test] 80 | fn test_basic() { 81 | check!(0.0); 82 | check!(-0.0); 83 | check!(1.0); 84 | check!(-1.0); 85 | assert_eq!(pretty(f64::NAN.copysign(1.0)), "NaN"); 86 | assert_eq!(pretty(f64::NAN.copysign(-1.0)), "NaN"); 87 | assert_eq!(pretty(f64::INFINITY), "inf"); 88 | assert_eq!(pretty(f64::NEG_INFINITY), "-inf"); 89 | } 90 | 91 | #[test] 92 | fn test_switch_to_subnormal() { 93 | check!(2.2250738585072014e-308); 94 | } 95 | 96 | #[test] 97 | fn test_min_and_max() { 98 | assert_eq!(f64::from_bits(0x7fefffffffffffff), 1.7976931348623157e308); 99 | check!(1.7976931348623157e308); 100 | assert_eq!(f64::from_bits(1), 5e-324); 101 | check!(5e-324); 102 | } 103 | 104 | #[test] 105 | fn test_lots_of_trailing_zeros() { 106 | check!(2.9802322387695312e-8); 107 | } 108 | 109 | #[test] 110 | fn test_regression() { 111 | check!(-2.109808898695963e16); 112 | check!(4.940656e-318); 113 | check!(1.18575755e-316); 114 | check!(2.989102097996e-312); 115 | check!(9060801153433600.0); 116 | check!(4.708356024711512e18); 117 | check!(9.409340012568248e18); 118 | check!(1.2345678); 119 | } 120 | 121 | #[test] 122 | fn test_looks_like_pow5() { 123 | // These numbers have a mantissa that is a multiple of the largest power of 124 | // 5 that fits, and an exponent that causes the computation for q to result 125 | // in 22, which is a corner case for Ryū. 126 | assert_eq!(f64::from_bits(0x4830F0CF064DD592), 5.764607523034235e39); 127 | check!(5.764607523034235e39); 128 | assert_eq!(f64::from_bits(0x4840F0CF064DD592), 1.152921504606847e40); 129 | check!(1.152921504606847e40); 130 | assert_eq!(f64::from_bits(0x4850F0CF064DD592), 2.305843009213694e40); 131 | check!(2.305843009213694e40); 132 | } 133 | 134 | #[test] 135 | fn test_output_length() { 136 | check!(1.0); // already tested in Basic 137 | check!(1.2); 138 | check!(1.23); 139 | check!(1.234); 140 | check!(1.2345); 141 | check!(1.23456); 142 | check!(1.234567); 143 | check!(1.2345678); // already tested in Regression 144 | check!(1.23456789); 145 | check!(1.234567895); // 1.234567890 would be trimmed 146 | check!(1.2345678901); 147 | check!(1.23456789012); 148 | check!(1.234567890123); 149 | check!(1.2345678901234); 150 | check!(1.23456789012345); 151 | check!(1.234567890123456); 152 | check!(1.2345678901234567); 153 | 154 | // Test 32-bit chunking 155 | check!(4.294967294); // 2^32 - 2 156 | check!(4.294967295); // 2^32 - 1 157 | check!(4.294967296); // 2^32 158 | check!(4.294967297); // 2^32 + 1 159 | check!(4.294967298); // 2^32 + 2 160 | } 161 | 162 | // Test min, max shift values in shiftright128 163 | #[test] 164 | fn test_min_max_shift() { 165 | let max_mantissa = (1u64 << 53) - 1; 166 | 167 | // 32-bit opt-size=0: 49 <= dist <= 50 168 | // 32-bit opt-size=1: 30 <= dist <= 50 169 | // 64-bit opt-size=0: 50 <= dist <= 50 170 | // 64-bit opt-size=1: 30 <= dist <= 50 171 | assert_eq!(1.7800590868057611E-307, ieee_parts_to_double(false, 4, 0)); 172 | check!(1.7800590868057611e-307); 173 | // 32-bit opt-size=0: 49 <= dist <= 49 174 | // 32-bit opt-size=1: 28 <= dist <= 49 175 | // 64-bit opt-size=0: 50 <= dist <= 50 176 | // 64-bit opt-size=1: 28 <= dist <= 50 177 | assert_eq!( 178 | 2.8480945388892175E-306, 179 | ieee_parts_to_double(false, 6, max_mantissa) 180 | ); 181 | check!(2.8480945388892175e-306); 182 | // 32-bit opt-size=0: 52 <= dist <= 53 183 | // 32-bit opt-size=1: 2 <= dist <= 53 184 | // 64-bit opt-size=0: 53 <= dist <= 53 185 | // 64-bit opt-size=1: 2 <= dist <= 53 186 | assert_eq!(2.446494580089078E-296, ieee_parts_to_double(false, 41, 0)); 187 | check!(2.446494580089078e-296); 188 | // 32-bit opt-size=0: 52 <= dist <= 52 189 | // 32-bit opt-size=1: 2 <= dist <= 52 190 | // 64-bit opt-size=0: 53 <= dist <= 53 191 | // 64-bit opt-size=1: 2 <= dist <= 53 192 | assert_eq!( 193 | 4.8929891601781557E-296, 194 | ieee_parts_to_double(false, 40, max_mantissa) 195 | ); 196 | check!(4.8929891601781557e-296); 197 | 198 | // 32-bit opt-size=0: 57 <= dist <= 58 199 | // 32-bit opt-size=1: 57 <= dist <= 58 200 | // 64-bit opt-size=0: 58 <= dist <= 58 201 | // 64-bit opt-size=1: 58 <= dist <= 58 202 | assert_eq!(1.8014398509481984E16, ieee_parts_to_double(false, 1077, 0)); 203 | check!(1.8014398509481984e16); 204 | // 32-bit opt-size=0: 57 <= dist <= 57 205 | // 32-bit opt-size=1: 57 <= dist <= 57 206 | // 64-bit opt-size=0: 58 <= dist <= 58 207 | // 64-bit opt-size=1: 58 <= dist <= 58 208 | assert_eq!( 209 | 3.6028797018963964E16, 210 | ieee_parts_to_double(false, 1076, max_mantissa) 211 | ); 212 | check!(3.6028797018963964e16); 213 | // 32-bit opt-size=0: 51 <= dist <= 52 214 | // 32-bit opt-size=1: 51 <= dist <= 59 215 | // 64-bit opt-size=0: 52 <= dist <= 52 216 | // 64-bit opt-size=1: 52 <= dist <= 59 217 | assert_eq!(2.900835519859558E-216, ieee_parts_to_double(false, 307, 0)); 218 | check!(2.900835519859558e-216); 219 | // 32-bit opt-size=0: 51 <= dist <= 51 220 | // 32-bit opt-size=1: 51 <= dist <= 59 221 | // 64-bit opt-size=0: 52 <= dist <= 52 222 | // 64-bit opt-size=1: 52 <= dist <= 59 223 | assert_eq!( 224 | 5.801671039719115E-216, 225 | ieee_parts_to_double(false, 306, max_mantissa) 226 | ); 227 | check!(5.801671039719115e-216); 228 | 229 | // https://github.com/ulfjack/ryu/commit/19e44d16d80236f5de25800f56d82606d1be00b9#commitcomment-30146483 230 | // 32-bit opt-size=0: 49 <= dist <= 49 231 | // 32-bit opt-size=1: 44 <= dist <= 49 232 | // 64-bit opt-size=0: 50 <= dist <= 50 233 | // 64-bit opt-size=1: 44 <= dist <= 50 234 | assert_eq!( 235 | 3.196104012172126E-27, 236 | ieee_parts_to_double(false, 934, 0x000FA7161A4D6E0C) 237 | ); 238 | check!(3.196104012172126e-27); 239 | } 240 | 241 | #[test] 242 | fn test_small_integers() { 243 | check!(9007199254740991.0); // 2^53-1 244 | check!(9007199254740992.0); // 2^53 245 | 246 | check!(1.0); 247 | check!(12.0); 248 | check!(123.0); 249 | check!(1234.0); 250 | check!(12345.0); 251 | check!(123456.0); 252 | check!(1234567.0); 253 | check!(12345678.0); 254 | check!(123456789.0); 255 | check!(1234567890.0); 256 | check!(1234567895.0); 257 | check!(12345678901.0); 258 | check!(123456789012.0); 259 | check!(1234567890123.0); 260 | check!(12345678901234.0); 261 | check!(123456789012345.0); 262 | check!(1234567890123456.0); 263 | 264 | // 10^i 265 | check!(1.0); 266 | check!(10.0); 267 | check!(100.0); 268 | check!(1000.0); 269 | check!(10000.0); 270 | check!(100000.0); 271 | check!(1000000.0); 272 | check!(10000000.0); 273 | check!(100000000.0); 274 | check!(1000000000.0); 275 | check!(10000000000.0); 276 | check!(100000000000.0); 277 | check!(1000000000000.0); 278 | check!(10000000000000.0); 279 | check!(100000000000000.0); 280 | check!(1000000000000000.0); 281 | 282 | // 10^15 + 10^i 283 | check!(1000000000000001.0); 284 | check!(1000000000000010.0); 285 | check!(1000000000000100.0); 286 | check!(1000000000001000.0); 287 | check!(1000000000010000.0); 288 | check!(1000000000100000.0); 289 | check!(1000000001000000.0); 290 | check!(1000000010000000.0); 291 | check!(1000000100000000.0); 292 | check!(1000001000000000.0); 293 | check!(1000010000000000.0); 294 | check!(1000100000000000.0); 295 | check!(1001000000000000.0); 296 | check!(1010000000000000.0); 297 | check!(1100000000000000.0); 298 | 299 | // Largest power of 2 <= 10^(i+1) 300 | check!(8.0); 301 | check!(64.0); 302 | check!(512.0); 303 | check!(8192.0); 304 | check!(65536.0); 305 | check!(524288.0); 306 | check!(8388608.0); 307 | check!(67108864.0); 308 | check!(536870912.0); 309 | check!(8589934592.0); 310 | check!(68719476736.0); 311 | check!(549755813888.0); 312 | check!(8796093022208.0); 313 | check!(70368744177664.0); 314 | check!(562949953421312.0); 315 | check!(9007199254740992.0); 316 | 317 | // 1000 * (Largest power of 2 <= 10^(i+1)) 318 | check!(8000.0); 319 | check!(64000.0); 320 | check!(512000.0); 321 | check!(8192000.0); 322 | check!(65536000.0); 323 | check!(524288000.0); 324 | check!(8388608000.0); 325 | check!(67108864000.0); 326 | check!(536870912000.0); 327 | check!(8589934592000.0); 328 | check!(68719476736000.0); 329 | check!(549755813888000.0); 330 | check!(8796093022208000.0); 331 | } 332 | -------------------------------------------------------------------------------- /tests/exhaustive.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(check_cfg), allow(unexpected_cfgs))] 2 | #![allow(clippy::cast_possible_truncation)] 3 | 4 | use std::str; 5 | use std::sync::atomic::{AtomicUsize, Ordering}; 6 | use std::sync::Arc; 7 | use std::thread; 8 | 9 | #[test] 10 | #[cfg_attr(not(exhaustive), ignore = "requires cfg(exhaustive)")] 11 | fn test_exhaustive() { 12 | const BATCH_SIZE: u32 = 1_000_000; 13 | let counter = Arc::new(AtomicUsize::new(0)); 14 | let finished = Arc::new(AtomicUsize::new(0)); 15 | 16 | let mut workers = Vec::new(); 17 | for _ in 0..num_cpus::get() { 18 | let counter = counter.clone(); 19 | let finished = finished.clone(); 20 | workers.push(thread::spawn(move || loop { 21 | let batch = counter.fetch_add(1, Ordering::Relaxed) as u32; 22 | if batch > u32::max_value() / BATCH_SIZE { 23 | return; 24 | } 25 | 26 | let min = batch * BATCH_SIZE; 27 | let max = if batch == u32::max_value() / BATCH_SIZE { 28 | u32::max_value() 29 | } else { 30 | min + BATCH_SIZE - 1 31 | }; 32 | 33 | let mut bytes = [0u8; 24]; 34 | let mut buffer = ryu::Buffer::new(); 35 | for u in min..=max { 36 | let f = f32::from_bits(u); 37 | if !f.is_finite() { 38 | continue; 39 | } 40 | let n = unsafe { ryu::raw::format32(f, &mut bytes[0]) }; 41 | assert_eq!(Ok(Ok(f)), str::from_utf8(&bytes[..n]).map(str::parse)); 42 | assert_eq!(Ok(f), buffer.format_finite(f).parse()); 43 | } 44 | 45 | let increment = (max - min + 1) as usize; 46 | let update = finished.fetch_add(increment, Ordering::Relaxed); 47 | println!("{}", update + increment); 48 | })); 49 | } 50 | 51 | for w in workers { 52 | w.join().unwrap(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/f2s_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow( 22 | clippy::approx_constant, 23 | clippy::float_cmp, 24 | clippy::non_ascii_literal, 25 | clippy::unreadable_literal, 26 | clippy::unseparated_literal_suffix 27 | )] 28 | 29 | #[macro_use] 30 | mod macros; 31 | 32 | use std::f32; 33 | 34 | fn pretty(f: f32) -> String { 35 | ryu::Buffer::new().format(f).to_owned() 36 | } 37 | 38 | #[test] 39 | fn test_ryu() { 40 | check!(0.3); 41 | check!(1234000000000.0); 42 | check!(1.234e13); 43 | check!(2.71828); 44 | check!(1.1e32); 45 | check!(1.1e-32); 46 | check!(2.7182817); 47 | check!(1e-45); 48 | check!(3.4028235e38); 49 | check!(-0.001234); 50 | } 51 | 52 | #[test] 53 | fn test_random() { 54 | let n = if cfg!(miri) { 100 } else { 1000000 }; 55 | let mut buffer = ryu::Buffer::new(); 56 | for _ in 0..n { 57 | let f: f32 = rand::random(); 58 | assert_eq!(f, buffer.format_finite(f).parse().unwrap()); 59 | } 60 | } 61 | 62 | #[test] 63 | #[cfg_attr(miri, ignore = "too slow for miri")] 64 | fn test_non_finite() { 65 | for i in 0u32..1 << 23 { 66 | let f = f32::from_bits((((1 << 8) - 1) << 23) + i); 67 | assert!(!f.is_finite(), "f={}", f); 68 | ryu::Buffer::new().format_finite(f); 69 | } 70 | } 71 | 72 | #[test] 73 | fn test_basic() { 74 | check!(0.0); 75 | check!(-0.0); 76 | check!(1.0); 77 | check!(-1.0); 78 | assert_eq!(pretty(f32::NAN.copysign(1.0)), "NaN"); 79 | assert_eq!(pretty(f32::NAN.copysign(-1.0)), "NaN"); 80 | assert_eq!(pretty(f32::INFINITY), "inf"); 81 | assert_eq!(pretty(f32::NEG_INFINITY), "-inf"); 82 | } 83 | 84 | #[test] 85 | fn test_switch_to_subnormal() { 86 | check!(1.1754944e-38); 87 | } 88 | 89 | #[test] 90 | fn test_min_and_max() { 91 | assert_eq!(f32::from_bits(0x7f7fffff), 3.4028235e38); 92 | check!(3.4028235e38); 93 | assert_eq!(f32::from_bits(1), 1e-45); 94 | check!(1e-45); 95 | } 96 | 97 | // Check that we return the exact boundary if it is the shortest 98 | // representation, but only if the original floating point number is even. 99 | #[test] 100 | fn test_boundary_round_even() { 101 | check!(33554450.0); 102 | check!(9000000000.0); 103 | check!(34366720000.0); 104 | } 105 | 106 | // If the exact value is exactly halfway between two shortest representations, 107 | // then we round to even. It seems like this only makes a difference if the 108 | // last two digits are ...2|5 or ...7|5, and we cut off the 5. 109 | #[test] 110 | fn test_exact_value_round_even() { 111 | check!(305404.12); 112 | check!(8099.0312); 113 | } 114 | 115 | #[test] 116 | fn test_lots_of_trailing_zeros() { 117 | // Pattern for the first test: 00111001100000000000000000000000 118 | check!(0.00024414062); 119 | check!(0.0024414062); 120 | check!(0.0043945312); 121 | check!(0.0063476562); 122 | } 123 | 124 | #[test] 125 | fn test_regression() { 126 | check!(4.7223665e21); 127 | check!(8388608.0); 128 | check!(16777216.0); 129 | check!(33554436.0); 130 | check!(67131496.0); 131 | check!(1.9310392e-38); 132 | check!(-2.47e-43); 133 | check!(1.993244e-38); 134 | check!(4103.9004); 135 | check!(5339999700.0); 136 | check!(6.0898e-39); 137 | check!(0.0010310042); 138 | check!(2.882326e17); 139 | check!(7.038531e-26); 140 | check!(9.223404e17); 141 | check!(67108870.0); 142 | check!(1e-44); 143 | check!(2.816025e14); 144 | check!(9.223372e18); 145 | check!(1.5846086e29); 146 | check!(1.1811161e19); 147 | check!(5.368709e18); 148 | check!(4.6143166e18); 149 | check!(0.007812537); 150 | check!(1e-45); 151 | check!(1.18697725e20); 152 | check!(1.00014165e-36); 153 | check!(200.0); 154 | check!(33554432.0); 155 | } 156 | 157 | #[test] 158 | fn test_looks_like_pow5() { 159 | // These numbers have a mantissa that is the largest power of 5 that fits, 160 | // and an exponent that causes the computation for q to result in 10, which 161 | // is a corner case for Ryū. 162 | assert_eq!(f32::from_bits(0x5D1502F9), 6.7108864e17); 163 | check!(6.7108864e17); 164 | assert_eq!(f32::from_bits(0x5D9502F9), 1.3421773e18); 165 | check!(1.3421773e18); 166 | assert_eq!(f32::from_bits(0x5E1502F9), 2.6843546e18); 167 | check!(2.6843546e18); 168 | } 169 | 170 | #[test] 171 | fn test_output_length() { 172 | check!(1.0); // already tested in Basic 173 | check!(1.2); 174 | check!(1.23); 175 | check!(1.234); 176 | check!(1.2345); 177 | check!(1.23456); 178 | check!(1.234567); 179 | check!(1.2345678); 180 | check!(1.23456735e-36); 181 | } 182 | -------------------------------------------------------------------------------- /tests/macros/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! check { 2 | ($f:tt) => { 3 | assert_eq!(pretty($f), stringify!($f)); 4 | }; 5 | (-$f:tt) => { 6 | assert_eq!(pretty(-$f), concat!("-", stringify!($f))); 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /tests/s2d_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![cfg(not(feature = "small"))] 22 | #![allow(dead_code)] 23 | #![allow( 24 | clippy::cast_lossless, 25 | clippy::cast_possible_truncation, 26 | clippy::cast_possible_wrap, 27 | clippy::cast_sign_loss, 28 | clippy::excessive_precision, 29 | clippy::float_cmp, 30 | clippy::manual_range_contains, 31 | clippy::similar_names, 32 | clippy::too_many_lines, 33 | clippy::unreadable_literal, 34 | clippy::unseparated_literal_suffix, 35 | clippy::wildcard_imports 36 | )] 37 | 38 | #[path = "../src/common.rs"] 39 | mod common; 40 | 41 | #[cfg(not(feature = "small"))] 42 | #[path = "../src/d2s_full_table.rs"] 43 | mod d2s_full_table; 44 | 45 | #[path = "../src/d2s_intrinsics.rs"] 46 | mod d2s_intrinsics; 47 | 48 | #[cfg(feature = "small")] 49 | #[path = "../src/d2s_small_table.rs"] 50 | mod d2s_small_table; 51 | 52 | #[path = "../src/d2s.rs"] 53 | mod d2s; 54 | 55 | #[path = "../src/s2d.rs"] 56 | mod s2d; 57 | 58 | #[path = "../src/parse.rs"] 59 | mod parse; 60 | 61 | use crate::parse::Error; 62 | use crate::s2d::s2d; 63 | 64 | impl PartialEq for Error { 65 | fn eq(&self, other: &Self) -> bool { 66 | *self as u8 == *other as u8 67 | } 68 | } 69 | 70 | #[test] 71 | fn test_bad_input() { 72 | assert_eq!(Error::MalformedInput, s2d(b"x").unwrap_err()); 73 | assert_eq!(Error::MalformedInput, s2d(b"1..1").unwrap_err()); 74 | assert_eq!(Error::MalformedInput, s2d(b"..").unwrap_err()); 75 | assert_eq!(Error::MalformedInput, s2d(b"1..1").unwrap_err()); 76 | assert_eq!(Error::MalformedInput, s2d(b"1ee1").unwrap_err()); 77 | assert_eq!(Error::MalformedInput, s2d(b"1e.1").unwrap_err()); 78 | assert_eq!(Error::InputTooShort, s2d(b"").unwrap_err()); 79 | assert_eq!(Error::InputTooLong, s2d(b"123456789012345678").unwrap_err()); 80 | assert_eq!(Error::InputTooLong, s2d(b"1e12345").unwrap_err()); 81 | } 82 | 83 | #[test] 84 | fn test_basic() { 85 | assert_eq!(0.0, s2d(b"0").unwrap()); 86 | assert_eq!(-0.0, s2d(b"-0").unwrap()); 87 | assert_eq!(1.0, s2d(b"1").unwrap()); 88 | assert_eq!(2.0, s2d(b"2").unwrap()); 89 | assert_eq!(123456789.0, s2d(b"123456789").unwrap()); 90 | assert_eq!(123.456, s2d(b"123.456").unwrap()); 91 | assert_eq!(123.456, s2d(b"123456e-3").unwrap()); 92 | assert_eq!(123.456, s2d(b"1234.56e-1").unwrap()); 93 | assert_eq!(1.453, s2d(b"1.453").unwrap()); 94 | assert_eq!(1453.0, s2d(b"1.453e+3").unwrap()); 95 | assert_eq!(0.0, s2d(b".0").unwrap()); 96 | assert_eq!(1.0, s2d(b"1e0").unwrap()); 97 | assert_eq!(1.0, s2d(b"1E0").unwrap()); 98 | assert_eq!(1.0, s2d(b"000001.000000").unwrap()); 99 | assert_eq!(0.2316419, s2d(b"0.2316419").unwrap()); 100 | } 101 | 102 | #[test] 103 | fn test_min_max() { 104 | assert_eq!( 105 | 1.7976931348623157e308, 106 | s2d(b"1.7976931348623157e308").unwrap(), 107 | ); 108 | assert_eq!(5E-324, s2d(b"5E-324").unwrap()); 109 | } 110 | 111 | #[test] 112 | fn test_mantissa_rounding_overflow() { 113 | // This results in binary mantissa that is all ones and requires rounding up 114 | // because it is closer to 1 than to the next smaller float. This is a 115 | // regression test that the mantissa overflow is handled correctly by 116 | // increasing the exponent. 117 | assert_eq!(1.0, s2d(b"0.99999999999999999").unwrap()); 118 | // This number overflows the mantissa *and* the IEEE exponent. 119 | assert_eq!(f64::INFINITY, s2d(b"1.7976931348623159e308").unwrap()); 120 | } 121 | 122 | #[test] 123 | fn test_underflow() { 124 | assert_eq!(0.0, s2d(b"2.4e-324").unwrap()); 125 | assert_eq!(0.0, s2d(b"1e-324").unwrap()); 126 | assert_eq!(0.0, s2d(b"9.99999e-325").unwrap()); 127 | // These are just about halfway between 0 and the smallest float. 128 | // The first is just below the halfway point, the second just above. 129 | assert_eq!(0.0, s2d(b"2.4703282292062327e-324").unwrap()); 130 | assert_eq!(5e-324, s2d(b"2.4703282292062328e-324").unwrap()); 131 | } 132 | 133 | #[test] 134 | fn test_overflow() { 135 | assert_eq!(f64::INFINITY, s2d(b"2e308").unwrap()); 136 | assert_eq!(f64::INFINITY, s2d(b"1e309").unwrap()); 137 | } 138 | 139 | #[test] 140 | fn test_table_size_denormal() { 141 | assert_eq!(5e-324, s2d(b"4.9406564584124654e-324").unwrap()); 142 | } 143 | 144 | #[test] 145 | fn test_issue157() { 146 | assert_eq!( 147 | 1.2999999999999999E+154, 148 | s2d(b"1.2999999999999999E+154").unwrap(), 149 | ); 150 | } 151 | 152 | #[test] 153 | fn test_issue173() { 154 | // Denormal boundary 155 | assert_eq!( 156 | 2.2250738585072012e-308, 157 | s2d(b"2.2250738585072012e-308").unwrap(), 158 | ); 159 | assert_eq!( 160 | 2.2250738585072013e-308, 161 | s2d(b"2.2250738585072013e-308").unwrap(), 162 | ); 163 | assert_eq!( 164 | 2.2250738585072014e-308, 165 | s2d(b"2.2250738585072014e-308").unwrap(), 166 | ); 167 | } 168 | -------------------------------------------------------------------------------- /tests/s2f_test.rs: -------------------------------------------------------------------------------- 1 | // Translated from C to Rust. The original C code can be found at 2 | // https://github.com/ulfjack/ryu and carries the following license: 3 | // 4 | // Copyright 2018 Ulf Adams 5 | // 6 | // The contents of this file may be used under the terms of the Apache License, 7 | // Version 2.0. 8 | // 9 | // (See accompanying file LICENSE-Apache or copy at 10 | // http://www.apache.org/licenses/LICENSE-2.0) 11 | // 12 | // Alternatively, the contents of this file may be used under the terms of 13 | // the Boost Software License, Version 1.0. 14 | // (See accompanying file LICENSE-Boost or copy at 15 | // https://www.boost.org/LICENSE_1_0.txt) 16 | // 17 | // Unless required by applicable law or agreed to in writing, this software 18 | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 | // KIND, either express or implied. 20 | 21 | #![allow(dead_code)] 22 | #![allow( 23 | clippy::cast_lossless, 24 | clippy::cast_possible_truncation, 25 | clippy::cast_possible_wrap, 26 | clippy::cast_possible_wrap, 27 | clippy::cast_sign_loss, 28 | clippy::checked_conversions, 29 | clippy::float_cmp, 30 | clippy::manual_range_contains, 31 | clippy::similar_names, 32 | clippy::too_many_lines, 33 | clippy::unreadable_literal, 34 | clippy::unseparated_literal_suffix, 35 | clippy::wildcard_imports 36 | )] 37 | 38 | #[path = "../src/common.rs"] 39 | mod common; 40 | 41 | #[cfg(not(feature = "small"))] 42 | #[path = "../src/d2s_full_table.rs"] 43 | mod d2s_full_table; 44 | 45 | #[path = "../src/d2s_intrinsics.rs"] 46 | mod d2s_intrinsics; 47 | 48 | #[cfg(feature = "small")] 49 | #[path = "../src/d2s_small_table.rs"] 50 | mod d2s_small_table; 51 | 52 | #[path = "../src/d2s.rs"] 53 | mod d2s; 54 | 55 | #[path = "../src/f2s_intrinsics.rs"] 56 | mod f2s_intrinsics; 57 | 58 | #[path = "../src/f2s.rs"] 59 | mod f2s; 60 | 61 | #[path = "../src/s2f.rs"] 62 | mod s2f; 63 | 64 | #[path = "../src/parse.rs"] 65 | mod parse; 66 | 67 | use crate::parse::Error; 68 | use crate::s2f::s2f; 69 | 70 | impl PartialEq for Error { 71 | fn eq(&self, other: &Self) -> bool { 72 | *self as u8 == *other as u8 73 | } 74 | } 75 | 76 | #[test] 77 | fn test_basic() { 78 | assert_eq!(0.0, s2f(b"0").unwrap()); 79 | assert_eq!(-0.0, s2f(b"-0").unwrap()); 80 | assert_eq!(1.0, s2f(b"1").unwrap()); 81 | assert_eq!(-1.0, s2f(b"-1").unwrap()); 82 | assert_eq!(123456792.0, s2f(b"123456789").unwrap()); 83 | assert_eq!(299792448.0, s2f(b"299792458").unwrap()); 84 | } 85 | 86 | #[test] 87 | fn test_min_max() { 88 | assert_eq!(1e-45, s2f(b"1e-45").unwrap()); 89 | assert_eq!(f32::MIN_POSITIVE, s2f(b"1.1754944e-38").unwrap()); 90 | assert_eq!(f32::MAX, s2f(b"3.4028235e+38").unwrap()); 91 | } 92 | 93 | #[test] 94 | fn test_mantissa_rounding_overflow() { 95 | assert_eq!(1.0, s2f(b"0.999999999").unwrap()); 96 | assert_eq!(f32::INFINITY, s2f(b"3.4028236e+38").unwrap()); 97 | assert_eq!(1.1754944e-38, s2f(b"1.17549430e-38").unwrap()); // FLT_MIN 98 | assert_eq!(1.1754944e-38, s2f(b"1.17549431e-38").unwrap()); 99 | assert_eq!(1.1754944e-38, s2f(b"1.17549432e-38").unwrap()); 100 | assert_eq!(1.1754944e-38, s2f(b"1.17549433e-38").unwrap()); 101 | assert_eq!(1.1754944e-38, s2f(b"1.17549434e-38").unwrap()); 102 | assert_eq!(1.1754944e-38, s2f(b"1.17549435e-38").unwrap()); 103 | } 104 | 105 | #[test] 106 | fn test_trailing_zeros() { 107 | assert_eq!(26843550.0, s2f(b"26843549.5").unwrap()); 108 | assert_eq!(50000004.0, s2f(b"50000002.5").unwrap()); 109 | assert_eq!(99999992.0, s2f(b"99999989.5").unwrap()); 110 | } 111 | --------------------------------------------------------------------------------