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-precisionf16
andbf16
floats.std
(Default) - Disable to allow use in ano_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:
- The
NumberFormatBuilder
, creating a packed struct with custom formatting options. - The
Options
API.
§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:
JSON
,XML
,TOML
,YAML
,SQLite
, and many more.Rust
,Python
,C#
,FORTRAN
,COBOL
literals and strings, and many more.
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 tob'e'
orb'^'
, depending on the radix). - The
decimal point
character (defaults tob'.'
). - Custom
NaN
andInfinity
stringrepresentations
. - Whether to
trim
the fraction component from integral floats. - The exponent
break-point
for scientific notation. - The
maximum
andminimum
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.
- Compact for decimal strings uses the Grisu algorithm.
- An optimized algorithm based on the Dragonbox algorithm.
- An optimized algorithm for formatting to string with power-of-two radixes.
- An optimized algorithm for hexadecimal floats.
- 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§
- Number
Format - Helper to access features from the packed format struct.
- Number
Format Builder - Validating builder for
NumberFormat
from the provided specifications. - Options
- Options to customize writing floats.
- Options
Builder - Builder for
Options
. - bf16
f16
- A 16-bit floating point type implementing the
bfloat16
format. - f16
f16
- 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.
- Round
Mode - 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§
- Formatted
Size - The size, in bytes, of formatted values.
- ToLexical
- Trait for numerical types that can be serialized to bytes.
- ToLexical
With Options - Trait for numerical types that can be serialized to bytes with custom options.
- Write
Options - Shared trait for all writer options.