Crate lexical_write_float

Crate lexical_write_float 

Source
Expand description

Fast and compact float-to-string conversions.

This contains high-performance methods to write floating-point numbers directly to bytes, can be converted to str using str::from_utf8. Using to_lexical is analogous to to_string, just writing to an existing buffer.

It also contains extensively formatting control, including the use of exponent notation, if to round or truncate floats, the number of significant digits, and more.

§Getting Started

To write a number to bytes, use to_lexical:

use lexical_write_float::{FormattedSize, ToLexical};

let mut buffer = [0u8; f64::FORMATTED_SIZE_DECIMAL];
let digits = 1.234f64.to_lexical(&mut buffer);
assert_eq!(str::from_utf8(digits), Ok("1.234"));

With the default options, using FORMATTED_SIZE_DECIMAL guarantees the buffer will be large enough to write the digits for all numbers of that type.

§Options/Formatting API

Each float formatter contains extensive formatting control, including a maximum number of significant digits written, a minimum number of significant digits remaining, the positive and negative exponent break points (at what exponent, in scientific-notation, to force scientific notation), whether to force or disable scientific notation, the rounding mode for truncated float strings, and how to display non-finite floats. While using custom float options, you must use Options::buffer_size_const to determine the correct buffer size:

use lexical_write_float::{format, options, ToLexicalWithOptions};

const BUFFER_SIZE: usize = options::RUST_LITERAL
    .buffer_size_const::<f64, { format::RUST_LITERAL }>();

fn write_rust_float(f: f64) -> ([u8; BUFFER_SIZE], usize) {
    let mut buffer = [0u8; BUFFER_SIZE];
    let digits = f.to_lexical_with_options::<{ format::RUST_LITERAL }>(
        &mut buffer,
        &options::RUST_LITERAL
    );
    let count = digits.len();
    (buffer, count)
}

let (digits, count) = write_rust_float(3.5);
assert_eq!(str::from_utf8(&digits[..count]), Ok("3.5"));

For additional supported options for customizing how to write floats, see the OptionsBuilder. If you’re looking to parse floats with a grammar for a programming language, many pre-defined options such as for JSON exist in options. For even more customization, see the format and Comprehensive Configuration sections below.

§Features

  • format - Add support for custom float formatting.
  • power-of-two - Add support for writing power-of-two float strings.
  • radix - Add support for strings of any radix.
  • compact - Reduce code size at the cost of performance.
  • f16 - Enable support for half-precision f16 and bf16 floats.
  • std (Default) - Disable to allow use in a no_std environment.

A complete description of supported features includes:

§format

Add support custom float formatting specifications. This should be used in conjunction with Options for extensible float writing. You must use Options::buffer_size_const to determine the number of bytes requires in the buffer. This allows changing the use of exponent notation, requiring or not allowing signs, and more.

§JSON

For example, in JSON, the following floats are valid or invalid:

-1          // valid
+1          // invalid
1           // valid
1.          // invalid
.1          // invalid
0.1         // valid
nan         // invalid
inf         // invalid
Infinity    // invalid

All of the finite numbers are valid in Rust, and Rust supports non-finite floats. In order to write standard-conforming JSON floats using lexical-core, you may use the following approach:

use lexical_write_float::{format, options, ToLexicalWithOptions};

const BUFFER_SIZE: usize = options::JSON.buffer_size_const::<f64, { format::JSON }>();

fn write_json_float(f: f64) -> ([u8; BUFFER_SIZE], usize) {
    let mut buffer = [0u8; BUFFER_SIZE];
    let digits = f.to_lexical_with_options::<{ format::JSON }>(
        &mut buffer,
        &options::JSON
    );
    let count = digits.len();
    (buffer, count)
}

let (digits, count) = write_json_float(3.5);
assert_eq!(str::from_utf8(&digits[..count]), Ok("3.5"));
§Custom Signs

An example of building a custom format to ensure positive signs are always written is as follows:

use lexical_write_float::{FormattedSize, NumberFormatBuilder, Options, ToLexicalWithOptions};

