Skip to main content

endbasic_std/
numerics.rs

1// EndBASIC
2// Copyright 2020 Julio Merino
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Numerical functions for EndBASIC.
18
19use endbasic_core::{
20    ArgSep, ArgSepSyntax, CallError, CallResult, Callable, CallableMetadata,
21    CallableMetadataBuilder, ExprType, RepeatedSyntax, RepeatedTypeSyntax, RequiredValueSyntax,
22    Scope, SingularArgSyntax,
23};
24use rand::rngs::SmallRng;
25use rand::{RngCore, SeedableRng};
26use std::borrow::Cow;
27use std::cell::RefCell;
28use std::cmp::Ordering;
29use std::rc::Rc;
30
31use crate::{Clearable, MachineBuilder};
32
33/// Category description for all symbols provided by this module.
34const CATEGORY: &str = "Numerical functions";
35
36/// Converts the double `d` to an integer and fails if the conversion is not possible.
37pub fn double_to_integer(d: f64) -> Result<i32, String> {
38    let d = d.round();
39    if d.is_finite() && d >= (i32::MIN as f64) && (d <= i32::MAX as f64) {
40        Ok(d as i32)
41    } else {
42        Err(format!("Cannot cast {} to integer due to overflow", d))
43    }
44}
45
46/// Indicates the calculation mode for trigonometric functions.
47pub enum AngleMode {
48    /// Specifies degrees mode of calculation.
49    Degrees,
50
51    /// Specifies radians mode of calculation.
52    Radians,
53}
54
55struct ClearableAngleMode {
56    angle_mode: Rc<RefCell<AngleMode>>,
57}
58
59impl Clearable for ClearableAngleMode {
60    fn reset_state(&self) {
61        *self.angle_mode.borrow_mut() = AngleMode::Radians;
62    }
63}
64
65/// Gets the single argument to a trigonometric function, which is its angle.  Applies units
66/// conversion based on `angle_mode`.
67fn get_angle(scope: &mut Scope<'_>, angle_mode: &AngleMode) -> CallResult<f64> {
68    debug_assert_eq!(1, scope.nargs());
69    let angle = scope.get_double(0);
70
71    match angle_mode {
72        AngleMode::Degrees => Ok(angle.to_radians()),
73        AngleMode::Radians => Ok(angle),
74    }
75}
76
77/// Converts an angle in radians to the representation described by `angle_mode`.
78fn to_angle(angle: f64, angle_mode: &AngleMode) -> f64 {
79    match angle_mode {
80        AngleMode::Degrees => angle.to_degrees(),
81        AngleMode::Radians => angle,
82    }
83}
84
85/// Tracks the state of the PRNG used by the random number manipulation functions and commands.
86///
87/// The PRNG implemented here is intentionally simplistic and has no cryptographical guarantees.
88pub struct Prng {
89    prng: SmallRng,
90    last: u32,
91}
92
93impl Prng {
94    /// Generates a new PRNG based on system entropy.
95    pub fn new_from_entryopy() -> Self {
96        let mut prng = SmallRng::from_entropy();
97        let last = prng.next_u32();
98        Self { prng, last }
99    }
100
101    /// Generates a new PRNG based on the given seed.
102    pub fn new_from_seed(seed: i32) -> Self {
103        let mut prng = SmallRng::seed_from_u64(seed as u64);
104        let last = prng.next_u32();
105        Self { prng, last }
106    }
107
108    /// Returns the previously returned random number.
109    fn last(&self) -> f64 {
110        (self.last as f64) / (u32::MAX as f64)
111    }
112
113    /// Computes the next random number and returns it.
114    fn next(&mut self) -> f64 {
115        self.last = self.prng.next_u32();
116        self.last()
117    }
118}
119
120/// The `ABS` function.
121pub struct AbsFunction {
122    metadata: Rc<CallableMetadata>,
123}
124
125impl AbsFunction {
126    /// Creates a new instance of the function.
127    pub fn new() -> Rc<Self> {
128        Rc::from(Self {
129            metadata: CallableMetadataBuilder::new("ABS")
130                .with_return_type(ExprType::Double)
131                .with_syntax(&[(
132                    &[SingularArgSyntax::RequiredValue(
133                        RequiredValueSyntax {
134                            name: Cow::Borrowed("expr"),
135                            vtype: ExprType::Double,
136                        },
137                        ArgSepSyntax::End,
138                    )],
139                    None,
140                )])
141                .with_category(CATEGORY)
142                .with_description("Returns the absolute value of a number.")
143                .build(),
144        })
145    }
146}
147
148impl Callable for AbsFunction {
149    fn metadata(&self) -> Rc<CallableMetadata> {
150        self.metadata.clone()
151    }
152
153    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
154        debug_assert_eq!(1, scope.nargs());
155        let value = scope.get_double(0);
156        scope.return_double(value.abs())
157    }
158}
159
160/// The `ACOS` function.
161pub struct AcosFunction {
162    metadata: Rc<CallableMetadata>,
163    angle_mode: Rc<RefCell<AngleMode>>,
164}
165
166impl AcosFunction {
167    /// Creates a new instance of the function.
168    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
169        Rc::from(Self {
170            metadata: CallableMetadataBuilder::new("ACOS")
171                .with_return_type(ExprType::Double)
172                .with_syntax(&[(
173                    &[SingularArgSyntax::RequiredValue(
174                        RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
175                        ArgSepSyntax::End,
176                    )],
177                    None,
178                )])
179                .with_category(CATEGORY)
180                .with_description(
181                    "Computes the arc-cosine of a number.
182The resulting angle is measured in degrees or radians depending on the angle mode as selected by \
183the DEG and RAD commands.",
184                )
185                .build(),
186            angle_mode,
187        })
188    }
189}
190
191impl Callable for AcosFunction {
192    fn metadata(&self) -> Rc<CallableMetadata> {
193        self.metadata.clone()
194    }
195
196    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
197        debug_assert_eq!(1, scope.nargs());
198        let n = scope.get_double(0);
199        if !(-1.0..=1.0).contains(&n) {
200            return Err(CallError::Syntax(
201                scope.get_pos(0),
202                "Cannot take arc-cosine of a number outside of [-1, 1]".to_owned(),
203            ));
204        }
205
206        scope.return_double(to_angle(n.acos(), &self.angle_mode.borrow()))
207    }
208}
209
210/// The `ASIN` function.
211pub struct AsinFunction {
212    metadata: Rc<CallableMetadata>,
213    angle_mode: Rc<RefCell<AngleMode>>,
214}
215
216impl AsinFunction {
217    /// Creates a new instance of the function.
218    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
219        Rc::from(Self {
220            metadata: CallableMetadataBuilder::new("ASIN")
221                .with_return_type(ExprType::Double)
222                .with_syntax(&[(
223                    &[SingularArgSyntax::RequiredValue(
224                        RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
225                        ArgSepSyntax::End,
226                    )],
227                    None,
228                )])
229                .with_category(CATEGORY)
230                .with_description(
231                    "Computes the arc-sine of a number.
232The resulting angle is measured in degrees or radians depending on the angle mode as selected by \
233the DEG and RAD commands.",
234                )
235                .build(),
236            angle_mode,
237        })
238    }
239}
240
241impl Callable for AsinFunction {
242    fn metadata(&self) -> Rc<CallableMetadata> {
243        self.metadata.clone()
244    }
245
246    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
247        debug_assert_eq!(1, scope.nargs());
248        let n = scope.get_double(0);
249        if !(-1.0..=1.0).contains(&n) {
250            return Err(CallError::Syntax(
251                scope.get_pos(0),
252                "Cannot take arc-sine of a number outside of [-1, 1]".to_owned(),
253            ));
254        }
255
256        scope.return_double(to_angle(n.asin(), &self.angle_mode.borrow()))
257    }
258}
259
260/// The `ATN` function.
261pub struct AtnFunction {
262    metadata: Rc<CallableMetadata>,
263    angle_mode: Rc<RefCell<AngleMode>>,
264}
265
266impl AtnFunction {
267    /// Creates a new instance of the function.
268    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
269        Rc::from(Self {
270            metadata: CallableMetadataBuilder::new("ATN")
271                .with_return_type(ExprType::Double)
272                .with_syntax(&[(
273                    &[SingularArgSyntax::RequiredValue(
274                        RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
275                        ArgSepSyntax::End,
276                    )],
277                    None,
278                )])
279                .with_category(CATEGORY)
280                .with_description(
281                    "Computes the arc-tangent of a number.
282The resulting angle is measured in degrees or radians depending on the angle mode as selected by \
283the DEG and RAD commands.",
284                )
285                .build(),
286            angle_mode,
287        })
288    }
289}
290
291impl Callable for AtnFunction {
292    fn metadata(&self) -> Rc<CallableMetadata> {
293        self.metadata.clone()
294    }
295
296    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
297        debug_assert_eq!(1, scope.nargs());
298        let n = scope.get_double(0);
299
300        scope.return_double(to_angle(n.atan(), &self.angle_mode.borrow()))
301    }
302}
303
304/// The `CINT` function.
305pub struct CintFunction {
306    metadata: Rc<CallableMetadata>,
307}
308
309impl CintFunction {
310    /// Creates a new instance of the function.
311    pub fn new() -> Rc<Self> {
312        Rc::from(Self {
313            metadata: CallableMetadataBuilder::new("CINT")
314                .with_return_type(ExprType::Integer)
315                .with_syntax(&[(
316                    &[SingularArgSyntax::RequiredValue(
317                        RequiredValueSyntax {
318                            name: Cow::Borrowed("expr"),
319                            vtype: ExprType::Double,
320                        },
321                        ArgSepSyntax::End,
322                    )],
323                    None,
324                )])
325                .with_category(CATEGORY)
326                .with_description(
327                    "Casts the given numeric expression to an integer (with rounding).
328When casting a double value to an integer, the double value is first rounded to the closest \
329integer.  For example, 4.4 becomes 4, but both 4.5 and 4.6 become 5.",
330                )
331                .build(),
332        })
333    }
334}
335
336impl Callable for CintFunction {
337    fn metadata(&self) -> Rc<CallableMetadata> {
338        self.metadata.clone()
339    }
340
341    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
342        debug_assert_eq!(1, scope.nargs());
343        let value = scope.get_double(0);
344
345        let i = double_to_integer(value)
346            .map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
347        scope.return_integer(i)
348    }
349}
350
351/// The `COS` function.
352pub struct CosFunction {
353    metadata: Rc<CallableMetadata>,
354    angle_mode: Rc<RefCell<AngleMode>>,
355}
356
357impl CosFunction {
358    /// Creates a new instance of the function.
359    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
360        Rc::from(Self {
361            metadata: CallableMetadataBuilder::new("COS")
362                .with_return_type(ExprType::Double)
363                .with_syntax(&[(
364                    &[SingularArgSyntax::RequiredValue(
365                        RequiredValueSyntax {
366                            name: Cow::Borrowed("angle"),
367                            vtype: ExprType::Double,
368                        },
369                        ArgSepSyntax::End,
370                    )],
371                    None,
372                )])
373                .with_category(CATEGORY)
374                .with_description(
375                    "Computes the cosine of an angle.
376The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
377selected by the DEG and RAD commands.",
378                )
379                .build(),
380            angle_mode,
381        })
382    }
383}
384
385impl Callable for CosFunction {
386    fn metadata(&self) -> Rc<CallableMetadata> {
387        self.metadata.clone()
388    }
389
390    fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
391        let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
392        scope.return_double(angle.cos())
393    }
394}
395
396/// The `DEG` command.
397pub struct DegCommand {
398    metadata: Rc<CallableMetadata>,
399    angle_mode: Rc<RefCell<AngleMode>>,
400}
401
402impl DegCommand {
403    /// Creates a new instance of the command.
404    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
405        Rc::from(Self {
406            metadata: CallableMetadataBuilder::new("DEG")
407                .with_syntax(&[(&[], None)])
408                .with_category(CATEGORY)
409                .with_description(
410                    "Sets degrees mode of calculation.
411The default condition for the trigonometric functions is to use radians.  DEG configures the \
412environment to use degrees until instructed otherwise.",
413                )
414                .build(),
415            angle_mode,
416        })
417    }
418}
419
420impl Callable for DegCommand {
421    fn metadata(&self) -> Rc<CallableMetadata> {
422        self.metadata.clone()
423    }
424
425    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
426        debug_assert_eq!(0, scope.nargs());
427        *self.angle_mode.borrow_mut() = AngleMode::Degrees;
428        Ok(())
429    }
430}
431
432/// The `EXP` function.
433pub struct ExpFunction {
434    metadata: Rc<CallableMetadata>,
435}
436
437impl ExpFunction {
438    /// Creates a new instance of the function.
439    pub fn new() -> Rc<Self> {
440        Rc::from(Self {
441            metadata: CallableMetadataBuilder::new("EXP")
442                .with_return_type(ExprType::Double)
443                .with_syntax(&[(
444                    &[SingularArgSyntax::RequiredValue(
445                        RequiredValueSyntax {
446                            name: Cow::Borrowed("expr"),
447                            vtype: ExprType::Double,
448                        },
449                        ArgSepSyntax::End,
450                    )],
451                    None,
452                )])
453                .with_category(CATEGORY)
454                .with_description("Raises Euler's number to the power of a number.")
455                .build(),
456        })
457    }
458}
459
460impl Callable for ExpFunction {
461    fn metadata(&self) -> Rc<CallableMetadata> {
462        self.metadata.clone()
463    }
464
465    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
466        debug_assert_eq!(1, scope.nargs());
467        let value = scope.get_double(0);
468        let exp = value.exp();
469        if !exp.is_finite() {
470            return Err(CallError::Syntax(
471                scope.get_pos(0),
472                format!("Cannot exponentiate {} due to overflow", value),
473            ));
474        }
475        scope.return_double(exp)
476    }
477}
478
479/// The `FIX` function.
480pub struct FixFunction {
481    metadata: Rc<CallableMetadata>,
482}
483
484impl FixFunction {
485    /// Creates a new instance of the function.
486    pub fn new() -> Rc<Self> {
487        Rc::from(Self {
488            metadata: CallableMetadataBuilder::new("FIX")
489                .with_return_type(ExprType::Integer)
490                .with_syntax(&[(
491                    &[SingularArgSyntax::RequiredValue(
492                        RequiredValueSyntax {
493                            name: Cow::Borrowed("expr"),
494                            vtype: ExprType::Double,
495                        },
496                        ArgSepSyntax::End,
497                    )],
498                    None,
499                )])
500                .with_category(CATEGORY)
501                .with_description(
502                    "Casts the given numeric expression to an integer (towards zero).
503When casting a double value to an integer, the double value is first truncated towards zero.  For \
504example, 4.9 becomes 4 and -4.9 becomes -4.",
505                )
506                .build(),
507        })
508    }
509}
510
511impl Callable for FixFunction {
512    fn metadata(&self) -> Rc<CallableMetadata> {
513        self.metadata.clone()
514    }
515
516    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
517        debug_assert_eq!(1, scope.nargs());
518        let value = scope.get_double(0);
519
520        let i = double_to_integer(value.trunc())
521            .map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
522        scope.return_integer(i)
523    }
524}
525
526/// The `INT` function.
527pub struct IntFunction {
528    metadata: Rc<CallableMetadata>,
529}
530
531impl IntFunction {
532    /// Creates a new instance of the function.
533    pub fn new() -> Rc<Self> {
534        Rc::from(Self {
535            metadata: CallableMetadataBuilder::new("INT")
536                .with_return_type(ExprType::Integer)
537                .with_syntax(&[(
538                    &[SingularArgSyntax::RequiredValue(
539                        RequiredValueSyntax {
540                            name: Cow::Borrowed("expr"),
541                            vtype: ExprType::Double,
542                        },
543                        ArgSepSyntax::End,
544                    )],
545                    None,
546                )])
547                .with_category(CATEGORY)
548                .with_description(
549                    "Casts the given numeric expression to an integer (with truncation).
550When casting a double value to an integer, the double value is first truncated to the smallest \
551integer that is not larger than the double value.  For example, all of 4.4, 4.5 and 4.6 become 4.",
552                )
553                .build(),
554        })
555    }
556}
557
558impl Callable for IntFunction {
559    fn metadata(&self) -> Rc<CallableMetadata> {
560        self.metadata.clone()
561    }
562
563    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
564        debug_assert_eq!(1, scope.nargs());
565        let value = scope.get_double(0);
566
567        let i = double_to_integer(value.floor())
568            .map_err(|e| CallError::Syntax(scope.get_pos(0), e.to_string()))?;
569        scope.return_integer(i)
570    }
571}
572
573/// The `LOG` function.
574pub struct LogFunction {
575    metadata: Rc<CallableMetadata>,
576}
577
578impl LogFunction {
579    /// Creates a new instance of the function.
580    pub fn new() -> Rc<Self> {
581        Rc::from(Self {
582            metadata: CallableMetadataBuilder::new("LOG")
583                .with_return_type(ExprType::Double)
584                .with_syntax(&[(
585                    &[SingularArgSyntax::RequiredValue(
586                        RequiredValueSyntax { name: Cow::Borrowed("num"), vtype: ExprType::Double },
587                        ArgSepSyntax::End,
588                    )],
589                    None,
590                )])
591                .with_category(CATEGORY)
592                .with_description("Computes the natural logarithm of the given number.")
593                .build(),
594        })
595    }
596}
597
598impl Callable for LogFunction {
599    fn metadata(&self) -> Rc<CallableMetadata> {
600        self.metadata.clone()
601    }
602
603    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
604        debug_assert_eq!(1, scope.nargs());
605        let num = scope.get_double(0);
606
607        if num <= 0.0 {
608            return Err(CallError::Syntax(
609                scope.get_pos(0),
610                "Cannot take logarithm of zero or a negative number".to_owned(),
611            ));
612        }
613        scope.return_double(num.ln())
614    }
615}
616
617/// The `MAX` function.
618pub struct MaxFunction {
619    metadata: Rc<CallableMetadata>,
620}
621
622impl MaxFunction {
623    /// Creates a new instance of the function.
624    pub fn new() -> Rc<Self> {
625        Rc::from(Self {
626            metadata: CallableMetadataBuilder::new("MAX")
627                .with_return_type(ExprType::Double)
628                .with_syntax(&[(
629                    &[],
630                    Some(&RepeatedSyntax {
631                        name: Cow::Borrowed("expr"),
632                        type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
633                        sep: ArgSepSyntax::Exactly(ArgSep::Long),
634                        require_one: true,
635                        allow_missing: false,
636                    }),
637                )])
638                .with_category(CATEGORY)
639                .with_description("Returns the maximum number out of a set of numbers.")
640                .build(),
641        })
642    }
643}
644
645impl Callable for MaxFunction {
646    fn metadata(&self) -> Rc<CallableMetadata> {
647        self.metadata.clone()
648    }
649
650    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
651        let mut max = f64::MIN;
652        for i in 0..(scope.nargs() as u8) {
653            let n = scope.get_double(i);
654            if n > max {
655                max = n;
656            }
657        }
658        scope.return_double(max)
659    }
660}
661
662/// The `MIN` function.
663pub struct MinFunction {
664    metadata: Rc<CallableMetadata>,
665}
666
667impl MinFunction {
668    /// Creates a new instance of the function.
669    pub fn new() -> Rc<Self> {
670        Rc::from(Self {
671            metadata: CallableMetadataBuilder::new("MIN")
672                .with_return_type(ExprType::Double)
673                .with_syntax(&[(
674                    &[],
675                    Some(&RepeatedSyntax {
676                        name: Cow::Borrowed("expr"),
677                        type_syn: RepeatedTypeSyntax::TypedValue(ExprType::Double),
678                        sep: ArgSepSyntax::Exactly(ArgSep::Long),
679                        require_one: true,
680                        allow_missing: false,
681                    }),
682                )])
683                .with_category(CATEGORY)
684                .with_description("Returns the minimum number out of a set of numbers.")
685                .build(),
686        })
687    }
688}
689
690impl Callable for MinFunction {
691    fn metadata(&self) -> Rc<CallableMetadata> {
692        self.metadata.clone()
693    }
694
695    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
696        let mut min = f64::MAX;
697        for i in 0..(scope.nargs() as u8) {
698            let n = scope.get_double(i);
699            if n < min {
700                min = n;
701            }
702        }
703        scope.return_double(min)
704    }
705}
706
707/// The `PI` function.
708pub struct PiFunction {
709    metadata: Rc<CallableMetadata>,
710}
711
712impl PiFunction {
713    /// Creates a new instance of the function.
714    pub fn new() -> Rc<Self> {
715        Rc::from(Self {
716            metadata: CallableMetadataBuilder::new("PI")
717                .with_return_type(ExprType::Double)
718                .with_syntax(&[(&[], None)])
719                .with_category(CATEGORY)
720                .with_description("Returns the Archimedes' constant.")
721                .build(),
722        })
723    }
724}
725
726impl Callable for PiFunction {
727    fn metadata(&self) -> Rc<CallableMetadata> {
728        self.metadata.clone()
729    }
730
731    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
732        debug_assert_eq!(0, scope.nargs());
733        scope.return_double(std::f64::consts::PI)
734    }
735}
736
737/// The `RAD` command.
738pub struct RadCommand {
739    metadata: Rc<CallableMetadata>,
740    angle_mode: Rc<RefCell<AngleMode>>,
741}
742
743impl RadCommand {
744    /// Creates a new instance of the command.
745    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
746        Rc::from(Self {
747            metadata: CallableMetadataBuilder::new("RAD")
748                .with_syntax(&[(&[], None)])
749                .with_category(CATEGORY)
750                .with_description(
751                    "Sets radians mode of calculation.
752The default condition for the trigonometric functions is to use radians but it can be set to \
753degrees with the DEG command.  RAD restores the environment to use radians mode.",
754                )
755                .build(),
756            angle_mode,
757        })
758    }
759}
760
761impl Callable for RadCommand {
762    fn metadata(&self) -> Rc<CallableMetadata> {
763        self.metadata.clone()
764    }
765
766    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
767        debug_assert_eq!(0, scope.nargs());
768        *self.angle_mode.borrow_mut() = AngleMode::Radians;
769        Ok(())
770    }
771}
772
773/// The `RANDOMIZE` command.
774pub struct RandomizeCommand {
775    metadata: Rc<CallableMetadata>,
776    prng: Rc<RefCell<Prng>>,
777}
778
779impl RandomizeCommand {
780    /// Creates a new command that updates `code` with the exit code once called.
781    pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
782        Rc::from(Self {
783            metadata: CallableMetadataBuilder::new("RANDOMIZE")
784                .with_syntax(&[
785                    (&[], None),
786                    (
787                        &[SingularArgSyntax::RequiredValue(
788                            RequiredValueSyntax {
789                                name: Cow::Borrowed("seed"),
790                                vtype: ExprType::Integer,
791                            },
792                            ArgSepSyntax::End,
793                        )],
794                        None,
795                    ),
796                ])
797                .with_category(CATEGORY)
798                .with_description(
799                    "Reinitializes the pseudo-random number generator.
800If no seed is given, uses system entropy to create a new sequence of random numbers.
801WARNING: These random numbers offer no cryptographic guarantees.",
802                )
803                .build(),
804            prng,
805        })
806    }
807}
808
809impl Callable for RandomizeCommand {
810    fn metadata(&self) -> Rc<CallableMetadata> {
811        self.metadata.clone()
812    }
813
814    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
815        if scope.nargs() == 0 {
816            *self.prng.borrow_mut() = Prng::new_from_entryopy();
817        } else {
818            debug_assert_eq!(1, scope.nargs());
819            let n = scope.get_integer(0);
820            *self.prng.borrow_mut() = Prng::new_from_seed(n);
821        }
822        Ok(())
823    }
824}
825
826/// The `RND` function.
827pub struct RndFunction {
828    metadata: Rc<CallableMetadata>,
829    prng: Rc<RefCell<Prng>>,
830}
831
832impl RndFunction {
833    /// Creates a new instance of the function.
834    pub fn new(prng: Rc<RefCell<Prng>>) -> Rc<Self> {
835        Rc::from(Self {
836            metadata: CallableMetadataBuilder::new("RND")
837                .with_return_type(ExprType::Double)
838                .with_syntax(&[
839                    (&[], None),
840                    (
841                        &[SingularArgSyntax::RequiredValue(
842                            RequiredValueSyntax {
843                                name: Cow::Borrowed("n"),
844                                vtype: ExprType::Integer,
845                            },
846                            ArgSepSyntax::End,
847                        )],
848                        None,
849                    ),
850                ])
851                .with_category(CATEGORY)
852                .with_description(
853                    "Returns a random number in the [0..1] range.
854If n% is negative, resets the pseudo-random number generator to a sequence derived from n% and \
855returns its first value.  If n% is zero, returns the previously generated random number.  If n% \
856is positive or is not specified, returns a new random number.
857If you need to generate an integer random number within a specific range, say [0..100], compute it \
858with an expression like CINT%(RND#(1) * 100.0).
859WARNING: These random numbers offer no cryptographic guarantees.",
860                )
861                .build(),
862            prng,
863        })
864    }
865}
866
867impl Callable for RndFunction {
868    fn metadata(&self) -> Rc<CallableMetadata> {
869        self.metadata.clone()
870    }
871
872    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
873        if scope.nargs() == 0 {
874            scope.return_double(self.prng.borrow_mut().next())
875        } else {
876            debug_assert_eq!(1, scope.nargs());
877            let n = scope.get_integer(0);
878            match n.cmp(&0) {
879                Ordering::Equal => scope.return_double(self.prng.borrow_mut().last()),
880                Ordering::Greater => scope.return_double(self.prng.borrow_mut().next()),
881                Ordering::Less => {
882                    let mut prng = self.prng.borrow_mut();
883                    *prng = Prng::new_from_seed(n);
884                    scope.return_double(prng.last())
885                }
886            }
887        }
888    }
889}
890
891/// The `ROUND` function.
892pub struct RoundFunction {
893    metadata: Rc<CallableMetadata>,
894}
895
896impl RoundFunction {
897    /// Creates a new instance of the function.
898    pub fn new() -> Rc<Self> {
899        Rc::from(Self {
900            metadata: CallableMetadataBuilder::new("ROUND")
901                .with_return_type(ExprType::Double)
902                .with_syntax(&[
903                    (
904                        &[SingularArgSyntax::RequiredValue(
905                            RequiredValueSyntax {
906                                name: Cow::Borrowed("expr"),
907                                vtype: ExprType::Double,
908                            },
909                            ArgSepSyntax::End,
910                        )],
911                        None,
912                    ),
913                    (
914                        &[
915                            SingularArgSyntax::RequiredValue(
916                                RequiredValueSyntax {
917                                    name: Cow::Borrowed("expr"),
918                                    vtype: ExprType::Double,
919                                },
920                                ArgSepSyntax::Exactly(ArgSep::Long),
921                            ),
922                            SingularArgSyntax::RequiredValue(
923                                RequiredValueSyntax {
924                                    name: Cow::Borrowed("decimals"),
925                                    vtype: ExprType::Integer,
926                                },
927                                ArgSepSyntax::End,
928                            ),
929                        ],
930                        None,
931                    ),
932                ])
933                .with_category(CATEGORY)
934                .with_description(
935                    "Rounds a number to the nearest value.
936If decimals% is omitted, rounds to the nearest integer.  If decimals% is given, rounds to that \
937many digits after the decimal point.  Negative values round digits before the decimal point.",
938                )
939                .build(),
940        })
941    }
942}
943
944impl Callable for RoundFunction {
945    fn metadata(&self) -> Rc<CallableMetadata> {
946        self.metadata.clone()
947    }
948
949    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
950        let value = scope.get_double(0);
951        if scope.nargs() == 1 {
952            scope.return_double(value.round())
953        } else {
954            debug_assert_eq!(2, scope.nargs());
955            let decimals = scope.get_integer(1);
956            let factor = 10f64.powi(decimals);
957            scope.return_double((value * factor).round() / factor)
958        }
959    }
960}
961
962/// The `SGN` function.
963pub struct SgnFunction {
964    metadata: Rc<CallableMetadata>,
965}
966
967impl SgnFunction {
968    /// Creates a new instance of the function.
969    pub fn new() -> Rc<Self> {
970        Rc::from(Self {
971            metadata: CallableMetadataBuilder::new("SGN")
972                .with_return_type(ExprType::Integer)
973                .with_syntax(&[(
974                    &[SingularArgSyntax::RequiredValue(
975                        RequiredValueSyntax { name: Cow::Borrowed("n"), vtype: ExprType::Double },
976                        ArgSepSyntax::End,
977                    )],
978                    None,
979                )])
980                .with_category(CATEGORY)
981                .with_description("Returns -1, 0, or 1 depending on the sign of a number.")
982                .build(),
983        })
984    }
985}
986
987impl Callable for SgnFunction {
988    fn metadata(&self) -> Rc<CallableMetadata> {
989        self.metadata.clone()
990    }
991
992    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
993        debug_assert_eq!(1, scope.nargs());
994        let n = scope.get_double(0);
995        let sign = if n > 0.0 {
996            1
997        } else if n < 0.0 {
998            -1
999        } else {
1000            0
1001        };
1002        scope.return_integer(sign)
1003    }
1004}
1005
1006/// The `SIN` function.
1007pub struct SinFunction {
1008    metadata: Rc<CallableMetadata>,
1009    angle_mode: Rc<RefCell<AngleMode>>,
1010}
1011
1012impl SinFunction {
1013    /// Creates a new instance of the function.
1014    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
1015        Rc::from(Self {
1016            metadata: CallableMetadataBuilder::new("SIN")
1017                .with_return_type(ExprType::Double)
1018                .with_syntax(&[(
1019                    &[SingularArgSyntax::RequiredValue(
1020                        RequiredValueSyntax {
1021                            name: Cow::Borrowed("angle"),
1022                            vtype: ExprType::Double,
1023                        },
1024                        ArgSepSyntax::End,
1025                    )],
1026                    None,
1027                )])
1028                .with_category(CATEGORY)
1029                .with_description(
1030                    "Computes the sine of an angle.
1031The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
1032selected by the DEG and RAD commands.",
1033                )
1034                .build(),
1035            angle_mode,
1036        })
1037    }
1038}
1039
1040impl Callable for SinFunction {
1041    fn metadata(&self) -> Rc<CallableMetadata> {
1042        self.metadata.clone()
1043    }
1044
1045    fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
1046        let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
1047        scope.return_double(angle.sin())
1048    }
1049}
1050
1051/// The `SQR` function.
1052pub struct SqrFunction {
1053    metadata: Rc<CallableMetadata>,
1054}
1055
1056impl SqrFunction {
1057    /// Creates a new instance of the function.
1058    pub fn new() -> Rc<Self> {
1059        Rc::from(Self {
1060            metadata: CallableMetadataBuilder::new("SQR")
1061                .with_return_type(ExprType::Double)
1062                .with_syntax(&[(
1063                    &[SingularArgSyntax::RequiredValue(
1064                        RequiredValueSyntax { name: Cow::Borrowed("num"), vtype: ExprType::Double },
1065                        ArgSepSyntax::End,
1066                    )],
1067                    None,
1068                )])
1069                .with_category(CATEGORY)
1070                .with_description("Computes the square root of the given number.")
1071                .build(),
1072        })
1073    }
1074}
1075
1076impl Callable for SqrFunction {
1077    fn metadata(&self) -> Rc<CallableMetadata> {
1078        self.metadata.clone()
1079    }
1080
1081    fn exec(&self, scope: Scope<'_>) -> CallResult<()> {
1082        debug_assert_eq!(1, scope.nargs());
1083        let num = scope.get_double(0);
1084
1085        if num < 0.0 {
1086            return Err(CallError::Syntax(
1087                scope.get_pos(0),
1088                "Cannot take square root of a negative number".to_owned(),
1089            ));
1090        }
1091        scope.return_double(num.sqrt())
1092    }
1093}
1094
1095/// The `TAN` function.
1096pub struct TanFunction {
1097    metadata: Rc<CallableMetadata>,
1098    angle_mode: Rc<RefCell<AngleMode>>,
1099}
1100
1101impl TanFunction {
1102    /// Creates a new instance of the function.
1103    pub fn new(angle_mode: Rc<RefCell<AngleMode>>) -> Rc<Self> {
1104        Rc::from(Self {
1105            metadata: CallableMetadataBuilder::new("TAN")
1106                .with_return_type(ExprType::Double)
1107                .with_syntax(&[(
1108                    &[SingularArgSyntax::RequiredValue(
1109                        RequiredValueSyntax {
1110                            name: Cow::Borrowed("angle"),
1111                            vtype: ExprType::Double,
1112                        },
1113                        ArgSepSyntax::End,
1114                    )],
1115                    None,
1116                )])
1117                .with_category(CATEGORY)
1118                .with_description(
1119                    "Computes the tangent of an angle.
1120The input angle% or angle# is measured in degrees or radians depending on the angle mode as \
1121selected by the DEG and RAD commands.",
1122                )
1123                .build(),
1124            angle_mode,
1125        })
1126    }
1127}
1128
1129impl Callable for TanFunction {
1130    fn metadata(&self) -> Rc<CallableMetadata> {
1131        self.metadata.clone()
1132    }
1133
1134    fn exec(&self, mut scope: Scope<'_>) -> CallResult<()> {
1135        let angle = get_angle(&mut scope, &self.angle_mode.borrow())?;
1136        scope.return_double(angle.tan())
1137    }
1138}
1139
1140/// Adds all symbols provided by this module to the given `machine`.
1141pub fn add_all(machine: &mut MachineBuilder) {
1142    let angle_mode = Rc::from(RefCell::from(AngleMode::Radians));
1143    let prng = Rc::from(RefCell::from(Prng::new_from_entryopy()));
1144    machine.add_clearable(Box::from(ClearableAngleMode { angle_mode: angle_mode.clone() }));
1145    machine.add_callable(AbsFunction::new());
1146    machine.add_callable(AcosFunction::new(angle_mode.clone()));
1147    machine.add_callable(AsinFunction::new(angle_mode.clone()));
1148    machine.add_callable(AtnFunction::new(angle_mode.clone()));
1149    machine.add_callable(CintFunction::new());
1150    machine.add_callable(CosFunction::new(angle_mode.clone()));
1151    machine.add_callable(DegCommand::new(angle_mode.clone()));
1152    machine.add_callable(ExpFunction::new());
1153    machine.add_callable(FixFunction::new());
1154    machine.add_callable(IntFunction::new());
1155    machine.add_callable(LogFunction::new());
1156    machine.add_callable(MaxFunction::new());
1157    machine.add_callable(MinFunction::new());
1158    machine.add_callable(PiFunction::new());
1159    machine.add_callable(RadCommand::new(angle_mode.clone()));
1160    machine.add_callable(RandomizeCommand::new(prng.clone()));
1161    machine.add_callable(RndFunction::new(prng));
1162    machine.add_callable(RoundFunction::new());
1163    machine.add_callable(SgnFunction::new());
1164    machine.add_callable(SinFunction::new(angle_mode.clone()));
1165    machine.add_callable(SqrFunction::new());
1166    machine.add_callable(TanFunction::new(angle_mode));
1167}
1168
1169#[cfg(test)]
1170mod tests {
1171    use crate::testutils::*;
1172
1173    #[test]
1174    fn test_abs() {
1175        check_expr_ok(0.0, "ABS(0)");
1176        check_expr_ok(3.0, "ABS(3)");
1177        check_expr_ok(3.5, "ABS(-3.5)");
1178
1179        check_expr_ok_with_vars(2.5, "ABS(d)", [("d", (-2.5f64).into())]);
1180
1181        check_expr_compilation_error("1:10: ABS expected expr#", "ABS()");
1182        check_expr_compilation_error("1:14: BOOLEAN is not a number", "ABS(FALSE)");
1183        check_expr_compilation_error("1:10: ABS expected expr#", "ABS(3, 4)");
1184    }
1185
1186    #[test]
1187    fn test_acos() {
1188        check_expr_ok(1f64.acos(), "ACOS(1)");
1189        check_expr_ok(0.5f64.acos(), "ACOS(0.5)");
1190
1191        check_expr_ok_with_vars(0.5f64.acos(), "ACOS(d)", [("d", 0.5f64.into())]);
1192
1193        let mut t = Tester::default();
1194        t.run("DEG: result = ACOS(0.5)").expect_var("result", 0.5f64.acos().to_degrees()).check();
1195
1196        check_expr_compilation_error("1:10: ACOS expected n#", "ACOS()");
1197        check_expr_compilation_error("1:15: BOOLEAN is not a number", "ACOS(FALSE)");
1198        check_expr_compilation_error("1:10: ACOS expected n#", "ACOS(3, 4)");
1199        check_expr_error(
1200            "1:15: Cannot take arc-cosine of a number outside of [-1, 1]",
1201            "ACOS(1.1)",
1202        );
1203    }
1204
1205    #[test]
1206    fn test_asin() {
1207        check_expr_ok(0f64.asin(), "ASIN(0)");
1208        check_expr_ok(0.5f64.asin(), "ASIN(0.5)");
1209
1210        check_expr_ok_with_vars(0.5f64.asin(), "ASIN(d)", [("d", 0.5f64.into())]);
1211
1212        let mut t = Tester::default();
1213        t.run("DEG: result = ASIN(0.5)").expect_var("result", 0.5f64.asin().to_degrees()).check();
1214
1215        check_expr_compilation_error("1:10: ASIN expected n#", "ASIN()");
1216        check_expr_compilation_error("1:15: BOOLEAN is not a number", "ASIN(FALSE)");
1217        check_expr_compilation_error("1:10: ASIN expected n#", "ASIN(3, 4)");
1218        check_expr_error("1:15: Cannot take arc-sine of a number outside of [-1, 1]", "ASIN(-1.1)");
1219    }
1220
1221    #[test]
1222    fn test_atn() {
1223        check_expr_ok(123f64.atan(), "ATN(123)");
1224        check_expr_ok(45.5f64.atan(), "ATN(45.5)");
1225
1226        check_expr_ok_with_vars(123f64.atan(), "ATN(a)", [("a", 123i32.into())]);
1227
1228        check_expr_compilation_error("1:10: ATN expected n#", "ATN()");
1229        check_expr_compilation_error("1:14: BOOLEAN is not a number", "ATN(FALSE)");
1230        check_expr_compilation_error("1:10: ATN expected n#", "ATN(3, 4)");
1231    }
1232
1233    #[test]
1234    fn test_cint() {
1235        check_expr_ok(0, "CINT(0.1)");
1236        check_expr_ok(0, "CINT(-0.1)");
1237        check_expr_ok(1, "CINT(0.9)");
1238        check_expr_ok(-1, "CINT(-0.9)");
1239
1240        check_expr_ok_with_vars(1, "CINT(d)", [("d", 0.9f64.into())]);
1241
1242        check_expr_compilation_error("1:10: CINT expected expr#", "CINT()");
1243        check_expr_compilation_error("1:15: BOOLEAN is not a number", "CINT(FALSE)");
1244        check_expr_compilation_error("1:10: CINT expected expr#", "CINT(3.0, 4)");
1245
1246        check_expr_error(
1247            "1:15: Cannot cast -1234567890123456 to integer due to overflow",
1248            "CINT(-1234567890123456.0)",
1249        );
1250    }
1251
1252    #[test]
1253    fn test_cos() {
1254        check_expr_ok(123f64.cos(), "COS(123)");
1255        check_expr_ok(45.5f64.cos(), "COS(45.5)");
1256
1257        check_expr_ok_with_vars(123f64.cos(), "COS(i)", [("i", 123i32.into())]);
1258
1259        check_expr_compilation_error("1:10: COS expected angle#", "COS()");
1260        check_expr_compilation_error("1:14: BOOLEAN is not a number", "COS(FALSE)");
1261        check_expr_compilation_error("1:10: COS expected angle#", "COS(3, 4)");
1262    }
1263
1264    #[test]
1265    fn test_deg_rad_commands() {
1266        let mut t = Tester::default();
1267        t.run("result = SIN(90)").expect_var("result", 90f64.sin()).check();
1268        t.run("DEG: result = SIN(90)").expect_var("result", 1.0).check();
1269        t.run("RAD: result = SIN(90)").expect_var("result", 90f64.sin()).check();
1270    }
1271
1272    #[test]
1273    fn test_deg_rad_reset_on_clear() {
1274        Tester::default()
1275            .run("DEG")
1276            .check()
1277            .clear()
1278            .run("result = SIN(90)")
1279            .expect_clear()
1280            .expect_var("result", 90f64.sin())
1281            .check();
1282    }
1283
1284    #[test]
1285    fn test_deg_rad_errors() {
1286        check_stmt_compilation_err("1:1: DEG expected no arguments", "DEG 1");
1287        check_stmt_compilation_err("1:1: RAD expected no arguments", "RAD 1");
1288    }
1289
1290    #[test]
1291    fn test_exp() {
1292        check_expr_ok(0f64.exp(), "EXP(0)");
1293        check_expr_ok(1f64.exp(), "EXP(1)");
1294        check_expr_ok((-2.5f64).exp(), "EXP(-2.5)");
1295
1296        check_expr_ok_with_vars(2f64.exp(), "EXP(i)", [("i", 2i32.into())]);
1297
1298        check_expr_compilation_error("1:10: EXP expected expr#", "EXP()");
1299        check_expr_compilation_error("1:14: BOOLEAN is not a number", "EXP(FALSE)");
1300        check_expr_compilation_error("1:10: EXP expected expr#", "EXP(3, 4)");
1301        check_expr_error("1:14: Cannot exponentiate 1000 due to overflow", "EXP(1000)");
1302    }
1303
1304    #[test]
1305    fn test_fix() {
1306        check_expr_ok(0, "FIX(0.1)");
1307        check_expr_ok(0, "FIX(-0.1)");
1308        check_expr_ok(4, "FIX(4.9)");
1309        check_expr_ok(-4, "FIX(-4.9)");
1310
1311        check_expr_ok_with_vars(-4, "FIX(d)", [("d", (-4.9f64).into())]);
1312
1313        check_expr_compilation_error("1:10: FIX expected expr#", "FIX()");
1314        check_expr_compilation_error("1:14: BOOLEAN is not a number", "FIX(FALSE)");
1315        check_expr_compilation_error("1:10: FIX expected expr#", "FIX(3.0, 4)");
1316
1317        check_expr_error(
1318            "1:14: Cannot cast -1234567890123456 to integer due to overflow",
1319            "FIX(-1234567890123456.0)",
1320        );
1321    }
1322
1323    #[test]
1324    fn test_int() {
1325        check_expr_ok(0, "INT(0.1)");
1326        check_expr_ok(-1, "INT(-0.1)");
1327        check_expr_ok(0, "INT(0.9)");
1328        check_expr_ok(-1, "INT(-0.9)");
1329
1330        check_expr_ok_with_vars(0, "INT(d)", [("d", 0.9f64.into())]);
1331
1332        check_expr_compilation_error("1:10: INT expected expr#", "INT()");
1333        check_expr_compilation_error("1:14: BOOLEAN is not a number", "INT(FALSE)");
1334        check_expr_compilation_error("1:10: INT expected expr#", "INT(3.0, 4)");
1335
1336        check_expr_error(
1337            "1:14: Cannot cast -1234567890123456 to integer due to overflow",
1338            "INT(-1234567890123456.0)",
1339        );
1340    }
1341
1342    #[test]
1343    fn test_log() {
1344        check_expr_ok(1f64.ln(), "LOG(1)");
1345        check_expr_ok(10f64.ln(), "LOG(10)");
1346
1347        check_expr_ok_with_vars(10f64.ln(), "LOG(i)", [("i", 10i32.into())]);
1348
1349        check_expr_compilation_error("1:10: LOG expected num#", "LOG()");
1350        check_expr_compilation_error("1:14: BOOLEAN is not a number", "LOG(FALSE)");
1351        check_expr_compilation_error("1:10: LOG expected num#", "LOG(3, 4)");
1352        check_expr_error("1:14: Cannot take logarithm of zero or a negative number", "LOG(0)");
1353        check_expr_error("1:14: Cannot take logarithm of zero or a negative number", "LOG(-0.1)");
1354    }
1355
1356    #[test]
1357    fn test_max() {
1358        check_expr_ok(0.0, "MAX(0)");
1359        check_expr_ok(0.0, "MAX(0, 0)");
1360
1361        check_expr_ok(0.0, "MAX(0.0)");
1362        check_expr_ok(0.0, "MAX(0.0, 0.0)");
1363
1364        check_expr_ok(1.0, "MAX(1)");
1365        check_expr_ok(5.0, "MAX(5, 3, 4)");
1366        check_expr_ok(-3.0, "MAX(-5, -3, -4)");
1367
1368        check_expr_ok(1.0, "MAX(1.0)");
1369        check_expr_ok(5.3, "MAX(5.3, 3.5, 4.2)");
1370        check_expr_ok(-3.5, "MAX(-5.3, -3.5, -4.2)");
1371
1372        check_expr_ok(2.5, "MAX(1, 0.5, 2.5, 2)");
1373
1374        check_expr_ok_with_vars(
1375            5.0,
1376            "MAX(i, j, k)",
1377            [("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
1378        );
1379
1380        check_expr_compilation_error("1:10: MAX expected expr1#[, .., exprN#]", "MAX()");
1381        check_expr_compilation_error("1:14: BOOLEAN is not a number", "MAX(FALSE)");
1382    }
1383
1384    #[test]
1385    fn test_min() {
1386        check_expr_ok(0.0, "MIN(0)");
1387        check_expr_ok(0.0, "MIN(0, 0)");
1388
1389        check_expr_ok(0.0, "MIN(0.0)");
1390        check_expr_ok(0.0, "MIN(0.0, 0.0)");
1391
1392        check_expr_ok(1.0, "MIN(1)");
1393        check_expr_ok(3.0, "MIN(5, 3, 4)");
1394        check_expr_ok(-5.0, "MIN(-5, -3, -4)");
1395
1396        check_expr_ok(1.0, "MIN(1.0)");
1397        check_expr_ok(3.5, "MIN(5.3, 3.5, 4.2)");
1398        check_expr_ok(-5.3, "MIN(-5.3, -3.5, -4.2)");
1399
1400        check_expr_ok(0.5, "MIN(1, 0.5, 2.5, 2)");
1401
1402        check_expr_ok_with_vars(
1403            3.0,
1404            "MIN(i, j, k)",
1405            [("i", 5i32.into()), ("j", 3i32.into()), ("k", 4i32.into())],
1406        );
1407
1408        check_expr_compilation_error("1:10: MIN expected expr1#[, .., exprN#]", "MIN()");
1409        check_expr_compilation_error("1:14: BOOLEAN is not a number", "MIN(FALSE)");
1410    }
1411
1412    #[test]
1413    fn test_pi() {
1414        check_expr_ok(std::f64::consts::PI, "PI");
1415
1416        check_expr_compilation_error("1:10: PI expected no arguments", "PI()");
1417        check_expr_compilation_error("1:10: PI expected no arguments", "PI(3)");
1418    }
1419
1420    #[test]
1421    fn test_randomize_and_rnd() {
1422        // These tests could lead to flakiness if the PRNG happens to yield the same number twice
1423        // in a row because we did not previously configure the seed.  It is very unlikely though,
1424        // and we need a way to test that the PRNG was initialized before we call RANDOMIZE.
1425        check_expr_ok(false, "RND(1) = RND(1)");
1426        check_expr_ok(false, "RND(1) = RND(10)");
1427        check_expr_ok(true, "RND(-1) = RND(-1)");
1428        check_expr_ok(false, "RND(-1) = RND(-2)");
1429        check_expr_ok(true, "RND(0) = RND(0)");
1430
1431        Tester::default()
1432            .run("RANDOMIZE 10")
1433            .check()
1434            .run("result = RND(1)")
1435            .expect_var("result", 0.7097578208683426)
1436            .check()
1437            .run("result = RND(-1)")
1438            .expect_var("result", 0.6150244305876607)
1439            .check()
1440            .run("result = RND(1)")
1441            .expect_var("result", 0.11707478019340774)
1442            .check()
1443            .run("result = RND(-1)")
1444            .expect_var("result", 0.6150244305876607)
1445            .check()
1446            .run("result = RND(1.1)")
1447            .expect_var("result", 0.11707478019340774)
1448            .check()
1449            .run("result = RND(0)")
1450            .expect_var("result", 0.11707478019340774)
1451            .check()
1452            .run("result = RND(10)")
1453            .expect_var("result", 0.8423819585801992)
1454            .check()
1455            .run("RANDOMIZE 10.2")
1456            .expect_var("result", 0.8423819585801992)
1457            .check()
1458            .run("result = RND(1)")
1459            .expect_var("result", 0.7097578208683426)
1460            .check();
1461
1462        check_expr_compilation_error("1:10: RND expected <> | <n%>", "RND(1, 7)");
1463        check_expr_compilation_error("1:14: BOOLEAN is not a number", "RND(FALSE)");
1464
1465        check_stmt_compilation_err("1:1: RANDOMIZE expected <> | <seed%>", "RANDOMIZE ,");
1466        check_stmt_compilation_err("1:11: BOOLEAN is not a number", "RANDOMIZE TRUE");
1467    }
1468
1469    #[test]
1470    fn test_round() {
1471        check_expr_ok(1.0, "ROUND(0.5)");
1472        check_expr_ok(-1.0, "ROUND(-0.5)");
1473        check_expr_ok(12.35, "ROUND(12.345, 2)");
1474        check_expr_ok(12.3, "ROUND(12.345, 1)");
1475        check_expr_ok(120.0, "ROUND(123.45, -1)");
1476
1477        check_expr_ok_with_vars(12.35, "ROUND(d, 2)", [("d", 12.345f64.into())]);
1478
1479        check_expr_compilation_error(
1480            "1:10: ROUND expected <expr#> | <expr#, decimals%>",
1481            "ROUND()",
1482        );
1483        check_expr_compilation_error("1:16: BOOLEAN is not a number", "ROUND(FALSE)");
1484        check_expr_compilation_error(
1485            "1:10: ROUND expected <expr#> | <expr#, decimals%>",
1486            "ROUND(1, 2, 3)",
1487        );
1488    }
1489
1490    #[test]
1491    fn test_sgn() {
1492        check_expr_ok(-1, "SGN(-3)");
1493        check_expr_ok(0, "SGN(0)");
1494        check_expr_ok(1, "SGN(3.5)");
1495
1496        check_expr_ok_with_vars(-1, "SGN(d)", [("d", (-1.5f64).into())]);
1497
1498        check_expr_compilation_error("1:10: SGN expected n#", "SGN()");
1499        check_expr_compilation_error("1:14: BOOLEAN is not a number", "SGN(FALSE)");
1500        check_expr_compilation_error("1:10: SGN expected n#", "SGN(3, 4)");
1501    }
1502
1503    #[test]
1504    fn test_sin() {
1505        check_expr_ok(123f64.sin(), "SIN(123)");
1506        check_expr_ok(45.5f64.sin(), "SIN(45.5)");
1507
1508        check_expr_ok_with_vars(123f64.sin(), "SIN(i)", [("i", 123i32.into())]);
1509
1510        check_expr_compilation_error("1:10: SIN expected angle#", "SIN()");
1511        check_expr_compilation_error("1:14: BOOLEAN is not a number", "SIN(FALSE)");
1512        check_expr_compilation_error("1:10: SIN expected angle#", "SIN(3, 4)");
1513    }
1514
1515    #[test]
1516    fn test_sqr() {
1517        check_expr_ok(0f64.sqrt(), "SQR(0)");
1518        check_expr_ok(-0f64.sqrt(), "SQR(-0.0)");
1519        check_expr_ok(9f64.sqrt(), "SQR(9)");
1520        check_expr_ok(100.50f64.sqrt(), "SQR(100.50)");
1521
1522        check_expr_ok_with_vars(9f64.sqrt(), "SQR(i)", [("i", 9i32.into())]);
1523
1524        check_expr_compilation_error("1:10: SQR expected num#", "SQR()");
1525        check_expr_compilation_error("1:14: BOOLEAN is not a number", "SQR(FALSE)");
1526        check_expr_compilation_error("1:10: SQR expected num#", "SQR(3, 4)");
1527        check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-3)");
1528        check_expr_error("1:14: Cannot take square root of a negative number", "SQR(-0.1)");
1529    }
1530
1531    #[test]
1532    fn test_tan() {
1533        check_expr_ok(123f64.tan(), "TAN(123)");
1534        check_expr_ok(45.5f64.tan(), "TAN(45.5)");
1535
1536        check_expr_ok_with_vars(123f64.tan(), "TAN(i)", [("i", 123i32.into())]);
1537
1538        check_expr_compilation_error("1:10: TAN expected angle#", "TAN()");
1539        check_expr_compilation_error("1:14: BOOLEAN is not a number", "TAN(FALSE)");
1540        check_expr_compilation_error("1:10: TAN expected angle#", "TAN(3, 4)");
1541    }
1542}