kcl_lib/std/
math.rs

1//! Functions related to mathematics.
2
3use anyhow::Result;
4
5use crate::{
6    errors::{KclError, KclErrorDetails},
7    execution::{
8        types::{ArrayLen, NumericType, RuntimeType},
9        ExecState, KclValue,
10    },
11    std::args::{Args, TyF64},
12    CompilationError,
13};
14
15/// Compute the remainder after dividing `num` by `div`.
16/// If `num` is negative, the result will be too.
17pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
18    let n: TyF64 = args.get_unlabeled_kw_arg_typed("number to divide", &RuntimeType::num_any(), exec_state)?;
19    let d: TyF64 = args.get_kw_arg_typed("divisor", &RuntimeType::num_any(), exec_state)?;
20
21    let (n, d, ty) = NumericType::combine_div(n, d);
22    if ty == NumericType::Unknown {
23        exec_state.err(CompilationError::err(
24            args.source_range,
25            "Calling `rem` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n  using a numeric suffix: `42{ty}`\n  or using type ascription: `foo(): number({ty})`"
26        ));
27    }
28    let remainder = n % d;
29
30    Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
31}
32
33/// Compute the cosine of a number (in radians).
34pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
35    let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
36    let num = num.to_radians();
37    Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.cos())))
38}
39
40/// Compute the sine of a number (in radians).
41pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
42    let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
43    let num = num.to_radians();
44    Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.sin())))
45}
46
47/// Compute the tangent of a number (in radians).
48pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
49    let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
50    let num = num.to_radians();
51    Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.tan())))
52}
53
54/// Compute the square root of a number.
55pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
56    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
57
58    if input.n < 0.0 {
59        return Err(KclError::new_semantic(KclErrorDetails::new(
60            format!(
61                "Attempt to take square root (`sqrt`) of a number less than zero ({})",
62                input.n
63            ),
64            vec![args.source_range],
65        )));
66    }
67
68    let result = input.n.sqrt();
69
70    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
71}
72
73/// Compute the absolute value of a number.
74pub async fn abs(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
75    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
76    let result = input.n.abs();
77
78    Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
79}
80
81/// Round a number to the nearest integer.
82pub async fn round(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
83    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
84    let result = input.n.round();
85
86    Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
87}
88
89/// Compute the largest integer less than or equal to a number.
90pub async fn floor(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
91    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
92    let result = input.n.floor();
93
94    Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
95}
96
97/// Compute the smallest integer greater than or equal to a number.
98pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
99    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
100    let result = input.n.ceil();
101
102    Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
103}
104
105/// Compute the minimum of the given arguments.
106pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
107    let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
108        "input",
109        &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
110        exec_state,
111    )?;
112    let (nums, ty) = NumericType::combine_eq_array(&nums);
113    if ty == NumericType::Unknown {
114        exec_state.warn(CompilationError::err(
115            args.source_range,
116            "Calling `min` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n  using a numeric suffix: `42{ty}`\n  or using type ascription: `foo(): number({ty})`",
117        ));
118    }
119
120    let mut result = f64::MAX;
121    for num in nums {
122        if num < result {
123            result = num;
124        }
125    }
126
127    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
128}
129
130/// Compute the maximum of the given arguments.
131pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
132    let nums: Vec<TyF64> = args.get_unlabeled_kw_arg_typed(
133        "input",
134        &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
135        exec_state,
136    )?;
137    let (nums, ty) = NumericType::combine_eq_array(&nums);
138    if ty == NumericType::Unknown {
139        exec_state.warn(CompilationError::err(
140            args.source_range,
141            "Calling `max` on numbers which have unknown or incompatible units.\n\nYou may need to add information about the type of the argument, for example:\n  using a numeric suffix: `42{ty}`\n  or using type ascription: `foo(): number({ty})`",
142        ));
143    }
144
145    let mut result = f64::MIN;
146    for num in nums {
147        if num > result {
148            result = num;
149        }
150    }
151
152    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
153}
154
155/// Compute the number to a power.
156pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
157    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
158    let exp: TyF64 = args.get_kw_arg_typed("exp", &RuntimeType::count(), exec_state)?;
159    let result = input.n.powf(exp.n);
160
161    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
162}
163
164/// Compute the arccosine of a number (in radians).
165pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
166    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::count(), exec_state)?;
167    let result = input.n.acos();
168
169    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
170}
171
172/// Compute the arcsine of a number (in radians).
173pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
174    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::count(), exec_state)?;
175    let result = input.n.asin();
176
177    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
178}
179
180/// Compute the arctangent of a number (in radians).
181pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
182    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::count(), exec_state)?;
183    let result = input.n.atan();
184
185    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
186}
187
188/// Compute the four quadrant arctangent of Y and X (in radians).
189pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
190    let y = args.get_kw_arg_typed("y", &RuntimeType::length(), exec_state)?;
191    let x = args.get_kw_arg_typed("x", &RuntimeType::length(), exec_state)?;
192    let (y, x, _) = NumericType::combine_eq_coerce(y, x);
193    let result = y.atan2(x);
194
195    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
196}
197
198/// Compute the logarithm of the number with respect to an arbitrary base.
199///
200/// The result might not be correctly rounded owing to implementation
201/// details; `log2()` can produce more accurate results for base 2,
202/// and `log10()` can produce more accurate results for base 10.
203pub async fn log(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
204    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
205    let base: TyF64 = args.get_kw_arg_typed("base", &RuntimeType::count(), exec_state)?;
206    let result = input.n.log(base.n);
207
208    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
209}
210
211/// Compute the base 2 logarithm of the number.
212pub async fn log2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
213    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
214    let result = input.n.log2();
215
216    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
217}
218
219/// Compute the base 10 logarithm of the number.
220pub async fn log10(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
221    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
222    let result = input.n.log10();
223
224    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
225}
226
227/// Compute the natural logarithm of the number.
228pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
229    let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
230    let result = input.n.ln();
231
232    Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
233}
234
235/// Compute the length of the given leg.
236pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
237    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
238    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
239    let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
240    let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt();
241    Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
242}
243
244/// Compute the angle of the given leg for x.
245pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
246    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
247    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
248    let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
249    let result = (leg.min(hypotenuse) / hypotenuse).acos().to_degrees();
250    Ok(KclValue::from_number_with_type(
251        result,
252        NumericType::degrees(),
253        vec![args.into()],
254    ))
255}
256
257/// Compute the angle of the given leg for y.
258pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
259    let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
260    let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
261    let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
262    let result = (leg.min(hypotenuse) / hypotenuse).asin().to_degrees();
263    Ok(KclValue::from_number_with_type(
264        result,
265        NumericType::degrees(),
266        vec![args.into()],
267    ))
268}