diff options
| author | Laurenz <laurmaedje@gmail.com> | 2024-10-27 19:04:55 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-10-27 18:04:55 +0000 |
| commit | be7cfc85d08c545abfac08098b7b33b4bd71f37e (patch) | |
| tree | f4137fa2aaa57babae1f7603a9b2ed7e688f43d8 /crates/typst-library/src/foundations/float.rs | |
| parent | b8034a343831e8609aec2ec81eb7eeda57aa5d81 (diff) | |
Split out four new crates (#5302)
Diffstat (limited to 'crates/typst-library/src/foundations/float.rs')
| -rw-r--r-- | crates/typst-library/src/foundations/float.rs | 190 |
1 files changed, 190 insertions, 0 deletions
diff --git a/crates/typst-library/src/foundations/float.rs b/crates/typst-library/src/foundations/float.rs new file mode 100644 index 00000000..bb3232ee --- /dev/null +++ b/crates/typst-library/src/foundations/float.rs @@ -0,0 +1,190 @@ +use std::num::ParseFloatError; + +use ecow::{eco_format, EcoString}; + +use crate::diag::{bail, StrResult}; +use crate::foundations::{ + cast, func, repr, scope, ty, Bytes, Decimal, Endianness, Repr, Str, +}; +use crate::layout::Ratio; + +/// A floating-point number. +/// +/// A limited-precision representation of a real number. Typst uses 64 bits to +/// store floats. Wherever a float is expected, you can also pass an +/// [integer]($int). +/// +/// You can convert a value to a float with this type's constructor. +/// +/// NaN and positive infinity are available as `{float.nan}` and `{float.inf}` +/// respectively. +/// +/// # Example +/// ```example +/// #3.14 \ +/// #1e4 \ +/// #(10 / 4) +/// ``` +#[ty(scope, cast, name = "float")] +type f64; + +#[scope] +impl f64 { + /// Positive infinity. + const INF: f64 = f64::INFINITY; + + /// A NaN value, as defined by the + /// [IEEE 754 standard](https://en.wikipedia.org/wiki/IEEE_754). + const NAN: f64 = f64::NAN; + + /// Converts a value to a float. + /// + /// - Booleans are converted to `0.0` or `1.0`. + /// - Integers are converted to the closest 64-bit float. For integers with + /// absolute value less than `{calc.pow(2, 53)}`, this conversion is + /// exact. + /// - Ratios are divided by 100%. + /// - Strings are parsed in base 10 to the closest 64-bit float. Exponential + /// notation is supported. + /// + /// ```example + /// #float(false) \ + /// #float(true) \ + /// #float(4) \ + /// #float(40%) \ + /// #float("2.7") \ + /// #float("1e5") + /// ``` + #[func(constructor)] + pub fn construct( + /// The value that should be converted to a float. + value: ToFloat, + ) -> f64 { + value.0 + } + + /// Checks if a float is not a number. + /// + /// In IEEE 754, more than one bit pattern represents a NaN. This function + /// returns `true` if the float is any of those bit patterns. + /// + /// ```example + /// #float.is-nan(0) \ + /// #float.is-nan(1) \ + /// #float.is-nan(float.nan) + /// ``` + #[func] + pub fn is_nan(self) -> bool { + f64::is_nan(self) + } + + /// Checks if a float is infinite. + /// + /// Floats can represent positive infinity and negative infinity. This + /// function returns `{true}` if the float is an infinity. + /// + /// ```example + /// #float.is-infinite(0) \ + /// #float.is-infinite(1) \ + /// #float.is-infinite(float.inf) + /// ``` + #[func] + pub fn is_infinite(self) -> bool { + f64::is_infinite(self) + } + + /// Calculates the sign of a floating point number. + /// + /// - If the number is positive (including `{+0.0}`), returns `{1.0}`. + /// - If the number is negative (including `{-0.0}`), returns `{-1.0}`. + /// - If the number is NaN, returns `{float.nan}`. + /// + /// ```example + /// #(5.0).signum() \ + /// #(-5.0).signum() \ + /// #(0.0).signum() \ + /// #float.nan.signum() + /// ``` + #[func] + pub fn signum(self) -> f64 { + f64::signum(self) + } + + /// Converts bytes to a float. + /// + /// ```example + /// #float.from-bytes(bytes((0, 0, 0, 0, 0, 0, 240, 63))) \ + /// #float.from-bytes(bytes((63, 240, 0, 0, 0, 0, 0, 0)), endian: "big") + /// ``` + #[func] + pub fn from_bytes( + /// The bytes that should be converted to a float. + /// + /// Must be of length exactly 8 so that the result fits into a 64-bit + /// float. + bytes: Bytes, + /// The endianness of the conversion. + #[named] + #[default(Endianness::Little)] + endian: Endianness, + ) -> StrResult<f64> { + // Convert slice to an array of length 8. + let buf: [u8; 8] = match bytes.as_ref().try_into() { + Ok(buffer) => buffer, + Err(_) => bail!("bytes must have a length of exactly 8"), + }; + + Ok(match endian { + Endianness::Little => f64::from_le_bytes(buf), + Endianness::Big => f64::from_be_bytes(buf), + }) + } + + /// Converts a float to bytes. + /// + /// ```example + /// #array(1.0.to-bytes(endian: "big")) \ + /// #array(1.0.to-bytes()) + /// ``` + #[func] + pub fn to_bytes( + self, + /// The endianness of the conversion. + #[named] + #[default(Endianness::Little)] + endian: Endianness, + ) -> Bytes { + match endian { + Endianness::Little => self.to_le_bytes(), + Endianness::Big => self.to_be_bytes(), + } + .as_slice() + .into() + } +} + +impl Repr for f64 { + fn repr(&self) -> EcoString { + repr::format_float(*self, None, true, "") + } +} + +/// A value that can be cast to a float. +pub struct ToFloat(f64); + +cast! { + ToFloat, + v: f64 => Self(v), + v: bool => Self(v as i64 as f64), + v: i64 => Self(v as f64), + v: Decimal => Self(f64::try_from(v).map_err(|_| eco_format!("invalid float: {}", v))?), + v: Ratio => Self(v.get()), + v: Str => Self( + parse_float(v.clone().into()) + .map_err(|_| eco_format!("invalid float: {}", v))? + ), +} + +fn parse_float(s: EcoString) -> Result<f64, ParseFloatError> { + s.replace(repr::MINUS_SIGN, "-").parse() +} |
