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
use crate::convert::UnboxRubyError;
use crate::core::{ConvertMut, TryConvert, Value as _};
use crate::error::Error;
use crate::sys;
use crate::types::{Ruby, Rust};
use crate::value::Value;
use crate::Artichoke;

// TODO: when ,mruby is gone, float conversion should not allocate.
impl ConvertMut<f64, Value> for Artichoke {
    fn convert_mut(&mut self, value: f64) -> Value {
        let float = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_sys_float_value(mrb, value)) };
        self.protect(Value::from(float.unwrap()))
    }
}

impl TryConvert<Value, f64> for Artichoke {
    type Error = Error;

    fn try_convert(&self, value: Value) -> Result<f64, Self::Error> {
        if let Ruby::Float = value.ruby_type() {
            let value = value.inner();
            Ok(unsafe { sys::mrb_sys_float_to_cdouble(value) })
        } else {
            Err(UnboxRubyError::new(&value, Rust::Float).into())
        }
    }
}

#[cfg(test)]
mod tests {
    use quickcheck::quickcheck;

    use crate::test::prelude::*;

    #[test]
    fn fail_convert() {
        let mut interp = interpreter();
        // get a Ruby Value that can't be converted to a primitive type.
        let value = interp.eval(b"Object.new").unwrap();
        let result = value.try_convert_into::<f64>(&interp);
        assert!(result.is_err());
    }

    quickcheck! {
        fn convert_to_float(f: f64) -> bool {
            let mut interp = interpreter();
            let value = interp.convert_mut(f);
            value.ruby_type() == Ruby::Float
        }

        fn float_with_value(f: f64) -> bool {
            let mut interp = interpreter();
            let value = interp.convert_mut(f);
            let inner = value.inner();
            let cdouble = unsafe { sys::mrb_sys_float_to_cdouble(inner) };
            if f.is_nan() {
                cdouble.is_nan()
            } else if f.is_infinite() {
                f.is_infinite() && cdouble.signum() == f.signum()
            } else if cdouble >= f {
                let difference = cdouble - f;
                difference < f64::EPSILON
            } else if f >= cdouble {
                let difference = f - cdouble;
                difference < f64::EPSILON
            } else {
                false
            }
        }

        fn roundtrip(f: f64) -> bool {
            let mut interp = interpreter();
            let value = interp.convert_mut(f);
            let value = value.try_convert_into::<f64>(&interp).unwrap();
            if f.is_nan() {
                value.is_nan()
            } else if f.is_infinite() {
                value.is_infinite() && value.signum() == f.signum()
            } else if value >= f {
                let difference = value - f;
                difference < f64::EPSILON
            } else if f >= value {
                let difference = f - value;
                difference < f64::EPSILON
            } else {
                false
            }
        }

        fn roundtrip_err(b: bool) -> bool {
            let interp = interpreter();
            let value = interp.convert(b);
            let value = value.try_convert_into::<f64>(&interp);
            value.is_err()
        }
    }
}