1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use core::{
    cmp::Ordering,
    convert::TryFrom,
    fmt,
    hash::{Hash, Hasher},
};

use necsim_core_maths::MathsCore;
use serde::{Deserialize, Serialize};

use crate::NonPositiveF64;

#[derive(Debug)]
#[allow(clippy::module_name_repetitions)]
pub struct OpenClosedUnitF64Error(f64);

impl fmt::Display for OpenClosedUnitF64Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{} is not in (0.0, 1.0].", self.0)
    }
}

#[allow(clippy::unsafe_derive_deserialize)]
#[derive(Copy, Clone, Deserialize, Serialize, TypeLayout)]
#[repr(transparent)]
#[serde(try_from = "f64", into = "f64")]
pub struct OpenClosedUnitF64(f64);

impl fmt::Display for OpenClosedUnitF64 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt::Display::fmt(&self.0, fmt)
    }
}

impl TryFrom<f64> for OpenClosedUnitF64 {
    type Error = OpenClosedUnitF64Error;

    fn try_from(value: f64) -> Result<Self, Self::Error> {
        Self::new(value)
    }
}

impl From<OpenClosedUnitF64> for f64 {
    fn from(val: OpenClosedUnitF64) -> Self {
        val.get()
    }
}

impl fmt::Debug for OpenClosedUnitF64 {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        struct OpenClosedUnitF64Range(f64);

        impl fmt::Debug for OpenClosedUnitF64Range {
            fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
                write!(fmt, "0.0 < {} <= 1.0", self.0)
            }
        }

        fmt.debug_tuple("OpenClosedUnitF64")
            .field(&OpenClosedUnitF64Range(self.0))
            .finish()
    }
}

impl OpenClosedUnitF64 {
    /// # Errors
    ///
    /// Returns `OpenClosedUnitF64Error` if not `0.0 < value <= 1.0`
    pub const fn new(value: f64) -> Result<Self, OpenClosedUnitF64Error> {
        if value > 0.0 && value <= 1.0 {
            Ok(Self(value))
        } else {
            Err(OpenClosedUnitF64Error(value))
        }
    }

    /// # Safety
    ///
    /// Only safe iff `0.0 < value <= 1.0`
    #[must_use]
    pub const unsafe fn new_unchecked(value: f64) -> Self {
        Self(value)
    }

    #[must_use]
    pub const fn get(self) -> f64 {
        self.0
    }

    #[must_use]
    pub fn ln<M: MathsCore>(self) -> NonPositiveF64 {
        unsafe { NonPositiveF64::new_unchecked(M::ln(self.0)) }
    }
}

impl PartialEq for OpenClosedUnitF64 {
    #[allow(clippy::unconditional_recursion)]
    fn eq(&self, other: &Self) -> bool {
        self.0.eq(&other.0)
    }
}

impl Eq for OpenClosedUnitF64 {}

impl PartialOrd for OpenClosedUnitF64 {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for OpenClosedUnitF64 {
    fn cmp(&self, other: &Self) -> Ordering {
        self.0.total_cmp(&other.0)
    }
}

impl Hash for OpenClosedUnitF64 {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.to_bits().hash(state);
    }
}

impl PartialEq<f64> for OpenClosedUnitF64 {
    fn eq(&self, other: &f64) -> bool {
        self.0.eq(other)
    }
}

impl PartialOrd<f64> for OpenClosedUnitF64 {
    fn partial_cmp(&self, other: &f64) -> Option<Ordering> {
        self.0.partial_cmp(other)
    }
}