1use anyhow::Result;
4
5use crate::CompilationIssue;
6use crate::errors::KclError;
7use crate::errors::KclErrorDetails;
8use crate::execution::ExecState;
9use crate::execution::KclValue;
10use crate::execution::annotations;
11use crate::execution::types::ArrayLen;
12use crate::execution::types::NumericType;
13use crate::execution::types::NumericTypeExt;
14use crate::execution::types::RuntimeType;
15use crate::std::args::Args;
16use crate::std::args::TyF64;
17use crate::util::MathExt;
18
19pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
22 let n: TyF64 = args.get_unlabeled_kw_arg("number to divide", &RuntimeType::num_any(), exec_state)?;
23 let d: TyF64 = args.get_kw_arg("divisor", &RuntimeType::num_any(), exec_state)?;
24 let valid_d = d.n != 0.0;
25 if !valid_d {
26 exec_state.warn(
27 CompilationIssue::err(args.source_range, "Divisor cannot be 0".to_string()),
28 annotations::WARN_INVALID_MATH,
29 );
30 }
31
32 let (n, d, ty) = NumericType::combine_mod(n, d);
33 if ty == NumericType::Unknown {
34 exec_state.err(CompilationIssue::err(
35 args.source_range,
36 "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})`"
37 ));
38 }
39 let remainder = n % d;
40
41 Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
42}
43
44pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
46 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
47 let num = num.to_radians(exec_state, args.source_range);
48 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::cos(num), exec_state.current_default_units())))
49}
50
51pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
53 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
54 let num = num.to_radians(exec_state, args.source_range);
55 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::sin(num), exec_state.current_default_units())))
56}
57
58pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
60 let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?;
61 let num = num.to_radians(exec_state, args.source_range);
62 Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::tan(num), exec_state.current_default_units())))
63}
64
65pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
67 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
68
69 if input.n < 0.0 {
70 return Err(KclError::new_semantic(KclErrorDetails::new(
71 format!(
72 "Attempt to take square root (`sqrt`) of a number less than zero ({})",
73 input.n
74 ),
75 vec![args.source_range],
76 )));
77 }
78
79 let result = input.n.sqrt();
80
81 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
82}
83
84pub async fn abs(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
86 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
87 let result = input.n.abs();
88
89 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
90}
91
92pub async fn round(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
94 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
95 let result = input.n.round();
96
97 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
98}
99
100pub async fn floor(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
102 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
103 let result = input.n.floor();
104
105 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
106}
107
108pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
110 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
111 let result = input.n.ceil();
112
113 Ok(args.make_user_val_from_f64_with_type(input.map_value(result)))
114}
115
116pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
118 let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
119 "input",
120 &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
121 exec_state,
122 )?;
123 let (nums, ty) = NumericType::combine_eq_array(&nums);
124 if ty == NumericType::Unknown {
125 exec_state.warn(CompilationIssue::err(
126 args.source_range,
127 "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})`",
128 ), annotations::WARN_UNKNOWN_UNITS);
129 }
130
131 let mut result = f64::MAX;
132 for num in nums {
133 if num < result {
134 result = num;
135 }
136 }
137
138 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
139}
140
141pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
143 let nums: Vec<TyF64> = args.get_unlabeled_kw_arg(
144 "input",
145 &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)),
146 exec_state,
147 )?;
148 let (nums, ty) = NumericType::combine_eq_array(&nums);
149 if ty == NumericType::Unknown {
150 exec_state.warn(CompilationIssue::err(
151 args.source_range,
152 "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})`",
153 ), annotations::WARN_UNKNOWN_UNITS);
154 }
155
156 let mut result = f64::MIN;
157 for num in nums {
158 if num > result {
159 result = num;
160 }
161 }
162
163 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
164}
165
166pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
168 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
169 let exp: TyF64 = args.get_kw_arg("exp", &RuntimeType::count(), exec_state)?;
170 let exp_is_int = exp.n.fract() == 0.0;
171 if input.n < 0.0 && !exp_is_int {
172 exec_state.warn(
173 CompilationIssue::err(
174 args.source_range,
175 format!(
176 "Exponent must be an integer when input is negative, but it was {}",
177 exp.n
178 ),
179 ),
180 annotations::WARN_INVALID_MATH,
181 );
182 }
183 let valid_input = !(input.n == 0.0 && exp.n < 0.0);
184 if !valid_input {
185 exec_state.warn(
186 CompilationIssue::err(args.source_range, "Input cannot be 0 when exp < 0".to_string()),
187 annotations::WARN_INVALID_MATH,
188 );
189 }
190 let result = libm::pow(input.n, exp.n);
191
192 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
193}
194
195pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
197 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
198 let in_range = (-1.0..=1.0).contains(&input.n);
199 if !in_range {
200 exec_state.warn(
201 CompilationIssue::err(
202 args.source_range,
203 format!("The argument must be between -1 and 1, but it was {}", input.n),
204 ),
205 annotations::WARN_INVALID_MATH,
206 );
207 }
208 let result = libm::acos(input.n);
209
210 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
211}
212
213pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
215 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
216 let in_range = (-1.0..=1.0).contains(&input.n);
217 if !in_range {
218 exec_state.warn(
219 CompilationIssue::err(
220 args.source_range,
221 format!("The argument must be between -1 and 1, but it was {}", input.n),
222 ),
223 annotations::WARN_INVALID_MATH,
224 );
225 }
226 let result = libm::asin(input.n);
227
228 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
229}
230
231pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
233 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?;
234 let result = libm::atan(input.n);
235
236 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
237}
238
239pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
241 let y = args.get_kw_arg("y", &RuntimeType::length(), exec_state)?;
242 let x = args.get_kw_arg("x", &RuntimeType::length(), exec_state)?;
243 let (y, x, _) = NumericType::combine_eq_coerce(y, x, Some((exec_state, args.source_range)));
244 let result = libm::atan2(y, x);
245
246 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
247}
248
249pub async fn log(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
255 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
256 let base: TyF64 = args.get_kw_arg("base", &RuntimeType::count(), exec_state)?;
257 let valid_input = input.n > 0.0;
258 if !valid_input {
259 exec_state.warn(
260 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
261 annotations::WARN_INVALID_MATH,
262 );
263 }
264 let valid_base = base.n > 0.0;
265 if !valid_base {
266 exec_state.warn(
267 CompilationIssue::err(args.source_range, format!("Base must be > 0, but it was {}", base.n)),
268 annotations::WARN_INVALID_MATH,
269 );
270 }
271 let base_not_1 = base.n != 1.0;
272 if !base_not_1 {
273 exec_state.warn(
274 CompilationIssue::err(args.source_range, "Base cannot be 1".to_string()),
275 annotations::WARN_INVALID_MATH,
276 );
277 }
278 let result = input.n.log(base.n);
279
280 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
281}
282
283pub async fn log2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
285 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
286 let valid_input = input.n > 0.0;
287 if !valid_input {
288 exec_state.warn(
289 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
290 annotations::WARN_INVALID_MATH,
291 );
292 }
293 let result = input.n.log2();
294
295 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
296}
297
298pub async fn log10(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
300 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
301 let valid_input = input.n > 0.0;
302 if !valid_input {
303 exec_state.warn(
304 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
305 annotations::WARN_INVALID_MATH,
306 );
307 }
308 let result = input.n.log10();
309
310 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
311}
312
313pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
315 let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::num_any(), exec_state)?;
316 let valid_input = input.n > 0.0;
317 if !valid_input {
318 exec_state.warn(
319 CompilationIssue::err(args.source_range, format!("Input must be > 0, but it was {}", input.n)),
320 annotations::WARN_INVALID_MATH,
321 );
322 }
323 let result = input.n.ln();
324
325 Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units())))
326}
327
328pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
330 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
331 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
332 let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
333 let result = (hypotenuse.squared() - libm::fmin(hypotenuse.abs(), leg.abs()).squared()).sqrt();
334 Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
335}
336
337pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
339 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
340 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
341 let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
342 let valid_hypotenuse = hypotenuse > 0.0;
343 if !valid_hypotenuse {
344 exec_state.warn(
345 CompilationIssue::err(
346 args.source_range,
347 format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
348 ),
349 annotations::WARN_INVALID_MATH,
350 );
351 }
352 let ratio = libm::fmin(leg, hypotenuse) / hypotenuse;
353 let in_range = (-1.0..=1.0).contains(&ratio);
354 if !in_range {
355 exec_state.warn(
356 CompilationIssue::err(
357 args.source_range,
358 format!("The argument must be between -1 and 1, but it was {}", ratio),
359 ),
360 annotations::WARN_INVALID_MATH,
361 );
362 }
363 let result = libm::acos(ratio).to_degrees();
364 Ok(KclValue::from_number_with_type(
365 result,
366 NumericType::degrees(),
367 vec![args.into()],
368 ))
369}
370
371pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
373 let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?;
374 let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?;
375 let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg, Some((exec_state, args.source_range)));
376 let valid_hypotenuse = hypotenuse > 0.0;
377 if !valid_hypotenuse {
378 exec_state.warn(
379 CompilationIssue::err(
380 args.source_range,
381 format!("Hypotenuse must be > 0, but it was {}", hypotenuse),
382 ),
383 annotations::WARN_INVALID_MATH,
384 );
385 }
386 let ratio = libm::fmin(leg, hypotenuse) / hypotenuse;
387 let in_range = (-1.0..=1.0).contains(&ratio);
388 if !in_range {
389 exec_state.warn(
390 CompilationIssue::err(
391 args.source_range,
392 format!("The argument must be between -1 and 1, but it was {}", ratio),
393 ),
394 annotations::WARN_INVALID_MATH,
395 );
396 }
397 let result = libm::asin(ratio).to_degrees();
398 Ok(KclValue::from_number_with_type(
399 result,
400 NumericType::degrees(),
401 vec![args.into()],
402 ))
403}