Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apimachinery::pkg::api::resource::Quantity compatibility with Go #135

Open
imp opened this issue Jan 5, 2023 · 2 comments
Open

apimachinery::pkg::api::resource::Quantity compatibility with Go #135

imp opened this issue Jan 5, 2023 · 2 comments

Comments

@imp
Copy link

imp commented Jan 5, 2023

The type in question in Go is significantly more complex than just string. It might be beneficial to mimic its Go behavior in this library as well. Perhaps adding various methods similar to its Go counterpart. That will be backwards compatible I think. Or perhaps even replacing the type itself to imitate Go more closely, however that will definitely be incompatible change.

@Arnavion
Copy link
Owner

Arnavion commented Jan 5, 2023

Ref:


Perhaps adding various methods similar to its Go counterpart.

Methods are not the hard part. The serialization and deserialization is, because it has to enforce all the rules that the type has. The deserializer has to replicate the parsing of the suffix, and the serializer has to perform canonicalization.

Eg, let's say we represent it as:

pub struct Quantity {
    pub value: i64, // Incorporates the exponent
    pub suffix: Suffix,
}

pub enum Suffix {
    None,
    Kibi,
    Mibi,
    Kilo,
    Mega,
    ...
}

This cannot be naively serialized by just serializing the value and the suffix one after the other, because Quantity { value: 1000, suffix: Suffix::None } needs to be serialized as "1e3" and not "1000", Quantity { value: 1000, suffix: Suffix::Kilo } needs to be serialized as "1M" and not "1000k", etc.

All that logic is in the files I linked above plus the go-inf library, so all of that would need to be translated to Rust and would need to be kept in sync with changes.


That will be backwards compatible I think. Or perhaps even replacing the type itself to imitate Go more closely, however that will definitely be incompatible change.

Compatibility is not a concern. Almost every release of this crate is already semver-major.

@Arnavion
Copy link
Owner

Arnavion commented Feb 3, 2024

$dayjob needed code for the specific purpose of deserializing a Quantity into a u64 in a lossy, saturating way, so I ended up writing two versions related to that. Neither is complete enough for me to publish to crates.io, but I'm putting them here so that I don't lose them.

