Skip to main content

otter/codec/
float.rs

1//! `Encoder`/`Decoder` for Rust's floating-point types.
2//!
3//! Erlang floats are always finite IEEE-754 doubles. Encoding therefore rejects
4//! a non-finite value with [`CodecError::NotFinite`] (surfaced as `badret` on a
5//! NIF return) rather than handing NaN/infinity to the BEAM. `f32` widens
6//! losslessly to `f64` on the way out; on the way in it range-checks against the
7//! finite `f32` range — a value larger than `f32::MAX` would narrow to infinity,
8//! so it is rejected with [`CodecError::FloatRange`] (accepting ordinary
9//! rounding within range).
10
11use crate::codec::{CodecError, Decoder, Encoder};
12use crate::types::{AnyTerm, Env, Float};
13
14/// Rejects a non-finite value (NaN/infinity) with
15/// [`NotFinite`](CodecError::NotFinite); otherwise `enif_make_double`.
16impl<'id> Encoder<'id> for f64 {
17    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
18        Float::from_f64(env, *self).ok_or(CodecError::NotFinite)?.encode(env)
19    }
20}
21
22/// Reads a float term (`enif_get_double`); an Erlang float is always finite, so
23/// this never fails on range. [`WrongType`](CodecError::WrongType) if not a float.
24impl<'id> Decoder<'id> for f64 {
25    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
26        Ok(Float::decode(term, env)?.to_f64(env))
27    }
28}
29
30/// Widens to `f64` and encodes, rejecting non-finite values with
31/// [`NotFinite`](CodecError::NotFinite).
32impl<'id> Encoder<'id> for f32 {
33    fn encode(&self, env: impl Env<'id>) -> Result<AnyTerm<'id>, CodecError> {
34        Float::from_f64(env, f64::from(*self)).ok_or(CodecError::NotFinite)?.encode(env)
35    }
36}
37
38/// Reads as `f64` and narrows; a magnitude beyond `f32::MAX` would round to
39/// infinity, so it is rejected with [`FloatRange`](CodecError::FloatRange).
40impl<'id> Decoder<'id> for f32 {
41    fn decode(term: AnyTerm<'id>, env: impl Env<'id>) -> Result<Self, CodecError> {
42        let val = Float::decode(term, env)?.to_f64(env);
43        // An Erlang float is always finite, so the only failure is magnitude:
44        // narrowing a value beyond f32::MAX would produce infinity.
45        if val.abs() > f64::from(f32::MAX) {
46            return Err(CodecError::FloatRange);
47        }
48        Ok(val as f32)
49    }
50}