diff --git a/sonic-number/src/lib.rs b/sonic-number/src/lib.rs index fbfbbe6d..2095d8c3 100644 --- a/sonic-number/src/lib.rs +++ b/sonic-number/src/lib.rs @@ -13,10 +13,10 @@ pub mod swar; mod table; use self::{common::BiasedFp, float::RawFloat, table::POWER_OF_FIVE_128}; -pub use crate::arch::simd_str2int; -pub use crate::swar::swar_str2int; +pub use crate::{arch::simd_str2int, swar::swar_str2int}; const FLOATING_LONGEST_DIGITS: usize = 17; +const FLOATING_LONGEST_DIGITS_F32: usize = 9; const F64_BITS: u32 = 64; const F64_SIG_BITS: u32 = 52; const F64_SIG_FULL_BITS: u32 = 53; @@ -179,7 +179,8 @@ fn parse_number_fraction( tail_n }; let total = 8 + tail_n; - *significant = *significant * POW10_UINT[total] + first8 * POW10_UINT[tail_n] + tail_val; + *significant = + *significant * POW10_UINT[total] + first8 * POW10_UINT[tail_n] + tail_val; *index += total; } else { // c.len() < 16: not enough bytes for tolerant SWAR on tail. @@ -310,8 +311,8 @@ pub fn parse_number(data: &[u8], index: &mut usize, negative: bool) -> Result= 8 && swar::is_eight_digits(&data[*index..]) { - significant = significant * 100_000_000 - + swar::parse_eight_digits(&data[*index..]) as u64; + significant = + significant * 100_000_000 + swar::parse_eight_digits(&data[*index..]) as u64; *index += 8; } @@ -386,8 +387,14 @@ pub fn parse_number(data: &[u8], index: &mut usize, negative: bool) -> Result Result Result { + let mut significant: u64 = 0; + let mut exponent: i32 = 0; + let mut trunc = false; + let raw_num = &data[*index..]; + + if match_digit!(data, *index, b'0') { + *index += 1; + + if *index >= data.len() || !matches!(data[*index], b'.' | b'e' | b'E') { + let zero = 0.0f32; + return Ok(if negative { -zero } else { zero }); + } + + match data[*index] { + b'.' => { + *index += 1; + let dot_pos = *index; + check_digit!(data, *index); + while match_digit!(data, *index, b'0') { + *index += 1; + } + + if match_digit!(data, *index, b'e' | b'E') { + *index += 1; + if match_digit!(data, *index, b'-' | b'+') { + *index += 1; + } + check_digit!(data, *index); + while is_digit!(data, *index) { + *index += 1; + } + let zero = 0.0f32; + return Ok(if negative { -zero } else { zero }); + } + + if !is_digit!(data, *index) { + let zero = 0.0f32; + return Ok(if negative { -zero } else { zero }); + } + + significant = digit!(data, *index); + *index += 1; + + if is_digit!(data, *index) { + let need = FLOATING_LONGEST_DIGITS_F32 as isize - 1; + trunc = parse_number_fraction( + data, + index, + &mut significant, + &mut exponent, + need, + dot_pos, + )?; + } else { + exponent -= *index as i32 - dot_pos as i32; + if match_digit!(data, *index, b'e' | b'E') { + *index += 1; + exponent += parse_exponent(data, &mut *index)?; + } + } + } + b'e' | b'E' => { + *index += 1; + if match_digit!(data, *index, b'-' | b'+') { + *index += 1; + } + check_digit!(data, *index); + while is_digit!(data, *index) { + *index += 1; + } + let zero = 0.0f32; + return Ok(if negative { -zero } else { zero }); + } + _ => unreachable!("unreachable branch in parse_float32"), + } + } else { + let digit_start = *index; + let remaining = unsafe { data.get_unchecked(*index..) }; + + let digits_cnt; + if remaining.len() >= 8 && swar::is_eight_digits(remaining) { + significant = swar::parse_eight_digits(remaining) as u64; + *index += 8; + + if data.len() - *index >= 8 && swar::is_eight_digits(&data[*index..]) { + significant = + significant * 100_000_000 + swar::parse_eight_digits(&data[*index..]) as u64; + *index += 8; + } + + while (*index - digit_start) < 19 && is_digit!(data, *index) { + significant = significant * 10 + digit!(data, *index); + *index += 1; + } + digits_cnt = *index - digit_start; + + while is_digit!(data, *index) { + exponent += 1; + *index += 1; + trunc = true; + } + } else { + if !is_digit!(data, *index) { + return Err(Error::InvalidNumber); + } + significant = digit!(data, *index); + *index += 1; + + if is_digit!(data, *index) { + while is_digit!(data, *index) { + significant = significant * 10 + digit!(data, *index); + *index += 1; + } + digits_cnt = *index - digit_start; + } else if !match_digit!(data, *index, b'.' | b'e' | b'E') { + let mut float = significant as f32; + if negative { + float = -float; + } + return Ok(float); + } else { + digits_cnt = 1; + } + } + + if match_digit!(data, *index, b'e' | b'E') { + *index += 1; + exponent += parse_exponent(data, index)?; + } else if match_digit!(data, *index, b'.') { + *index += 1; + check_digit!(data, *index); + let dot_pos = *index; + + if digits_cnt < 8 { + let mut need = FLOATING_LONGEST_DIGITS_F32 as isize - digits_cnt as isize; + while need > 0 && is_digit!(data, *index) { + significant = significant * 10 + digit!(data, *index); + *index += 1; + need -= 1; + } + exponent -= *index as i32 - dot_pos as i32; + while is_digit!(data, *index) { + trunc = true; + *index += 1; + } + if match_digit!(data, *index, b'e' | b'E') { + *index += 1; + exponent += parse_exponent(data, &mut *index)?; + } + } else { + let need = FLOATING_LONGEST_DIGITS_F32 as isize - digits_cnt as isize; + trunc = parse_number_fraction( + data, + index, + &mut significant, + &mut exponent, + need, + dot_pos, + )?; + } + } else { + if exponent == 0 { + let mut float = significant as f32; + if negative { + float = -float; + } + return Ok(float); + } else if exponent == 1 { + let last = digit!(data, *index - 1); + let (out, ov0) = significant.overflowing_mul(10); + let (out, ov1) = out.overflowing_add(last); + if !ov0 && !ov1 { + significant = out; + let mut float = significant as f32; + if negative { + float = -float; + } + return Ok(float); + } + } + trunc = true; + } + } + + parse_float_generic::(significant, exponent, negative, trunc, raw_num) +} + /// Unchecked version — caller must ensure data has >=64 bytes padding. #[inline(always)] -pub unsafe fn parse_number_unchecked(data: &[u8], index: &mut usize, negative: bool) -> Result { +pub unsafe fn parse_number_unchecked( + data: &[u8], + index: &mut usize, + negative: bool, +) -> Result { let mut significant: u64 = 0; let mut exponent: i32 = 0; let mut trunc = false; @@ -521,7 +722,9 @@ pub unsafe fn parse_number_unchecked(data: &[u8], index: &mut usize, negative: b *index += 8; // Try second 8-digit batch - if data.len() - *index >= 8 && swar::is_eight_digits(unsafe { data.get_unchecked(*index..) }) { + if data.len() - *index >= 8 + && swar::is_eight_digits(unsafe { data.get_unchecked(*index..) }) + { significant = significant * 100_000_000 + swar::parse_eight_digits(unsafe { data.get_unchecked(*index..) }) as u64; *index += 8; @@ -617,7 +820,6 @@ pub unsafe fn parse_number_unchecked(data: &[u8], index: &mut usize, negative: b parse_float(significant, exponent, negative, trunc, raw_num) } - #[inline(always)] fn parse_float( significant: u64, @@ -674,6 +876,42 @@ fn parse_float( Ok(ParserNumber::Float(float)) } +#[inline(always)] +fn parse_float_generic( + significant: u64, + exponent: i32, + negative: bool, + trunc: bool, + raw_num: &[u8], +) -> Result { + if let Some(mut float) = parse_float_fast_generic::(exponent, significant) { + if negative { + float = -float; + } + return Ok(float); + } + + let exponent = exponent as i64; + let mut fp = lemire::compute_float::(exponent, significant); + if trunc && fp.e >= 0 && fp != lemire::compute_float::(exponent, significant + 1) { + fp.e = -1; + } + + if fp.e < 0 { + fp = slow::parse_long_mantissa::(raw_num); + } + + let mut float = biased_fp_to_float::(fp); + if negative { + float = -float; + } + + if matches!(float.classify(), core::num::FpCategory::Infinite) { + return Err(Error::FloatMustBeFinite); + } + Ok(float) +} + // This function is modified from yyjson #[inline(always)] fn parse_floating_normal_fast(exp10: i32, man: u64) -> Option { @@ -735,6 +973,33 @@ fn biased_fp_to_float(x: BiasedFp) -> T { T::from_u64_bits(word) } +#[inline(always)] +fn parse_float_fast_generic(mut exp10: i32, mut significant: u64) -> Option { + if significant > T::MAX_MANTISSA_FAST_PATH { + return None; + } + + while (exp10 as i64) > T::MAX_EXPONENT_FAST_PATH { + if significant % 10 != 0 { + return None; + } + significant /= 10; + exp10 -= 1; + } + + if (exp10 as i64) < T::MIN_EXPONENT_FAST_PATH || (exp10 as i64) > T::MAX_EXPONENT_FAST_PATH { + return None; + } + + let mut float = T::from_u64(significant); + if exp10 > 0 { + float = float * T::pow10_fast_path(exp10 as usize); + } else if exp10 < 0 { + float = float / T::pow10_fast_path((-exp10) as usize); + } + Some(float) +} + #[inline(always)] fn parse_float_fast(exp10: i32, significant: u64) -> Option { let mut d = significant as f64; @@ -763,7 +1028,7 @@ const POW10_FLOAT: [f64; 23] = [ #[cfg(test)] mod test { - use crate::{parse_number, ParserNumber}; + use crate::{parse_float32, parse_number, ParserNumber}; fn test_parse_ok(input: &str, expect: f64) { assert_eq!(input.parse::().unwrap(), expect); @@ -796,6 +1061,23 @@ mod test { assert_eq!(data[index], b' ', "trailing byte for {}", input); } + fn test_parse_f32_ok(input: &str, expect: f32) { + assert_eq!(input.parse::().unwrap().to_bits(), expect.to_bits()); + + let mut data = input.as_bytes().to_vec(); + data.push(b' '); + let mut index = if input.starts_with('-') { 1 } else { 0 }; + let num = parse_float32(&data, &mut index, input.starts_with('-')).unwrap(); + assert_eq!( + num.to_bits(), + expect.to_bits(), + "parsed is {:?} failed num is {}", + num, + input + ); + assert_eq!(data[index], b' ', "failed num is {}", input); + } + fn test_parse_signed_ok(input: &str, expected: i64) { let mut data = input.as_bytes().to_vec(); data.push(b' '); @@ -914,4 +1196,23 @@ mod test { 3.469446951536142e-26, ); } + + #[test] + fn test_parse_float32() { + test_parse_f32_ok("0", 0.0); + test_parse_f32_ok("-0", -0.0); + test_parse_f32_ok("1", 1.0); + test_parse_f32_ok("0.1", 0.1); + test_parse_f32_ok("1.23", 1.23); + test_parse_f32_ok( + "17005001.000000000000130", + "17005001.000000000000130".parse().unwrap(), + ); + test_parse_f32_ok("3.4028235e38", "3.4028235e38".parse().unwrap()); + test_parse_f32_ok("1.17549435e-38", "1.17549435e-38".parse().unwrap()); + test_parse_f32_ok( + "12448139190673828122020e-47", + "12448139190673828122020e-47".parse().unwrap(), + ); + } } diff --git a/src/parser.rs b/src/parser.rs index 709735f6..0350b865 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -10,7 +10,7 @@ use std::{ use faststr::FastStr; use serde::de::{self, Expected, Unexpected}; -use sonic_number::{parse_number, ParserNumber}; +use sonic_number::{parse_float32, parse_number, ParserNumber}; #[cfg(all(target_feature = "neon", target_arch = "aarch64"))] use sonic_simd::bits::NeonBits; use sonic_simd::{i8x32, m8x32, u8x32, u8x64, Mask, Simd}; @@ -308,6 +308,17 @@ where ret.map_err(|err| self.error(err.into())) } + #[inline(always)] + pub fn parse_float32(&mut self, first: u8) -> Result { + let reader = &mut self.read; + let neg = first == b'-'; + let mut now = reader.index() - (!neg as usize); + let data = reader.as_u8_slice(); + let ret = parse_float32(data, &mut now, neg); + reader.set_index(now); + ret.map_err(|err| self.error(err.into())) + } + /// Unchecked number parsing — requires padded buffer (PaddedSliceRead). /// Uses the full padded buffer (including padding) so unchecked access is safe. #[inline(always)] diff --git a/src/serde/de.rs b/src/serde/de.rs index 58ec572d..814b5505 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -536,7 +536,28 @@ impl<'de, 'a, R: Reader<'de>> de::Deserializer<'de> for &'a mut Deserializer impl_deserialize_number!(deserialize_u16); impl_deserialize_number!(deserialize_u32); impl_deserialize_number!(deserialize_u64); - impl_deserialize_number!(deserialize_f32); + /// Deserialize f32 using a dedicated single-pass parser to avoid both + /// the f64→f32 rounding pitfall and the extra fallback parse. + /// + /// The f64→f32 cast can produce an off-by-one ULP error when the original + /// decimal is at a tie-breaking boundary (e.g., "17005001.000000000000130"). + fn deserialize_f32(self, visitor: V) -> Result + where + V: de::Visitor<'de>, + { + let Some(peek) = self.parser.skip_space() else { + return Err(self.parser.error(EofWhileParsing)); + }; + + let value = match peek { + c @ b'-' | c @ b'0'..=b'9' => { + let f = tri!(self.parser.parse_float32(c)); + visitor.visit_f32(f) + } + _ => Err(self.peek_invalid_type(peek, &visitor)), + }; + self.fix_position(value) + } impl_deserialize_number!(deserialize_f64); fn deserialize_i128(self, visitor: V) -> Result diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 684f745a..9656ce54 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -577,6 +577,19 @@ mod test { test_from!(BTreeMap, from_str, "{\"1.23\":null}" ); } + #[test] + fn test_deserialize_f32_single_pass_precision() { + let input = "17005001.000000000000130"; + let got: f32 = crate::from_str(input).unwrap(); + let expect: f32 = input.parse().unwrap(); + assert_eq!(got.to_bits(), expect.to_bits()); + + let input = "-0"; + let got: f32 = crate::from_str(input).unwrap(); + let expect: f32 = input.parse().unwrap(); + assert_eq!(got.to_bits(), expect.to_bits()); + } + // test deserialize into different mapkeys #[derive(PartialEq, Debug)] struct MapKeys<'a> {