const FORMAT: u128 = NumberFormatBuilder::new()
    // require a `+` or `-` sign before the number
    .required_mantissa_sign(true)
    // require a `+` or `-` sign before the exponent digits
    .required_exponent_sign(true)
    // build the format, panicking if the format is invalid
    .build_strict();
const OPTIONS: Options = Options::new();

const BUFFER_SIZE: usize = OPTIONS.buffer_size_const::<f64, FORMAT>();
let mut buffer = [0u8; BUFFER_SIZE];

let digits = 1.234e300f64.to_lexical_with_options::<FORMAT>(&mut buffer, &OPTIONS);
assert_eq!(str::from_utf8(digits), Ok("+1.234e+300"));

Enabling the format API significantly increases compile times, however, it enables a large amount of customization in how floats are written.

§power-of-two

Enable writing numbers that are powers of two, that is, 2, 4, 8, 16, and 32. In these cases, you should use FORMATTED_SIZE to create a sufficiently large buffer.

use lexical_write_float::{FormattedSize, NumberFormatBuilder, Options, ToLexicalWithOptions};

let mut buffer = [0u8; f64::FORMATTED_SIZE];
const BINARY: u128 = NumberFormatBuilder::binary();
const OPTIONS: Options = Options::new();
let digits = 1.234f64.to_lexical_with_options::<BINARY>(&mut buffer, &OPTIONS);
assert_eq!(str::from_utf8(digits), Ok("1.0011101111100111011011001000101101000011100101011"));
§radix

Enable writing numbers using all radixes from 2 to 36. This requires more static storage than power-of-two, and increases compile times, but can be quite useful for esoteric programming languages which use duodecimal floats, for example.

use lexical_write_float::{FormattedSize, NumberFormatBuilder, Options, ToLexicalWithOptions};

const FORMAT: u128 = NumberFormatBuilder::from_radix(12);
const OPTIONS: Options = Options::new();

let mut buffer = [0u8; f64::FORMATTED_SIZE];
let digits = 1.234f64.to_lexical_with_options::<FORMAT>(&mut buffer, &OPTIONS);
assert_eq!(str::from_utf8(digits), Ok("1.29842830A44BAA2"));
§compact

Reduce the generated code size at the cost of performance. This minimizes the number of static tables, inlining, and generics used, drastically reducing the size of the generated binaries. However, this resulting performance of the generated code is much lower.

§f16

This enables the use of the half-precision floats f16 and bf16. However, since these have limited hardware support and are primarily used for vectorized operations, they are formatted as if they were an f32. Due to the low precision of 16-bit floats, the results may appear to have significant rounding error.

use lexical_write_float::{f16, FormattedSize, ToLexical};

let mut buffer = [0u8; f16::FORMATTED_SIZE];
let value = f16::from_f64_const(1.234f64);
let digits = value.to_lexical(&mut buffer);
assert_eq!(str::from_utf8(digits), Ok("1.234375"));
§std

Enable use of the standard library. Currently, the standard library is not used, and may be disabled without any change in functionality on stable.

§Comprehensive Configuration

lexical-write-float provides two main levels of configuration:

§Number Format

The number format class provides numerous flags to specify number writing. When the power-of-two feature is enabled, additional flags are added:

  • The radix for the significant digits (default 10).
  • The radix for the exponent base (default 10).
  • The radix for the exponent digits (default 10).

When the format feature is enabled, numerous other syntax and digit separator flags are enabled, including:

  • Requiring or ommitting + signs.
  • If to use exponent notation.

Many pre-defined constants therefore exist to simplify common use-cases, including:

For a list of all supported fields, see Write Float Fields.

§Options API

The Options API provides high-level options to specify number parsing or writing, options not intrinsically tied to a number format. For example, the Options API provides:

  • The exponent character (defaults to b'e' or b'^', depending on the radix).
  • The decimal point character (defaults to b'.').
  • Custom NaN and Infinity string representations.
  • Whether to trim the fraction component from integral floats.
  • The exponent break-point for scientific notation.
  • The maximum and minimum number of significant digits to write.
  • The rounding mode when truncating significant digits while writing.

