use crate::error::ParamError;
macro_rules! finite_unit {
($name:ident, $doc:literal, $field:literal) => {
#[doc = $doc]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct $name(f64);
impl $name {
pub fn new(value: f64) -> Result<Self, ParamError> {
if value.is_finite() {
Ok(Self(value))
} else {
Err(ParamError::NonFinite { name: $field })
}
}
#[must_use]
pub const fn get(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for $name {
type Error = ParamError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<$name> for f64 {
fn from(value: $name) -> Self {
value.get()
}
}
};
}
finite_unit!(
LogMoneyness,
r"A finite forward log-moneyness `ln(strike / forward)`.
# Examples
```
# fn main() -> Result<(), Box<dyn std::error::Error>> {
use regit_svi::LogMoneyness;
let k = LogMoneyness::new(-0.25)?;
assert_eq!(k.get(), -0.25);
# Ok(())
# }
```",
"log_moneyness"
);
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Maturity(f64);
impl Maturity {
pub fn new(value: f64) -> Result<Self, ParamError> {
if !value.is_finite() {
return Err(ParamError::NonFinite { name: "maturity" });
}
if value <= 0.0 {
return Err(ParamError::NonPositiveMaturity { t: value });
}
Ok(Self(value))
}
#[must_use]
pub const fn get(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for Maturity {
type Error = ParamError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<Maturity> for f64 {
fn from(value: Maturity) -> Self {
value.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct TotalVariance(f64);
impl TotalVariance {
pub fn new(value: f64) -> Result<Self, ParamError> {
if !value.is_finite() {
return Err(ParamError::NonFinite {
name: "total_variance",
});
}
if value < 0.0 {
return Err(ParamError::NegativeTotalVariance { w: value });
}
Ok(Self(value))
}
#[must_use]
pub const fn get(self) -> f64 {
self.0
}
}
impl TryFrom<f64> for TotalVariance {
type Error = ParamError;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value)
}
}
impl From<TotalVariance> for f64 {
fn from(value: TotalVariance) -> Self {
value.get()
}
}
#[cfg(test)]
#[allow(clippy::expect_used)] mod tests {
use super::*;
#[test]
fn validated_units_enforce_boundaries() {
assert!(LogMoneyness::new(0.0).is_ok());
assert!(LogMoneyness::new(f64::NAN).is_err());
assert!(Maturity::new(f64::MIN_POSITIVE).is_ok());
assert!(Maturity::new(0.0).is_err());
assert!(TotalVariance::new(0.0).is_ok());
assert!(TotalVariance::new(-f64::MIN_POSITIVE).is_err());
}
}