-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
Merge branch 'sp'
Showing
197 changed files
with
58,327 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Copyright 2024 Shift Crypto AG | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
[package] | ||
name = "streaming-silent-payments" | ||
version = "0.1.0" | ||
authors = ["Shift Crypto AG <support@bitbox.swiss>"] | ||
edition = "2021" | ||
license = "Apache-2.0" | ||
|
||
[dependencies] | ||
bitcoin = { workspace = true } | ||
bech32 = { workspace = true } | ||
bitbox02 = { path = "../bitbox02" } | ||
|
||
[dev-dependencies] | ||
serde = { version = "1.0", features = ["derive"] } | ||
hex = { workspace = true } | ||
serde_json = "1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// File copied and adapted from: | ||
// https://github.com/cygnet3/rust-silentpayments/blob/395b153b6d98ea33a59306c1a8a189d4ca152571/src/utils/hash.rs | ||
|
||
#![allow(non_snake_case)] | ||
|
||
use bitcoin::hashes::{sha256t_hash_newtype, Hash, HashEngine}; | ||
use bitcoin::secp256k1::{PublicKey, Scalar}; | ||
|
||
sha256t_hash_newtype! { | ||
struct InputsTag = hash_str("BIP0352/Inputs"); | ||
|
||
/// BIP0352-tagged hash with tag \"Inputs\". | ||
/// | ||
/// This is used for computing the inputs hash. | ||
#[hash_newtype(forward)] | ||
struct InputsHash(_); | ||
|
||
pub(crate) struct SharedSecretTag = hash_str("BIP0352/SharedSecret"); | ||
|
||
/// BIP0352-tagged hash with tag \"SharedSecret\". | ||
/// | ||
/// This hash type is for computing the shared secret. | ||
#[hash_newtype(forward)] | ||
pub(crate) struct SharedSecretHash(_); | ||
} | ||
|
||
impl InputsHash { | ||
pub(crate) fn from_outpoint_and_A_sum( | ||
smallest_outpoint: &bitcoin::OutPoint, | ||
A_sum: PublicKey, | ||
) -> InputsHash { | ||
let mut eng = InputsHash::engine(); | ||
eng.input(&bitcoin::consensus::serialize(smallest_outpoint)); | ||
eng.input(&A_sum.serialize()); | ||
InputsHash::from_engine(eng) | ||
} | ||
pub(crate) fn to_scalar(self) -> Scalar { | ||
// This is statistically extremely unlikely to panic. | ||
Scalar::from_be_bytes(self.to_byte_array()).unwrap() | ||
} | ||
} | ||
|
||
impl SharedSecretHash { | ||
pub(crate) fn from_ecdh_and_k(ecdh: &PublicKey, k: u32) -> SharedSecretHash { | ||
let mut eng = SharedSecretHash::engine(); | ||
eng.input(&ecdh.serialize()); | ||
eng.input(&k.to_be_bytes()); | ||
SharedSecretHash::from_engine(eng) | ||
} | ||
} | ||
|
||
pub(crate) fn calculate_input_hash(outpoint: &bitcoin::OutPoint, A_sum: PublicKey) -> Scalar { | ||
InputsHash::from_outpoint_and_A_sum(outpoint, A_sum).to_scalar() | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// Copyright 2024 Shift Crypto AG | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
#![allow(non_snake_case)] | ||
|
||
use serde::Deserialize; | ||
use std::fs::File; | ||
use std::io::BufReader; | ||
use std::str::FromStr; | ||
|
||
use streaming_silent_payments::{ | ||
bitcoin, | ||
bitcoin::secp256k1::{SecretKey, XOnlyPublicKey}, | ||
InputType, Network, SilentPayment, | ||
}; | ||
|
||
/// The following structs have been copied from: | ||
/// https://github.com/cygnet3/rust-silentpayments/blob/395b153b6d98ea33a59306c1a8a189d4ca152571/tests/common/structs.rs | ||
#[derive(Debug, Deserialize)] | ||
pub struct TestData { | ||
pub comment: String, | ||
pub sending: Vec<SendingData>, | ||
pub receiving: Vec<ReceivingData>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ReceivingData { | ||
pub given: ReceivingDataGiven, | ||
pub expected: ReceivingDataExpected, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ReceivingKeyMaterial { | ||
pub scan_priv_key: String, | ||
pub spend_priv_key: String, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct HexStr { | ||
pub hex: String, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ScriptPubKey { | ||
pub scriptPubKey: HexStr, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ReceivingVinData { | ||
pub txid: String, | ||
pub vout: u32, | ||
pub scriptSig: String, | ||
pub txinwitness: String, | ||
pub prevout: ScriptPubKey, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ReceivingDataGiven { | ||
pub vin: Vec<ReceivingVinData>, | ||
pub key_material: ReceivingKeyMaterial, | ||
pub labels: Vec<u32>, | ||
pub outputs: Vec<String>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct ReceivingDataExpected { | ||
pub addresses: Vec<String>, | ||
pub outputs: Vec<OutputWithSignature>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct SendingData { | ||
pub given: SendingDataGiven, | ||
pub expected: SendingDataExpected, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct SendingDataGiven { | ||
pub vin: Vec<SendingVinData>, | ||
pub recipients: Vec<String>, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct SendingVinData { | ||
pub txid: String, | ||
pub vout: u32, | ||
pub scriptSig: String, | ||
pub txinwitness: String, | ||
pub prevout: ScriptPubKey, | ||
pub private_key: String, | ||
} | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct SendingDataExpected { | ||
pub outputs: Vec<Vec<String>>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Eq, PartialEq)] | ||
pub struct OutputWithSignature { | ||
pub pub_key: String, | ||
pub priv_key_tweak: String, | ||
pub signature: String, | ||
} | ||
|
||
#[test] | ||
fn test_sending() { | ||
let reader = | ||
BufReader::new(File::open("./tests/testdata/send_and_receive_test_vectors.json").unwrap()); | ||
let tests: Vec<TestData> = serde_json::from_reader(reader).unwrap(); | ||
for (i, test) in tests.iter().enumerate() { | ||
if test.comment == "Single recipient: taproot input with NUMS point" { | ||
// SilentPayment API does not allow passing P2TR script path spends - We only support BIP-86 | ||
// Taproot key-path spends. | ||
continue; | ||
} | ||
if test.comment == "P2PKH and P2WPKH Uncompressed Keys are skipped" { | ||
// We don't support uncompressed keys. | ||
continue; | ||
} | ||
if test.comment == "Skip invalid P2SH inputs" { | ||
// SilentPayment API does not allow passing invalid P2SH inputs. | ||
continue; | ||
} | ||
|
||
for (j, sending_data) in test.sending.iter().enumerate() { | ||
if sending_data.expected.outputs.len() != 1 | ||
|| sending_data.expected.outputs[0].len() != 1 | ||
{ | ||
println!("Skipping test #{}/{}", i, j); | ||
continue; | ||
} | ||
println!("Running test #{}/{} - {}", i, j, test.comment); | ||
|
||
let expected = XOnlyPublicKey::from_str(&sending_data.expected.outputs[0][0]).unwrap(); | ||
|
||
// One SP recipient results in one output. | ||
assert_eq!(sending_data.given.recipients.len(), 1); | ||
let sp_address = sending_data.given.recipients[0].as_str(); | ||
|
||
let mut v = SilentPayment::new(Network::Btc); | ||
for inp in sending_data.given.vin.iter() { | ||
let pk_script_hex = inp.prevout.scriptPubKey.hex.as_str(); | ||
let pk_script_bytes = hex::decode(pk_script_hex).unwrap(); | ||
let pk_script = bitcoin::Script::from_bytes(&pk_script_bytes); | ||
|
||
let input_type = if pk_script.is_p2pkh() { | ||
InputType::P2pkh | ||
} else if pk_script.is_p2wpkh() { | ||
InputType::P2wpkh | ||
} else if pk_script.is_p2tr() { | ||
let witness: bitcoin::Witness = | ||
bitcoin::consensus::deserialize(&hex::decode(&inp.txinwitness).unwrap()) | ||
.unwrap(); | ||
// Regular keypath spend. One test case was skipped above (NUMS) which is a | ||
// script path spend, which we currently don't support. | ||
assert!(witness.len() == 1 && witness.nth(0).unwrap().len() == 64); | ||
InputType::P2trKeypathSpend | ||
} else if pk_script.is_p2sh() { | ||
panic!("tests don't include p2sh - parse for p2sh-p2wpkh if needed") | ||
} else { | ||
panic!("unrecognized input") | ||
}; | ||
v.add_input( | ||
input_type, | ||
&SecretKey::from_str(&inp.private_key).unwrap(), | ||
bitcoin::OutPoint::new(bitcoin::Txid::from_str(&inp.txid).unwrap(), inp.vout), | ||
) | ||
.unwrap(); | ||
} | ||
|
||
assert_eq!(v.create_output(sp_address).unwrap().pubkey, expected); | ||
} | ||
} | ||
} |
2,673 changes: 2,673 additions & 0 deletions
2,673
src/rust/streaming-silent-payments/tests/testdata/send_and_receive_test_vectors.json
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"files":{"Cargo.toml":"4b12156f19f1d3a10516ba0177197325f072340e40de4b6a376732fcef3105b2","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-MIT":"23f18e03dc49df91622fe2a76176497404e46ced8a715d9d2b67a7446571cca3","README.md":"48573443063fa4e0786c3b46f42b6efd1f171c6b73408a64afc1b34de89f31fe","benches/bench.rs":"636f3093bd461210ad3063289d455f90669c4a1be3273bcd30898de39f02c641","src/lib.rs":"22f02b06399d4c6849dac5a1b517d3e5736dd33edc3955101ee0be6afc8376eb","src/udiv128.rs":"d28c1872c37ee2185931babcb20a221b8706a5aa8abc4963419763888023ff17","tests/test.rs":"aa1e910573a1d847d39773b4a2e4c597a8d3810070332673df0f6864cab24807"},"package":"49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
# | ||
# When uploading crates to the registry Cargo will automatically | ||
# "normalize" Cargo.toml files for maximal compatibility | ||
# with all versions of Cargo and also rewrite `path` dependencies | ||
# to registry (e.g., crates.io) dependencies. | ||
# | ||
# If you are reading this file be aware that the original Cargo.toml | ||
# will likely look very different (and much more reasonable). | ||
# See Cargo.toml.orig for the original contents. | ||
|
||
[package] | ||
edition = "2018" | ||
rust-version = "1.36" | ||
name = "itoa" | ||
version = "1.0.11" | ||
authors = ["David Tolnay <dtolnay@gmail.com>"] | ||
exclude = [ | ||
"performance.png", | ||
"chart/**", | ||
] | ||
description = "Fast integer primitive to string conversion" | ||
documentation = "https://docs.rs/itoa" | ||
readme = "README.md" | ||
keywords = ["integer"] | ||
categories = [ | ||
"value-formatting", | ||
"no-std", | ||
"no-std::no-alloc", | ||
] | ||
license = "MIT OR Apache-2.0" | ||
repository = "https://github.com/dtolnay/itoa" | ||
|
||
[package.metadata.docs.rs] | ||
rustdoc-args = ["--generate-link-to-definition"] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[lib] | ||
doc-scrape-examples = false | ||
|
||
[dependencies.no-panic] | ||
version = "0.1" | ||
optional = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
Apache License | ||
Version 2.0, January 2004 | ||
http://www.apache.org/licenses/ | ||
|
||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
|
||
1. Definitions. | ||
|
||
"License" shall mean the terms and conditions for use, reproduction, | ||
and distribution as defined by Sections 1 through 9 of this document. | ||
|
||
"Licensor" shall mean the copyright owner or entity authorized by | ||
the copyright owner that is granting the License. | ||
|
||
"Legal Entity" shall mean the union of the acting entity and all | ||
other entities that control, are controlled by, or are under common | ||
control with that entity. For the purposes of this definition, | ||
"control" means (i) the power, direct or indirect, to cause the | ||
direction or management of such entity, whether by contract or | ||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
outstanding shares, or (iii) beneficial ownership of such entity. | ||
|
||
"You" (or "Your") shall mean an individual or Legal Entity | ||
exercising permissions granted by this License. | ||
|
||
"Source" form shall mean the preferred form for making modifications, | ||
including but not limited to software source code, documentation | ||
source, and configuration files. | ||
|
||
"Object" form shall mean any form resulting from mechanical | ||
transformation or translation of a Source form, including but | ||
not limited to compiled object code, generated documentation, | ||
and conversions to other media types. | ||
|
||
"Work" shall mean the work of authorship, whether in Source or | ||
Object form, made available under the License, as indicated by a | ||
copyright notice that is included in or attached to the work | ||
(an example is provided in the Appendix below). | ||
|
||
"Derivative Works" shall mean any work, whether in Source or Object | ||
form, that is based on (or derived from) the Work and for which the | ||
editorial revisions, annotations, elaborations, or other modifications | ||
represent, as a whole, an original work of authorship. For the purposes | ||
of this License, Derivative Works shall not include works that remain | ||
separable from, or merely link (or bind by name) to the interfaces of, | ||
the Work and Derivative Works thereof. | ||
|
||
"Contribution" shall mean any work of authorship, including | ||
the original version of the Work and any modifications or additions | ||
to that Work or Derivative Works thereof, that is intentionally | ||
submitted to Licensor for inclusion in the Work by the copyright owner | ||
or by an individual or Legal Entity authorized to submit on behalf of | ||
the copyright owner. For the purposes of this definition, "submitted" | ||
means any form of electronic, verbal, or written communication sent | ||
to the Licensor or its representatives, including but not limited to | ||
communication on electronic mailing lists, source code control systems, | ||
and issue tracking systems that are managed by, or on behalf of, the | ||
Licensor for the purpose of discussing and improving the Work, but | ||
excluding communication that is conspicuously marked or otherwise | ||
designated in writing by the copyright owner as "Not a Contribution." | ||
|
||
"Contributor" shall mean Licensor and any individual or Legal Entity | ||
on behalf of whom a Contribution has been received by Licensor and | ||
subsequently incorporated within the Work. | ||
|
||
2. Grant of Copyright License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
copyright license to reproduce, prepare Derivative Works of, | ||
publicly display, publicly perform, sublicense, and distribute the | ||
Work and such Derivative Works in Source or Object form. | ||
|
||
3. Grant of Patent License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
(except as stated in this section) patent license to make, have made, | ||
use, offer to sell, sell, import, and otherwise transfer the Work, | ||
where such license applies only to those patent claims licensable | ||
by such Contributor that are necessarily infringed by their | ||
Contribution(s) alone or by combination of their Contribution(s) | ||
with the Work to which such Contribution(s) was submitted. If You | ||
institute patent litigation against any entity (including a | ||
cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
or a Contribution incorporated within the Work constitutes direct | ||
or contributory patent infringement, then any patent licenses | ||
granted to You under this License for that Work shall terminate | ||
as of the date such litigation is filed. | ||
|
||
4. Redistribution. You may reproduce and distribute copies of the | ||
Work or Derivative Works thereof in any medium, with or without | ||
modifications, and in Source or Object form, provided that You | ||
meet the following conditions: | ||
|
||
(a) You must give any other recipients of the Work or | ||
Derivative Works a copy of this License; and | ||
|
||
(b) You must cause any modified files to carry prominent notices | ||
stating that You changed the files; and | ||
|
||
(c) You must retain, in the Source form of any Derivative Works | ||
that You distribute, all copyright, patent, trademark, and | ||
attribution notices from the Source form of the Work, | ||
excluding those notices that do not pertain to any part of | ||
the Derivative Works; and | ||
|
||
(d) If the Work includes a "NOTICE" text file as part of its | ||
distribution, then any Derivative Works that You distribute must | ||
include a readable copy of the attribution notices contained | ||
within such NOTICE file, excluding those notices that do not | ||
pertain to any part of the Derivative Works, in at least one | ||
of the following places: within a NOTICE text file distributed | ||
as part of the Derivative Works; within the Source form or | ||
documentation, if provided along with the Derivative Works; or, | ||
within a display generated by the Derivative Works, if and | ||
wherever such third-party notices normally appear. The contents | ||
of the NOTICE file are for informational purposes only and | ||
do not modify the License. You may add Your own attribution | ||
notices within Derivative Works that You distribute, alongside | ||
or as an addendum to the NOTICE text from the Work, provided | ||
that such additional attribution notices cannot be construed | ||
as modifying the License. | ||
|
||
You may add Your own copyright statement to Your modifications and | ||
may provide additional or different license terms and conditions | ||
for use, reproduction, or distribution of Your modifications, or | ||
for any such Derivative Works as a whole, provided Your use, | ||
reproduction, and distribution of the Work otherwise complies with | ||
the conditions stated in this License. | ||
|
||
5. Submission of Contributions. Unless You explicitly state otherwise, | ||
any Contribution intentionally submitted for inclusion in the Work | ||
by You to the Licensor shall be under the terms and conditions of | ||
this License, without any additional terms or conditions. | ||
Notwithstanding the above, nothing herein shall supersede or modify | ||
the terms of any separate license agreement you may have executed | ||
with Licensor regarding such Contributions. | ||
|
||
6. Trademarks. This License does not grant permission to use the trade | ||
names, trademarks, service marks, or product names of the Licensor, | ||
except as required for reasonable and customary use in describing the | ||
origin of the Work and reproducing the content of the NOTICE file. | ||
|
||
7. Disclaimer of Warranty. Unless required by applicable law or | ||
agreed to in writing, Licensor provides the Work (and each | ||
Contributor provides its Contributions) on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
implied, including, without limitation, any warranties or conditions | ||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
PARTICULAR PURPOSE. You are solely responsible for determining the | ||
appropriateness of using or redistributing the Work and assume any | ||
risks associated with Your exercise of permissions under this License. | ||
|
||
8. Limitation of Liability. In no event and under no legal theory, | ||
whether in tort (including negligence), contract, or otherwise, | ||
unless required by applicable law (such as deliberate and grossly | ||
negligent acts) or agreed to in writing, shall any Contributor be | ||
liable to You for damages, including any direct, indirect, special, | ||
incidental, or consequential damages of any character arising as a | ||
result of this License or out of the use or inability to use the | ||
Work (including but not limited to damages for loss of goodwill, | ||
work stoppage, computer failure or malfunction, or any and all | ||
other commercial damages or losses), even if such Contributor | ||
has been advised of the possibility of such damages. | ||
|
||
9. Accepting Warranty or Additional Liability. While redistributing | ||
the Work or Derivative Works thereof, You may choose to offer, | ||
and charge a fee for, acceptance of support, warranty, indemnity, | ||
or other liability obligations and/or rights consistent with this | ||
License. However, in accepting such obligations, You may act only | ||
on Your own behalf and on Your sole responsibility, not on behalf | ||
of any other Contributor, and only if You agree to indemnify, | ||
defend, and hold each Contributor harmless for any liability | ||
incurred by, or claims asserted against, such Contributor by reason | ||
of your accepting any such warranty or additional liability. | ||
|
||
END OF TERMS AND CONDITIONS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
Permission is hereby granted, free of charge, to any | ||
person obtaining a copy of this software and associated | ||
documentation files (the "Software"), to deal in the | ||
Software without restriction, including without | ||
limitation the rights to use, copy, modify, merge, | ||
publish, distribute, sublicense, and/or sell copies of | ||
the Software, and to permit persons to whom the Software | ||
is furnished to do so, subject to the following | ||
conditions: | ||
|
||
The above copyright notice and this permission notice | ||
shall be included in all copies or substantial portions | ||
of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF | ||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED | ||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT | ||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | ||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR | ||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
itoa | ||
==== | ||
|
||
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/itoa-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/itoa) | ||
[<img alt="crates.io" src="https://img.shields.io/crates/v/itoa.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/itoa) | ||
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-itoa-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/itoa) | ||
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/itoa/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/itoa/actions?query=branch%3Amaster) | ||
|
||
This crate provides a fast conversion of integer primitives to decimal strings. | ||
The implementation comes straight from [libcore] but avoids the performance | ||
penalty of going through [`core::fmt::Formatter`]. | ||
|
||
See also [`ryu`] for printing floating point primitives. | ||
|
||
*Version requirement: rustc 1.36+* | ||
|
||
[libcore]: https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L201-L254 | ||
[`core::fmt::Formatter`]: https://doc.rust-lang.org/std/fmt/struct.Formatter.html | ||
[`ryu`]: https://github.com/dtolnay/ryu | ||
|
||
```toml | ||
[dependencies] | ||
itoa = "1.0" | ||
``` | ||
|
||
<br> | ||
|
||
## Example | ||
|
||
```rust | ||
fn main() { | ||
let mut buffer = itoa::Buffer::new(); | ||
let printed = buffer.format(128u64); | ||
assert_eq!(printed, "128"); | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
## Performance (lower is better) | ||
|
||
![performance](https://raw.githubusercontent.com/dtolnay/itoa/master/performance.png) | ||
|
||
<br> | ||
|
||
#### License | ||
|
||
<sup> | ||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version | ||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option. | ||
</sup> | ||
|
||
<br> | ||
|
||
<sub> | ||
Unless you explicitly state otherwise, any contribution intentionally submitted | ||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall | ||
be dual licensed as above, without any additional terms or conditions. | ||
</sub> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
#![feature(test)] | ||
#![allow(non_snake_case)] | ||
#![allow(clippy::cast_lossless)] | ||
|
||
extern crate test; | ||
|
||
macro_rules! benches { | ||
($($name:ident($value:expr))*) => { | ||
mod bench_itoa_format { | ||
use test::{Bencher, black_box}; | ||
|
||
$( | ||
#[bench] | ||
fn $name(b: &mut Bencher) { | ||
let mut buffer = itoa::Buffer::new(); | ||
|
||
b.iter(|| { | ||
let printed = buffer.format(black_box($value)); | ||
black_box(printed); | ||
}); | ||
} | ||
)* | ||
} | ||
|
||
mod bench_std_fmt { | ||
use std::io::Write; | ||
use test::{Bencher, black_box}; | ||
|
||
$( | ||
#[bench] | ||
fn $name(b: &mut Bencher) { | ||
let mut buf = Vec::with_capacity(40); | ||
|
||
b.iter(|| { | ||
buf.clear(); | ||
write!(&mut buf, "{}", black_box($value)).unwrap(); | ||
black_box(&buf); | ||
}); | ||
} | ||
)* | ||
} | ||
} | ||
} | ||
|
||
benches! { | ||
bench_u64_0(0u64) | ||
bench_u64_half(u32::max_value() as u64) | ||
bench_u64_max(u64::max_value()) | ||
|
||
bench_i16_0(0i16) | ||
bench_i16_min(i16::min_value()) | ||
|
||
bench_u128_0(0u128) | ||
bench_u128_max(u128::max_value()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,309 @@ | ||
//! [![github]](https://github.com/dtolnay/itoa) [![crates-io]](https://crates.io/crates/itoa) [![docs-rs]](https://docs.rs/itoa) | ||
//! | ||
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github | ||
//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust | ||
//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs | ||
//! | ||
//! <br> | ||
//! | ||
//! This crate provides a fast conversion of integer primitives to decimal | ||
//! strings. The implementation comes straight from [libcore] but avoids the | ||
//! performance penalty of going through [`core::fmt::Formatter`]. | ||
//! | ||
//! See also [`ryu`] for printing floating point primitives. | ||
//! | ||
//! [libcore]: https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L201-L254 | ||
//! [`core::fmt::Formatter`]: https://doc.rust-lang.org/std/fmt/struct.Formatter.html | ||
//! [`ryu`]: https://github.com/dtolnay/ryu | ||
//! | ||
//! # Example | ||
//! | ||
//! ``` | ||
//! fn main() { | ||
//! let mut buffer = itoa::Buffer::new(); | ||
//! let printed = buffer.format(128u64); | ||
//! assert_eq!(printed, "128"); | ||
//! } | ||
//! ``` | ||
//! | ||
//! # Performance (lower is better) | ||
//! | ||
//! ![performance](https://raw.githubusercontent.com/dtolnay/itoa/master/performance.png) | ||
#![doc(html_root_url = "https://docs.rs/itoa/1.0.11")] | ||
#![no_std] | ||
#![allow( | ||
clippy::cast_lossless, | ||
clippy::cast_possible_truncation, | ||
clippy::expl_impl_clone_on_copy, | ||
clippy::must_use_candidate, | ||
clippy::needless_doctest_main, | ||
clippy::unreadable_literal | ||
)] | ||
|
||
mod udiv128; | ||
|
||
use core::mem::{self, MaybeUninit}; | ||
use core::{ptr, slice, str}; | ||
#[cfg(feature = "no-panic")] | ||
use no_panic::no_panic; | ||
|
||
/// A correctly sized stack allocation for the formatted integer to be written | ||
/// into. | ||
/// | ||
/// # Example | ||
/// | ||
/// ``` | ||
/// let mut buffer = itoa::Buffer::new(); | ||
/// let printed = buffer.format(1234); | ||
/// assert_eq!(printed, "1234"); | ||
/// ``` | ||
pub struct Buffer { | ||
bytes: [MaybeUninit<u8>; I128_MAX_LEN], | ||
} | ||
|
||
impl Default for Buffer { | ||
#[inline] | ||
fn default() -> Buffer { | ||
Buffer::new() | ||
} | ||
} | ||
|
||
impl Copy for Buffer {} | ||
|
||
impl Clone for Buffer { | ||
#[inline] | ||
#[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072 | ||
fn clone(&self) -> Self { | ||
Buffer::new() | ||
} | ||
} | ||
|
||
impl Buffer { | ||
/// This is a cheap operation; you don't need to worry about reusing buffers | ||
/// for efficiency. | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn new() -> Buffer { | ||
let bytes = [MaybeUninit::<u8>::uninit(); I128_MAX_LEN]; | ||
Buffer { bytes } | ||
} | ||
|
||
/// Print an integer into this buffer and return a reference to its string | ||
/// representation within the buffer. | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn format<I: Integer>(&mut self, i: I) -> &str { | ||
i.write(unsafe { | ||
&mut *(&mut self.bytes as *mut [MaybeUninit<u8>; I128_MAX_LEN] | ||
as *mut <I as private::Sealed>::Buffer) | ||
}) | ||
} | ||
} | ||
|
||
/// An integer that can be written into an [`itoa::Buffer`][Buffer]. | ||
/// | ||
/// This trait is sealed and cannot be implemented for types outside of itoa. | ||
pub trait Integer: private::Sealed {} | ||
|
||
// Seal to prevent downstream implementations of the Integer trait. | ||
mod private { | ||
pub trait Sealed: Copy { | ||
type Buffer: 'static; | ||
fn write(self, buf: &mut Self::Buffer) -> &str; | ||
} | ||
} | ||
|
||
const DEC_DIGITS_LUT: &[u8] = b"\ | ||
0001020304050607080910111213141516171819\ | ||
2021222324252627282930313233343536373839\ | ||
4041424344454647484950515253545556575859\ | ||
6061626364656667686970717273747576777879\ | ||
8081828384858687888990919293949596979899"; | ||
|
||
// Adaptation of the original implementation at | ||
// https://github.com/rust-lang/rust/blob/b8214dc6c6fc20d0a660fb5700dca9ebf51ebe89/src/libcore/fmt/num.rs#L188-L266 | ||
macro_rules! impl_Integer { | ||
($($max_len:expr => $t:ident),* as $conv_fn:ident) => {$( | ||
impl Integer for $t {} | ||
|
||
impl private::Sealed for $t { | ||
type Buffer = [MaybeUninit<u8>; $max_len]; | ||
|
||
#[allow(unused_comparisons)] | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
fn write(self, buf: &mut [MaybeUninit<u8>; $max_len]) -> &str { | ||
let is_nonnegative = self >= 0; | ||
let mut n = if is_nonnegative { | ||
self as $conv_fn | ||
} else { | ||
// Convert negative number to positive by summing 1 to its two's complement. | ||
(!(self as $conv_fn)).wrapping_add(1) | ||
}; | ||
let mut curr = buf.len() as isize; | ||
let buf_ptr = buf.as_mut_ptr() as *mut u8; | ||
let lut_ptr = DEC_DIGITS_LUT.as_ptr(); | ||
|
||
// Need at least 16 bits for the 4-digits-at-a-time to work. | ||
if mem::size_of::<$t>() >= 2 { | ||
// Eagerly decode 4 digits at a time. | ||
while n >= 10000 { | ||
let rem = (n % 10000) as isize; | ||
n /= 10000; | ||
|
||
let d1 = (rem / 100) << 1; | ||
let d2 = (rem % 100) << 1; | ||
curr -= 4; | ||
unsafe { | ||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); | ||
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2); | ||
} | ||
} | ||
} | ||
|
||
// If we reach here, numbers are <=9999 so at most 4 digits long. | ||
let mut n = n as isize; // Possibly reduce 64-bit math. | ||
|
||
// Decode 2 more digits, if >2 digits. | ||
if n >= 100 { | ||
let d1 = (n % 100) << 1; | ||
n /= 100; | ||
curr -= 2; | ||
unsafe { | ||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); | ||
} | ||
} | ||
|
||
// Decode last 1 or 2 digits. | ||
if n < 10 { | ||
curr -= 1; | ||
unsafe { | ||
*buf_ptr.offset(curr) = (n as u8) + b'0'; | ||
} | ||
} else { | ||
let d1 = n << 1; | ||
curr -= 2; | ||
unsafe { | ||
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); | ||
} | ||
} | ||
|
||
if !is_nonnegative { | ||
curr -= 1; | ||
unsafe { | ||
*buf_ptr.offset(curr) = b'-'; | ||
} | ||
} | ||
|
||
let len = buf.len() - curr as usize; | ||
let bytes = unsafe { slice::from_raw_parts(buf_ptr.offset(curr), len) }; | ||
unsafe { str::from_utf8_unchecked(bytes) } | ||
} | ||
} | ||
)*}; | ||
} | ||
|
||
const I8_MAX_LEN: usize = 4; | ||
const U8_MAX_LEN: usize = 3; | ||
const I16_MAX_LEN: usize = 6; | ||
const U16_MAX_LEN: usize = 5; | ||
const I32_MAX_LEN: usize = 11; | ||
const U32_MAX_LEN: usize = 10; | ||
const I64_MAX_LEN: usize = 20; | ||
const U64_MAX_LEN: usize = 20; | ||
|
||
impl_Integer!( | ||
I8_MAX_LEN => i8, | ||
U8_MAX_LEN => u8, | ||
I16_MAX_LEN => i16, | ||
U16_MAX_LEN => u16, | ||
I32_MAX_LEN => i32, | ||
U32_MAX_LEN => u32 | ||
as u32); | ||
|
||
impl_Integer!(I64_MAX_LEN => i64, U64_MAX_LEN => u64 as u64); | ||
|
||
#[cfg(target_pointer_width = "16")] | ||
impl_Integer!(I16_MAX_LEN => isize, U16_MAX_LEN => usize as u16); | ||
|
||
#[cfg(target_pointer_width = "32")] | ||
impl_Integer!(I32_MAX_LEN => isize, U32_MAX_LEN => usize as u32); | ||
|
||
#[cfg(target_pointer_width = "64")] | ||
impl_Integer!(I64_MAX_LEN => isize, U64_MAX_LEN => usize as u64); | ||
|
||
macro_rules! impl_Integer128 { | ||
($($max_len:expr => $t:ident),*) => {$( | ||
impl Integer for $t {} | ||
|
||
impl private::Sealed for $t { | ||
type Buffer = [MaybeUninit<u8>; $max_len]; | ||
|
||
#[allow(unused_comparisons)] | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
fn write(self, buf: &mut [MaybeUninit<u8>; $max_len]) -> &str { | ||
let is_nonnegative = self >= 0; | ||
let n = if is_nonnegative { | ||
self as u128 | ||
} else { | ||
// Convert negative number to positive by summing 1 to its two's complement. | ||
(!(self as u128)).wrapping_add(1) | ||
}; | ||
let mut curr = buf.len() as isize; | ||
let buf_ptr = buf.as_mut_ptr() as *mut u8; | ||
|
||
// Divide by 10^19 which is the highest power less than 2^64. | ||
let (n, rem) = udiv128::udivmod_1e19(n); | ||
let buf1 = unsafe { buf_ptr.offset(curr - U64_MAX_LEN as isize) as *mut [MaybeUninit<u8>; U64_MAX_LEN] }; | ||
curr -= rem.write(unsafe { &mut *buf1 }).len() as isize; | ||
|
||
if n != 0 { | ||
// Memset the base10 leading zeros of rem. | ||
let target = buf.len() as isize - 19; | ||
unsafe { | ||
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize); | ||
} | ||
curr = target; | ||
|
||
// Divide by 10^19 again. | ||
let (n, rem) = udiv128::udivmod_1e19(n); | ||
let buf2 = unsafe { buf_ptr.offset(curr - U64_MAX_LEN as isize) as *mut [MaybeUninit<u8>; U64_MAX_LEN] }; | ||
curr -= rem.write(unsafe { &mut *buf2 }).len() as isize; | ||
|
||
if n != 0 { | ||
// Memset the leading zeros. | ||
let target = buf.len() as isize - 38; | ||
unsafe { | ||
ptr::write_bytes(buf_ptr.offset(target), b'0', (curr - target) as usize); | ||
} | ||
curr = target; | ||
|
||
// There is at most one digit left | ||
// because u128::MAX / 10^19 / 10^19 is 3. | ||
curr -= 1; | ||
unsafe { | ||
*buf_ptr.offset(curr) = (n as u8) + b'0'; | ||
} | ||
} | ||
} | ||
|
||
if !is_nonnegative { | ||
curr -= 1; | ||
unsafe { | ||
*buf_ptr.offset(curr) = b'-'; | ||
} | ||
} | ||
|
||
let len = buf.len() - curr as usize; | ||
let bytes = unsafe { slice::from_raw_parts(buf_ptr.offset(curr), len) }; | ||
unsafe { str::from_utf8_unchecked(bytes) } | ||
} | ||
} | ||
)*}; | ||
} | ||
|
||
const U128_MAX_LEN: usize = 39; | ||
const I128_MAX_LEN: usize = 40; | ||
|
||
impl_Integer128!(I128_MAX_LEN => i128, U128_MAX_LEN => u128); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#[cfg(feature = "no-panic")] | ||
use no_panic::no_panic; | ||
|
||
/// Multiply unsigned 128 bit integers, return upper 128 bits of the result | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
fn u128_mulhi(x: u128, y: u128) -> u128 { | ||
let x_lo = x as u64; | ||
let x_hi = (x >> 64) as u64; | ||
let y_lo = y as u64; | ||
let y_hi = (y >> 64) as u64; | ||
|
||
// handle possibility of overflow | ||
let carry = (x_lo as u128 * y_lo as u128) >> 64; | ||
let m = x_lo as u128 * y_hi as u128 + carry; | ||
let high1 = m >> 64; | ||
|
||
let m_lo = m as u64; | ||
let high2 = (x_hi as u128 * y_lo as u128 + m_lo as u128) >> 64; | ||
|
||
x_hi as u128 * y_hi as u128 + high1 + high2 | ||
} | ||
|
||
/// Divide `n` by 1e19 and return quotient and remainder | ||
/// | ||
/// Integer division algorithm is based on the following paper: | ||
/// | ||
/// T. Granlund and P. Montgomery, “Division by Invariant Integers Using Multiplication” | ||
/// in Proc. of the SIGPLAN94 Conference on Programming Language Design and | ||
/// Implementation, 1994, pp. 61–72 | ||
/// | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn udivmod_1e19(n: u128) -> (u128, u64) { | ||
let d = 10_000_000_000_000_000_000_u64; // 10^19 | ||
|
||
let quot = if n < 1 << 83 { | ||
((n >> 19) as u64 / (d >> 19)) as u128 | ||
} else { | ||
u128_mulhi(n, 156927543384667019095894735580191660403) >> 62 | ||
}; | ||
|
||
let rem = (n - quot * d as u128) as u64; | ||
debug_assert_eq!(quot, n / d as u128); | ||
debug_assert_eq!(rem as u128, n % d as u128); | ||
|
||
(quot, rem) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
#![allow(non_snake_case)] | ||
#![allow(clippy::cast_lossless)] | ||
|
||
macro_rules! test { | ||
($($name:ident($value:expr, $expected:expr))*) => { | ||
$( | ||
#[test] | ||
fn $name() { | ||
let mut buffer = itoa::Buffer::new(); | ||
let s = buffer.format($value); | ||
assert_eq!(s, $expected); | ||
} | ||
)* | ||
} | ||
} | ||
|
||
test! { | ||
test_u64_0(0u64, "0") | ||
test_u64_half(u32::max_value() as u64, "4294967295") | ||
test_u64_max(u64::max_value(), "18446744073709551615") | ||
test_i64_min(i64::min_value(), "-9223372036854775808") | ||
|
||
test_i16_0(0i16, "0") | ||
test_i16_min(i16::min_value(), "-32768") | ||
|
||
test_u128_0(0u128, "0") | ||
test_u128_max(u128::max_value(), "340282366920938463463374607431768211455") | ||
test_i128_min(i128::min_value(), "-170141183460469231731687303715884105728") | ||
test_i128_max(i128::max_value(), "170141183460469231731687303715884105727") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"files":{"Cargo.lock":"2154f7e5f06489f3b42120f2165661fd552aa03b3d1f4f0cfeb380acad362b22","Cargo.toml":"708b4546804f6c500c45fd048921400f4328fe8f7159e0491faa4a64e38a2b0f","LICENSE-APACHE":"62c7a1e35f56406896d7aa7ca52d0cc0d272ac022b5d2796e7d6905db8a3636a","LICENSE-BOOST":"c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566","README.md":"86f0a92cf076f4983f99926607ea272c9650a5996fa3921fc5ca5abceb0f18db","benches/bench.rs":"703521c8cb9c6959ee305776a9971d24754b6fff5c1737741be04f956a3692e8","examples/upstream_benchmark.rs":"f702d3598a8fac59134a8058ebf74ba90163b1f23ebbd6c5978a7bd8a888d357","src/buffer/mod.rs":"e32f3fa7e994ff704796e58e115c5258e94a79a184d1608864772f2f2f5274fc","src/common.rs":"cae347e97fc30c50a964f80425e8c3e69ece2b8ab81f9b81b9baa7fcec64a001","src/d2s.rs":"f2612785ebe510c935b979dc5f66f6b8c818ca8a4cf0364ce1fe1d41fea39592","src/d2s_full_table.rs":"9b0186acbc6d65dc55c17e16125be707a2bfb920d22b35d33234b4cc38566a36","src/d2s_intrinsics.rs":"bbf15472f4299942312e80a992cbc2f47f85f17ed193f24084534434dbfb26e7","src/d2s_small_table.rs":"db3bbe4002d816785b0ee233c330f19fa7002f31dab47dc6f67b266996fe3ae4","src/digit_table.rs":"02351ca54cb8cb3679f635115dd094f32fd91750e9f66103c1ee9ec3db507072","src/f2s.rs":"cb96f61d8c6c6c941803a7b629f2bf835e1a20ad9d3e5d3454a30ed3391c3515","src/f2s_intrinsics.rs":"97bab98093838e30c60f5135f54f5ccb039ff7d9f35553ac8e74437743ca47e2","src/lib.rs":"8020040eaf88b50ffe542691d4c5a63e9f07e8ae6893ab433ef86762655dc941","src/parse.rs":"7f8aa7e007caf5dcb03abdc4238157724bb742d0823a3b8a01646fa1f1129154","src/pretty/exponent.rs":"fa914ec63b3f86cbdaf7933d7c44e1bc1f93c1239a29a5f86934680a7e957570","src/pretty/mantissa.rs":"40cb00efe1c3fab559ab58389bd519d556548aa18fb261a90dd48138911d039b","src/pretty/mod.rs":"eb0a8c78019f55a1767943821340e8b1278455e0d88bb4f63f4bd3dde340e387","src/s2d.rs":"c804518a771654e3786bde2b776c56e94e198ce6d3fe1e4e5e2f2a9cb9e607e3","src/s2f.rs":"11d528931ce1a01a93f39efb3fe99fdc3041b41fefafb2efd6a338d2a12b628c","tests/common_test.rs":"599781a637d9b9756858aabfe5c38a0734a550debd3d94774f33792b7b3c8240","tests/d2s_intrinsics_test.rs":"15d11b70810bf04f33f8b185bf7f010a436a4edb47fa4648b1a036568c2c5d15","tests/d2s_table_test.rs":"819c39cc94e3462138d3be337d06e7334de126642d34bf1394e03d2df9c0c90c","tests/d2s_test.rs":"d72aaf37c76a4042ecc12b7d6faf844696016bb72bb20d142ecab3bd6c87e29f","tests/exhaustive.rs":"f475ed9008a2cd86ce95abb577a4b01e9fed23fc16f7e217ccffb3b834005fa0","tests/f2s_test.rs":"ad9e6fe46e712c488b876428c144c79bdff0349b41c57eee5506fc3c9c156624","tests/macros/mod.rs":"8e90a674b3960f9516cb38f4eea0e0981ff902c3b33572ebdb6c5528d3ffa72c","tests/s2d_test.rs":"75c3a1044881718db65e05f25c9f6e1d005392dddb2e8dafb799668bb6a9a5c3","tests/s2f_test.rs":"1ec06646cb65229bfe866ec913901a0d8d736668f30b812fc4b00136a43f5142"},"package":"f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"} |
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
# | ||
# When uploading crates to the registry Cargo will automatically | ||
# "normalize" Cargo.toml files for maximal compatibility | ||
# with all versions of Cargo and also rewrite `path` dependencies | ||
# to registry (e.g., crates.io) dependencies. | ||
# | ||
# If you are reading this file be aware that the original Cargo.toml | ||
# will likely look very different (and much more reasonable). | ||
# See Cargo.toml.orig for the original contents. | ||
|
||
[package] | ||
edition = "2018" | ||
rust-version = "1.36" | ||
name = "ryu" | ||
version = "1.0.18" | ||
authors = ["David Tolnay <dtolnay@gmail.com>"] | ||
build = false | ||
exclude = [ | ||
"build.rs", | ||
"performance.png", | ||
"chart/**", | ||
] | ||
autobins = false | ||
autoexamples = false | ||
autotests = false | ||
autobenches = false | ||
description = "Fast floating point to string conversion" | ||
documentation = "https://docs.rs/ryu" | ||
readme = "README.md" | ||
keywords = ["float"] | ||
categories = [ | ||
"value-formatting", | ||
"no-std", | ||
"no-std::no-alloc", | ||
] | ||
license = "Apache-2.0 OR BSL-1.0" | ||
repository = "https://github.com/dtolnay/ryu" | ||
|
||
[package.metadata.docs.rs] | ||
rustdoc-args = ["--generate-link-to-definition"] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[lib] | ||
name = "ryu" | ||
path = "src/lib.rs" | ||
doc-scrape-examples = false | ||
|
||
[[example]] | ||
name = "upstream_benchmark" | ||
path = "examples/upstream_benchmark.rs" | ||
|
||
[[test]] | ||
name = "s2f_test" | ||
path = "tests/s2f_test.rs" | ||
|
||
[[test]] | ||
name = "common_test" | ||
path = "tests/common_test.rs" | ||
|
||
[[test]] | ||
name = "s2d_test" | ||
path = "tests/s2d_test.rs" | ||
|
||
[[test]] | ||
name = "d2s_test" | ||
path = "tests/d2s_test.rs" | ||
|
||
[[test]] | ||
name = "f2s_test" | ||
path = "tests/f2s_test.rs" | ||
|
||
[[test]] | ||
name = "d2s_table_test" | ||
path = "tests/d2s_table_test.rs" | ||
|
||
[[test]] | ||
name = "exhaustive" | ||
path = "tests/exhaustive.rs" | ||
|
||
[[test]] | ||
name = "d2s_intrinsics_test" | ||
path = "tests/d2s_intrinsics_test.rs" | ||
|
||
[[bench]] | ||
name = "bench" | ||
path = "benches/bench.rs" | ||
|
||
[dependencies.no-panic] | ||
version = "0.1" | ||
optional = true | ||
|
||
[dev-dependencies.num_cpus] | ||
version = "1.8" | ||
|
||
[dev-dependencies.rand] | ||
version = "0.8" | ||
|
||
[dev-dependencies.rand_xorshift] | ||
version = "0.3" | ||
|
||
[features] | ||
small = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
Apache License | ||
Version 2.0, January 2004 | ||
http://www.apache.org/licenses/ | ||
|
||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
|
||
1. Definitions. | ||
|
||
"License" shall mean the terms and conditions for use, reproduction, | ||
and distribution as defined by Sections 1 through 9 of this document. | ||
|
||
"Licensor" shall mean the copyright owner or entity authorized by | ||
the copyright owner that is granting the License. | ||
|
||
"Legal Entity" shall mean the union of the acting entity and all | ||
other entities that control, are controlled by, or are under common | ||
control with that entity. For the purposes of this definition, | ||
"control" means (i) the power, direct or indirect, to cause the | ||
direction or management of such entity, whether by contract or | ||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
outstanding shares, or (iii) beneficial ownership of such entity. | ||
|
||
"You" (or "Your") shall mean an individual or Legal Entity | ||
exercising permissions granted by this License. | ||
|
||
"Source" form shall mean the preferred form for making modifications, | ||
including but not limited to software source code, documentation | ||
source, and configuration files. | ||
|
||
"Object" form shall mean any form resulting from mechanical | ||
transformation or translation of a Source form, including but | ||
not limited to compiled object code, generated documentation, | ||
and conversions to other media types. | ||
|
||
"Work" shall mean the work of authorship, whether in Source or | ||
Object form, made available under the License, as indicated by a | ||
copyright notice that is included in or attached to the work | ||
(an example is provided in the Appendix below). | ||
|
||
"Derivative Works" shall mean any work, whether in Source or Object | ||
form, that is based on (or derived from) the Work and for which the | ||
editorial revisions, annotations, elaborations, or other modifications | ||
represent, as a whole, an original work of authorship. For the purposes | ||
of this License, Derivative Works shall not include works that remain | ||
separable from, or merely link (or bind by name) to the interfaces of, | ||
the Work and Derivative Works thereof. | ||
|
||
"Contribution" shall mean any work of authorship, including | ||
the original version of the Work and any modifications or additions | ||
to that Work or Derivative Works thereof, that is intentionally | ||
submitted to Licensor for inclusion in the Work by the copyright owner | ||
or by an individual or Legal Entity authorized to submit on behalf of | ||
the copyright owner. For the purposes of this definition, "submitted" | ||
means any form of electronic, verbal, or written communication sent | ||
to the Licensor or its representatives, including but not limited to | ||
communication on electronic mailing lists, source code control systems, | ||
and issue tracking systems that are managed by, or on behalf of, the | ||
Licensor for the purpose of discussing and improving the Work, but | ||
excluding communication that is conspicuously marked or otherwise | ||
designated in writing by the copyright owner as "Not a Contribution." | ||
|
||
"Contributor" shall mean Licensor and any individual or Legal Entity | ||
on behalf of whom a Contribution has been received by Licensor and | ||
subsequently incorporated within the Work. | ||
|
||
2. Grant of Copyright License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
copyright license to reproduce, prepare Derivative Works of, | ||
publicly display, publicly perform, sublicense, and distribute the | ||
Work and such Derivative Works in Source or Object form. | ||
|
||
3. Grant of Patent License. Subject to the terms and conditions of | ||
this License, each Contributor hereby grants to You a perpetual, | ||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
(except as stated in this section) patent license to make, have made, | ||
use, offer to sell, sell, import, and otherwise transfer the Work, | ||
where such license applies only to those patent claims licensable | ||
by such Contributor that are necessarily infringed by their | ||
Contribution(s) alone or by combination of their Contribution(s) | ||
with the Work to which such Contribution(s) was submitted. If You | ||
institute patent litigation against any entity (including a | ||
cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
or a Contribution incorporated within the Work constitutes direct | ||
or contributory patent infringement, then any patent licenses | ||
granted to You under this License for that Work shall terminate | ||
as of the date such litigation is filed. | ||
|
||
4. Redistribution. You may reproduce and distribute copies of the | ||
Work or Derivative Works thereof in any medium, with or without | ||
modifications, and in Source or Object form, provided that You | ||
meet the following conditions: | ||
|
||
(a) You must give any other recipients of the Work or | ||
Derivative Works a copy of this License; and | ||
|
||
(b) You must cause any modified files to carry prominent notices | ||
stating that You changed the files; and | ||
|
||
(c) You must retain, in the Source form of any Derivative Works | ||
that You distribute, all copyright, patent, trademark, and | ||
attribution notices from the Source form of the Work, | ||
excluding those notices that do not pertain to any part of | ||
the Derivative Works; and | ||
|
||
(d) If the Work includes a "NOTICE" text file as part of its | ||
distribution, then any Derivative Works that You distribute must | ||
include a readable copy of the attribution notices contained | ||
within such NOTICE file, excluding those notices that do not | ||
pertain to any part of the Derivative Works, in at least one | ||
of the following places: within a NOTICE text file distributed | ||
as part of the Derivative Works; within the Source form or | ||
documentation, if provided along with the Derivative Works; or, | ||
within a display generated by the Derivative Works, if and | ||
wherever such third-party notices normally appear. The contents | ||
of the NOTICE file are for informational purposes only and | ||
do not modify the License. You may add Your own attribution | ||
notices within Derivative Works that You distribute, alongside | ||
or as an addendum to the NOTICE text from the Work, provided | ||
that such additional attribution notices cannot be construed | ||
as modifying the License. | ||
|
||
You may add Your own copyright statement to Your modifications and | ||
may provide additional or different license terms and conditions | ||
for use, reproduction, or distribution of Your modifications, or | ||
for any such Derivative Works as a whole, provided Your use, | ||
reproduction, and distribution of the Work otherwise complies with | ||
the conditions stated in this License. | ||
|
||
5. Submission of Contributions. Unless You explicitly state otherwise, | ||
any Contribution intentionally submitted for inclusion in the Work | ||
by You to the Licensor shall be under the terms and conditions of | ||
this License, without any additional terms or conditions. | ||
Notwithstanding the above, nothing herein shall supersede or modify | ||
the terms of any separate license agreement you may have executed | ||
with Licensor regarding such Contributions. | ||
|
||
6. Trademarks. This License does not grant permission to use the trade | ||
names, trademarks, service marks, or product names of the Licensor, | ||
except as required for reasonable and customary use in describing the | ||
origin of the Work and reproducing the content of the NOTICE file. | ||
|
||
7. Disclaimer of Warranty. Unless required by applicable law or | ||
agreed to in writing, Licensor provides the Work (and each | ||
Contributor provides its Contributions) on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
implied, including, without limitation, any warranties or conditions | ||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
PARTICULAR PURPOSE. You are solely responsible for determining the | ||
appropriateness of using or redistributing the Work and assume any | ||
risks associated with Your exercise of permissions under this License. | ||
|
||
8. Limitation of Liability. In no event and under no legal theory, | ||
whether in tort (including negligence), contract, or otherwise, | ||
unless required by applicable law (such as deliberate and grossly | ||
negligent acts) or agreed to in writing, shall any Contributor be | ||
liable to You for damages, including any direct, indirect, special, | ||
incidental, or consequential damages of any character arising as a | ||
result of this License or out of the use or inability to use the | ||
Work (including but not limited to damages for loss of goodwill, | ||
work stoppage, computer failure or malfunction, or any and all | ||
other commercial damages or losses), even if such Contributor | ||
has been advised of the possibility of such damages. | ||
|
||
9. Accepting Warranty or Additional Liability. While redistributing | ||
the Work or Derivative Works thereof, You may choose to offer, | ||
and charge a fee for, acceptance of support, warranty, indemnity, | ||
or other liability obligations and/or rights consistent with this | ||
License. However, in accepting such obligations, You may act only | ||
on Your own behalf and on Your sole responsibility, not on behalf | ||
of any other Contributor, and only if You agree to indemnify, | ||
defend, and hold each Contributor harmless for any liability | ||
incurred by, or claims asserted against, such Contributor by reason | ||
of your accepting any such warranty or additional liability. | ||
|
||
END OF TERMS AND CONDITIONS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
Boost Software License - Version 1.0 - August 17th, 2003 | ||
|
||
Permission is hereby granted, free of charge, to any person or organization | ||
obtaining a copy of the software and accompanying documentation covered by | ||
this license (the "Software") to use, reproduce, display, distribute, | ||
execute, and transmit the Software, and to prepare derivative works of the | ||
Software, and to permit third-parties to whom the Software is furnished to | ||
do so, all subject to the following: | ||
|
||
The copyright notices in the Software and this entire statement, including | ||
the above license grant, this restriction and the following disclaimer, | ||
must be included in all copies of the Software, in whole or in part, and | ||
all derivative works of the Software, unless such copies or derivative | ||
works are solely in the form of machine-executable object code generated by | ||
a source language processor. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT | ||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE | ||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, | ||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Ryū | ||
|
||
[<img alt="github" src="https://img.shields.io/badge/github-dtolnay/ryu-8da0cb?style=for-the-badge&labelColor=555555&logo=github" height="20">](https://github.com/dtolnay/ryu) | ||
[<img alt="crates.io" src="https://img.shields.io/crates/v/ryu.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/ryu) | ||
[<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-ryu-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/ryu) | ||
[<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/dtolnay/ryu/ci.yml?branch=master&style=for-the-badge" height="20">](https://github.com/dtolnay/ryu/actions?query=branch%3Amaster) | ||
|
||
Pure Rust implementation of Ryū, an algorithm to quickly convert floating point | ||
numbers to decimal strings. | ||
|
||
The PLDI'18 paper [*Ryū: fast float-to-string conversion*][paper] by Ulf Adams | ||
includes a complete correctness proof of the algorithm. The paper is available | ||
under the creative commons CC-BY-SA license. | ||
|
||
This Rust implementation is a line-by-line port of Ulf Adams' implementation in | ||
C, [https://github.com/ulfjack/ryu][upstream]. | ||
|
||
*Requirements: this crate supports any compiler version back to rustc 1.36; it | ||
uses nothing from the Rust standard library so is usable from no_std crates.* | ||
|
||
[paper]: https://dl.acm.org/citation.cfm?id=3192369 | ||
[upstream]: https://github.com/ulfjack/ryu/tree/77e767f5e056bab96e895072fc21618ecff2f44b | ||
|
||
```toml | ||
[dependencies] | ||
ryu = "1.0" | ||
``` | ||
|
||
<br> | ||
|
||
## Example | ||
|
||
```rust | ||
fn main() { | ||
let mut buffer = ryu::Buffer::new(); | ||
let printed = buffer.format(1.234); | ||
assert_eq!(printed, "1.234"); | ||
} | ||
``` | ||
|
||
<br> | ||
|
||
## Performance (lower is better) | ||
|
||
![performance](https://raw.githubusercontent.com/dtolnay/ryu/master/performance.png) | ||
|
||
You can run upstream's benchmarks with: | ||
|
||
```console | ||
$ git clone https://github.com/ulfjack/ryu c-ryu | ||
$ cd c-ryu | ||
$ bazel run -c opt //ryu/benchmark:ryu_benchmark | ||
``` | ||
|
||
And the same benchmark against our implementation with: | ||
|
||
```console | ||
$ git clone https://github.com/dtolnay/ryu rust-ryu | ||
$ cd rust-ryu | ||
$ cargo run --example upstream_benchmark --release | ||
``` | ||
|
||
These benchmarks measure the average time to print a 32-bit float and average | ||
time to print a 64-bit float, where the inputs are distributed as uniform random | ||
bit patterns 32 and 64 bits wide. | ||
|
||
The upstream C code, the unsafe direct Rust port, and the safe pretty Rust API | ||
all perform the same, taking around 21 nanoseconds to format a 32-bit float and | ||
31 nanoseconds to format a 64-bit float. | ||
|
||
There is also a Rust-specific benchmark comparing this implementation to the | ||
standard library which you can run with: | ||
|
||
```console | ||
$ cargo bench | ||
``` | ||
|
||
The benchmark shows Ryū approximately 2-5x faster than the standard library | ||
across a range of f32 and f64 inputs. Measurements are in nanoseconds per | ||
iteration; smaller is better. | ||
|
||
## Formatting | ||
|
||
This library tends to produce more human-readable output than the standard | ||
library's to\_string, which never uses scientific notation. Here are two | ||
examples: | ||
|
||
- *ryu:* 1.23e40, *std:* 12300000000000000000000000000000000000000 | ||
- *ryu:* 1.23e-40, *std:* 0.000000000000000000000000000000000000000123 | ||
|
||
Both libraries print short decimals such as 0.0000123 without scientific | ||
notation. | ||
|
||
<br> | ||
|
||
#### License | ||
|
||
<sup> | ||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version | ||
2.0</a> or <a href="LICENSE-BOOST">Boost Software License 1.0</a> at your | ||
option. | ||
</sup> | ||
|
||
<br> | ||
|
||
<sub> | ||
Unless you explicitly state otherwise, any contribution intentionally submitted | ||
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall | ||
be dual licensed as above, without any additional terms or conditions. | ||
</sub> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// cargo bench | ||
|
||
#![feature(test)] | ||
#![allow( | ||
clippy::approx_constant, | ||
clippy::excessive_precision, | ||
clippy::unreadable_literal | ||
)] | ||
|
||
extern crate test; | ||
|
||
use std::io::Write; | ||
use std::{f32, f64}; | ||
use test::{black_box, Bencher}; | ||
|
||
macro_rules! benches { | ||
($($name:ident($value:expr),)*) => { | ||
mod bench_ryu { | ||
use super::*; | ||
$( | ||
#[bench] | ||
fn $name(b: &mut Bencher) { | ||
let mut buf = ryu::Buffer::new(); | ||
|
||
b.iter(move || { | ||
let value = black_box($value); | ||
let formatted = buf.format_finite(value); | ||
black_box(formatted); | ||
}); | ||
} | ||
)* | ||
} | ||
|
||
mod bench_std_fmt { | ||
use super::*; | ||
$( | ||
#[bench] | ||
fn $name(b: &mut Bencher) { | ||
let mut buf = Vec::with_capacity(20); | ||
|
||
b.iter(|| { | ||
buf.clear(); | ||
let value = black_box($value); | ||
write!(&mut buf, "{}", value).unwrap(); | ||
black_box(buf.as_slice()); | ||
}); | ||
} | ||
)* | ||
} | ||
}; | ||
} | ||
|
||
benches! { | ||
bench_0_f64(0f64), | ||
bench_short_f64(0.1234f64), | ||
bench_e_f64(2.718281828459045f64), | ||
bench_max_f64(f64::MAX), | ||
bench_0_f32(0f32), | ||
bench_short_f32(0.1234f32), | ||
bench_e_f32(2.718281828459045f32), | ||
bench_max_f32(f32::MAX), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// cargo run --example upstream_benchmark --release | ||
|
||
use rand::{Rng, SeedableRng}; | ||
|
||
const SAMPLES: usize = 10000; | ||
const ITERATIONS: usize = 1000; | ||
|
||
struct MeanAndVariance { | ||
n: i64, | ||
mean: f64, | ||
m2: f64, | ||
} | ||
|
||
impl MeanAndVariance { | ||
fn new() -> Self { | ||
MeanAndVariance { | ||
n: 0, | ||
mean: 0.0, | ||
m2: 0.0, | ||
} | ||
} | ||
|
||
fn update(&mut self, x: f64) { | ||
self.n += 1; | ||
let d = x - self.mean; | ||
self.mean += d / self.n as f64; | ||
let d2 = x - self.mean; | ||
self.m2 += d * d2; | ||
} | ||
|
||
fn variance(&self) -> f64 { | ||
self.m2 / (self.n - 1) as f64 | ||
} | ||
|
||
fn stddev(&self) -> f64 { | ||
self.variance().sqrt() | ||
} | ||
} | ||
|
||
macro_rules! benchmark { | ||
($name:ident, $ty:ident) => { | ||
fn $name() -> usize { | ||
let mut rng = rand_xorshift::XorShiftRng::from_seed([123u8; 16]); | ||
let mut mv = MeanAndVariance::new(); | ||
let mut throwaway = 0; | ||
for _ in 0..SAMPLES { | ||
let f = loop { | ||
let f = $ty::from_bits(rng.gen()); | ||
if f.is_finite() { | ||
break f; | ||
} | ||
}; | ||
|
||
let t1 = std::time::SystemTime::now(); | ||
for _ in 0..ITERATIONS { | ||
throwaway += ryu::Buffer::new().format_finite(f).len(); | ||
} | ||
let duration = t1.elapsed().unwrap(); | ||
let nanos = duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64; | ||
mv.update(nanos as f64 / ITERATIONS as f64); | ||
} | ||
println!( | ||
"{:12} {:8.3} {:8.3}", | ||
concat!(stringify!($name), ":"), | ||
mv.mean, | ||
mv.stddev(), | ||
); | ||
throwaway | ||
} | ||
}; | ||
} | ||
|
||
benchmark!(pretty32, f32); | ||
benchmark!(pretty64, f64); | ||
|
||
fn main() { | ||
println!("{:>20}{:>9}", "Average", "Stddev"); | ||
let mut throwaway = 0; | ||
throwaway += pretty32(); | ||
throwaway += pretty64(); | ||
if std::env::var_os("ryu-benchmark").is_some() { | ||
// Prevent the compiler from optimizing the code away. | ||
println!("{}", throwaway); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
use crate::raw; | ||
use core::mem::MaybeUninit; | ||
use core::{slice, str}; | ||
#[cfg(feature = "no-panic")] | ||
use no_panic::no_panic; | ||
|
||
const NAN: &str = "NaN"; | ||
const INFINITY: &str = "inf"; | ||
const NEG_INFINITY: &str = "-inf"; | ||
|
||
/// Safe API for formatting floating point numbers to text. | ||
/// | ||
/// ## Example | ||
/// | ||
/// ``` | ||
/// let mut buffer = ryu::Buffer::new(); | ||
/// let printed = buffer.format_finite(1.234); | ||
/// assert_eq!(printed, "1.234"); | ||
/// ``` | ||
pub struct Buffer { | ||
bytes: [MaybeUninit<u8>; 24], | ||
} | ||
|
||
impl Buffer { | ||
/// This is a cheap operation; you don't need to worry about reusing buffers | ||
/// for efficiency. | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn new() -> Self { | ||
let bytes = [MaybeUninit::<u8>::uninit(); 24]; | ||
Buffer { bytes } | ||
} | ||
|
||
/// Print a floating point number into this buffer and return a reference to | ||
/// its string representation within the buffer. | ||
/// | ||
/// # Special cases | ||
/// | ||
/// This function formats NaN as the string "NaN", positive infinity as | ||
/// "inf", and negative infinity as "-inf" to match std::fmt. | ||
/// | ||
/// If your input is known to be finite, you may get better performance by | ||
/// calling the `format_finite` method instead of `format` to avoid the | ||
/// checks for special cases. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn format<F: Float>(&mut self, f: F) -> &str { | ||
if f.is_nonfinite() { | ||
f.format_nonfinite() | ||
} else { | ||
self.format_finite(f) | ||
} | ||
} | ||
|
||
/// Print a floating point number into this buffer and return a reference to | ||
/// its string representation within the buffer. | ||
/// | ||
/// # Special cases | ||
/// | ||
/// This function **does not** check for NaN or infinity. If the input | ||
/// number is not a finite float, the printed representation will be some | ||
/// correctly formatted but unspecified numerical value. | ||
/// | ||
/// Please check [`is_finite`] yourself before calling this function, or | ||
/// check [`is_nan`] and [`is_infinite`] and handle those cases yourself. | ||
/// | ||
/// [`is_finite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_finite | ||
/// [`is_nan`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan | ||
/// [`is_infinite`]: https://doc.rust-lang.org/std/primitive.f64.html#method.is_infinite | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
pub fn format_finite<F: Float>(&mut self, f: F) -> &str { | ||
unsafe { | ||
let n = f.write_to_ryu_buffer(self.bytes.as_mut_ptr() as *mut u8); | ||
debug_assert!(n <= self.bytes.len()); | ||
let slice = slice::from_raw_parts(self.bytes.as_ptr() as *const u8, n); | ||
str::from_utf8_unchecked(slice) | ||
} | ||
} | ||
} | ||
|
||
impl Copy for Buffer {} | ||
|
||
impl Clone for Buffer { | ||
#[inline] | ||
#[allow(clippy::non_canonical_clone_impl)] // false positive https://github.com/rust-lang/rust-clippy/issues/11072 | ||
fn clone(&self) -> Self { | ||
Buffer::new() | ||
} | ||
} | ||
|
||
impl Default for Buffer { | ||
#[inline] | ||
#[cfg_attr(feature = "no-panic", no_panic)] | ||
fn default() -> Self { | ||
Buffer::new() | ||
} | ||
} | ||
|
||
/// A floating point number, f32 or f64, that can be written into a | ||
/// [`ryu::Buffer`][Buffer]. | ||
/// | ||
/// This trait is sealed and cannot be implemented for types outside of the | ||
/// `ryu` crate. | ||
pub trait Float: Sealed {} | ||
impl Float for f32 {} | ||
impl Float for f64 {} | ||
|
||
pub trait Sealed: Copy { | ||
fn is_nonfinite(self) -> bool; | ||
fn format_nonfinite(self) -> &'static str; | ||
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize; | ||
} | ||
|
||
impl Sealed for f32 { | ||
#[inline] | ||
fn is_nonfinite(self) -> bool { | ||
const EXP_MASK: u32 = 0x7f800000; | ||
let bits = self.to_bits(); | ||
bits & EXP_MASK == EXP_MASK | ||
} | ||
|
||
#[cold] | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
fn format_nonfinite(self) -> &'static str { | ||
const MANTISSA_MASK: u32 = 0x007fffff; | ||
const SIGN_MASK: u32 = 0x80000000; | ||
let bits = self.to_bits(); | ||
if bits & MANTISSA_MASK != 0 { | ||
NAN | ||
} else if bits & SIGN_MASK != 0 { | ||
NEG_INFINITY | ||
} else { | ||
INFINITY | ||
} | ||
} | ||
|
||
#[inline] | ||
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { | ||
raw::format32(self, result) | ||
} | ||
} | ||
|
||
impl Sealed for f64 { | ||
#[inline] | ||
fn is_nonfinite(self) -> bool { | ||
const EXP_MASK: u64 = 0x7ff0000000000000; | ||
let bits = self.to_bits(); | ||
bits & EXP_MASK == EXP_MASK | ||
} | ||
|
||
#[cold] | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
fn format_nonfinite(self) -> &'static str { | ||
const MANTISSA_MASK: u64 = 0x000fffffffffffff; | ||
const SIGN_MASK: u64 = 0x8000000000000000; | ||
let bits = self.to_bits(); | ||
if bits & MANTISSA_MASK != 0 { | ||
NAN | ||
} else if bits & SIGN_MASK != 0 { | ||
NEG_INFINITY | ||
} else { | ||
INFINITY | ||
} | ||
} | ||
|
||
#[inline] | ||
unsafe fn write_to_ryu_buffer(self, result: *mut u8) -> usize { | ||
raw::format64(self, result) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
// Translated from C to Rust. The original C code can be found at | ||
// https://github.com/ulfjack/ryu and carries the following license: | ||
// | ||
// Copyright 2018 Ulf Adams | ||
// | ||
// The contents of this file may be used under the terms of the Apache License, | ||
// Version 2.0. | ||
// | ||
// (See accompanying file LICENSE-Apache or copy at | ||
// http://www.apache.org/licenses/LICENSE-2.0) | ||
// | ||
// Alternatively, the contents of this file may be used under the terms of | ||
// the Boost Software License, Version 1.0. | ||
// (See accompanying file LICENSE-Boost or copy at | ||
// https://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
// Unless required by applicable law or agreed to in writing, this software | ||
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
// KIND, either express or implied. | ||
|
||
// Returns the number of decimal digits in v, which must not contain more than 9 | ||
// digits. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn decimal_length9(v: u32) -> u32 { | ||
// Function precondition: v is not a 10-digit number. | ||
// (f2s: 9 digits are sufficient for round-tripping.) | ||
debug_assert!(v < 1000000000); | ||
|
||
if v >= 100000000 { | ||
9 | ||
} else if v >= 10000000 { | ||
8 | ||
} else if v >= 1000000 { | ||
7 | ||
} else if v >= 100000 { | ||
6 | ||
} else if v >= 10000 { | ||
5 | ||
} else if v >= 1000 { | ||
4 | ||
} else if v >= 100 { | ||
3 | ||
} else if v >= 10 { | ||
2 | ||
} else { | ||
1 | ||
} | ||
} | ||
|
||
// Returns e == 0 ? 1 : [log_2(5^e)]; requires 0 <= e <= 3528. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
#[allow(dead_code)] | ||
pub fn log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ { | ||
// This approximation works up to the point that the multiplication | ||
// overflows at e = 3529. If the multiplication were done in 64 bits, it | ||
// would fail at 5^4004 which is just greater than 2^9297. | ||
debug_assert!(e >= 0); | ||
debug_assert!(e <= 3528); | ||
((e as u32 * 1217359) >> 19) as i32 | ||
} | ||
|
||
// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn pow5bits(e: i32) -> i32 /* or u32 -> u32 */ { | ||
// This approximation works up to the point that the multiplication | ||
// overflows at e = 3529. If the multiplication were done in 64 bits, it | ||
// would fail at 5^4004 which is just greater than 2^9297. | ||
debug_assert!(e >= 0); | ||
debug_assert!(e <= 3528); | ||
(((e as u32 * 1217359) >> 19) + 1) as i32 | ||
} | ||
|
||
#[cfg_attr(feature = "no-panic", inline)] | ||
#[allow(dead_code)] | ||
pub fn ceil_log2_pow5(e: i32) -> i32 /* or u32 -> u32 */ { | ||
log2_pow5(e) + 1 | ||
} | ||
|
||
// Returns floor(log_10(2^e)); requires 0 <= e <= 1650. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn log10_pow2(e: i32) -> u32 /* or u32 -> u32 */ { | ||
// The first value this approximation fails for is 2^1651 which is just greater than 10^297. | ||
debug_assert!(e >= 0); | ||
debug_assert!(e <= 1650); | ||
(e as u32 * 78913) >> 18 | ||
} | ||
|
||
// Returns floor(log_10(5^e)); requires 0 <= e <= 2620. | ||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn log10_pow5(e: i32) -> u32 /* or u32 -> u32 */ { | ||
// The first value this approximation fails for is 5^2621 which is just greater than 10^1832. | ||
debug_assert!(e >= 0); | ||
debug_assert!(e <= 2620); | ||
(e as u32 * 732923) >> 20 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,302 @@ | ||
// Translated from C to Rust. The original C code can be found at | ||
// https://github.com/ulfjack/ryu and carries the following license: | ||
// | ||
// Copyright 2018 Ulf Adams | ||
// | ||
// The contents of this file may be used under the terms of the Apache License, | ||
// Version 2.0. | ||
// | ||
// (See accompanying file LICENSE-Apache or copy at | ||
// http://www.apache.org/licenses/LICENSE-2.0) | ||
// | ||
// Alternatively, the contents of this file may be used under the terms of | ||
// the Boost Software License, Version 1.0. | ||
// (See accompanying file LICENSE-Boost or copy at | ||
// https://www.boost.org/LICENSE_1_0.txt) | ||
// | ||
// Unless required by applicable law or agreed to in writing, this software | ||
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
// KIND, either express or implied. | ||
|
||
use crate::common::{log10_pow2, log10_pow5, pow5bits}; | ||
#[cfg(not(feature = "small"))] | ||
pub use crate::d2s_full_table::{DOUBLE_POW5_INV_SPLIT, DOUBLE_POW5_SPLIT}; | ||
use crate::d2s_intrinsics::{ | ||
div10, div100, div5, mul_shift_all_64, multiple_of_power_of_2, multiple_of_power_of_5, | ||
}; | ||
#[cfg(feature = "small")] | ||
pub use crate::d2s_small_table::{compute_inv_pow5, compute_pow5}; | ||
use core::mem::MaybeUninit; | ||
|
||
pub const DOUBLE_MANTISSA_BITS: u32 = 52; | ||
pub const DOUBLE_EXPONENT_BITS: u32 = 11; | ||
pub const DOUBLE_BIAS: i32 = 1023; | ||
pub const DOUBLE_POW5_INV_BITCOUNT: i32 = 125; | ||
pub const DOUBLE_POW5_BITCOUNT: i32 = 125; | ||
|
||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn decimal_length17(v: u64) -> u32 { | ||
// This is slightly faster than a loop. | ||
// The average output length is 16.38 digits, so we check high-to-low. | ||
// Function precondition: v is not an 18, 19, or 20-digit number. | ||
// (17 digits are sufficient for round-tripping.) | ||
debug_assert!(v < 100000000000000000); | ||
|
||
if v >= 10000000000000000 { | ||
17 | ||
} else if v >= 1000000000000000 { | ||
16 | ||
} else if v >= 100000000000000 { | ||
15 | ||
} else if v >= 10000000000000 { | ||
14 | ||
} else if v >= 1000000000000 { | ||
13 | ||
} else if v >= 100000000000 { | ||
12 | ||
} else if v >= 10000000000 { | ||
11 | ||
} else if v >= 1000000000 { | ||
10 | ||
} else if v >= 100000000 { | ||
9 | ||
} else if v >= 10000000 { | ||
8 | ||
} else if v >= 1000000 { | ||
7 | ||
} else if v >= 100000 { | ||
6 | ||
} else if v >= 10000 { | ||
5 | ||
} else if v >= 1000 { | ||
4 | ||
} else if v >= 100 { | ||
3 | ||
} else if v >= 10 { | ||
2 | ||
} else { | ||
1 | ||
} | ||
} | ||
|
||
// A floating decimal representing m * 10^e. | ||
pub struct FloatingDecimal64 { | ||
pub mantissa: u64, | ||
// Decimal exponent's range is -324 to 308 | ||
// inclusive, and can fit in i16 if needed. | ||
pub exponent: i32, | ||
} | ||
|
||
#[cfg_attr(feature = "no-panic", inline)] | ||
pub fn d2d(ieee_mantissa: u64, ieee_exponent: u32) -> FloatingDecimal64 { | ||
let (e2, m2) = if ieee_exponent == 0 { | ||
( | ||
// We subtract 2 so that the bounds computation has 2 additional bits. | ||
1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2, | ||
ieee_mantissa, | ||
) | ||
} else { | ||
( | ||
ieee_exponent as i32 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS as i32 - 2, | ||
(1u64 << DOUBLE_MANTISSA_BITS) | ieee_mantissa, | ||
) | ||
}; | ||
let even = (m2 & 1) == 0; | ||
let accept_bounds = even; | ||
|
||
// Step 2: Determine the interval of valid decimal representations. | ||
let mv = 4 * m2; | ||
// Implicit bool -> int conversion. True is 1, false is 0. | ||
let mm_shift = (ieee_mantissa != 0 || ieee_exponent <= 1) as u32; | ||
// We would compute mp and mm like this: | ||
// uint64_t mp = 4 * m2 + 2; | ||
// uint64_t mm = mv - 1 - mm_shift; | ||
|
||
// Step 3: Convert to a decimal power base using 128-bit arithmetic. | ||
let mut vr: u64; | ||
let mut vp: u64; | ||
let mut vm: u64; | ||
let mut vp_uninit: MaybeUninit<u64> = MaybeUninit::uninit(); | ||
let mut vm_uninit: MaybeUninit<u64> = MaybeUninit::uninit(); | ||
let e10: i32; | ||
let mut vm_is_trailing_zeros = false; | ||
let mut vr_is_trailing_zeros = false; | ||
if e2 >= 0 { | ||
// I tried special-casing q == 0, but there was no effect on performance. | ||
// This expression is slightly faster than max(0, log10_pow2(e2) - 1). | ||
let q = log10_pow2(e2) - (e2 > 3) as u32; | ||
e10 = q as i32; | ||
let k = DOUBLE_POW5_INV_BITCOUNT + pow5bits(q as i32) - 1; | ||
let i = -e2 + q as i32 + k; | ||
vr = unsafe { | ||
mul_shift_all_64( | ||
m2, | ||
#[cfg(feature = "small")] | ||
&compute_inv_pow5(q), | ||
#[cfg(not(feature = "small"))] | ||
{ | ||
debug_assert!(q < DOUBLE_POW5_INV_SPLIT.len() as u32); | ||
DOUBLE_POW5_INV_SPLIT.get_unchecked(q as usize) | ||
}, | ||
i as u32, | ||
vp_uninit.as_mut_ptr(), | ||
vm_uninit.as_mut_ptr(), | ||
mm_shift, | ||
) | ||
}; | ||
vp = unsafe { vp_uninit.assume_init() }; | ||
vm = unsafe { vm_uninit.assume_init() }; | ||
if q <= 21 { | ||
// This should use q <= 22, but I think 21 is also safe. Smaller values | ||
// may still be safe, but it's more difficult to reason about them. | ||
// Only one of mp, mv, and mm can be a multiple of 5, if any. | ||
let mv_mod5 = (mv as u32).wrapping_sub(5u32.wrapping_mul(div5(mv) as u32)); | ||
if mv_mod5 == 0 { | ||
vr_is_trailing_zeros = multiple_of_power_of_5(mv, q); | ||
} else if accept_bounds { | ||
// Same as min(e2 + (~mm & 1), pow5_factor(mm)) >= q | ||
// <=> e2 + (~mm & 1) >= q && pow5_factor(mm) >= q | ||
// <=> true && pow5_factor(mm) >= q, since e2 >= q. | ||
vm_is_trailing_zeros = multiple_of_power_of_5(mv - 1 - mm_shift as u64, q); | ||
} else { | ||
// Same as min(e2 + 1, pow5_factor(mp)) >= q. | ||
vp -= multiple_of_power_of_5(mv + 2, q) as u64; | ||
} | ||
} | ||
} else { | ||
// This expression is slightly faster than max(0, log10_pow5(-e2) - 1). | ||
let q = log10_pow5(-e2) - (-e2 > 1) as u32; | ||
e10 = q as i32 + e2; | ||
let i = -e2 - q as i32; | ||
let k = pow5bits(i) - DOUBLE_POW5_BITCOUNT; | ||
let j = q as i32 - k; | ||
vr = unsafe { | ||
mul_shift_all_64( | ||
m2, | ||
#[cfg(feature = "small")] | ||
&compute_pow5(i as u32), | ||
#[cfg(not(feature = "small"))] | ||
{ | ||
debug_assert!(i < DOUBLE_POW5_SPLIT.len() as i32); | ||
DOUBLE_POW5_SPLIT.get_unchecked(i as usize) | ||
}, | ||
j as u32, | ||
vp_uninit.as_mut_ptr(), | ||
vm_uninit.as_mut_ptr(), | ||
mm_shift, | ||
) | ||
}; | ||
vp = unsafe { vp_uninit.assume_init() }; | ||
vm = unsafe { vm_uninit.assume_init() }; | ||
if q <= 1 { | ||
// {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. | ||
// mv = 4 * m2, so it always has at least two trailing 0 bits. | ||
vr_is_trailing_zeros = true; | ||
if accept_bounds { | ||
// mm = mv - 1 - mm_shift, so it has 1 trailing 0 bit iff mm_shift == 1. | ||
vm_is_trailing_zeros = mm_shift == 1; | ||
} else { | ||
// mp = mv + 2, so it always has at least one trailing 0 bit. | ||
vp -= 1; | ||
} | ||
} else if q < 63 { | ||
// TODO(ulfjack): Use a tighter bound here. | ||
// We want to know if the full product has at least q trailing zeros. | ||
// We need to compute min(p2(mv), p5(mv) - e2) >= q | ||
// <=> p2(mv) >= q && p5(mv) - e2 >= q | ||
// <=> p2(mv) >= q (because -e2 >= q) | ||
vr_is_trailing_zeros = multiple_of_power_of_2(mv, q); | ||
} | ||
} | ||
|
||
// Step 4: Find the shortest decimal representation in the interval of valid representations. | ||
let mut removed = 0i32; | ||
let mut last_removed_digit = 0u8; | ||
// On average, we remove ~2 digits. | ||
let output = if vm_is_trailing_zeros || vr_is_trailing_zeros { | ||
// General case, which happens rarely (~0.7%). | ||
loop { | ||
let vp_div10 = div10(vp); | ||
let vm_div10 = div10(vm); | ||
if vp_div10 <= vm_div10 { | ||
break; | ||
} | ||
let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32)); | ||
let vr_div10 = div10(vr); | ||
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); | ||
vm_is_trailing_zeros &= vm_mod10 == 0; | ||
vr_is_trailing_zeros &= last_removed_digit == 0; | ||
last_removed_digit = vr_mod10 as u8; | ||
vr = vr_div10; | ||
vp = vp_div10; | ||
vm = vm_div10; | ||
removed += 1; | ||
} | ||
if vm_is_trailing_zeros { | ||
loop { | ||
let vm_div10 = div10(vm); | ||
let vm_mod10 = (vm as u32).wrapping_sub(10u32.wrapping_mul(vm_div10 as u32)); | ||
if vm_mod10 != 0 { | ||
break; | ||
} | ||
let vp_div10 = div10(vp); | ||
let vr_div10 = div10(vr); | ||
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); | ||
vr_is_trailing_zeros &= last_removed_digit == 0; | ||
last_removed_digit = vr_mod10 as u8; | ||
vr = vr_div10; | ||
vp = vp_div10; | ||
vm = vm_div10; | ||
removed += 1; | ||
} | ||
} | ||
if vr_is_trailing_zeros && last_removed_digit == 5 && vr % 2 == 0 { | ||
// Round even if the exact number is .....50..0. | ||
last_removed_digit = 4; | ||
} | ||
// We need to take vr + 1 if vr is outside bounds or we need to round up. | ||
vr + ((vr == vm && (!accept_bounds || !vm_is_trailing_zeros)) || last_removed_digit >= 5) | ||
as u64 | ||
} else { | ||
// Specialized for the common case (~99.3%). Percentages below are relative to this. | ||
let mut round_up = false; | ||
let vp_div100 = div100(vp); | ||
let vm_div100 = div100(vm); | ||
// Optimization: remove two digits at a time (~86.2%). | ||
if vp_div100 > vm_div100 { | ||
let vr_div100 = div100(vr); | ||
let vr_mod100 = (vr as u32).wrapping_sub(100u32.wrapping_mul(vr_div100 as u32)); | ||
round_up = vr_mod100 >= 50; | ||
vr = vr_div100; | ||
vp = vp_div100; | ||
vm = vm_div100; | ||
removed += 2; | ||
} | ||
// Loop iterations below (approximately), without optimization above: | ||
// 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% | ||
// Loop iterations below (approximately), with optimization above: | ||
// 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% | ||
loop { | ||
let vp_div10 = div10(vp); | ||
let vm_div10 = div10(vm); | ||
if vp_div10 <= vm_div10 { | ||
break; | ||
} | ||
let vr_div10 = div10(vr); | ||
let vr_mod10 = (vr as u32).wrapping_sub(10u32.wrapping_mul(vr_div10 as u32)); | ||
round_up = vr_mod10 >= 5; | ||
vr = vr_div10; | ||
vp = vp_div10; | ||
vm = vm_div10; | ||
removed += 1; | ||
} | ||
// We need to take vr + 1 if vr is outside bounds or we need to round up. | ||
vr + (vr == vm || round_up) as u64 | ||
}; | ||
let exp = e10 + removed; | ||
|
||
FloatingDecimal64 { | ||
exponent: exp, | ||
mantissa: output, | ||
} | ||
} |
Oops, something went wrong.