In addition, pre-defined constants for each category of options may be found in their respective modules, for example, JSON.

§Examples

An example of creating your own options to parse European-style numbers (which use commas as decimal points, controlling the number of significant digits, special number representations, and more, is as follows:

use lexical_write_float::{FormattedSize, Options, ToLexicalWithOptions};

const FORMAT: u128 = lexical_write_float::format::STANDARD;
const CUSTOM: Options = Options::builder()
    // write exponents as "1.2^10" and not "1.2e10"
    .exponent(b'^')
    // use the European decimal point, so "1,2" and not "1.2"
    .decimal_point(b',')
    // write NaN and Infinity using the following formats
    .nan_string(Some(b"nan"))
    .inf_string(Some(b"inf"))
    // set the minimum and maximum number of significant digits to write;
    .min_significant_digits(num::NonZeroUsize::new(3))
    .max_significant_digits(num::NonZeroUsize::new(5))
    .build_strict();

const BUFFER_SIZE: usize = CUSTOM.buffer_size_const::<f64, FORMAT>();
let mut buffer = [0u8; BUFFER_SIZE];

// write 4 digits, no exponent notation
let digits = 1.234f64.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("1,234"));

// write 6 digits, rounding to 5
let digits = 1.23456f64.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("1,2346"));

// write 6 digits, rounding to 5, with exponent notation
let digits = 1.23456e300f64.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("1,2346^300"));

// write 4 digits, no exponent notation
let digits = 1.2f64.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("1,20"));

// write a literal NaN string
let digits = f64::NAN.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("nan"));

// write a literal +Infinity string
let digits = f64::INFINITY.to_lexical_with_options::<FORMAT>(&mut buffer, &CUSTOM);
assert_eq!(str::from_utf8(digits), Ok("inf"));

§Higher-Level APIs

If you would like support for writing to String directly, use lexical instead. If you would like an API that supports multiple numeric conversions rather than just writing integers, use lexical-core instead.

§Version Support

The minimum, standard, required version is 1.63.0, for const generic support. Older versions of lexical support older Rust versions.

§Algorithms

There’s currently 5 algorithms used, depending on the requirements.

  1. Compact for decimal strings uses the Grisu algorithm.
  2. An optimized algorithm based on the Dragonbox algorithm.
  3. An optimized algorithm for formatting to string with power-of-two radixes.
  4. An optimized algorithm for hexadecimal floats.
  5. A fallback algorithm for all other radixes.

The Grisu algorithm is based on “Printing Floating-Point Numbers Quickly and Accurately with Integers”, by Florian Loitsch, available online here. The dragonbox algorithm is based on the reference C++ implementation, hosted here, and the algorithm is described in depth here. The radix algorithm is adapted from the V8 codebase, and may be found here.

§Design

Modules§

format
The creation and processing of number format packed structs.
options
Configuration options for writing floats.

Structs§

NumberFormat
Helper to access features from the packed format struct.
NumberFormatBuilder
Validating builder for NumberFormat from the provided specifications.
Options
Options to customize writing floats.
OptionsBuilder
Builder for Options.
bf16f16
A 16-bit floating point type implementing the bfloat16 format.
f16f16
A 16-bit floating point type implementing the IEEE 754-2008 standard binary16 a.k.a “half” format.

Enums§

Error
Error code during parsing, indicating failure type.
RoundMode
Enumeration for how to round floats with precision control.

Constants§

BUFFER_SIZE
Maximum number of bytes required to serialize any number with default options to string.

Traits§

FormattedSize
The size, in bytes, of formatted values.
ToLexical
Trait for numerical types that can be serialized to bytes.
ToLexicalWithOptions
Trait for numerical types that can be serialized to bytes with custom options.
WriteOptions
Shared trait for all writer options.

Type Aliases§

Result
A specialized Result type for lexical operations.