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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! Static fractions are generalized fixed-point numbers. They can scale a
//! compactly stored (eg. u8 typed) mantissa to any fractional range
//! expressible by a fraction of a more expressive (eg. u32) fraction.
//!
//! They are useful not only for accessing stored sensor data without manually
//! following the operations to convert them into a standard unit, but should
//! also be usable in APIs of embedded systems, especially where measurements
//! are post-processed in a fashion known at compile time, which can then be
//! simplified by the compiler.
//!
//! For example, when a check like
//!
//! ```
//! if get_system_voltage() < 3.1 { prinln!("Undervoltage warning!"); }
//! ```
//!
//! should finally be optimizable at compile time into an integer comparison
//! against the value read from a hardware device without the user of the API
//! having to know the constants involved explicitly.
//!
//! A note on generic names: The `S` parameter always indicates with which data
//! type the numerator is *s*tored. The `C` parameter always indicates with
//! which data type *c*alculations happen, which is also the type of the stored
//! denominator. The `D` parameter indicates the
//! [StaticFractionDescription](trait.StaticFractionDescription) that holds the
//! numerator scaling and the denominator.

#![feature(associated_consts)]
#![feature(try_from)]

use std::marker::PhantomData;

extern crate num;

#[cfg(feature="to_fraction")]
extern crate fraction;

// FIXME there has to be a better way
pub trait HasOverflowingMul : Sized {
    fn overflowing_mul(self, rhs: Self) -> (Self, bool);
}
impl HasOverflowingMul for u32 {
    fn overflowing_mul(self, rhs: Self) -> (Self, bool) { self.overflowing_mul(rhs) }
}


/// Description of transformation parameters
///
/// A datum *x* is transformed to a value *y* by the formula
/// *y = (NUM_OFFSET + x \*  NUM_FACTOR) / DENOM*.
///
/// It is required that those values are reduced, ie. that there is no prime
/// factor common to the three constants.
///
/// You can create descriptions like in this example:
///
/// ```
/// #[derive(Default)]
/// struct VoltageFromADCDescription;
/// impl staticfraction::StaticFractionDescription<u32> for VoltageFromADCDescription {
///     const NUM_OFFSET: u32 = 0;
///     const NUM_FACTOR: u32 = 1;
///     const DENOM: u32 = 204;
/// }
/// type VoltageFromADC = staticfraction::StaticFraction<u8, u32, VoltageFromADCDescription>;
/// ```
///
/// TODO: Provide a macro that expresses the same with
/// `StaticFractionType!(VoltageFromADC, 0u8, 255u8, 0, 1, 125, 100)`.
pub trait StaticFractionDescription<C> where
{
    const NUM_OFFSET: C;
    const NUM_FACTOR: C;
    const DENOM: C;
}

/// Stored numeric datum that is, for all mathematical operations, seen under
/// the transformation described by D.
///
/// The valid range for the stored datum depends on D; only numbers x where
/// *NUM_OFFSET + x \* NUM_FACTOR* can be calculated in C without overflowing are
/// admissible.
#[derive(Debug)]
pub struct StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
{
    stored: S,
    calculation_type: PhantomData<C>, // is this the right workaround for "the compiler doesn't see that i need to be generic on C to become generic on D which depends on the presence of a C"?
    description: D,
}

impl<S, C, D> StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
{
    /// Create a StaticFraction right from the stored scaled numerator. This is
    /// intended for adding semantics to data that already comes along scaled.
    ///
    /// The value is only checked for being in the right range if debug
    /// assertions are enabled (just like other checks for wrapping behavior).
    pub fn new_from_stored(stored: S) -> Self {
        debug_assert!(stored <= Self::max_stored());
        Self { stored: stored, calculation_type: PhantomData, description: D::default()}
    }

    /// Variant of [new_from_stored](struct.StaticFraction.new_from_stored)
    /// that returns out-of-range errors as None options.
    pub fn new_from_stored_checked(stored: S) -> Option<Self> {
        if stored <= Self::max_stored() {
            Some(Self { stored: stored, calculation_type: PhantomData, description: D::default()})
        } else {
            None
        }
    }

    fn numerator(&self) -> C { D::NUM_OFFSET + D::NUM_FACTOR * C::from(self.stored) }
    fn max_stored() -> S {
        let max_from_params = C::max_value() / D::NUM_FACTOR - D::NUM_OFFSET;
        if max_from_params < C::from(S::max_value()) {
            // FIXME this should be optimizable to return a constant even in this case, but isn't with 1.19.0-nightly (0ed1ec9f9 2017-05-18)
            S::try_from(max_from_params).unwrap_or_else(|_| unreachable!())
        } else {
            S::max_value()
        }
    }
    fn min_numerator(&self) -> C { D::NUM_OFFSET + D::NUM_FACTOR * C::from(S::min_value()) }
    fn max_numerator(&self) -> C { D::NUM_OFFSET + D::NUM_FACTOR * C::from(S::max_value()) }
}

