artichoke_backend/convert/
float.rs

1use crate::Artichoke;
2use crate::convert::UnboxRubyError;
3use crate::core::{ConvertMut, TryConvert, Value as _};
4use crate::error::Error;
5use crate::sys;
6use crate::types::{Ruby, Rust};
7use crate::value::Value;
8
9// TODO: when ,mruby is gone, float conversion should not allocate.
10impl ConvertMut<f64, Value> for Artichoke {
11    fn convert_mut(&mut self, value: f64) -> Value {
12        let float = unsafe { self.with_ffi_boundary(|mrb| sys::mrb_sys_float_value(mrb, value)) };
13        self.protect(Value::from(float.unwrap()))
14    }
15}
16
17impl TryConvert<Value, f64> for Artichoke {
18    type Error = Error;
19
20    fn try_convert(&self, value: Value) -> Result<f64, Self::Error> {
21        let Ruby::Float = value.ruby_type() else {
22            return Err(UnboxRubyError::new(&value, Rust::Float).into());
23        };
24        let value = value.inner();
25        // SAFETY: `f64` Ruby Values do not need to be protected because they
26        // are immediates and do not live on the mruby heap.
27        // `mrb_sys_float_to_cdouble` is a safe ffi call when given an `f64`.
28        Ok(unsafe { sys::mrb_sys_float_to_cdouble(value) })
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use crate::test::prelude::*;
35
36    #[test]
37    fn fail_convert() {
38        let mut interp = interpreter();
39        // get a Ruby Value that can't be converted to a primitive type.
40        let value = interp.eval(b"Object.new").unwrap();
41        let result = value.try_convert_into::<f64>(&interp);
42        assert!(result.is_err());
43    }
44
45    #[test]
46    fn prop_convert_to_float() {
47        let mut interp = interpreter();
48        run_arbitrary::<f64>(|f| {
49            let value = interp.convert_mut(f);
50            assert_eq!(value.ruby_type(), Ruby::Float);
51        });
52    }
53
54    #[test]
55    fn prop_float_with_value() {
56        let mut interp = interpreter();
57        run_arbitrary::<f64>(|f| {
58            let value = interp.convert_mut(f);
59            let inner = value.inner();
60            let cdouble = unsafe { sys::mrb_sys_float_to_cdouble(inner) };
61            if f.is_nan() {
62                assert!(cdouble.is_nan());
63            } else if f.is_infinite() {
64                assert!(f.is_infinite() && cdouble.signum() == f.signum());
65            } else if cdouble >= f {
66                let difference = cdouble - f;
67                assert!(difference < f64::EPSILON);
68            } else if f >= cdouble {
69                let difference = f - cdouble;
70                assert!(difference < f64::EPSILON);
71            } else {
72                panic!("Unexpected branch in float_with_value");
73            }
74        });
75    }
76
77    #[test]
78    fn prop_roundtrip() {
79        let mut interp = interpreter();
80        run_arbitrary::<f64>(|f| {
81            let value = interp.convert_mut(f);
82            let roundtrip_value = value.try_convert_into::<f64>(&interp).unwrap();
83            if f.is_nan() {
84                assert!(roundtrip_value.is_nan());
85            } else if f.is_infinite() {
86                assert!(roundtrip_value.is_infinite() && roundtrip_value.signum() == f.signum());
87            } else if roundtrip_value >= f {
88                let difference = roundtrip_value - f;
89                assert!(difference < f64::EPSILON);
90            } else if f >= roundtrip_value {
91                let difference = f - roundtrip_value;
92                assert!(difference < f64::EPSILON);
93            } else {
94                panic!("Unexpected branch in roundtrip");
95            }
96        });
97    }
98
99    #[test]
100    fn prop_roundtrip_err() {
101        let interp = interpreter();
102        run_arbitrary::<bool>(|b| {
103            let value = interp.convert(b);
104            let result = value.try_convert_into::<f64>(&interp);
105            assert!(result.is_err());
106        });
107    }
108}