Version 1: Deserializes into a lossless typed representation. Conversion to `u64` doesn't support binary suffixes.
use std::{collections::VecDeque, str::FromStr};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Quantity {
    pub sign: Sign,
    pub integral: Vec<u8>,
    pub fractional: Vec<u8>,
    pub suffix: Suffix,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Sign {
    Positive,
    Negative,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Suffix {
    BinarySI(BinarySISuffix),
    DecimalExponent(DecimalExponentSuffix),
    DecimalSI(DecimalSISuffix),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BinarySISuffix {
    Kibi,
    Mebi,
    Gibi,
    Tebi,
    Pebi,
    Exbi,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DecimalExponentSuffix {
    pub sign: Sign,
    pub digits: Vec<u8>,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DecimalSISuffix {
    Nano,
    Micro,
    Milli,
    None,
    Kilo,
    Mega,
    Giga,
    Tera,
    Peta,
    Exa,
}

fn split_first(s: &[u8]) -> Option<(u8, &[u8])> {
    s.split_first().map(|(first, rest)| (*first, rest))
}

impl FromStr for Quantity {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let rest = &mut s.as_bytes();

        let sign = parse_sign(rest);

        let integral = parse_digits(rest).unwrap_or_default();

        let decimal_point;
        (decimal_point, *rest) = match split_first(*rest) {
            Some((b'.', rest)) => (true, rest),
            _ => (false, *rest),
        };

        let fractional = if decimal_point {
            parse_digits(rest).unwrap_or_default()
        } else if integral.is_empty() {
            return Err(format!(
                "expected signed number, found {:?}",
                rest.escape_ascii().to_string(),
            ));
        } else {
            vec![]
        };

        let suffix = parse_suffix(rest)?;

        Ok(Self {
            sign,
            integral,
            fractional,
            suffix,
        })
    }
}

fn parse_sign(rest: &mut &[u8]) -> Sign {
    let sign;
    (sign, *rest) = match split_first(*rest) {
        Some((b'+', rest)) => (Sign::Positive, rest),
        Some((b'-', rest)) => (Sign::Negative, rest),
        _ => (Sign::Positive, *rest),
    };
    sign
}

fn parse_suffix(rest: &mut &[u8]) -> Result<Suffix, String> {
    #[allow(clippy::match_same_arms)]
    {
        *rest = match split_first(rest) {
            Some((b'K', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Kibi)),
            Some((b'M', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Mebi)),
            Some((b'G', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Gibi)),
            Some((b'T', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Tebi)),
            Some((b'P', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Pebi)),
            Some((b'E', b"i")) => return Ok(Suffix::BinarySI(BinarySISuffix::Exbi)),

            Some((b'n', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Nano)),
            Some((b'u', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Micro)),
            Some((b'm', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Milli)),
            None => return Ok(Suffix::DecimalSI(DecimalSISuffix::None)),
            Some((b'k', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Kilo)),
            Some((b'M', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Mega)),
            Some((b'G', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Giga)),
            Some((b'T', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Tera)),
            Some((b'P', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Peta)),
            Some((b'E', b"")) => return Ok(Suffix::DecimalSI(DecimalSISuffix::Exa)),

            Some((b'e' | b'E', rest)) => rest,

            Some(_) => {
                return Err(format!(
                    "expected suffix, found {:?}",
                    rest.escape_ascii().to_string(),
                ))
            }
        };
    }

    let sign = parse_sign(rest);
    let digits = parse_digits(rest)?;

    if !rest.is_empty() {
        return Err(format!(
            "trailing garbage: {:?}",
            rest.escape_ascii().to_string()
        ));
    }

    Ok(Suffix::DecimalExponent(DecimalExponentSuffix {
        sign,
        digits,
    }))
}

fn parse_digits(rest: &mut &[u8]) -> Result<Vec<u8>, String> {
    let mut result = vec![];

    loop {
        let digit;
        (digit, *rest) = match split_first(*rest) {
            Some((digit @ b'0'..=b'9', rest)) => (Some(digit), rest),
            _ if !result.is_empty() => (None, *rest),
            _ => return Err("digits is empty".to_owned()),
        };
        if let Some(digit) = digit {
            result.push(digit - b'0');
        } else {
            break;
        }
    }

    Ok(result)
}

impl From<Quantity> for u64 {
    fn from(quantity: Quantity) -> Self {
        let Quantity {
            sign,
            mut integral,
            fractional,
            mut suffix,
        } = quantity;
        let mut fractional: VecDeque<_> = fractional.into();

        if integral.iter().all(|&digit| digit == 0) && fractional.iter().all(|&digit| digit == 0) {
            return 0;
        }

        if matches!(sign, Sign::Negative) {
            return 0;
        }

        loop {
            match suffix {
                Suffix::BinarySI(_) => unimplemented!(),

                Suffix::DecimalExponent(DecimalExponentSuffix { sign, digits }) => {
                    let value = digits.into_iter().fold(0_u8, |value, digit| {
                        value.saturating_mul(10).saturating_add(digit)
                    });
                    for _ in 0..value {
                        match sign {
                            Sign::Positive => integral.push(fractional.pop_front().unwrap_or(0)),
                            Sign::Negative => fractional.push_front(integral.pop().unwrap_or(0)),
                        }
                    }

                    break;
                }

                Suffix::DecimalSI(DecimalSISuffix::Nano) => {
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Micro);
                }

                Suffix::DecimalSI(DecimalSISuffix::Micro) => {
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Milli);
                }

                Suffix::DecimalSI(DecimalSISuffix::Milli) => {
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    fractional.push_front(integral.pop().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::None);
                }

                Suffix::DecimalSI(DecimalSISuffix::None) => break,

                Suffix::DecimalSI(DecimalSISuffix::Kilo) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::None);
                }

                Suffix::DecimalSI(DecimalSISuffix::Mega) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Kilo);
                }

                Suffix::DecimalSI(DecimalSISuffix::Giga) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Mega);
                }

                Suffix::DecimalSI(DecimalSISuffix::Tera) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Giga);
                }

                Suffix::DecimalSI(DecimalSISuffix::Peta) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Tera);
                }

                Suffix::DecimalSI(DecimalSISuffix::Exa) => {
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    integral.push(fractional.pop_front().unwrap_or(0));
                    suffix = Suffix::DecimalSI(DecimalSISuffix::Peta);
                }
            }
        }

        let mut integral = integral.into_iter().fold(0_u64, |value, digit| {
            value.saturating_mul(10).saturating_add(digit.into())
        });

        if fractional.iter().any(|&digit| digit != 0) {
            integral = integral.saturating_add(1);
        }

        integral
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go
    #[test]
    fn ok() {
        #[rustfmt::skip]
        let test_cases = [
            ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),

            ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("10", 10, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("500", 500, Quantity { sign: Sign::Positive, integral: vec![5, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("8000", 8000, Quantity { sign: Sign::Positive, integral: vec![8, 0, 0 ,0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("2", 2, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.03", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 3], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.004", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),

            ("9223372036854775808", 9223372036854775808, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("92233720368547758080", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("922337203685477580800", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("922337203685477580.8", 922337203685477581, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![8], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("92233720368547758.08", 92233720368547759, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 8], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-9223372036854775809", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-92233720368547758090", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-922337203685477580900", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-922337203685477580.9", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![9], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-92233720368547758.09", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 9], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),

            ("0.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            ("1.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            ("-1.025Ti", 0, Quantity { sign: Sign::Negative, integral: vec![1], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            (".", 0, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("-.", 0, Quantity { sign: Sign::Negative, integral: vec![], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![3] }) }),

            ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0n", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }),
            ("0u", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Micro) }),
            ("0m", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Milli) }),
            ("0Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            ("0k", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("0Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }),
            ("0M", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }),
            ("0Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }),
            ("0G", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }),
            ("0Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            ("0T", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),

            ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),

            ("1Ki", 0, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            ("8Ki", 0, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            ("7Mi", 0, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }),
            ("6Gi", 0, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }),
            ("5Ti", 0, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            ("4Pi", 0, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Pebi) }),
            ("3Ei", 0, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Exbi) }),

            ("10Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),
            ("100Ti", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),

            ("5n", 1, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }),
            ("4u", 1, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Micro) }),
            ("3m", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Milli) }),
            ("9", 9, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("8k", 8000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("50k", 50000, Quantity { sign: Sign::Positive, integral: vec![5, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("7M", 7000000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }),
            ("6G", 6000000000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }),
            ("5T", 5000000000000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("40T", 40000000000000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("300T", 300000000000000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("2P", 2000000000000000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Peta) }),
            ("1E", 1000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }),

            ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![3] }) }),
            ("1e3", 1000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }),
            ("1E6", 1000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![6] }) }),
            ("1e9", 1000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![9] }) }),
            ("1E12", 1000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 2] }) }),
            ("1e15", 1000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }),
            ("1E18", 1000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 8] }) }),

            ("1e14", 100000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 4] }) }),
            ("1e13", 10000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 3] }) }),
            ("1e15", 1000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }),
            ("1e3", 1000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }),
            ("100.035k", 100035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),

            ("0.001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.0005k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("0.005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.05", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.5", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.00050k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("0.00500", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.05000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5, 0, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.50000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5, 0, 0, 0, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![0] }) }),
            ("0.5e-1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![1] }) }),
            ("0.5e-2", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![2] }) }),
            ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![0] }) }),
            ("10.035M", 10035000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![0, 3, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }),

            ("1.2e3", 1200, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![2], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![3] }) }),
            ("1.3E+6", 1300000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![3], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![6] }) }),
            ("1.40e9", 1400000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![4, 0], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![9] }) }),
            ("1.53E12", 1530000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![5, 3], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 2] }) }),
            ("1.6e15", 1600000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![6], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 5] }) }),
            ("1.7E18", 1700000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![7], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Positive, digits: vec![1, 8] }) }),

            ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("8.1k", 8100, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("7.123456M", 7123456, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![1, 2, 3, 4, 5, 6], suffix: Suffix::DecimalSI(DecimalSISuffix::Mega) }),
            ("6.987654321G", 6987654321, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![9, 8, 7, 6, 5, 4, 3, 2, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }),
            ("5.444T", 5444000000000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![4, 4, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("40.1T", 40100000000000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![1], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("300.2T", 300200000000000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![2], suffix: Suffix::DecimalSI(DecimalSISuffix::Tera) }),
            ("2.5P", 2500000000000000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![5], suffix: Suffix::DecimalSI(DecimalSISuffix::Peta) }),
            ("1.01E", 1010000000000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }),

            ("3.001n", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }),
            ("1.1E-9", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![1], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![9] }) }),
            ("0.0000000001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.0000000005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.00000000050", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.5e-9", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::DecimalExponent(DecimalExponentSuffix { sign: Sign::Negative, digits: vec![9] }) }),
            ("0.9n", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![9], suffix: Suffix::DecimalSI(DecimalSISuffix::Nano) }),
            ("0.00000012345", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("0.00000012354", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 5, 4], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("9Ei", 0, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Exbi) }),
            ("9223372036854775807Ki", 0, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7], fractional: vec![], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            ("12E", 12000000000000000000, Quantity { sign: Sign::Positive, integral: vec![1, 2], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Exa) }),

            ("100.035Ki", 0, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            ("0.5Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], suffix: Suffix::BinarySI(BinarySISuffix::Mebi) }),
            ("0.05Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], suffix: Suffix::BinarySI(BinarySISuffix::Gibi) }),
            ("0.025Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], suffix: Suffix::BinarySI(BinarySISuffix::Tebi) }),

            ("0.000000000001Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], suffix: Suffix::BinarySI(BinarySISuffix::Kibi) }),
            (".001", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            (".0001k", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::Kilo) }),
            ("1.", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
            ("1.G", 1000000000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], suffix: Suffix::DecimalSI(DecimalSISuffix::Giga) }),

            ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),

            ("-9.01", 0, Quantity { sign: Sign::Negative, integral: vec![9], fractional: vec![0, 1], suffix: Suffix::DecimalSI(DecimalSISuffix::None) }),
        ];

        for (input, expected_u64, expected_quantity) in test_cases {
            eprintln!("input: {input}");

            let actual_quantity: Quantity = input.parse().unwrap();
            assert_eq!(expected_quantity, actual_quantity);

            if !matches!(actual_quantity.suffix, Suffix::BinarySI(_)) {
                let actual_u64: u64 = actual_quantity.into();
                assert_eq!(expected_u64, actual_u64);
            }
        }
    }

    // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go
    #[test]
    fn err() {
        for (input, expected) in [
            ("1.1.M", r#"expected suffix, found ".M""#),
            ("1+1.0M", r#"expected suffix, found "+1.0M""#),
            ("0.1mi", r#"expected suffix, found "mi""#),
            ("0.1am", r#"expected suffix, found "am""#),
            ("aoeu", r#"expected signed number, found "aoeu""#),
            (".5i", r#"expected suffix, found "i""#),
            ("1i", r#"expected suffix, found "i""#),
            ("-3.01i", r#"expected suffix, found "i""#),
            ("-3.01e-", r#"digits is empty"#),
            (" 1", r#"expected signed number, found " 1""#),
            ("1 ", r#"expected suffix, found " ""#),
        ] {
            eprintln!("input: {input}");
            let actual = input.parse::<Quantity>().unwrap_err();
            assert_eq!(expected, actual);
        }
    }
}
Version 2: Deserializes into a lossy typed representation that treats binary suffixes as the closest decimal prefix.
use std::{cmp::Ordering, collections::VecDeque, str::FromStr};

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Quantity {
    pub sign: Sign,
    pub integral: Vec<u8>,
    pub fractional: Vec<u8>,
    pub exponent: i8,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Sign {
    Positive,
    Negative,
}

fn split_first(s: &[u8]) -> Option<(u8, &[u8])> {
    s.split_first().map(|(first, rest)| (*first, rest))
}

impl FromStr for Quantity {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let rest = &mut s.as_bytes();

        let sign = parse_sign(rest);

        let integral = parse_digits(rest).unwrap_or_default();

        let decimal_point;
        (decimal_point, *rest) = match split_first(rest) {
            Some((b'.', rest)) => (true, rest),
            _ => (false, *rest),
        };

        let fractional = if decimal_point {
            parse_digits(rest).unwrap_or_default()
        } else if integral.is_empty() {
            return Err(format!(
                "expected signed number, found {:?}",
                rest.escape_ascii().to_string(),
            ));
        } else {
            vec![]
        };

        let exponent = parse_exponent(rest)?;

        Ok(Self {
            sign,
            integral,
            fractional,
            exponent,
        })
    }
}

fn parse_sign(rest: &mut &[u8]) -> Sign {
    let sign;
    (sign, *rest) = match split_first(rest) {
        Some((b'+', rest)) => (Sign::Positive, rest),
        Some((b'-', rest)) => (Sign::Negative, rest),
        _ => (Sign::Positive, *rest),
    };
    sign
}

fn parse_exponent(rest: &mut &[u8]) -> Result<i8, String> {
    #[allow(clippy::match_same_arms)]
    {
        *rest = match split_first(rest) {
            // Binary suffixes are treated like decimal suffixes.
            // This means the computed value will be a little smaller than the intended value,
            // but the difference is small enough that it won't matter.
            Some((b'K', b"i")) => return Ok(3),
            Some((b'M', b"i")) => return Ok(6),
            Some((b'G', b"i")) => return Ok(9),
            Some((b'T', b"i")) => return Ok(12),
            Some((b'P', b"i")) => return Ok(15),
            Some((b'E', b"i")) => return Ok(18),

            Some((b'n', b"")) => return Ok(-9),
            Some((b'u', b"")) => return Ok(-6),
            Some((b'm', b"")) => return Ok(-3),
            None => return Ok(0),
            Some((b'k', b"")) => return Ok(3),
            Some((b'M', b"")) => return Ok(6),
            Some((b'G', b"")) => return Ok(9),
            Some((b'T', b"")) => return Ok(12),
            Some((b'P', b"")) => return Ok(15),
            Some((b'E', b"")) => return Ok(18),

            Some((b'e' | b'E', rest)) => rest,

            Some(_) => {
                return Err(format!(
                    "expected suffix, found {:?}",
                    rest.escape_ascii().to_string(),
                ))
            }
        };
    }

    let sign = parse_sign(rest);
    let digits = parse_digits(rest)?;
    let value = digits.into_iter().fold(0_i8, |value, digit| {
        let value = value.saturating_mul(10);
        match sign {
            Sign::Positive => value.saturating_add(digit.try_into().expect("digit is 0..=9")),
            Sign::Negative => value.saturating_sub(digit.try_into().expect("digit is 0..=9")),
        }
    });

    if !rest.is_empty() {
        return Err(format!(
            "trailing garbage: {:?}",
            rest.escape_ascii().to_string(),
        ));
    }

    Ok(value)
}

fn parse_digits(rest: &mut &[u8]) -> Result<Vec<u8>, String> {
    let mut result = vec![];

    loop {
        let digit;
        (digit, *rest) = match split_first(rest) {
            Some((digit @ b'0'..=b'9', rest)) => (Some(digit), rest),
            _ if !result.is_empty() => (None, *rest),
            _ => return Err("digits is empty".to_owned()),
        };
        if let Some(digit) = digit {
            result.push(digit - b'0');
        } else {
            break;
        }
    }

    Ok(result)
}

impl From<Quantity> for u64 {
    fn from(quantity: Quantity) -> Self {
        let Quantity {
            sign,
            integral,
            fractional,
            exponent,
        } = quantity;
        let mut fractional: VecDeque<_> = fractional.into();

        if integral.iter().all(|&digit| digit == 0) && fractional.iter().all(|&digit| digit == 0) {
            return 0;
        }

        if matches!(sign, Sign::Negative) {
            return 0;
        }

        let mut result = integral.into_iter().fold(0_u64, |value, digit| {
            value.saturating_mul(10).saturating_add(digit.into())
        });

        let mut has_fractional_digits = fractional.iter().any(|&digit| digit != 0);

        match exponent.cmp(&0) {
            Ordering::Less => {
                for _ in exponent..0 {
                    let last_integer_digit = result % 10;
                    result /= 10;
                    has_fractional_digits |= last_integer_digit != 0;
                }
            }

            Ordering::Equal => (),

            Ordering::Greater => {
                for _ in 0..exponent {
                    result = result
                        .saturating_mul(10)
                        .saturating_add(fractional.pop_front().unwrap_or(0).into());
                }

                has_fractional_digits = fractional.iter().any(|&digit| digit != 0);
            }
        }

        if has_fractional_digits {
            result = result.saturating_add(1);
        }

        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go
    #[test]
    fn ok() {
        #[rustfmt::skip]
        let test_cases = [
            ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 0 }),

            ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }),
            ("10", 10, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], exponent: 0 }),
            ("500", 500, Quantity { sign: Sign::Positive, integral: vec![5, 0, 0], fractional: vec![], exponent: 0 }),
            ("8000", 8_000, Quantity { sign: Sign::Positive, integral: vec![8, 0, 0 ,0], fractional: vec![], exponent: 0 }),
            ("2", 2, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], exponent: 0 }),
            ("0.1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![1], exponent: 0 }),
            ("0.03", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 3], exponent: 0 }),
            ("0.004", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 4], exponent: 0 }),

            ("9223372036854775808", 9_223_372_036_854_775_808, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8], fractional: vec![], exponent: 0 }),
            ("92233720368547758080", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0], fractional: vec![], exponent: 0 }),
            ("922337203685477580800", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8, 0, 0], fractional: vec![], exponent: 0 }),
            ("922337203685477580.8", 922_337_203_685_477_581, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![8], exponent: 0 }),
            ("92233720368547758.08", 92_233_720_368_547_759, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 8], exponent: 0 }),
            ("-9223372036854775809", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9], fractional: vec![], exponent: 0 }),
            ("-92233720368547758090", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0], fractional: vec![], exponent: 0 }),
            ("-922337203685477580900", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 9, 0, 0], fractional: vec![], exponent: 0 }),
            ("-922337203685477580.9", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0], fractional: vec![9], exponent: 0 }),
            ("-92233720368547758.09", 0, Quantity { sign: Sign::Negative, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8], fractional: vec![0, 9], exponent: 0 }),

            ("0.025Ti", 25_000_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], exponent: 12 }),
            ("1.025Ti", 1_025_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 2, 5], exponent: 12 }),
            ("-1.025Ti", 0, Quantity { sign: Sign::Negative, integral: vec![1], fractional: vec![0, 2, 5], exponent: 12 }),
            (".", 0, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![], exponent: 0 }),
            ("-.", 0, Quantity { sign: Sign::Negative, integral: vec![], fractional: vec![], exponent: 0 }),
            ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: -3 }),

            ("0", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 0 }),
            ("0n", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -9 }),
            ("0u", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -6 }),
            ("0m", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: -3 }),
            ("0Ki", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 3 }),
            ("0k", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 3 }),
            ("0Mi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 6 }),
            ("0M", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 6 }),
            ("0Gi", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 9 }),
            ("0G", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 9 }),
            ("0Ti", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 12 }),
            ("0T", 0, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![], exponent: 12 }),

            ("1", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }),

            ("1Ki", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }),
            ("8Ki", 8_000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], exponent: 3 }),
            ("7Mi", 7_000_000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], exponent: 6 }),
            ("6Gi", 6_000_000_000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], exponent: 9 }),
            ("5Ti", 5_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: 12 }),
            ("4Pi", 4_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], exponent: 15 }),
            ("3Ei", 3_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], exponent: 18 }),

            ("10Ti", 10_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![], exponent: 12 }),
            ("100Ti", 100_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![], exponent: 12 }),

            ("5n", 1, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: -9 }),
            ("4u", 1, Quantity { sign: Sign::Positive, integral: vec![4], fractional: vec![], exponent: -6 }),
            ("3m", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![], exponent: -3 }),
            ("9", 9, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], exponent: 0 }),
            ("8k", 8_000, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![], exponent: 3 }),
            ("50k", 50_000, Quantity { sign: Sign::Positive, integral: vec![5, 0], fractional: vec![], exponent: 3 }),
            ("7M", 7_000_000, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![], exponent: 6 }),
            ("6G", 6_000_000_000, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![], exponent: 9 }),
            ("5T", 5_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![], exponent: 12 }),
            ("40T", 40_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![], exponent: 12 }),
            ("300T", 300_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![], exponent: 12 }),
            ("2P", 2_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![], exponent: 15 }),
            ("1E", 1_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 18 }),

            ("1E-3", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: -3 }),
            ("1e3", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }),
            ("1E6", 1_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 6 }),
            ("1e9", 1_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 9 }),
            ("1E12", 1_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 12 }),
            ("1e15", 1_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 15 }),
            ("1E18", 1_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 18 }),

            ("1e14", 100_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 14 }),
            ("1e13", 10_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 13 }),
            ("1e15", 1_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 15 }),
            ("1e3", 1_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 3 }),
            ("100.035k", 100_035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], exponent: 3 }),

            ("0.001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 1], exponent: 0 }),
            ("0.0005k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5], exponent: 3 }),
            ("0.005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5], exponent: 0 }),
            ("0.05", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], exponent: 0 }),
            ("0.5", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }),
            ("0.00050k", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 5, 0], exponent: 3 }),
            ("0.00500", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 5, 0, 0], exponent: 0 }),
            ("0.05000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5, 0, 0, 0], exponent: 0 }),
            ("0.50000", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5, 0, 0, 0, 0], exponent: 0 }),
            ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }),
            ("0.5e-1", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -1 }),
            ("0.5e-2", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -2 }),
            ("0.5e0", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 0 }),
            ("10.035M", 10_035_000, Quantity { sign: Sign::Positive, integral: vec![1, 0], fractional: vec![0, 3, 5], exponent: 6 }),

            ("1.2e3", 1_200, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![2], exponent: 3 }),
            ("1.3E+6", 1_300_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![3], exponent: 6 }),
            ("1.40e9", 1_400_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![4, 0], exponent: 9 }),
            ("1.53E12", 1_530_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![5, 3], exponent: 12 }),
            ("1.6e15", 1_600_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![6], exponent: 15 }),
            ("1.7E18", 1_700_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![7], exponent: 18 }),

            ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], exponent: 0 }),
            ("8.1k", 8_100, Quantity { sign: Sign::Positive, integral: vec![8], fractional: vec![1], exponent: 3 }),
            ("7.123456M", 7_123_456, Quantity { sign: Sign::Positive, integral: vec![7], fractional: vec![1, 2, 3, 4, 5, 6], exponent: 6 }),
            ("6.987654321G", 6_987_654_321, Quantity { sign: Sign::Positive, integral: vec![6], fractional: vec![9, 8, 7, 6, 5, 4, 3, 2, 1], exponent: 9 }),
            ("5.444T", 5_444_000_000_000, Quantity { sign: Sign::Positive, integral: vec![5], fractional: vec![4, 4, 4], exponent: 12 }),
            ("40.1T", 40_100_000_000_000, Quantity { sign: Sign::Positive, integral: vec![4, 0], fractional: vec![1], exponent: 12 }),
            ("300.2T", 300_200_000_000_000, Quantity { sign: Sign::Positive, integral: vec![3, 0, 0], fractional: vec![2], exponent: 12 }),
            ("2.5P", 2_500_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![2], fractional: vec![5], exponent: 15 }),
            ("1.01E", 1_010_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![0, 1], exponent: 18 }),

            ("3.001n", 1, Quantity { sign: Sign::Positive, integral: vec![3], fractional: vec![0, 0, 1], exponent: -9 }),
            ("1.1E-9", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![1], exponent: -9 }),
            ("0.0000000001", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1], exponent: 0 }),
            ("0.0000000005", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5], exponent: 0 }),
            ("0.00000000050", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0], exponent: 0 }),
            ("0.5e-9", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: -9 }),
            ("0.9n", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![9], exponent: -9 }),
            ("0.00000012345", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5], exponent: 0 }),
            ("0.00000012354", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 1, 2, 3, 5, 4], exponent: 0 }),
            ("9Ei", 9_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![], exponent: 18 }),
            ("9223372036854775807Ki", u64::MAX, Quantity { sign: Sign::Positive, integral: vec![9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 7], fractional: vec![], exponent: 3 }),
            ("12E", 12_000_000_000_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1, 2], fractional: vec![], exponent: 18 }),

            ("100.035Ki", 100_035, Quantity { sign: Sign::Positive, integral: vec![1, 0, 0], fractional: vec![0, 3, 5], exponent: 3 }),
            ("0.5Mi", 500_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![5], exponent: 6 }),
            ("0.05Gi", 50_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 5], exponent: 9 }),
            ("0.025Ti", 25_000_000_000, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 2, 5], exponent: 12 }),

            ("0.000000000001Ki", 1, Quantity { sign: Sign::Positive, integral: vec![0], fractional: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], exponent: 3 }),
            (".001", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 1], exponent: 0 }),
            (".0001k", 1, Quantity { sign: Sign::Positive, integral: vec![], fractional: vec![0, 0, 0, 1], exponent: 3 }),
            ("1.", 1, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 0 }),
            ("1.G", 1_000_000_000, Quantity { sign: Sign::Positive, integral: vec![1], fractional: vec![], exponent: 9 }),

            ("9.01", 10, Quantity { sign: Sign::Positive, integral: vec![9], fractional: vec![0, 1], exponent: 0 }),

            ("-9.01", 0, Quantity { sign: Sign::Negative, integral: vec![9], fractional: vec![0, 1], exponent: 0 }),
        ];

        for (input, expected_u64, expected_quantity) in test_cases {
            eprintln!("input: {input}");

            let actual_quantity: Quantity = input.parse().unwrap();
            assert_eq!(expected_quantity, actual_quantity);

            let actual_u64: u64 = actual_quantity.into();
            assert_eq!(expected_u64, actual_u64);
        }
    }

    // Ref: https://github.com/kubernetes/kubernetes/blob/v1.29.1/staging/src/k8s.io/apimachinery/pkg/api/resource/quantity_test.go
    #[test]
    fn err() {
        for (input, expected) in [
            ("1.1.M", r#"expected suffix, found ".M""#),
            ("1+1.0M", r#"expected suffix, found "+1.0M""#),
            ("0.1mi", r#"expected suffix, found "mi""#),
            ("0.1am", r#"expected suffix, found "am""#),
            ("aoeu", r#"expected signed number, found "aoeu""#),
            (".5i", r#"expected suffix, found "i""#),
            ("1i", r#"expected suffix, found "i""#),
            ("-3.01i", r#"expected suffix, found "i""#),
            ("-3.01e-", "digits is empty"),
            (" 1", r#"expected signed number, found " 1""#),
            ("1 ", r#"expected suffix, found " ""#),
        ] {
            eprintln!("input: {input}");
            let actual = input.parse::<Quantity>().unwrap_err();
            assert_eq!(expected, actual);
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants