Skip to content

Commit 84c1c49

Browse files
committed
Add in signed overloads for writing integers.
Fixes #191. Fixes apache/datafusion#13686
1 parent b6d0eb4 commit 84c1c49

File tree

14 files changed

+179
-33
lines changed

14 files changed

+179
-33
lines changed

CHANGELOG

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.0.5] 2024-12-08
11+
12+
### Fixed
13+
14+
- Only require 19 digits for writing `i64` and not 20 (#191).
15+
1016
## [1.0.4] 2024-12-07
1117

1218
### Changed

lexical-core/Cargo.toml

+6-6
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-core"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.3"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",
@@ -19,30 +19,30 @@ exclude = [
1919
]
2020

2121
[dependencies.lexical-util]
22-
version = "1.0.4"
22+
version = "1.0.5"
2323
default-features = false
2424
path = "../lexical-util"
2525

2626
[dependencies.lexical-parse-integer]
27-
version = "1.0.3"
27+
version = "1.0.5"
2828
optional = true
2929
default-features = false
3030
path = "../lexical-parse-integer"
3131

3232
[dependencies.lexical-parse-float]
33-
version = "1.0.3"
33+
version = "1.0.5"
3434
optional = true
3535
default-features = false
3636
path = "../lexical-parse-float"
3737

3838
[dependencies.lexical-write-integer]
39-
version = "1.0.3"
39+
version = "1.0.5"
4040
optional = true
4141
default-features = false
4242
path = "../lexical-write-integer"
4343

4444
[dependencies.lexical-write-float]
45-
version = "1.0.3"
45+
version = "1.0.5"
4646
optional = true
4747
default-features = false
4848
path = "../lexical-write-float"

lexical-parse-float/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-parse-float"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.3"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",
@@ -19,13 +19,13 @@ exclude = [
1919
]
2020

2121
[dependencies.lexical-util]
22-
version = "1.0.4"
22+
version = "1.0.5"
2323
path = "../lexical-util"
2424
default-features = false
2525
features = ["parse-floats"]
2626

2727
[dependencies.lexical-parse-integer]
28-
version = "1.0.3"
28+
version = "1.0.5"
2929
path = "../lexical-parse-integer"
3030
default-features = false
3131
features = []

lexical-parse-integer/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-parse-integer"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.3"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",
@@ -22,7 +22,7 @@ exclude = [
2222
static_assertions = "1"
2323

2424
[dependencies.lexical-util]
25-
version = "1.0.4"
25+
version = "1.0.5"
2626
path = "../lexical-util"
2727
default-features = false
2828
features = ["parse-integers"]

lexical-util/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-util"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.4"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",

lexical-write-float/Cargo.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-write-float"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.4"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",
@@ -19,13 +19,13 @@ exclude = [
1919
]
2020

2121
[dependencies.lexical-util]
22-
version = "1.0.4"
22+
version = "1.0.5"
2323
path = "../lexical-util"
2424
default-features = false
2525
features = ["write-floats"]
2626

2727
[dependencies.lexical-write-integer]
28-
version = "1.0.3"
28+
version = "1.0.5"
2929
path = "../lexical-write-integer"
3030
default-features = false
3131
features = []

lexical-write-float/src/shared.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ pub fn write_exponent<const FORMAT: u128>(
139139
bytes[*cursor] = exponent_character;
140140
*cursor += 1;
141141
let positive_exp: u32 = write_exponent_sign::<FORMAT>(bytes, cursor, exp);
142-
*cursor += positive_exp.write_exponent::<FORMAT>(&mut bytes[*cursor..]);
142+
*cursor += positive_exp.write_exponent_signed::<FORMAT>(&mut bytes[*cursor..]);
143143
}
144144

145145
/// Detect the notation to use for the float formatter and call the appropriate

lexical-write-integer/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ license = "MIT/Apache-2.0"
99
name = "lexical-write-integer"
1010
readme = "README.md"
1111
repository = "https://github.com/Alexhuszagh/rust-lexical"
12-
version = "1.0.4"
12+
version = "1.0.5"
1313
rust-version = "1.63.0"
1414
exclude = [
1515
"assets/*",
@@ -22,7 +22,7 @@ exclude = [
2222
static_assertions = "1.1.0"
2323

2424
[dependencies.lexical-util]
25-
version = "1.0.4"
25+
version = "1.0.5"
2626
path = "../lexical-util"
2727
default-features = false
2828
features = ["write-integers"]

lexical-write-integer/src/api.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ where
5555
let unsigned = Unsigned::as_cast(value.wrapping_neg());
5656
buffer[0] = b'-';
5757
let buffer = &mut buffer[1..];
58-
unsigned.write_mantissa::<FORMAT>(buffer) + 1
58+
unsigned.write_mantissa_signed::<FORMAT>(buffer) + 1
5959
} else if cfg!(feature = "format") && format.required_mantissa_sign() {
6060
let unsigned = Unsigned::as_cast(value);
6161
buffer[0] = b'+';
6262
let buffer = &mut buffer[1..];
63-
unsigned.write_mantissa::<FORMAT>(buffer) + 1
63+
unsigned.write_mantissa_signed::<FORMAT>(buffer) + 1
6464
} else {
6565
let unsigned = Unsigned::as_cast(value);
66-
unsigned.write_mantissa::<FORMAT>(buffer)
66+
unsigned.write_mantissa_signed::<FORMAT>(buffer)
6767
}
6868
}
6969

lexical-write-integer/src/decimal.rs

+35-7
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,19 @@ unsafe impl DecimalCount for usize {
239239

240240
/// Write integer to decimal string.
241241
pub trait Decimal: DecimalCount {
242-
/// # Safety
242+
fn decimal(self, buffer: &mut [u8]) -> usize;
243+
244+
/// Specialized overload is the type is sized.
243245
///
244-
/// Safe as long as buffer is at least [`FORMATTED_SIZE`] elements long,
245-
/// (or [`FORMATTED_SIZE_DECIMAL`] for decimal), and the radix is valid.
246+
/// # Panics
246247
///
247-
/// [`FORMATTED_SIZE`]: lexical_util::constants::FormattedSize::FORMATTED_SIZE
248-
/// [`FORMATTED_SIZE_DECIMAL`]: lexical_util::constants::FormattedSize::FORMATTED_SIZE_DECIMAL
249-
fn decimal(self, buffer: &mut [u8]) -> usize;
248+
/// If the data original provided was unsigned and therefore
249+
/// has more digits than the signed variant. This only affects
250+
/// `i64` (see #191).
251+
#[inline(always)]
252+
fn decimal_signed(self, buffer: &mut [u8]) -> usize {
253+
self.decimal(buffer)
254+
}
250255
}
251256

252257
// Implement decimal for type.
@@ -265,10 +270,21 @@ decimal_impl! {
265270
u8; from_u8
266271
u16; from_u16
267272
u32; from_u32
268-
u64; from_u64
269273
u128; from_u128
270274
}
271275

276+
impl Decimal for u64 {
277+
#[inline(always)]
278+
fn decimal(self, buffer: &mut [u8]) -> usize {
279+
jeaiii::from_u64(self, buffer)
280+
}
281+
282+
#[inline(always)]
283+
fn decimal_signed(self, buffer: &mut [u8]) -> usize {
284+
jeaiii::from_i64(self, buffer)
285+
}
286+
}
287+
272288
impl Decimal for usize {
273289
#[inline(always)]
274290
fn decimal(self, buffer: &mut [u8]) -> usize {
@@ -281,4 +297,16 @@ impl Decimal for usize {
281297
_ => unimplemented!(),
282298
}
283299
}
300+
301+
#[inline(always)]
302+
fn decimal_signed(self, buffer: &mut [u8]) -> usize {
303+
match usize::BITS {
304+
8 => (self as u8).decimal_signed(buffer),
305+
16 => (self as u16).decimal_signed(buffer),
306+
32 => (self as u32).decimal_signed(buffer),
307+
64 => (self as u64).decimal_signed(buffer),
308+
128 => (self as u128).decimal_signed(buffer),
309+
_ => unimplemented!(),
310+
}
311+
}
284312
}

lexical-write-integer/src/jeaiii.rs

+25-3
Original file line numberDiff line numberDiff line change
@@ -297,11 +297,16 @@ pub fn from_u32(n: u32, buffer: &mut [u8]) -> usize {
297297
/// Optimized jeaiii algorithm for u64.
298298
#[inline(always)]
299299
#[allow(clippy::collapsible_else_if)] // reason = "branching is fine-tuned for performance"
300-
pub fn from_u64(n: u64, buffer: &mut [u8]) -> usize {
300+
fn from_u64_impl(n: u64, buffer: &mut [u8], is_signed: bool) -> usize {
301301
// NOTE: Like before, this optimizes better for large and small
302302
// values if there's a flat comparison with larger values first.
303303
const FACTOR: u64 = 100_0000_0000;
304-
let buffer = &mut buffer[..20];
304+
// NOTE `i64` takes a max of 19 digits, while `u64` takes a max of 20.
305+
let buffer = if is_signed {
306+
&mut buffer[..19]
307+
} else {
308+
&mut buffer[..20]
309+
};
305310
if n < 1_0000 {
306311
// 1 to 4 digits
307312
if n >= 100 {
@@ -326,7 +331,7 @@ pub fn from_u64(n: u64, buffer: &mut [u8]) -> usize {
326331
write_digits!(@5-6 buffer, n)
327332
}
328333
} else {
329-
// 11-20 digits, can do in 2 steps
334+
// 11-20 digits, can do in 2 steps (11-19 if is signed).
330335
// NOTE: `hi` has to be in `[0, 2^31)`, while `lo` is in `[0, 10^11)`
331336
// So, we can use our `from_u64_small` for hi. For our `lo`, we always
332337
// need to write 10 digits. However, the `jeaiii` algorithm is too
@@ -340,6 +345,23 @@ pub fn from_u64(n: u64, buffer: &mut [u8]) -> usize {
340345
}
341346
}
342347

348+
/// Optimized jeaiii algorithm for u64.
349+
#[inline(always)]
350+
pub fn from_u64(n: u64, buffer: &mut [u8]) -> usize {
351+
from_u64_impl(n, buffer, false)
352+
}
353+
354+
/// Optimized jeaiii algorithm for i64, which must be positive.
355+
///
356+
/// This value **MUST** have originally been from an `i64`, since it
357+
/// uses `19` for the bounds checked, so this will panic if `>= 10^19`
358+
/// is passed to the function.
359+
#[inline(always)]
360+
pub fn from_i64(n: u64, buffer: &mut [u8]) -> usize {
361+
debug_assert!(n <= 1000_0000_0000_0000_0000u64);
362+
from_u64_impl(n, buffer, true)
363+
}
364+
343365
/// Optimized jeaiii algorithm for u128.
344366
#[inline(always)]
345367
#[allow(clippy::collapsible_else_if)] // reason = "branching is fine-tuned for performance"

0 commit comments

Comments
 (0)