use crate::{Odds, OddsError, OddsFormat};
use num_integer::gcd;
pub(crate) fn normalize_american_odds(odds: i32) -> i32 {
if odds > 0 && odds < 100 {
-((100 * 100) / odds)
} else if odds < 0 && odds > -100 {
(100 * 100) / (-odds)
} else {
odds
}
}
impl Odds {
pub fn to_american(&self) -> Result<i32, OddsError> {
match &self.format {
OddsFormat::American(value) => Ok(*value),
OddsFormat::Decimal(decimal) => {
if *decimal >= 2.0 {
let american = ((decimal - 1.0) * 100.0).round() as i32;
Ok(normalize_american_odds(american))
} else if *decimal > 1.0 {
Ok((-100.0 / (decimal - 1.0)).round() as i32)
} else {
Err(OddsError::InvalidDecimalOdds(format!(
"Decimal odds must be greater than 1.0, got: {}",
decimal
)))
}
}
OddsFormat::Fractional(num, den) => {
let decimal = (*num as f64) / (*den as f64) + 1.0;
if decimal >= 2.0 {
let american = ((decimal - 1.0) * 100.0).round() as i32;
Ok(normalize_american_odds(american))
} else {
Ok((-100.0 / (decimal - 1.0)).round() as i32)
}
}
}
}
pub fn to_decimal(&self) -> Result<f64, OddsError> {
match &self.format {
OddsFormat::Decimal(value) => Ok(*value),
OddsFormat::American(american) => {
if *american > 0 {
Ok((*american as f64) / 100.0 + 1.0)
} else if *american < 0 {
Ok(100.0 / (-*american as f64) + 1.0)
} else {
Err(OddsError::InvalidAmericanOdds(
"American odds cannot be zero".to_string(),
))
}
}
OddsFormat::Fractional(num, den) => {
if *den == 0 {
Err(OddsError::ZeroDenominator)
} else {
Ok((*num as f64) / (*den as f64) + 1.0)
}
}
}
}
pub fn to_fractional(&self) -> Result<(u32, u32), OddsError> {
match &self.format {
OddsFormat::Fractional(num, den) => Ok((*num, *den)),
_ => {
let decimal = self.to_decimal()?;
let profit = decimal - 1.0;
let denominator = 1000;
let numerator = (profit * denominator as f64).round() as u32;
let common_divisor = gcd(numerator, denominator);
Ok((numerator / common_divisor, denominator / common_divisor))
}
}
}
pub fn implied_probability(&self) -> Result<f64, OddsError> {
let decimal = self.to_decimal()?;
Ok(1.0 / decimal)
}
}