#[cfg(feature="to_fraction")]
impl<S, C, D> StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
    // additional requirements for Fraction compatiblility
    C: Clone + num::Unsigned + num::integer::Integer + num::Bounded, // actually i'd prefer to have impl<S, C, D, F> ... with F: From<C>, and GenericFraction<F>
{
    pub fn to_fraction(&self) -> fraction::GenericFraction<C> {
        fraction::GenericFraction::new(self.numerator(), D::DENOM)
    }
}

impl<S, C, D, D2> std::cmp::PartialEq<StaticFraction<S, C, D2>> for StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
    D2: Default + StaticFractionDescription<C>,
{
    fn eq(&self, other: &StaticFraction<S, C, D2>) -> bool {
        if D::NUM_OFFSET == D2::NUM_OFFSET &&
            D::NUM_FACTOR == D2::NUM_FACTOR &&
            D::DENOM == D2::DENOM {
            return self.stored == other.stored;
        }

        if D::DENOM == D2::DENOM {
            return self.numerator() == other.numerator();
        }

        // Let's do relative reduction of the denominators. With coprime
        // denominators, fractions can only be equal if they are integers, and
        // that we can check with a single div_rem.

        let d1 = D::DENOM;
        let d2 = D2::DENOM;
        let gcd_d = d1.gcd(&d2);
        let d1 = d1 / gcd_d;
        let d2 = d2 / gcd_d;

        // Running against real numerators right away. TODO: Check with
        // numerator_min/max (min only if we allow signed numerators)
        // beforehand and, if it can be statically known to work, run the
        // multiplication unchecked

        let n1 = self.numerator();
        let n2 = other.numerator();

        let (mul_12, overflow_12) = n1.overflowing_mul(d2);
        let (mul_21, overflow_21) = n2.overflowing_mul(d1);

        if !overflow_12 && !overflow_21 {
            mul_12 == mul_21
        } else if overflow_12 != overflow_12 {
            false
        } else {
            // So that's it: we have to divide
            unimplemented!()
        }
    }
}

// FIXME copied and modified from PartialEq, can't i derive that?
impl<S, C, D, D2> std::cmp::PartialOrd<StaticFraction<S, C, D2>> for StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
    D2: Default + StaticFractionDescription<C>,
{
    fn partial_cmp(&self, other: &StaticFraction<S, C, D2>) -> Option<std::cmp::Ordering> {
        if D::NUM_OFFSET == D2::NUM_OFFSET &&
            D::NUM_FACTOR == D2::NUM_FACTOR &&
            D::DENOM == D2::DENOM {
            return self.stored.partial_cmp(&other.stored);
        }

        if D::DENOM == D2::DENOM {
            return self.numerator().partial_cmp(&other.numerator());
        }

        // Let's do relative reduction of the denominators. With coprime
        // denominators, fractions can only be equal if they are integers, and
        // that we can check with a single div_rem.

        let d1 = D::DENOM;
        let d2 = D2::DENOM;
        let gcd_d = d1.gcd(&d2);
        let d1 = d1 / gcd_d;
        let d2 = d2 / gcd_d;

        // Running against real numerators right away. TODO: Check with
        // numerator_min/max (min only if we allow signed numerators)
        // beforehand and, if it can be statically known to work, run the
        // multiplication unchecked unconditionally. The compiler could know
        // this to work only if it tracked the output range of numerator
        // autonomously or if we could annotate the numerator output manually
        // to be in a given range, which both seems not to happen right now.

        let n1 = self.numerator();
        let n2 = other.numerator();

        let (mul_12, overflow_12) = n1.overflowing_mul(d2);
        let (mul_21, overflow_21) = n2.overflowing_mul(d1);

        if !overflow_12 && !overflow_21 {
            mul_12.partial_cmp(&mul_21)
        } else if overflow_12 && !overflow_21 {
            Some(std::cmp::Ordering::Greater) 
        } else if !overflow_12 && overflow_21 {
            Some(std::cmp::Ordering::Less) 
        } else {
            // So that's it: we have to divide
            unimplemented!()
        }
    }
}

impl<S, C, D> std::cmp::PartialEq<C> for StaticFraction<S, C, D> where
    S: std::convert::TryFrom<C> + Copy + num::Unsigned + num::integer::Integer + num::Bounded, // Copy is there because I wasn't careful when writing the first functions; Clone is probably sufficient too, but if things got costly, I'd rather fix this not to require even Clone any more but to cleanly send references.
    C: From<S> + Copy + num::Unsigned + num::integer::Integer + num::Bounded + HasOverflowingMul,
    D: Default + StaticFractionDescription<C>,
{
    fn eq(&self, other: &C) -> bool {
        // FIXME this is where one should start thinking about overflows and what not

        *other * D::DENOM == D::NUM_OFFSET + D::NUM_FACTOR * C::from(self.stored)
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
    }
}