From 600cbe8696d011f890a6861b81ff5806eec0d4a5 Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 4 Sep 2026 09:45:03 +0200 Subject: [PATCH] Upstream magiclen/byte-unit state of the touched files before 277dd54cc5 --- src/common.rs | 126 ++++++++++++++++++++++++++++++++++++ tests/byte.rs | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 298 insertions(+) create mode 100644 src/common.rs create mode 100644 tests/byte.rs diff --git a/src/common.rs b/src/common.rs new file mode 100644 index 0000000..ee94712 --- /dev/null +++ b/src/common.rs @@ -0,0 +1,126 @@ +use core::str::Bytes; + +#[cfg(any(feature = "byte", feature = "bit"))] +use rust_decimal::prelude::*; + +/// # Safety +/// Make sure the input is valid on your own. +pub(crate) unsafe fn get_char_from_bytes(e: u8, mut bytes: Bytes) -> char { + let width = unsafe { utf8_width::get_width_assume_valid(e) }; + + let mut char_bytes = [0; 4]; + + char_bytes[0] = e; + + if width > 1 { + for e in char_bytes[1..].iter_mut().take(width - 1) { + *e = bytes.next().unwrap(); + } + } + + let s = unsafe { core::str::from_utf8_unchecked(&char_bytes[..width]) }; + + unsafe { s.chars().next().unwrap_unchecked() } +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(feature = "std")] +#[inline] +pub(crate) fn ceil_f64(v: f64) -> f64 { + v.ceil() +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(not(feature = "std"))] +#[inline] +pub(crate) fn ceil_f64(v: f64) -> f64 { + debug_assert!(v >= 0.0); + + Decimal::from_f64(v).unwrap().ceil().to_f64().unwrap() +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(feature = "std")] +#[inline] +pub(crate) fn ceil_f32(v: f32) -> f32 { + debug_assert!(v >= 0.0); + + v.ceil() +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(not(feature = "std"))] +#[inline] +pub(crate) fn ceil_f32(v: f32) -> f32 { + debug_assert!(v >= 0.0); + + Decimal::from_f32(v).unwrap().ceil().to_f32().unwrap() +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(feature = "std")] +#[inline] +pub fn round_fractional_part_f64(value: f64, mut precision: usize) -> f64 { + if precision > 16 { + precision = 16; + } else if precision == 0 { + return value.round(); + } + + let scale = 10f64.powi(precision as i32); + + (value * scale).round() / scale +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[cfg(not(feature = "std"))] +pub fn round_fractional_part_f64(value: f64, mut precision: usize) -> f64 { + debug_assert!(value >= 0.0); + + let value = Decimal::from_f64(value).unwrap(); + + if precision > 16 { + precision = 16; + } else if precision == 0 { + return value.round().to_f64().unwrap(); + } + + let trunc = value.trunc(); + let mut fract = value.fract(); + + let scale = Decimal::from(10u128.pow(precision as u32)); + fract = (fract * scale).round() / scale; + + (trunc + fract).to_f64().unwrap() +} + +#[cfg(any(feature = "byte", feature = "bit"))] +#[inline] +pub(crate) fn is_zero_remainder_decimal( + a: Decimal, + b: Decimal, + precision: usize, +) -> Option { + debug_assert!(a.is_sign_positive() && b.is_sign_positive()); + debug_assert!(b > Decimal::ZERO); + debug_assert!(precision <= 28); + + let quotient = a / b; + + let quotient_round = { + if precision == 0 { + quotient.round() + } else { + let trunc = quotient.trunc(); + let mut fract = quotient.fract(); + + let scale = Decimal::from(10u128.pow(precision as u32)); + + fract = (fract * scale).round() / scale; + + trunc + fract + } + }; + + if b * quotient_round == a { Some(quotient_round) } else { None } +} diff --git a/tests/byte.rs b/tests/byte.rs new file mode 100644 index 0000000..8687ce1 --- /dev/null +++ b/tests/byte.rs @@ -0,0 +1,172 @@ +#![cfg(feature = "byte")] + +use byte_unit::{Byte, ParseError, Unit, UnitType}; +use rust_decimal::prelude::*; + +#[test] +fn parse_str() { + #[allow(unused_mut, clippy::useless_vec)] + let mut cases = vec![ + (Err(()) as Result, "", false), + (Ok(Byte::from_u64(0)), "0", false), + (Err(()), "-0", false), + (Err(()), "b", false), + (Err(()), "i", false), + (Err(()), "c", false), + (Ok(Byte::from_u64(0)), "0b", false), + (Ok(Byte::from_u64(1)), "1", false), + (Err(()), "-1", false), + (Ok(Byte::from_u64(1)), "1b", false), + (Ok(Byte::from_u64(1)), "1b", true), + (Err(()), "1i", false), + (Err(()), "1c", false), + (Err(()), "1bc", false), + (Ok(Byte::from_u64(1)), "1 b", true), + (Ok(Byte::from_u64(1)), "1 b", true), + (Ok(Byte::from_u64(1)), "1 b", true), + (Ok(Byte::from_u64(2)), "1.1", false), + (Ok(Byte::from_u64(1)), "1.1b", false), + (Ok(Byte::from_u64(2)), "1.1b", true), + (Ok(Byte::from_u64(0)), "0kb", false), + (Ok(Byte::from_u64(150)), "1.2kb", false), + (Ok(Byte::from_u64(1200)), "1.2kb", true), + (Ok(Byte::from_u64(2048)), "2 kiB", true), + (Ok(Byte::from_u64(76650000)), "76.65 MB", false), + (Ok(Byte::from_u64(16000000000000000000)), "16 EB", false), + (Ok(Byte::from_u64(18446744073709551615)), "18446744073709551615", false), + ]; + + #[cfg(feature = "u128")] + cases.extend_from_slice(&[ + (Ok(Byte::from_u128(u64::MAX as u128 + 1).unwrap()), "16 EiB", false), + (Ok(Byte::from_u128(16000000000000000000000000).unwrap()), "16 YB", false), + (Ok(Byte::from_u128(999000000000000000000000000).unwrap()), "999 YB", false), + (Err(()), "1000 YB", false), + ]); + + #[cfg(not(feature = "u128"))] + cases.extend_from_slice(&[(Err(()), "16 EiB", false)]); + + for (i, case) in cases.iter().enumerate() { + let result = Byte::parse_str(case.1, case.2); + + match case.0 { + Ok(unit) => match result { + Ok(result) => { + assert_eq!(unit, result, "{i}"); + }, + Err(error) => { + panic!("{i}\n{error}"); + }, + }, + Err(_) => { + assert!(result.is_err(), "{i}") + }, + } + } +} + +#[test] +fn parse_str_unit_error_character() { + let cases = [('r', "2048r"), ('é', "2048é")]; + + for (i, (expected, input)) in cases.iter().enumerate() { + let error = Byte::parse_str(input, true).unwrap_err(); + + match error { + ParseError::Unit(error) => assert_eq!(*expected, error.character, "{i}"), + error => panic!("{i}\n{error}"), + } + } +} + +#[test] +fn exact_unit() { + #[allow(unused_mut, clippy::useless_vec)] + let mut cases = vec![ + ((0, Unit::B), "0"), + ((1, Unit::B), "1"), + ((123456, Unit::B), "123456B"), + ((1, Unit::TB), "1T"), + ((1, Unit::TiB), "1Ti"), + ((10, Unit::TB), "10T"), + ((10, Unit::TiB), "10Ti"), + ((1000, Unit::TiB), "1000Ti"), + ((1, Unit::PiB), "1024Ti"), + ]; + + #[cfg(feature = "u128")] + cases.extend_from_slice(&[((1, Unit::YiB), "1Yi")]); + + for (i, case) in cases.iter().enumerate() { + assert_eq!(case.0, Byte::parse_str(case.1, true).unwrap().get_exact_unit(true), "{i}"); + } +} + +#[test] +fn recoverable_unit() { + #[allow(unused_mut, clippy::useless_vec)] + let mut cases = vec![ + ((0.0, Unit::B), "0"), + ((1.0, Unit::B), "1"), + ((123.456, Unit::KB), "123456B"), + ((1.0, Unit::TB), "1T"), + ((1.0, Unit::TiB), "1Ti"), + ((10.0, Unit::TB), "10T"), + ((10.0, Unit::TiB), "10Ti"), + ((1000.0, Unit::TiB), "1000Ti"), + ((1.0, Unit::PiB), "1024Ti"), + ]; + + #[cfg(feature = "u128")] + cases.extend_from_slice(&[((1.0, Unit::YiB), "1Yi")]); + + for (i, case) in cases.iter().enumerate() { + assert_eq!( + (Decimal::from_f64(case.0.0).unwrap(), case.0.1), + Byte::parse_str(case.1, true).unwrap().get_recoverable_unit(true, 3), + "{i}" + ); + } +} + +#[test] +fn adjusted_unit() { + #[allow(unused_mut, clippy::useless_vec)] + let mut cases = vec![ + ("15 MB", 15000000.0, UnitType::Decimal), + ("15.000001 MB", 15000001.0, UnitType::Decimal), + ("14.30511474609375 MiB", 15000000.0, UnitType::Binary), + ("14.30511474609375 MiB", 15000000.0, UnitType::Both), + ]; + + #[cfg(feature = "u128")] + cases.extend_from_slice(&[("100 YB", 100000000000000000000000000.0, UnitType::Decimal)]); + + for (i, case) in cases.iter().enumerate() { + assert_eq!( + case.0, + Byte::from_f64(case.1).unwrap().get_appropriate_unit(case.2).to_string(), + "{i}" + ); + } +} + +#[cfg(feature = "serde")] +#[test] +fn tests() { + let cases = [ + ("\"1 MB\"", (1.0, Unit::MB)), + ("\"2 MiB\"", (2.0, Unit::MiB)), + ("\"5.5 KiB\"", (5.5, Unit::KiB)), + ("\"1.234 MB\"", (1.234, Unit::MB)), + ("\"1234.5 KB\"", (1.2345, Unit::MB)), + ]; + + for (i, case) in cases.iter().enumerate() { + let byte = Byte::from_f64_with_unit(case.1.0, case.1.1).unwrap(); + + assert_eq!(case.0, serde_json::to_string(&byte).unwrap(), "{i}"); + assert_eq!(byte, serde_json::from_str::(case.0).unwrap(), "{i}"); + } +}