typst_library/foundations/
float.rs

1use std::num::ParseFloatError;
2
3use ecow::{eco_format, EcoString};
4
5use crate::diag::{bail, StrResult};
6use crate::foundations::{
7    cast, func, repr, scope, ty, Bytes, Decimal, Endianness, Repr, Str,
8};
9use crate::layout::Ratio;
10
11/// A floating-point number.
12///
13/// A limited-precision representation of a real number. Typst uses 64 bits to
14/// store floats. Wherever a float is expected, you can also pass an
15/// [integer]($int).
16///
17/// You can convert a value to a float with this type's constructor.
18///
19/// NaN and positive infinity are available as `{float.nan}` and `{float.inf}`
20/// respectively.
21///
22/// # Example
23/// ```example
24/// #3.14 \
25/// #1e4 \
26/// #(10 / 4)
27/// ```
28#[ty(scope, cast, name = "float")]
29type f64;
30
31#[scope]
32impl f64 {
33    /// Positive infinity.
34    const INF: f64 = f64::INFINITY;
35
36    /// A NaN value, as defined by the
37    /// [IEEE 754 standard](https://en.wikipedia.org/wiki/IEEE_754).
38    const NAN: f64 = f64::NAN;
39
40    /// Converts a value to a float.
41    ///
42    /// - Booleans are converted to `0.0` or `1.0`.
43    /// - Integers are converted to the closest 64-bit float. For integers with
44    ///   absolute value less than `{calc.pow(2, 53)}`, this conversion is
45    ///   exact.
46    /// - Ratios are divided by 100%.
47    /// - Strings are parsed in base 10 to the closest 64-bit float. Exponential
48    ///   notation is supported.
49    ///
50    /// ```example
51    /// #float(false) \
52    /// #float(true) \
53    /// #float(4) \
54    /// #float(40%) \
55    /// #float("2.7") \
56    /// #float("1e5")
57    /// ```
58    #[func(constructor)]
59    pub fn construct(
60        /// The value that should be converted to a float.
61        value: ToFloat,
62    ) -> f64 {
63        value.0
64    }
65
66    /// Checks if a float is not a number.
67    ///
68    /// In IEEE 754, more than one bit pattern represents a NaN. This function
69    /// returns `true` if the float is any of those bit patterns.
70    ///
71    /// ```example
72    /// #float.is-nan(0) \
73    /// #float.is-nan(1) \
74    /// #float.is-nan(float.nan)
75    /// ```
76    #[func]
77    pub fn is_nan(self) -> bool {
78        f64::is_nan(self)
79    }
80
81    /// Checks if a float is infinite.
82    ///
83    /// Floats can represent positive infinity and negative infinity. This
84    /// function returns `{true}` if the float is an infinity.
85    ///
86    /// ```example
87    /// #float.is-infinite(0) \
88    /// #float.is-infinite(1) \
89    /// #float.is-infinite(float.inf)
90    /// ```
91    #[func]
92    pub fn is_infinite(self) -> bool {
93        f64::is_infinite(self)
94    }
95
96    /// Calculates the sign of a floating point number.
97    ///
98    /// - If the number is positive (including `{+0.0}`), returns `{1.0}`.
99    /// - If the number is negative (including `{-0.0}`), returns `{-1.0}`.
100    /// - If the number is NaN, returns `{float.nan}`.
101    ///
102    /// ```example
103    /// #(5.0).signum() \
104    /// #(-5.0).signum() \
105    /// #(0.0).signum() \
106    /// #float.nan.signum()
107    /// ```
108    #[func]
109    pub fn signum(self) -> f64 {
110        f64::signum(self)
111    }
112
113    /// Interprets bytes as a float.
114    ///
115    /// ```example
116    /// #float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))) \
117    /// #float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big")
118    /// ```
119    #[func]
120    pub fn from_bytes(
121        /// The bytes that should be converted to a float.
122        ///
123        /// Must have a length of either 4 or 8. The bytes are then
124        /// interpreted in [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)'s
125        /// binary32 (single-precision) or binary64 (double-precision) format
126        /// depending on the length of the bytes.
127        bytes: Bytes,
128        /// The endianness of the conversion.
129        #[named]
130        #[default(Endianness::Little)]
131        endian: Endianness,
132    ) -> StrResult<f64> {
133        // Convert slice to an array of length 4 or 8.
134        if let Ok(buffer) = <[u8; 8]>::try_from(bytes.as_ref()) {
135            return Ok(match endian {
136                Endianness::Little => f64::from_le_bytes(buffer),
137                Endianness::Big => f64::from_be_bytes(buffer),
138            });
139        };
140        if let Ok(buffer) = <[u8; 4]>::try_from(bytes.as_ref()) {
141            return Ok(match endian {
142                Endianness::Little => f32::from_le_bytes(buffer),
143                Endianness::Big => f32::from_be_bytes(buffer),
144            } as f64);
145        };
146
147        bail!("bytes must have a length of 4 or 8");
148    }
149
150    /// Converts a float to bytes.
151    ///
152    /// ```example
153    /// #array(1.0.to-bytes(endian: "big")) \
154    /// #array(1.0.to-bytes())
155    /// ```
156    #[func]
157    pub fn to_bytes(
158        self,
159        /// The endianness of the conversion.
160        #[named]
161        #[default(Endianness::Little)]
162        endian: Endianness,
163        /// The size of the resulting bytes.
164        ///
165        /// This must be either 4 or 8. The call will return the
166        /// representation of this float in either
167        /// [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754)'s binary32
168        /// (single-precision) or binary64 (double-precision) format
169        /// depending on the provided size.
170        #[named]
171        #[default(8)]
172        size: u32,
173    ) -> StrResult<Bytes> {
174        Ok(match size {
175            8 => Bytes::new(match endian {
176                Endianness::Little => self.to_le_bytes(),
177                Endianness::Big => self.to_be_bytes(),
178            }),
179            4 => Bytes::new(match endian {
180                Endianness::Little => (self as f32).to_le_bytes(),
181                Endianness::Big => (self as f32).to_be_bytes(),
182            }),
183            _ => bail!("size must be either 4 or 8"),
184        })
185    }
186}
187
188impl Repr for f64 {
189    fn repr(&self) -> EcoString {
190        repr::format_float(*self, None, true, "")
191    }
192}
193
194/// A value that can be cast to a float.
195pub struct ToFloat(f64);
196
197cast! {
198    ToFloat,
199    v: f64 => Self(v),
200    v: bool => Self(v as i64 as f64),
201    v: i64 => Self(v as f64),
202    v: Decimal => Self(f64::try_from(v).map_err(|_| eco_format!("invalid float: {}", v))?),
203    v: Ratio => Self(v.get()),
204    v: Str => Self(
205        parse_float(v.clone().into())
206            .map_err(|_| eco_format!("invalid float: {}", v))?
207    ),
208}
209
210fn parse_float(s: EcoString) -> Result<f64, ParseFloatError> {
211    s.replace(repr::MINUS_SIGN, "-").parse()
212}