mod decimals;
pub mod output;
use crate::{
error::GivError,
pi::decimals::{PI_DECIMALS, PI_MAX_DECIMALS},
};
pub use output::PiOutput;
use std::borrow::Cow;
pub const DEFAULT_ROUND: bool = true;
pub const PI_DEFAULT_PLACES: usize = 15;
const PI_PREFIX: &str = "3.";
const ROUND_UP_FROM: u8 = 5;
const BASE10: u32 = 10;
pub type RoundingFlags = (Option<bool>, Option<bool>);
pub fn get_pi(places: usize, round: bool) -> Result<String, GivError> {
if places == 0 || places > PI_MAX_DECIMALS {
Err(GivError::DecimalPlacesOutOfRange(places, PI_MAX_DECIMALS))
} else {
let mut decimals = Cow::Borrowed(&PI_DECIMALS[..places]);
if round && places < PI_MAX_DECIMALS {
let next_digit = PI_DECIMALS[places];
if next_digit >= ROUND_UP_FROM {
let decimals = decimals.to_mut();
for idx in (0..=decimals.len() - 1).rev() {
if decimals[idx] < 9 {
decimals[idx] += 1;
break;
} else {
decimals[idx] = 0;
debug_assert!(idx == 0, "Rounding PI can not carry over to the 0th index.");
}
}
}
}
let mut result = String::with_capacity(PI_PREFIX.len() + decimals.len());
result.push_str(PI_PREFIX);
for &digit in decimals.iter() {
result.push(char::from_digit(digit as u32, BASE10).unwrap());
}
Ok(result)
}
}
pub fn get_rounding(rounding_flags: RoundingFlags) -> Result<bool, GivError> {
match rounding_flags {
(Some(true), Some(true)) => Err(GivError::ConflictingFlags(
"cannot specify both --round and --no-round".to_string(),
)),
(Some(true), None) | (Some(true), Some(false)) => Ok(true),
(None, Some(true)) | (Some(false), Some(true)) => Ok(false),
(_, _) => Ok(DEFAULT_ROUND),
}
}
pub fn generate_pi(places: Option<usize>, round: Option<bool>) -> Result<PiOutput, GivError> {
let places = places.unwrap_or(PI_DEFAULT_PLACES);
let round = round.unwrap_or(DEFAULT_ROUND);
let pi_value = get_pi(places, round)?;
Ok(PiOutput {
pi: pi_value,
rounded: round,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_pi() {
let result = get_pi(PI_DEFAULT_PLACES, true);
assert!(result.is_ok());
assert_eq!(result.unwrap(), "3.141592653589793");
assert_eq!(get_pi(1, true).unwrap(), "3.1");
assert_eq!(get_pi(1, false).unwrap(), "3.1");
assert_eq!(get_pi(2, true).unwrap(), "3.14");
assert_eq!(get_pi(2, false).unwrap(), "3.14");
assert_eq!(get_pi(3, true).unwrap(), "3.142");
assert_eq!(get_pi(3, false).unwrap(), "3.141");
assert_eq!(get_pi(6, true).unwrap(), "3.141593");
assert_eq!(get_pi(6, false).unwrap(), "3.141592");
assert_eq!(get_pi(7, true).unwrap(), "3.1415927");
assert_eq!(get_pi(7, false).unwrap(), "3.1415926");
assert_eq!(get_pi(10, true).unwrap(), "3.1415926536");
assert_eq!(get_pi(10, false).unwrap(), "3.1415926535");
assert_eq!(get_pi(15, true).unwrap(), "3.141592653589793");
assert_eq!(get_pi(15, false).unwrap(), "3.141592653589793");
let f64_pi = std::f64::consts::PI.to_string();
assert_eq!(&get_pi(15, true).unwrap(), &f64_pi);
let f32_pi = std::f32::consts::PI.to_string();
assert_eq!(&get_pi(7, true).unwrap(), &f32_pi);
assert_eq!(get_pi(25, true).unwrap(), "3.1415926535897932384626434");
assert_eq!(get_pi(25, false).unwrap(), "3.1415926535897932384626433");
assert_eq!(
get_pi(50, true).unwrap(),
"3.14159265358979323846264338327950288419716939937511"
);
assert_eq!(
get_pi(50, false).unwrap(),
"3.14159265358979323846264338327950288419716939937510"
);
let full_decimals = format!(
"{}{}",
PI_PREFIX,
PI_DECIMALS
.iter()
.map(|&digit| char::from_digit(digit as u32, BASE10).unwrap())
.collect::<String>()
);
assert_eq!(get_pi(PI_DECIMALS.len(), true).unwrap(), full_decimals);
assert_eq!(get_pi(PI_DECIMALS.len(), false).unwrap(), full_decimals);
}
#[test]
fn test_get_pi_zero_places() {
let result = get_pi(0, true);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
GivError::DecimalPlacesOutOfRange(places, max) => {
assert_eq!(places, 0);
assert_eq!(max, PI_MAX_DECIMALS);
}
_ => {
panic!("Unexpected error type: {err}");
}
}
assert_eq!(
err.to_string(),
format!(
"Requested number of PI decimal places '{}' is not supported please select a value between '1' and '{}'",
0, PI_MAX_DECIMALS
)
);
}
#[test]
fn test_get_pi_too_many_places() {
let result = get_pi(PI_MAX_DECIMALS + 1, true);
assert!(result.is_err());
let err = result.unwrap_err();
match err {
GivError::DecimalPlacesOutOfRange(places, max) => {
assert_eq!(places, PI_MAX_DECIMALS + 1);
assert_eq!(max, PI_MAX_DECIMALS);
}
_ => {
panic!("Unexpected error type: {err}");
}
}
assert_eq!(
err.to_string(),
format!(
"Requested number of PI decimal places '{}' is not supported please select a value between '1' and '{}'",
PI_MAX_DECIMALS + 1,
PI_MAX_DECIMALS
)
);
}
#[test]
fn test_pi_decimals_length() {
assert_eq!(PI_DECIMALS.len(), 10_000);
}
#[test]
fn test_conflicting_rounding_flags() {
let result = get_rounding((Some(true), Some(true)));
assert!(result.is_err());
let err = result.unwrap_err();
match err {
GivError::ConflictingFlags(_) => {
}
_ => {
panic!("Unexpected error type: {err}");
}
}
assert_eq!(
err.to_string(),
"Conflicting flags: cannot specify both --round and --no-round"
);
}
}