#![no_std]
#![no_implicit_prelude]
#![deny(
unsafe_code,
unused,
warnings,
clippy::all,
clippy::cargo,
clippy::nursery,
clippy::pedantic
)]
#![allow(
clippy::implicit_return,
clippy::missing_trait_methods,
clippy::unseparated_literal_suffix
)]
extern crate alloc;
#[allow(unused_extern_crates)]
extern crate core;
#[allow(unused_extern_crates)]
extern crate num_integer;
#[allow(unused_extern_crates)]
extern crate num_rational;
#[allow(unused_extern_crates)]
extern crate num_traits;
#[allow(unused_extern_crates)]
extern crate serde;
use crate::FromDecStrErr::{IntParseErr, TooFewFractionalDigits, TooManyFractionalDigits};
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::clone::Clone;
use core::cmp::PartialOrd;
use core::convert::From;
use core::fmt::{self, Debug, Display, Formatter};
use core::marker::PhantomData;
use core::ops::Mul;
use core::option::Option;
use core::result::Result::{self, Err, Ok};
use core::str::FromStr;
use core::{unreachable, write};
use num_integer::Integer;
use num_rational::Ratio;
use num_traits::Pow;
pub struct MinMax<T> {
min: T,
max: T,
}
impl<T> MinMax<T> {
pub const fn min(&self) -> &T {
&self.min
}
pub const fn max(&self) -> &T {
&self.max
}
}
impl<T> MinMax<T>
where
T: PartialOrd<T>,
{
pub fn new(min: T, max: T) -> Option<Self> {
(min <= max).then_some(Self { min, max })
}
#[allow(unsafe_code)]
pub const unsafe fn new_unchecked(min: T, max: T) -> Self {
Self { min, max }
}
}
#[allow(clippy::exhaustive_enums)]
pub enum FromDecStrErr<T> {
IntParseErr(T),
TooFewFractionalDigits(usize),
TooManyFractionalDigits(usize),
}
impl<T> Display for FromDecStrErr<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
IntParseErr(ref x) => x.fmt(f),
TooFewFractionalDigits(ref x) => write!(
f,
"There were only {x} fractional digits which is fewer than the minimum required."
),
TooManyFractionalDigits(ref x) => write!(
f,
"There were {x} fractional digits which is more than the maximum required."
),
}
}
}
impl<T> Debug for FromDecStrErr<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl<T> From<T> for FromDecStrErr<T> {
fn from(x: T) -> Self {
IntParseErr(x)
}
}
#[allow(clippy::arithmetic_side_effects, clippy::single_char_lifetime_names)]
pub fn try_from_dec_str<T>(
val: &str,
frac_digit_count: &MinMax<usize>,
) -> Result<Ratio<T>, FromDecStrErr<<T as FromStr>::Err>>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
val.split_once('.').map_or_else(
|| {
if *frac_digit_count.min() == 0 {
Ok(Ratio::from(T::from_str(val)?))
} else {
Err(TooFewFractionalDigits(val.len()))
}
},
|(l, r)| {
if r.len() >= *frac_digit_count.min() {
if r.len() <= *frac_digit_count.max() {
let mult = T::from(10).pow(r.len());
Ok(Ratio::new(
(T::from_str(l)? * &mult) + T::from_str(r)?,
mult,
))
} else {
Err(TooManyFractionalDigits(r.len()))
}
} else {
Err(TooFewFractionalDigits(r.len()))
}
},
)
}
#[allow(clippy::exhaustive_enums)]
pub enum FromStrErr<T> {
IntParseErr(T),
DenominatorIsZero,
}
impl<T> Display for FromStrErr<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match *self {
Self::IntParseErr(ref x) => x.fmt(f),
Self::DenominatorIsZero => f.write_str("denominator is zero"),
}
}
}
impl<T> Debug for FromStrErr<T>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<Self as Display>::fmt(self, f)
}
}
impl<T> From<T> for FromStrErr<T> {
fn from(x: T) -> Self {
Self::IntParseErr(x)
}
}
#[allow(
unsafe_code,
clippy::arithmetic_side_effects,
clippy::single_char_lifetime_names,
clippy::unreachable
)]
pub fn try_from_str<T>(val: &str) -> Result<Ratio<T>, FromStrErr<<T as FromStr>::Err>>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
val.split_once('/').map_or_else(
|| {
try_from_dec_str(val, &unsafe{MinMax::new_unchecked(0, usize::MAX)}).map_err(|e| match e {
IntParseErr(x) => FromStrErr::IntParseErr(x),
TooFewFractionalDigits(_) | TooManyFractionalDigits(_) => unreachable!("There is a bug in rational::try_from_dec_str. 0 and usize::MAX were passed as the minimum and maximum number of fractional digits allowed respectively, but it still errored due to too few or too many fractional digits"),
})
},
|(l, r)| {
let denom = T::from_str(r)?;
if denom == T::from(0) {
Err(FromStrErr::DenominatorIsZero)
} else {
l.split_once(' ').map_or_else(
|| Ok(Ratio::new(T::from_str(l)?, denom.clone())),
|(l2, r2)| {
Ok(Ratio::new(
(T::from_str(l2)? * &denom) + T::from_str(r2)?,
denom.clone(),
))
},
)
}
},
)
}
#[allow(
unsafe_code,
clippy::arithmetic_side_effects,
clippy::integer_arithmetic
)]
pub fn to_dec_string<T>(val: &Ratio<T>, frac_digit_count: usize) -> String
where
T: Clone + Display + From<u8> + Integer + Pow<usize, Output = T>,
{
let mult = T::from(10).pow(frac_digit_count);
let (int, frac) = (val * &mult).round().numer().div_rem(&mult);
let int_str = int.to_string();
let mut v = Vec::with_capacity(int_str.len() + frac_digit_count + 1);
v.extend_from_slice(int.to_string().as_bytes());
if frac_digit_count > 0 {
v.push(b'.');
let len = v.len();
let frac_str = frac.to_string();
while v.len() < len + (frac_digit_count - frac_str.len()) {
v.push(b'0');
}
v.extend_from_slice(frac.to_string().as_bytes());
}
unsafe { String::from_utf8_unchecked(v) }
}
use serde::de::{self, Deserialize, Deserializer, Unexpected, Visitor};
#[allow(clippy::exhaustive_structs)]
pub struct Rational<T>(pub Ratio<T>);
#[allow(clippy::single_char_lifetime_names)]
impl<'de, T> Deserialize<'de> for Rational<T>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct RationalVisitor<T> {
_x: PhantomData<fn() -> T>,
}
#[allow(clippy::single_char_lifetime_names)]
impl<'de, T> Visitor<'de> for RationalVisitor<T>
where
T: Clone
+ From<u8>
+ FromStr
+ Integer
+ for<'a> Mul<&'a T, Output = T>
+ Pow<usize, Output = T>,
{
type Value = Rational<T>;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("struct Rational")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
try_from_str(v).map_or_else(
|_| {
Err(E::invalid_value(
Unexpected::Str(v),
&"a rational number in fraction or decimal notation",
))
},
|r| Ok(Rational(r)),
)
}
}
deserializer.deserialize_str(RationalVisitor { _x: PhantomData })
}
}
#[cfg(test)]
mod tests {
#[allow(unused_extern_crates)]
extern crate serde_json;
use super::*;
use alloc::format;
use core::assert_eq;
use core::num::ParseIntError;
#[test]
fn test_min_max() -> Result<(), String> {
let mut m: MinMax<u32>;
for i in 0..1000 {
for j in 0..1000 {
m = MinMax::new(i, i + j)
.ok_or_else(|| format!("Failed for {} and {}.", i, i + j))?;
assert_eq!(*m.min(), i);
assert_eq!(*m.max(), i + j);
}
}
Ok(())
}
#[test]
#[should_panic]
fn test_min_max_err() {
MinMax::new(f64::NAN, 0.0).unwrap();
}
#[test]
fn test_dec_string() -> Result<(), String> {
assert_eq!("0", &to_dec_string(&Ratio::<u32>::new(0, 1), 0));
assert_eq!("5.000", &to_dec_string(&Ratio::<u32>::new(5, 1), 3));
assert_eq!("0.66667", &to_dec_string(&Ratio::<u32>::new(2, 3), 5));
Ok(())
}
#[test]
fn test_from_str() -> Result<(), FromStrErr<ParseIntError>> {
assert_eq!(try_from_str::<u32>("4")?, Ratio::new(4, 1));
assert_eq!(try_from_str::<u32>("4/8")?, Ratio::new(1, 2));
assert_eq!(try_from_str::<u32>("5 9/8")?, Ratio::new(49, 8));
assert_eq!(try_from_str::<u32>("0.1249")?, Ratio::new(1249, 10000));
assert_eq!(try_from_str::<u32>("0.0")?, Ratio::new(0, 1));
try_from_str::<u32>("1/0").map_or_else(
|e| match e {
FromStrErr::DenominatorIsZero => (),
_ => assert_eq!(false, true),
},
|_| assert_eq!(false, true),
);
Ok(())
}
#[test]
fn test_serde() -> Result<(), serde_json::Error> {
assert_eq!(
Ratio::new(2u8, 3u8),
serde_json::from_str::<Rational::<u8>>(r#""2/3""#)?.0
);
assert_eq!(
Ratio::new(67u8, 100u8),
serde_json::from_str::<Rational::<u8>>(r#""0.67""#)?.0
);
Ok(())
}
}