base62-2.2.4/.cargo_vcs_info.json0000644000000001361046102023000121450ustar { "git": { "sha1": "2694f51fc093e93f9d69092d75cdf9885f1e87ef" }, "path_in_vcs": "" }base62-2.2.4/.gitignore000064400000000000000000000000221046102023000126750ustar 00000000000000target Cargo.lock base62-2.2.4/Cargo.lock0000644000000323351046102023000101260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base62" version = "2.2.4" dependencies = [ "criterion", "quickcheck", "rand 0.9.2", ] [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" [[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", "serde", ] [[package]] name = "ciborium-io" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", ] [[package]] name = "clap" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" version = "4.5.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" dependencies = [ "anstyle", "clap_lex", ] [[package]] name = "clap_lex" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "criterion" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", "itertools", "num-traits", "oorandom", "regex", "serde", "serde_json", "tinytemplate", "walkdir", ] [[package]] name = "criterion-plot" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", "itertools", ] [[package]] name = "crunchy" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "env_logger" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] name = "getrandom" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", "r-efi", "wasi 0.14.7+wasi-0.2.4", ] [[package]] name = "half" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger", "log", "rand 0.8.5", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha", "rand_core 0.9.3", ] [[package]] name = "rand_chacha" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core 0.9.3", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.16", ] [[package]] name = "rand_core" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.3", ] [[package]] name = "regex" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "serde" version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.225" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tinytemplate" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ "serde", "serde_json", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" dependencies = [ "wasip2", ] [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen", ] [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] name = "windows-link" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" [[package]] name = "windows-sys" version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" dependencies = [ "windows-link", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "zerocopy" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", "syn", ] base62-2.2.4/Cargo.toml0000644000000027131046102023000101460ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "base62" version = "2.2.4" authors = [ "François Bernier ", "Chai T. Rex ", ] build = false exclude = [".github/*"] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A Base62 encoding/decoding library" homepage = "https://github.com/fbernier/base62" documentation = "https://docs.rs/base62/" readme = "README.md" keywords = [ "base62", "encoding", "decoding", "url", "shortener", ] license = "MIT" repository = "https://github.com/fbernier/base62" [features] alloc = [] default = ["alloc"] std = ["alloc"] [lib] name = "base62" path = "src/lib.rs" bench = false [[bench]] name = "base62" path = "benches/base62.rs" harness = false required-features = ["alloc"] [dependencies] [dev-dependencies.criterion] version = "0.7" default-features = false [dev-dependencies.quickcheck] version = "1" [dev-dependencies.rand] version = "0.9" base62-2.2.4/Cargo.toml.orig000064400000000000000000000014161046102023000136040ustar 00000000000000[package] name = "base62" version = "2.2.4" authors = [ "François Bernier ", "Chai T. Rex ", ] edition = "2021" description = "A Base62 encoding/decoding library" documentation = "https://docs.rs/base62/" homepage = "https://github.com/fbernier/base62" repository = "https://github.com/fbernier/base62" readme = "README.md" keywords = ["base62", "encoding", "decoding", "url", "shortener"] license = "MIT" exclude = [ ".github/*", ] [features] default = ["alloc"] alloc = [] std = ["alloc"] [dependencies] [dev-dependencies] criterion = { version = "0.7", default-features = false } quickcheck = "1" rand = "0.9" [[bench]] name = "base62" harness = false required-features = ["alloc"] [lib] bench = false base62-2.2.4/LICENSE000064400000000000000000000020451046102023000117210ustar 00000000000000Copyright (c) 2015 François Bernier 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. base62-2.2.4/README.md000064400000000000000000000034241046102023000121750ustar 00000000000000# base62 A fast, zero-dependency base62 encoder/decoder library for Rust, typically used in URL shorteners. It supports both standard [0-9A-Za-z] and alternative [0-9a-zA-Z] variants. [![Build status](https://github.com/fbernier/base62/workflows/ci/badge.svg)](https://github.com/fbernier/base62/actions) [![Crates.io](https://img.shields.io/crates/v/base62.svg)](https://crates.io/crates/base62) [![Docs](https://docs.rs/base62/badge.svg)](https://docs.rs/base62) ## Features - `no_std` compatible with optional `alloc` and `std` support - Encodes integers up to `u128` - Zero-copy decoding - Efficient string handling - Two encoding variants: - Standard [0-9A-Za-z] - Alternative [0-9a-zA-Z] ## Usage Add this to your `Cargo.toml`: ```toml [dependencies] base62 = "2" ``` ### Basic Example ```rust use base62; // Encoding let encoded = base62::encode(1234567890); assert_eq!(encoded, "1LY7VK"); // Decoding let decoded = base62::decode("1LY7VK").unwrap(); assert_eq!(decoded, 1234567890); ``` ### No-std Usage The crate works in `no_std` environments by default: ```rust #![no_std] use base62; // Encode into a fixed buffer let mut buf = [0u8; 22]; // Maximum size needed for u128 let len = base62::encode_bytes(1234567890, &mut buf).unwrap(); assert_eq!(&buf[..len], b"1LY7VK"); // Decode from bytes let decoded = base62::decode(&buf[..len]).unwrap(); assert_eq!(decoded, 1234567890); ``` ## Feature Flags - `alloc`: Enables String allocation support (enabled by default) - `std`: Enables std::io traits support ## Performance The library is optimized for both encoding and decoding performance: - Zero-copy decoding - Efficient buffer management - Direct string manipulation for optimal performance when appending ## License Licensed under the MIT license. See LICENSE for details. base62-2.2.4/benches/base62.rs000064400000000000000000000232551046102023000137610ustar 00000000000000use base62::{ decode, decode_alternative, encode, encode_alternative, encode_alternative_buf, encode_alternative_bytes, encode_buf, encode_bytes, }; use std::hint::black_box; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use rand::distr::StandardUniform; use rand::{rng, Rng}; pub fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("decode"); // Fixed input benchmark group.bench_function("standard_fixed", |b| { b.iter(|| decode(black_box("7n42DGM5Tflk9n8mt7Fhc7"))) }); // Random input benchmark using iter_batched for setup group.bench_function("standard_random", |b| { b.iter_batched( || { // Setup (runs outside measured time) let random_num: u128 = rng().sample(StandardUniform); encode(random_num) }, decode, // Measured function BatchSize::SmallInput, ) }); group.bench_function("alternative_fixed", |b| { b.iter(|| decode_alternative(black_box("7N42dgm5tFLK9N8MT7fHC7"))) }); group.bench_function("alternative_random", |b| { b.iter_batched( || { // Setup (runs outside measured time) let random_num: u128 = rng().sample(StandardUniform); encode_alternative(random_num) }, decode_alternative, BatchSize::SmallInput, ) }); group.finish(); let mut group = c.benchmark_group("encode"); group.bench_function("standard_new_fixed", |b| { b.iter(|| encode(black_box(u128::MAX))) }); group.bench_function("standard_new_random", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u128| encode(black_box(num)), BatchSize::SmallInput, ) }); group.bench_function("standard_new_random_u64", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u64| encode(black_box(num)), BatchSize::SmallInput, ) }); group.bench_function("standard_bytes_fixed", |b| { let mut buf = [0; 22]; b.iter(|| encode_bytes(black_box(u128::MAX), black_box(&mut buf))) }); group.bench_function("standard_bytes_random", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u128| { let mut buf = [0; 22]; encode_bytes(black_box(num), black_box(&mut buf)) }, BatchSize::SmallInput, ) }); group.bench_function("standard_bytes_random_u64", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u64| { let mut buf = [0; 22]; encode_bytes(black_box(num), black_box(&mut buf)) }, BatchSize::SmallInput, ) }); group.bench_function("standard_buf_fixed", |b| { b.iter_batched_ref( || String::with_capacity(22), |buf| { buf.clear(); encode_buf(black_box(u128::MAX), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("standard_buf_random", |b| { b.iter_batched_ref( || { let num: u128 = rng().sample(StandardUniform); (num, String::with_capacity(22)) }, |(num, buf)| { buf.clear(); encode_buf(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("standard_buf_random_u64", |b| { b.iter_batched_ref( || { let num: u64 = rng().sample(StandardUniform); (num, String::with_capacity(11)) }, |(num, buf)| { buf.clear(); encode_buf(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_new_fixed", |b| { b.iter(|| encode_alternative(black_box(u128::MAX))) }); group.bench_function("alternative_new_random", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u128| encode_alternative(black_box(num)), BatchSize::SmallInput, ) }); group.bench_function("alternative_new_random_u64", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u64| encode_alternative(black_box(num)), BatchSize::SmallInput, ) }); group.bench_function("alternative_bytes_fixed", |b| { let mut buf = [0; 22]; b.iter(|| encode_alternative_bytes(black_box(u128::MAX), black_box(&mut buf))) }); group.bench_function("alternative_bytes_random", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u128| { let mut buf = [0; 22]; encode_alternative_bytes(black_box(num), black_box(&mut buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_bytes_random_u64", |b| { b.iter_batched( || rng().sample(StandardUniform), |num: u64| { let mut buf = [0; 11]; encode_alternative_bytes(black_box(num), black_box(&mut buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_buf_fixed", |b| { b.iter_batched_ref( || String::with_capacity(22), |buf| { buf.clear(); encode_alternative_buf(black_box(u128::MAX), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_buf_random", |b| { b.iter_batched_ref( || { let num: u128 = rng().sample(StandardUniform); (num, String::with_capacity(22)) }, |(num, buf)| { buf.clear(); encode_alternative_buf(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_buf_random_u64", |b| { b.iter_batched_ref( || { let num: u64 = rng().sample(StandardUniform); (num, String::with_capacity(11)) }, |(num, buf)| { buf.clear(); encode_alternative_buf(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); #[cfg(feature = "std")] { group.bench_function("standard_io_fixed", |b| { let mut buf = Vec::new(); b.iter(|| { buf.clear(); base62::encode_io(black_box(u128::MAX), black_box(&mut buf)) }) }); group.bench_function("standard_io_random", |b| { b.iter_batched_ref( || { let num: u128 = rng().sample(StandardUniform); (num, Vec::with_capacity(22)) }, |(num, buf)| { buf.clear(); base62::encode_io(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("standard_io_random_u64", |b| { b.iter_batched_ref( || { let num: u64 = rng().sample(StandardUniform); (num, Vec::with_capacity(11)) }, |(num, buf)| { buf.clear(); base62::encode_io(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_io_fixed", |b| { let mut buf = Vec::new(); b.iter(|| { buf.clear(); base62::encode_alternative_io(black_box(u128::MAX), black_box(&mut buf)) }) }); group.bench_function("alternative_io_random", |b| { b.iter_batched_ref( || { let num: u128 = rng().sample(StandardUniform); (num, Vec::with_capacity(22)) }, |(num, buf)| { buf.clear(); base62::encode_alternative_io(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); group.bench_function("alternative_io_random_u64", |b| { b.iter_batched_ref( || { let num: u64 = rng().sample(StandardUniform); (num, Vec::with_capacity(11)) }, |(num, buf)| { buf.clear(); base62::encode_alternative_io(black_box(*num), black_box(buf)) }, BatchSize::SmallInput, ) }); use std::io::BufWriter; group.bench_function("standard_io_bufwriter_random", |b| { b.iter_batched_ref( || { let num: u128 = rng().sample(StandardUniform); let vec = Vec::with_capacity(22); (num, BufWriter::new(vec)) }, |(num, buf_writer)| { *buf_writer = BufWriter::new(Vec::with_capacity(22)); base62::encode_io(black_box(*num), black_box(buf_writer)) }, BatchSize::SmallInput, ) }); } group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); base62-2.2.4/src/lib.rs000064400000000000000000001412701046102023000126230ustar 00000000000000/*! `base62` is a `no_std` crate that provides base62 encoding and decoding functionality. With the `alloc` feature (enabled by default), it provides functions for converting between `u128` values and base62 strings using both standard (0-9, A-Z, a-z) and alternative (0-9, a-z, A-Z) alphabets. When used without default features, it provides the core encoding and decoding primitives that operate on byte slices. For usage with the standard library's IO traits, enable the `std` feature. [![Build status](https://github.com/fbernier/base62/workflows/ci/badge.svg)](https://github.com/fbernier/base62/actions) [![Crates.io](https://img.shields.io/crates/v/base62.svg)](https://crates.io/crates/base62) [![Docs](https://docs.rs/base62/badge.svg)](https://docs.rs/base62) */ #![no_std] #[cfg(feature = "alloc")] extern crate alloc; #[cfg(feature = "std")] extern crate std; use core::{convert::TryInto, fmt, num::NonZeroU128}; const BASE: u64 = 62; const BASE_TO_2: u64 = BASE * BASE; const BASE_TO_3: u64 = BASE_TO_2 * BASE; const BASE_TO_4: u64 = BASE_TO_3 * BASE; const BASE_TO_5: u64 = BASE_TO_4 * BASE; const BASE_TO_6: u64 = BASE_TO_5 * BASE; const BASE_TO_7: u64 = BASE_TO_6 * BASE; const BASE_TO_8: u64 = BASE_TO_7 * BASE; const BASE_TO_9: u64 = BASE_TO_8 * BASE; const BASE_TO_10: u128 = (BASE_TO_9 * BASE) as u128; const BASE_TO_11: u128 = BASE_TO_10 * BASE as u128; const BASE_TO_12: u128 = BASE_TO_11 * BASE as u128; const BASE_TO_13: u128 = BASE_TO_12 * BASE as u128; const BASE_TO_14: u128 = BASE_TO_13 * BASE as u128; const BASE_TO_15: u128 = BASE_TO_14 * BASE as u128; const BASE_TO_16: u128 = BASE_TO_15 * BASE as u128; const BASE_TO_17: u128 = BASE_TO_16 * BASE as u128; const BASE_TO_18: u128 = BASE_TO_17 * BASE as u128; const BASE_TO_19: u128 = BASE_TO_18 * BASE as u128; const BASE_TO_20: u128 = BASE_TO_19 * BASE as u128; const BASE_TO_21: u128 = BASE_TO_20 * BASE as u128; // Rust does not apply strength reduction to integer division by u128s, instead it uses the very slow __udivti3 intrinsic. // This multiply and shift is equivalent to dividing by BASE_TO_10. const DIV_BASE_TO_10_MULTIPLY: u128 = 233718071534448225491982379416108680074; const DIV_BASE_TO_10_SHIFT: u8 = 59; struct StandardTables { encode_pairs: [[u8; 2]; BASE_TO_2 as usize], decode: [u8; 128], } struct AlternativeTables { encode_pairs: [[u8; 2]; BASE_TO_2 as usize], decode: [u8; 128], } impl StandardTables { const fn new() -> Self { // Standard encoding table (0-9A-Za-z) const ENCODE: [u8; 62] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', ]; // Generate pair table: index i represents (i / 62, i % 62) let mut encode_pairs = [[0u8; 2]; BASE_TO_2 as usize]; let mut i = 0usize; while i < BASE_TO_2 as usize { let hi = i / 62; let lo = i % 62; encode_pairs[i] = [ENCODE[hi], ENCODE[lo]]; i += 1; } let mut decode = [255u8; 128]; // Populate decode table let mut i = 0u8; while i < 10 { decode[(b'0' + i) as usize] = i; i += 1; } let mut i = 0u8; while i < 26 { decode[(b'A' + i) as usize] = i + 10; i += 1; } let mut i = 0u8; while i < 26 { decode[(b'a' + i) as usize] = i + 36; i += 1; } Self { encode_pairs, decode, } } } impl AlternativeTables { const fn new() -> Self { // Alternative encoding table (0-9a-zA-Z) const ENCODE: [u8; 62] = [ b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', ]; // Generate pair table: index i represents (i / 62, i % 62) let mut encode_pairs = [[0u8; 2]; BASE_TO_2 as usize]; let mut i = 0usize; while i < BASE_TO_2 as usize { let hi = i / 62; let lo = i % 62; encode_pairs[i] = [ENCODE[hi], ENCODE[lo]]; i += 1; } let mut decode = [255u8; 128]; // Populate decode table let mut i = 0u8; while i < 10 { decode[(b'0' + i) as usize] = i; i += 1; } let mut i = 0u8; while i < 26 { decode[(b'a' + i) as usize] = i + 10; i += 1; } let mut i = 0u8; while i < 26 { decode[(b'A' + i) as usize] = i + 36; i += 1; } Self { encode_pairs, decode, } } } static STANDARD_TABLES: StandardTables = StandardTables::new(); static ALTERNATIVE_TABLES: AlternativeTables = AlternativeTables::new(); /// Indicates the cause of a decoding failure in base62 decoding operations. #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum DecodeError { /// The decoded number cannot fit into a [`u128`]. ArithmeticOverflow, /// The encoded input is an empty string. EmptyInput, /// The encoded input contains the given invalid byte at the given index. InvalidBase62Byte(u8, usize), } /// Indicates the cause of an encoding failure in encoding operations. #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum EncodeError { BufferTooSmall, } impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { DecodeError::ArithmeticOverflow => { f.write_str("Decoded number cannot fit into a `u128`") } DecodeError::EmptyInput => f.write_str("Encoded input is an empty string"), DecodeError::InvalidBase62Byte(ch, idx) => { use fmt::Write; f.write_str("Encoded input contains the invalid byte b'")?; for char_in_escape in core::ascii::escape_default(ch) { f.write_char(char::from(char_in_escape))?; } write!(f, "' at index {idx}") } } } } // For DecodeError #[cfg(not(feature = "std"))] impl core::error::Error for DecodeError {} #[cfg(feature = "std")] impl std::error::Error for DecodeError {} // For EncodeError #[cfg(not(feature = "std"))] impl core::error::Error for EncodeError {} #[cfg(feature = "std")] impl std::error::Error for EncodeError {} impl fmt::Display for EncodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { EncodeError::BufferTooSmall => write!(f, "Buffer too small to encode the number"), } } } struct Fmt(u128); impl fmt::Display for Fmt { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut buf = [0u8; 22]; let digits = digit_count(self.0); unsafe { let len = _encode_buf(self.0, digits, &mut buf[..digits]); // Use pad() to handle formatting specifiers let s = core::str::from_utf8_unchecked(&buf[..len]); f.pad(s) } } } /// Writes the base62 representation of a number using the standard alphabet to any fmt::Write destination. /// /// # Example /// ```rust /// use core::fmt::Write; /// /// let mut output = String::new(); /// write!(output, "{}", base62::encode_fmt(1337_u32)); /// assert_eq!(output, "LZ"); /// ``` pub fn encode_fmt>(num: T) -> impl fmt::Display { Fmt(num.into()) } struct FmtAlternative(u128); impl fmt::Display for FmtAlternative { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut buf = [0u8; 22]; let digits = digit_count(self.0); unsafe { let len = _encode_alternative_buf(self.0, digits, &mut buf[..digits]); // Use pad() to handle formatting specifiers let s = core::str::from_utf8_unchecked(&buf[..len]); f.pad(s) } } } /// Writes the base62 representation of a number using the alternative alphabet to any fmt::Write destination. /// /// # Example /// ```rust /// use core::fmt::Write; /// /// let mut output = String::new(); /// write!(output, "{}", base62::encode_fmt_alt(1337_u32)); /// assert_eq!(output, "lz"); /// ``` pub fn encode_fmt_alt>(num: T) -> impl fmt::Display { FmtAlternative(num.into()) } /// Writes the base62 representation of a number using the standard alphabet to any io::Write destination. /// /// This function is only available when the `std` feature is enabled. /// /// # Example /// ```rust /// # use std::io::Write; /// let mut output = Vec::new(); /// base62::encode_io(1337_u32, &mut output).unwrap(); /// assert_eq!(output, b"LZ"); /// ``` #[cfg(feature = "std")] pub fn encode_io, W: std::io::Write + ?Sized>( num: T, w: &mut W, ) -> std::io::Result<()> { let num = num.into(); let digits = digit_count(num); let mut buf = core::mem::MaybeUninit::<[u8; 22]>::uninit(); unsafe { let ptr = buf.as_mut_ptr() as *mut u8; let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_buf(num, digits, slice); debug_assert_eq!(len, digits); w.write_all(&slice[..len]) } } /// Writes the base62 representation of a number using the alternative alphabet (0 to 9, then a to z, then A to Z) /// to any io::Write destination. /// /// This function is only available when the `std` feature is enabled. /// /// # Example /// ```rust /// # use std::io::Write; /// let mut output = Vec::new(); /// base62::encode_alternative_io(1337_u32, &mut output).unwrap(); /// assert_eq!(output, b"lz"); /// ``` #[cfg(feature = "std")] pub fn encode_alternative_io, W: std::io::Write + ?Sized>( num: T, w: &mut W, ) -> std::io::Result<()> { let num = num.into(); let digits = digit_count(num); let mut buf = core::mem::MaybeUninit::<[u8; 22]>::uninit(); unsafe { let ptr = buf.as_mut_ptr() as *mut u8; let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_alternative_buf(num, digits, slice); debug_assert_eq!(len, digits); w.write_all(&slice[..len]) } } // Internal functions used by both no_std and alloc features pub(crate) fn digit_count(n: u128) -> usize { const THRESHOLDS: [u128; 23] = [ 0, BASE as u128 - 1, BASE_TO_2 as u128 - 1, BASE_TO_3 as u128 - 1, BASE_TO_4 as u128 - 1, BASE_TO_5 as u128 - 1, BASE_TO_6 as u128 - 1, BASE_TO_7 as u128 - 1, BASE_TO_8 as u128 - 1, BASE_TO_9 as u128 - 1, BASE_TO_10 - 1, BASE_TO_11 - 1, BASE_TO_12 - 1, BASE_TO_13 - 1, BASE_TO_14 - 1, BASE_TO_15 - 1, BASE_TO_16 - 1, BASE_TO_17 - 1, BASE_TO_18 - 1, BASE_TO_19 - 1, BASE_TO_20 - 1, BASE_TO_21 - 1, u128::MAX, // sentinel, u128 cannot be larger than this value ]; let Some(n) = NonZeroU128::new(n) else { return 1; }; // We want to find floor(log62(n)) + 1 = floor(log2(n) / log2(62)) + 1 // First, approximate log2(n) with ilog2 = floor(log2(n)), underestimating by 0 <= err < 1 let ilog2 = n.ilog2() as usize; // Next, we find floor(ilog2/log2(62)), which is exactly equal to floor(ilog2 * 43/256) for all ilog2 in [0, 127] // The result is an underestimate by up to 1, purely because ilog2 is an underestimate let estimate = ((ilog2 * 43) >> 8) + 1; // SAFETY: estimate is in [1,22] since ilog2 is in [0,127] and (127*43)>>8 + 1 = 22 let threshold = unsafe { *THRESHOLDS.get_unchecked(estimate) }; let bump = (n.get() > threshold) as usize; estimate + bump } #[inline(always)] fn decode_char( result: &mut u64, ch: u8, i: usize, decode_table: &[u8; 128], ) -> Result<(), DecodeError> { if ch < 128 { let char_value = decode_table[ch as usize]; // avoid branching let is_valid = (char_value != 255) as u64; *result = result .wrapping_mul(BASE) .wrapping_add((char_value as u64) * is_valid); if char_value == 255 { Err(DecodeError::InvalidBase62Byte(ch, i)) } else { Ok(()) } } else { // non-ASCII character - always invalid Err(DecodeError::InvalidBase62Byte(ch, i)) } } // Common decoding function #[inline] fn decode_impl(mut input: &[u8], decode_table: &[u8; 128]) -> Result { if input.is_empty() { return Err(DecodeError::EmptyInput); } // Remove leading zeroes let chopped_count = input.iter().take_while(|&&ch| ch == b'0').count(); input = &input[chopped_count..]; let input_len = input.len(); if input_len <= 22 { const MULTIPLIERS: [(u128, u64); 23] = [ (0, 0), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (BASE as u128, 1), (BASE_TO_2 as u128, 1), (BASE_TO_3 as u128, 1), (BASE_TO_4 as u128, 1), (BASE_TO_5 as u128, 1), (BASE_TO_6 as u128, 1), (BASE_TO_7 as u128, 1), (BASE_TO_8 as u128, 1), (BASE_TO_9 as u128, 1), (BASE_TO_10, 1), (BASE_TO_11, BASE), (BASE_TO_12, BASE_TO_2), ]; let (a_power, b_power) = MULTIPLIERS[input_len]; let mut iter = (chopped_count..).zip(input.iter().copied()); // process first 10 characters let mut result_a = 0_u64; for (i, ch) in iter.by_ref().take(10) { decode_char(&mut result_a, ch, i, decode_table)?; } let result_a = (result_a as u128) .checked_mul(a_power) .ok_or(DecodeError::ArithmeticOverflow)?; // process next 10 characters let mut result_b = 0_u64; for (i, ch) in iter.by_ref().take(10) { decode_char(&mut result_b, ch, i, decode_table)?; } let result_b = (result_b as u128).wrapping_mul(b_power as u128); // process remaining characters let mut result_c = 0_u64; for (i, ch) in iter { decode_char(&mut result_c, ch, i, decode_table)?; } let result_c = result_c as u128; let result = result_a .checked_add(result_b.wrapping_add(result_c)) .ok_or(DecodeError::ArithmeticOverflow)?; Ok(result) } else { Err(DecodeError::ArithmeticOverflow) } } /// Encodes an unsigned integer into base62, using the standard digit ordering /// (0 to 9, then A to Z, then a to z), and writes it to the passed buffer. /// /// The return value is the number of bytes written to the buffer. /// /// # Safety /// - To encode all possible values of u128, the buffer must be at least 22 bytes long. However /// a smaller buffer may be used if the value to be encoded is known to be smaller. /// Base62 encoding adds 37.5% overhead to the size of the input. /// - The remaining end of the buffer is left untouched. It's up to the caller to zero it using /// the returned len if they want to. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let mut buf = [0; 22]; /// base62::encode_bytes(1337_u32, &mut buf); /// assert_eq!(&buf[..2], b"LZ"); /// ``` pub fn encode_bytes>(num: T, buf: &mut [u8]) -> Result { let num = num.into(); let digits = digit_count(num); if buf.len() < digits { return Err(EncodeError::BufferTooSmall); } unsafe { let len = _encode_buf(num, digits, &mut buf[..digits]); debug_assert_eq!(len, digits); } Ok(digits) } /// Encodes an unsigned integer into base62, using the alternative digit ordering /// (0 to 9, then a to z, then A to Z), and writes it to the passed buffer. /// /// The return value is the number of bytes written to the buffer. /// /// # Safety /// - To encode all possible values of u128, the buffer must be at least 22 bytes long. However /// a smaller buffer may be used if the value to be encoded is known to be smaller. /// Base62 encoding adds 37.5% overhead to the size of the input. /// - The remaining end of the buffer is left untouched. It's up to the caller to zero it using /// the returned len if they want to. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let mut buf = [0; 22]; /// base62::encode_bytes(1337_u32, &mut buf); /// assert_eq!(&buf[..2], b"LZ"); /// ``` pub fn encode_alternative_bytes>( num: T, buf: &mut [u8], ) -> Result { let num = num.into(); let digits = digit_count(num); if buf.len() < digits { return Err(EncodeError::BufferTooSmall); } unsafe { let len = _encode_alternative_buf(num, digits, &mut buf[..digits]); debug_assert_eq!(len, digits); } Ok(digits) } /// Decodes a base62 byte slice or an equivalent, like a `String`, /// using the standard digit ordering (0 to 9, then A to Z, then a to z). /// /// Returns a [`Result`] containing the decoded /// [`u128`] or a [`DecodeError`]. /// /// # Examples /// /// ```rust /// extern crate base62; /// /// let value = base62::decode("rustlang").unwrap(); /// assert_eq!(value, 189876682536016); /// ``` pub fn decode>(input: T) -> Result { decode_impl(input.as_ref(), &STANDARD_TABLES.decode) } /// Decodes a base62 byte slice or an equivalent, like a `String`, /// using the alternative digit ordering (0 to 9, then a to z, then A to Z) with lowercase /// letters before uppercase letters. /// /// Returns a [`Result`] containing the decoded /// [`u128`] or a [`DecodeError`]. /// /// # Examples /// /// ```rust /// extern crate base62; /// /// let value = base62::decode_alternative("rustlang").unwrap(); /// assert_eq!(value, 96813686712946); /// ``` pub fn decode_alternative>(input: T) -> Result { decode_impl(input.as_ref(), &ALTERNATIVE_TABLES.decode) } // Common encoding function unsafe fn encode_impl( num: u128, digits: usize, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { unsafe { if let Ok(num) = TryInto::::try_into(num) { encode_impl_u64(num, digits, buf, encode_pairs) } else if digits > 20 { encode_impl_over_20_digits(num, digits, buf, encode_pairs) } else if digits == 20 { // (AAAAAAAAAA, BBBBBBBBBB) let (first_u64, second_u64) = div_base_to_10(num); // AAAAAAAAAA let first_u64 = first_u64 as u64; encode_impl_20_digits(first_u64, second_u64, buf, encode_pairs) } else { // digits between 11 and 20 (10 digits would always fit into a u64, which we checked first) encode_impl_over_10_under_20_digits(num, digits, buf, encode_pairs) } } } // >20 digits requires two u128 divisions unsafe fn encode_impl_over_20_digits( num: u128, digits: usize, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { // input: [A]BCCCCCCCCCCDDDDDDDDDD // // ([A]BCCCCCCCCCC, DDDDDDDDDD) let (num, third_u64) = div_base_to_10(num); // ([A]B, CCCCCCCCCC) let (first_u64, second_u64) = div_base_to_10(num); // [A]B - no more than two digits as num was 22 digits let first_u64 = first_u64 as u64; // Branchless 21/22 digit handling of [A]B // For 21 digits: write 0 then overwrite with B at position 0 // For 22 digits: write A at position 0, B at position 1 unsafe { // [A, B] in 22 digit case or [0, B] in 21 digit case let [c1, c2] = *encode_pairs.get_unchecked(first_u64 as usize); let is_22 = digits - 21; // 0 or 1 *buf.get_unchecked_mut(0) = c1; *buf.get_unchecked_mut(is_22) = c2; } // encode the last 20 digits unsafe { encode_impl_20_digits( second_u64, third_u64, &mut buf[(digits - 20)..], encode_pairs, ); } digits } // 20 digit needs only u64 or u32 divisions and can be vectorised (four u32 divisions at once) unsafe fn encode_impl_20_digits( first_u64: u64, second_u64: u64, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { let first_u32 = (first_u64 / BASE_TO_5) as u32; let second_u32 = (first_u64 % BASE_TO_5) as u32; let third_u32 = (second_u64 / BASE_TO_5) as u32; let fourth_u32 = (second_u64 % BASE_TO_5) as u32; // [ABCDE, FGHIJ, KLMNO, PQRST] let mut nums = [first_u32, second_u32, third_u32, fourth_u32]; const BASE_POSITIONS: [usize; 4] = [0, 5, 10, 15]; for pair_idx in 0..2 { nums.iter_mut().zip(BASE_POSITIONS).for_each(|(num, base)| { // pair_idx 0: ABC, FGH, KLM, PQR // pair_idx 1: A, F, K, P let quotient = *num / BASE_TO_2 as u32; // pair_idx 0: DE, IJ, NO, ST // pair_idx 1: BC, GH, LM, QR let pair = (*num - BASE_TO_2 as u32 * quotient) as usize; *num = quotient; // Write positions: base+3,base+4 for pair_idx=0; base+1,base+2 for pair_idx=1 let pos = base + 4 - 2 * pair_idx; unsafe { // pair_idx 0: [D, E], [I, J], [N, O], [S, T] // pair_idx 1: [B, C], [G, H], [L, M], [Q, R] let [c1, c2] = *encode_pairs.get_unchecked(pair); *buf.get_unchecked_mut(pos - 1) = c1; *buf.get_unchecked_mut(pos) = c2; } }); } // A, F, K, P nums.iter() .zip(BASE_POSITIONS) .for_each(|(num, base)| unsafe { // num is now a single digit (0-61), use first byte of pair table *buf.get_unchecked_mut(base) = encode_pairs.get_unchecked(*num as usize)[1]; }); 20 } // 10-20 digit implementation needs only one u128 division, then a u64 division per digit unsafe fn encode_impl_over_10_under_20_digits( num: u128, digits: usize, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { let mut write_idx = digits; let mut digit_index = 0_usize; let (first_u64, mut num) = div_base_to_10(num); // as this number is <20 digits, once we remove the rightmost 10 digits, the remainder is a u64. let first_u64 = first_u64 as u64; while digit_index < digits { write_idx = write_idx.wrapping_sub(1); let remainder = num % BASE; num /= BASE; unsafe { *buf.get_unchecked_mut(write_idx) = encode_pairs.get_unchecked(remainder as usize)[1]; } digit_index = digit_index.wrapping_add(1); if digit_index == 10 { num = first_u64 } } digits } // u64 implementation can avoid any u128 operations unsafe fn encode_impl_u64( num: u64, digits: usize, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { if digits >= 10 { // Branchless 10/11 digit handling // For 10 digits: BBBBBBBBBB -> first_digit=0, remainder=BBBBBBBBBB, offset=0 (buf[0] gets overwritten) // For 11 digits: ABBBBBBBBBB -> first_digit=A, remainder=BBBBBBBBBB, offset=1 let first_digit = num / (BASE_TO_10 as u64); let remainder = num % (BASE_TO_10 as u64); let offset = digits - 10; // 0 or 1 unsafe { // This is unnecessary work for the 10 digit case, but its very cheap work and allows us to avoid a branch *buf.get_unchecked_mut(0) = encode_pairs.get_unchecked(first_digit as usize)[1]; encode_impl_u64_10_digits(remainder, &mut buf[offset..], encode_pairs); } digits } else { unsafe { encode_impl_u64_under_10_digits(num, digits, buf, encode_pairs) } } } unsafe fn encode_impl_u64_under_10_digits( mut num: u64, digits: usize, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { let mut write_idx = digits; let mut digit_index = 0_usize; while digit_index < digits { write_idx = write_idx.wrapping_sub(1); let remainder = num % BASE; num /= BASE; unsafe { *buf.get_unchecked_mut(write_idx) = encode_pairs.get_unchecked(remainder as usize)[1]; } digit_index = digit_index.wrapping_add(1); } digits } unsafe fn encode_impl_u64_10_digits( num: u64, buf: &mut [u8], encode_pairs: &[[u8; 2]; BASE_TO_2 as usize], ) -> usize { let first_u32 = (num / BASE_TO_5) as u32; let second_u32 = (num % BASE_TO_5) as u32; // [ABCDE, FGHIJ] let mut nums = [first_u32, second_u32]; const BASE_POSITIONS: [usize; 2] = [0, 5]; for pair_idx in 0..2 { nums.iter_mut().zip(BASE_POSITIONS).for_each(|(num, base)| { // pair_idx 0: ABC, FGH // pair_idx 1: A, F let quotient = *num / BASE_TO_2 as u32; // pair_idx 0: DE, IJ // pair_idx 1: BC, GH let pair = (*num - BASE_TO_2 as u32 * quotient) as usize; *num = quotient; // Write positions: base+3,base+4 for pair_idx=0; base+1,base+2 for pair_idx=1 let pos = base + 4 - 2 * pair_idx; unsafe { // pair_idx 0: [D, E], [I, J] // pair_idx 1: [B, C], [G, H] let [c1, c2] = *encode_pairs.get_unchecked(pair); *buf.get_unchecked_mut(pos - 1) = c1; *buf.get_unchecked_mut(pos) = c2; } }); } // A, F nums.iter() .zip(BASE_POSITIONS) .for_each(|(num, base)| unsafe { // num is now a single digit (0-61), use second byte of pair entry (the low digit) *buf.get_unchecked_mut(base) = encode_pairs.get_unchecked(*num as usize)[1]; }); 10 } fn div_base_to_10(num: u128) -> (u128, u64) { let quotient = mulh(DIV_BASE_TO_10_MULTIPLY, num) >> DIV_BASE_TO_10_SHIFT; let remainder = num - BASE_TO_10 * quotient; (quotient, remainder as u64) } // Multiply two u128 together, returning only the top half of the product. const fn mulh(x: u128, y: u128) -> u128 { const LOWER_HALF_MASK: u128 = (1 << 64) - 1; let x_low = x & LOWER_HALF_MASK; let y_low = y & LOWER_HALF_MASK; let t = x_low.wrapping_mul(y_low); let k = t >> 64; let x_high = x >> 64; let t = x_high.wrapping_mul(y_low) + k; let k = t & LOWER_HALF_MASK; let w1 = t >> 64; let y_high = y >> 64; let t = x_low.wrapping_mul(y_high) + k; let k = t >> 64; x_high.wrapping_mul(y_high) + w1 + k } unsafe fn _encode_buf(num: u128, digits: usize, buf: &mut [u8]) -> usize { unsafe { encode_impl(num, digits, buf, &STANDARD_TABLES.encode_pairs) } } unsafe fn _encode_alternative_buf(num: u128, digits: usize, buf: &mut [u8]) -> usize { unsafe { encode_impl(num, digits, buf, &ALTERNATIVE_TABLES.encode_pairs) } } #[cfg(feature = "alloc")] mod alloc_support { use super::*; use alloc::string::String; /// Encodes an unsigned integer into base62, using the standard digit ordering /// (0 to 9, then A to Z, then a to z), and returns the resulting /// [`String`]. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let b62 = base62::encode(1337_u32); /// assert_eq!(b62, "LZ"); /// ``` #[must_use = "this allocates a new `String`"] pub fn encode>(num: T) -> String { let num = num.into(); let digits = digit_count(num); unsafe { let mut buf = String::with_capacity(digits); let ptr = buf.as_mut_vec().as_mut_ptr(); let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_buf(num, digits, slice); debug_assert_eq!(len, digits); buf.as_mut_vec().set_len(digits); buf } } /// Encodes an unsigned integer into base62, using the standard digit ordering /// (0 to 9, then A to Z, then a to z), and then appends it onto the end of the given /// [`String`]. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let mut buf = String::from("1337 in base62: "); /// base62::encode_buf(1337_u32, &mut buf); /// assert_eq!(buf, "1337 in base62: LZ"); /// ``` pub fn encode_buf>(num: T, buf: &mut String) { let num = num.into(); let digits = digit_count(num); let old_len = buf.len(); buf.reserve(digits); unsafe { let vec = buf.as_mut_vec(); let ptr = vec.as_mut_ptr().add(old_len); let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_buf(num, digits, slice); debug_assert_eq!(len, digits); vec.set_len(old_len + digits); } } /// Encodes an unsigned integer into base62, using the alternative digit ordering /// (0 to 9, then a to z, then A to Z) with lowercase letters before uppercase letters, /// and returns the resulting [`String`]. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let b62 = base62::encode_alternative(1337_u32); /// assert_eq!(b62, "lz"); /// ``` #[must_use = "this allocates a new `String`"] pub fn encode_alternative>(num: T) -> String { let num = num.into(); let digits = digit_count(num); unsafe { let mut buf = String::with_capacity(digits); let ptr = buf.as_mut_vec().as_mut_ptr(); let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_alternative_buf(num, digits, slice); debug_assert_eq!(len, digits); buf.as_mut_vec().set_len(digits); buf } } /// Encodes an unsigned integer into base62, using the alternative digit ordering /// (0 to 9, then a to z, then A to Z) with lowercase letters before uppercase letters, and /// then appends it onto the end of the given [`String`]. /// /// # Example /// /// ```rust /// extern crate base62; /// /// let mut buf = String::from("1337 in base62 alternative: "); /// base62::encode_alternative_buf(1337_u32, &mut buf); /// assert_eq!(buf, "1337 in base62 alternative: lz"); /// ``` pub fn encode_alternative_buf>(num: T, buf: &mut String) { let num = num.into(); let digits = digit_count(num); let old_len = buf.len(); buf.reserve(digits); unsafe { let vec = buf.as_mut_vec(); let ptr = vec.as_mut_ptr().add(old_len); let slice = core::slice::from_raw_parts_mut(ptr, digits); let len = _encode_alternative_buf(num, digits, slice); debug_assert_eq!(len, digits); vec.set_len(old_len + digits); } } } #[cfg(feature = "alloc")] pub use alloc_support::*; #[cfg(test)] mod tests { use super::*; #[cfg(feature = "alloc")] use alloc::string::String; #[cfg(feature = "alloc")] use alloc::vec::Vec; // Don't run quickcheck tests under miri because that's infinitely slow #[cfg(all(test, feature = "alloc", not(miri)))] mod quickcheck_tests { use super::*; use alloc::string::ToString; use quickcheck::{quickcheck, TestResult}; quickcheck! { fn encode_decode(num: u128) -> bool { decode(encode(num)) == Ok(num) } } quickcheck! { fn encode_decode_alternative(num: u128) -> bool { decode_alternative(encode_alternative(num)) == Ok(num) } } quickcheck! { fn decode_bad(input: Vec) -> TestResult { if !input.is_empty() && input.iter().all(|ch| ch.is_ascii_alphanumeric()) { TestResult::discard() } else { TestResult::from_bool(decode(&input).is_err()) } } } quickcheck! { fn decode_good(input: Vec) -> TestResult { if !input.is_empty() && input.iter().all(|ch| ch.is_ascii_alphanumeric()) { TestResult::from_bool(decode(&input).is_ok()) } else { TestResult::discard() } } } quickcheck! { fn fmt_matches_encode(num: u128) -> bool { let direct = encode(num); let formatted = encode_fmt(num).to_string(); direct == formatted } } quickcheck! { fn fmt_alt_matches_encode_alt(num: u128) -> bool { let direct = encode_alternative(num); let formatted = encode_fmt_alt(num).to_string(); direct == formatted } } #[cfg(feature = "std")] quickcheck! { fn encode_io_decode(num: u128) -> bool { let mut v = Vec::new(); encode_io(num, &mut v).unwrap(); decode(&v) == Ok(num) } } #[cfg(feature = "std")] quickcheck! { fn encode_alternative_io_decode(num: u128) -> bool { let mut v = Vec::new(); encode_alternative_io(num, &mut v).unwrap(); decode_alternative(&v) == Ok(num) } } quickcheck! { fn encode_buf_decode(num: u128) -> bool { let mut s = String::new(); encode_buf(num, &mut s); decode(&s) == Ok(num) } } quickcheck! { fn encode_alternative_buf_decode(num: u128) -> bool { let mut s = String::new(); encode_alternative_buf(num, &mut s); decode_alternative(&s) == Ok(num) } } quickcheck! { fn encode_bytes_decode(num: u128) -> bool { let mut buf = [0u8; 22]; // Max size needed for u128 if let Ok(len) = encode_bytes(num, &mut buf) { decode(&buf[..len]) == Ok(num) } else { false } } } quickcheck! { fn encode_alternative_bytes_decode(num: u128) -> bool { let mut buf = [0u8; 22]; // Max size needed for u128 if let Ok(len) = encode_alternative_bytes(num, &mut buf) { decode_alternative(&buf[..len]) == Ok(num) } else { false } } } quickcheck! { fn div_base_to_10_matches_std(num: u128) -> bool { let (quotient_fast, remainder_fast) = div_base_to_10(num); let quotient = num / BASE_TO_10; let remainder = (num % BASE_TO_10) as u64; quotient == quotient_fast && remainder == remainder_fast } } } // Pure computation tests that don't need allocation #[test] fn test_digit_count() { // Test boundaries for each power of 62 for pow in 1..22 { let this_power = (BASE as u128).pow(pow as u32); assert_eq!(digit_count(this_power - 1), pow); assert_eq!(digit_count(this_power), pow + 1); } // Test specific boundary cases assert_eq!(digit_count(0), 1); assert_eq!(digit_count(1), 1); assert_eq!(digit_count(u64::MAX as u128), 11); assert_eq!(digit_count(u64::MAX as u128 + 1), 11); assert_eq!(digit_count(u128::MAX), 22); } #[test] fn test_decode_empty_input() { assert_eq!(decode([]), Err(DecodeError::EmptyInput)); } #[test] fn test_decode_overflow() { let long_input = [b'1'; 23]; assert_eq!(decode(long_input), Err(DecodeError::ArithmeticOverflow)); } #[test] fn test_decode_invalid_char() { assert_eq!(decode(b"!"), Err(DecodeError::InvalidBase62Byte(b'!', 0))); } #[test] fn test_decode_alternative_empty_input() { assert_eq!(decode_alternative([]), Err(DecodeError::EmptyInput)); } #[test] fn test_decode_alternative_overflow() { let long_input = [b'1'; 23]; assert_eq!( decode_alternative(long_input), Err(DecodeError::ArithmeticOverflow) ); } #[test] fn test_decode_alternative_invalid_char() { assert_eq!( decode_alternative(b"!"), Err(DecodeError::InvalidBase62Byte(b'!', 0)) ); } #[test] fn test_encode_bytes() { let mut buf = [0; 22]; // Test numeric type boundaries assert!(encode_bytes(u128::MAX, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"7n42DGM5Tflk9n8mt7Fhc7")); buf.fill(0); assert!(encode_bytes(u64::MAX as u128 + 1, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"LygHa16AHYG")); buf.fill(0); assert!(encode_bytes(u64::MAX, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"LygHa16AHYF")); buf.fill(0); assert!(encode_bytes(0_u8, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"0")); buf.fill(0); #[cfg(feature = "alloc")] { use alloc::string::String; // Test base62 length-change boundaries let mut power = 1_u128; let mut power_minus_one_str = String::with_capacity(21); let mut power_str = String::with_capacity(22); power_str.push('1'); for _ in 1..22 { power *= BASE as u128; power_minus_one_str.push('z'); power_str.push('0'); assert!(encode_bytes(power - 1, &mut buf).is_ok()); assert!(&buf[..].starts_with(power_minus_one_str.as_bytes())); buf.fill(0); assert!(encode_bytes(power, &mut buf).is_ok()); assert!(&buf[..].starts_with(power_str.as_bytes())); buf.fill(0); } } // Test cases that failed due to earlier bugs assert!(encode_bytes(691337691337_u64, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"CAcoUun")); buf.fill(0); assert!(encode_bytes(92202686130861137968548313400401640448_u128, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"26tF05fvSIgh0000000000")); } #[test] fn test_encode_alternative_bytes() { let mut buf = [0; 22]; // Test numeric type boundaries assert!(encode_alternative_bytes(u128::MAX, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"7N42dgm5tFLK9N8MT7fHC7")); buf.fill(0); assert!(encode_alternative_bytes(u64::MAX as u128 + 1, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"lYGhA16ahyg")); buf.fill(0); assert!(encode_alternative_bytes(u64::MAX, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"lYGhA16ahyf")); buf.fill(0); assert!(encode_alternative_bytes(0_u8, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"0")); buf.fill(0); #[cfg(feature = "alloc")] { use alloc::string::String; // Test base62 length-change boundaries let mut power = 1_u128; let mut power_minus_one_str = String::with_capacity(21); let mut power_str = String::with_capacity(22); power_str.push('1'); for _ in 1..22 { power *= BASE as u128; power_minus_one_str.push('Z'); power_str.push('0'); assert!(encode_alternative_bytes(power - 1, &mut buf).is_ok()); assert!(&buf[..].starts_with(power_minus_one_str.as_bytes())); buf.fill(0); assert!(encode_alternative_bytes(power, &mut buf).is_ok()); assert!(&buf[..].starts_with(power_str.as_bytes())); buf.fill(0); } } // Test cases that failed due to earlier bugs assert!(encode_alternative_bytes(691337691337_u64, &mut buf).is_ok()); assert!(&buf[..].starts_with(b"caCOuUN")); buf.fill(0); assert!( encode_alternative_bytes(92202686130861137968548313400401640448_u128, &mut buf).is_ok() ); assert!(&buf[..].starts_with(b"26Tf05FVsiGH0000000000")); } #[test] fn test_decode() { // Test leading zeroes handling assert_eq!( decode("00001000000000000000000000"), Ok((BASE as u128).pow(21)) ); // Test numeric type boundaries assert_eq!(decode("7n42DGM5Tflk9n8mt7Fhc7"), Ok(u128::MAX)); assert_eq!(decode("LygHa16AHYG"), Ok(u64::MAX as u128 + 1)); assert_eq!(decode("LygHa16AHYF"), Ok(u64::MAX as u128)); assert_eq!(decode("0"), Ok(0)); #[cfg(feature = "alloc")] { use alloc::string::String; // Test base62 length-change boundaries let mut power = 1_u128; let mut power_minus_one_str = String::with_capacity(21); let mut power_str = String::with_capacity(22); power_str.push('1'); for _ in 1..22 { power *= BASE as u128; power_minus_one_str.push('z'); power_str.push('0'); assert_eq!(decode(&power_minus_one_str), Ok(power - 1)); assert_eq!(decode(&power_str), Ok(power)); } } // Test cases that failed due to earlier bugs assert_eq!(decode("CAcoUun"), Ok(691337691337)); assert_eq!( decode("26tF05fvSIgh0000000000"), Ok(92202686130861137968548313400401640448) ); } #[test] fn test_decode_alternative() { // Test leading zeroes handling assert_eq!( decode_alternative("00001000000000000000000000"), Ok((BASE as u128).pow(21)) ); // Test numeric type boundaries assert_eq!(decode_alternative("7N42dgm5tFLK9N8MT7fHC7"), Ok(u128::MAX)); assert_eq!(decode_alternative("lYGhA16ahyg"), Ok(u64::MAX as u128 + 1)); assert_eq!(decode_alternative("lYGhA16ahyf"), Ok(u64::MAX as u128)); assert_eq!(decode_alternative("0"), Ok(0)); #[cfg(feature = "alloc")] { use alloc::string::String; // Test base62 length-change boundaries let mut power = 1_u128; let mut power_minus_one_str = String::with_capacity(21); let mut power_str = String::with_capacity(22); power_str.push('1'); for _ in 1..22 { power *= BASE as u128; power_minus_one_str.push('Z'); power_str.push('0'); assert_eq!(decode_alternative(&power_minus_one_str), Ok(power - 1)); assert_eq!(decode_alternative(&power_str), Ok(power)); } } // Test cases that failed due to earlier bugs assert_eq!(decode_alternative("caCOuUN"), Ok(691337691337)); assert_eq!( decode_alternative("26Tf05FVsiGH0000000000"), Ok(92202686130861137968548313400401640448) ); } // Tests requiring alloc #[cfg(feature = "alloc")] mod alloc_tests { use super::*; use alloc::{format, string::ToString}; #[test] fn test_encode() { // Test numeric type boundaries assert_eq!(encode(u128::MAX), "7n42DGM5Tflk9n8mt7Fhc7"); assert_eq!(encode(u64::MAX as u128 + 1), "LygHa16AHYG"); assert_eq!(encode(u64::MAX), "LygHa16AHYF"); assert_eq!(encode(0_u8), "0"); // Test base62 length-change boundaries let mut power = 1_u128; let mut power_minus_one_str = String::with_capacity(21); let mut power_str = String::with_capacity(22); power_str.push('1'); for _ in 1..22 { power *= BASE as u128; power_minus_one_str.push('z'); power_str.push('0'); assert_eq!(encode(power - 1), power_minus_one_str); assert_eq!(encode(power), power_str); } } #[test] fn test_encode_buf() { let mut buf = String::new(); // Test append functionality buf.push_str("Base62: "); encode_buf(10622433094_u64, &mut buf); assert_eq!(buf, "Base62: Base62"); buf.clear(); // Test numeric type boundaries encode_buf(u128::MAX, &mut buf); assert_eq!(buf, "7n42DGM5Tflk9n8mt7Fhc7"); buf.clear(); } #[test] fn test_encode_alternative() { assert_eq!(encode_alternative(u128::MAX), "7N42dgm5tFLK9N8MT7fHC7"); assert_eq!(encode_alternative(u64::MAX as u128 + 1), "lYGhA16ahyg"); assert_eq!(encode_alternative(u64::MAX), "lYGhA16ahyf"); assert_eq!(encode_alternative(0_u8), "0"); } #[test] fn test_encode_alternative_buf() { let mut buf = String::new(); buf.push_str("Base62: "); encode_alternative_buf(34051405518_u64, &mut buf); assert_eq!(buf, "Base62: Base62"); } #[test] fn test_fmt_basics() { assert_eq!(encode_fmt(1337u32).to_string(), "LZ"); assert_eq!(encode_fmt(0u32).to_string(), "0"); assert_eq!(encode_fmt(u128::MAX).to_string(), "7n42DGM5Tflk9n8mt7Fhc7"); } #[test] fn test_fmt_padding() { assert_eq!( format!("{:0>22}", encode_fmt(1337u32)), "00000000000000000000LZ" ); assert_eq!( format!("{:0>22}", encode_fmt(0u32)), "0000000000000000000000" ); } #[test] fn test_fmt_alternative_basics() { assert_eq!(encode_fmt_alt(1337u32).to_string(), "lz"); assert_eq!(encode_fmt_alt(0u32).to_string(), "0"); assert_eq!( encode_fmt_alt(u128::MAX).to_string(), "7N42dgm5tFLK9N8MT7fHC7" ); } #[test] fn test_fmt_alternative_padding() { assert_eq!( format!("{:0>22}", encode_fmt_alt(1337u32)), "00000000000000000000lz" ); assert_eq!( format!("{:0>22}", encode_fmt_alt(0u32)), "0000000000000000000000" ); } #[test] fn test_fmt_alignment() { let num = 1337u32; assert_eq!(format!("{:<5}", encode_fmt(num)), "LZ "); assert_eq!(format!("{:>5}", encode_fmt(num)), " LZ"); assert_eq!(format!("{:^5}", encode_fmt(num)), " LZ "); } #[test] fn test_fmt_with_prefix() { assert_eq!(format!("base62: {}", encode_fmt(1337u32)), "base62: LZ"); assert_eq!(format!("base62: {}", encode_fmt_alt(1337u32)), "base62: lz"); } } // Tests requiring std #[cfg(feature = "std")] mod std_tests { use super::*; #[test] fn test_encode_io() { let mut output = Vec::new(); encode_io(1337_u32, &mut output).unwrap(); assert_eq!(output, b"LZ"); output.clear(); encode_io(0_u8, &mut output).unwrap(); assert_eq!(output, b"0"); } #[test] fn test_encode_alternative_io() { let mut output = Vec::new(); encode_alternative_io(1337_u32, &mut output).unwrap(); assert_eq!(output, b"lz"); output.clear(); encode_alternative_io(0_u8, &mut output).unwrap(); assert_eq!(output, b"0"); // Test numeric type boundaries output.clear(); encode_alternative_io(u128::MAX, &mut output).unwrap(); assert_eq!(output, b"7N42dgm5tFLK9N8MT7fHC7"); output.clear(); encode_alternative_io(u64::MAX as u128 + 1, &mut output).unwrap(); assert_eq!(output, b"lYGhA16ahyg"); } #[test] fn test_encode_bytes_buffer_too_small() { let mut buf = [0; 1]; assert_eq!( encode_bytes(1337_u16, &mut buf), Err(EncodeError::BufferTooSmall) ); } } }