suiron/built_in_arithmetic.rs
1//! Suiron's arithmetic functions: add, subtract, multiply, divide.
2//!
3//! The functions defined in this module support Suiron's built-in
4//! arithmetic functions.<br>
5//! They are called from
6//! [unify_sfunction()](../built_in_functions/fn.unify_sfunction.html#)
7//! in built_in_functions.rs.
8//
9// Cleve Lendon 2023
10
11use std::rc::Rc;
12use super::substitution_set::*;
13use super::unifiable::Unifiable;
14
15/// Add arguments together.
16///
17/// If all the arguments are SIntegers, the function returns an SInteger.<br>
18/// If there is at least 1 SFloat in the list of argument, the function
19/// returns an SFloat.
20///
21/// This method is called by
22/// [unify_sfunction()](../built_in_functions/fn.unify_sfunction.html#).
23///
24/// # Arguments
25/// * list of [Unifiable](../unifiable/enum.Unifiable.html) terms
26/// * [SubstitutionSet](../substitution_set/index.html)
27/// # Returns
28/// * [SInteger](../unifiable/enum.Unifiable.html#variant.SInteger) or
29/// [SFloat](../unifiable/enum.Unifiable.html#variant.SFloat)
30/// # Panics
31/// * If a logic variable in the list of terms is not grounded.
32/// * If one of the ground terms is not an SInteger or SFloat.
33/// # Usage
34/// ```
35/// use std::rc::Rc;
36/// use suiron::*;
37///
38/// let ss = empty_ss!();
39/// let arguments = parse_arguments("1, 2, 3").unwrap();
40/// let result = evaluate_add(&arguments, &ss);
41/// println!("{}", result);
42/// // Prints: 6
43/// ```
44pub fn evaluate_add<'a>(arguments: &'a Vec<Unifiable>,
45 ss: &'a Rc<SubstitutionSet<'a>>) -> Unifiable {
46
47 let (numbers, has_float) = get_numbers(arguments, ss);
48 if has_float {
49 let f = get_floats(&numbers);
50 let sum = f.iter().fold(0.0, |mut sum, &x| {sum += x; sum});
51 return Unifiable::SFloat(sum);
52 }
53 else {
54 let i = get_integers(&numbers);
55 let sum = i.iter().fold(0, |mut sum, &x| {sum += x; sum});
56 return Unifiable::SInteger(sum);
57 }
58} // evaluate_add
59
60
61/// Subtract arguments from the first argument in a list.
62///
63/// If all the arguments are SIntegers, the function returns an SInteger.<br>
64/// If there is at least 1 SFloat in the list of argument, the function
65/// returns an SFloat.
66///
67/// This method is called by
68/// [unify_sfunction()](../built_in_functions/fn.unify_sfunction.html#).
69///
70/// # Arguments
71/// * list of [Unifiable](../unifiable/enum.Unifiable.html) terms
72/// * [SubstitutionSet](../substitution_set/index.html)
73/// # Returns
74/// * [SInteger](../unifiable/enum.Unifiable.html#variant.SInteger) or
75/// [SFloat](../unifiable/enum.Unifiable.html#variant.SFloat)
76/// # Panics
77/// * If a logic variable in the list of terms is not grounded.
78/// * If one of the ground terms is not an SInteger or SFloat.
79/// # Usage
80/// ```
81/// use std::rc::Rc;
82/// use suiron::*;
83///
84/// let ss = empty_ss!();
85/// let arguments = parse_arguments("5.2, 1, 2").unwrap();
86/// let result = evaluate_subtract(&arguments, &ss);
87/// println!("{}", result);
88/// // Prints: 2.2
89/// ```
90pub fn evaluate_subtract<'a>(arguments: &'a Vec<Unifiable>,
91 ss: &'a Rc<SubstitutionSet<'a>>) -> Unifiable {
92
93 let (numbers, has_float) = get_numbers(arguments, ss);
94 if has_float {
95 let mut f = get_floats(&numbers);
96 let first = f.remove(0);
97 let result = f.iter().fold(first, |mut result, &x| {result -= x; result});
98 return Unifiable::SFloat(result);
99 }
100 else {
101 let mut i = get_integers(&numbers);
102 let first = i.remove(0);
103 let result = i.iter().fold(first, |mut result, &x| {result -= x; result});
104 return Unifiable::SInteger(result);
105 }
106} // evaluate_subtract
107
108
109/// Multiply arguments together.
110///
111/// If all the arguments are SIntegers, the function returns an SInteger.<br>
112/// If there is at least 1 SFloat in the list of argument, the function
113/// returns an SFloat.
114///
115/// This method is called by
116/// [unify_sfunction()](../built_in_functions/fn.unify_sfunction.html#).
117///
118/// # Arguments
119/// * list of [Unifiable](../unifiable/enum.Unifiable.html) terms
120/// * [SubstitutionSet](../substitution_set/index.html)
121/// # Returns
122/// * [SInteger](../unifiable/enum.Unifiable.html#variant.SInteger) or
123/// [SFloat](../unifiable/enum.Unifiable.html#variant.SFloat)
124/// # Panics
125/// * If a logic variable in the list of terms is not grounded.
126/// * If one of the ground terms is not an SInteger or SFloat.
127/// # Usage
128/// ```
129/// use std::rc::Rc;
130/// use suiron::*;
131///
132/// let ss = empty_ss!();
133/// let arguments = parse_arguments("3, 4, 0.25").unwrap();
134/// let result = evaluate_multiply(&arguments, &ss);
135/// println!("{}", result);
136/// // Prints: 3
137/// ```
138///
139pub fn evaluate_multiply<'a>(arguments: &'a Vec<Unifiable>,
140 ss: &'a Rc<SubstitutionSet<'a>>) -> Unifiable {
141
142 let (numbers, has_float) = get_numbers(arguments, ss);
143 if has_float {
144 let f = get_floats(&numbers);
145 let result = f.iter().fold(1.0, |mut result, &x| {result *= x; result});
146 return Unifiable::SFloat(result);
147 }
148 else {
149 let i = get_integers(&numbers);
150 let result = i.iter().fold(1, |mut result, &x| {result *= x; result});
151 return Unifiable::SInteger(result);
152 }
153} // evaluate_multiply
154
155
156/// Divide the first argument by the following arguments.
157///
158/// If all the arguments are SIntegers, the function returns an SInteger.<br>
159/// If there is at least 1 SFloat in the list of argument, the function
160/// returns an SFloat.
161///
162/// If all the arguments are SIntegers, the function does an integer divide.<br>
163/// That is, all remainders are discarded:<br>
164/// <blockquote>
165/// 7 / 3 => 2
166/// </blockquote>
167///
168/// This method is called by
169/// [unify_sfunction()](../built_in_functions/fn.unify_sfunction.html#).
170///
171/// # Arguments
172/// * list of [Unifiable](../unifiable/enum.Unifiable.html) terms
173/// * [SubstitutionSet](../substitution_set/index.html)
174/// # Returns
175/// * [SInteger](../unifiable/enum.Unifiable.html#variant.SInteger) or
176/// [SFloat](../unifiable/enum.Unifiable.html#variant.SFloat)
177/// # Panics
178/// * If a logic variable in the list of terms is not grounded.
179/// * If one of the ground terms is not an SInteger or SFloat.
180/// # Usage
181/// ```
182/// use std::rc::Rc;
183/// use suiron::*;
184///
185/// // Floating point division.
186/// let ss = empty_ss!();
187/// let arguments = parse_arguments("12.6, 3, 3").unwrap();
188/// let result = evaluate_divide(&arguments, &ss);
189/// println!("{}", result);
190/// // Prints: 1.4000000000000001
191///
192/// // Integer division.
193/// let ss = empty_ss!();
194/// let arguments = parse_arguments("13, 3, 3").unwrap();
195/// let result = evaluate_divide(&arguments, &ss);
196/// println!("{}", result);
197/// // Prints: 1
198/// ```
199pub fn evaluate_divide<'a>(arguments: &'a Vec<Unifiable>,
200 ss: &'a Rc<SubstitutionSet<'a>>) -> Unifiable {
201
202 let (numbers, has_float) = get_numbers(arguments, ss);
203 if has_float {
204 let mut f = get_floats(&numbers);
205 let first = f.remove(0);
206 let result = f.iter().fold(first, |mut result, &x| {result /= x; result});
207 return Unifiable::SFloat(result);
208 }
209 else {
210 let mut i = get_integers(&numbers);
211 let first = i.remove(0);
212 let result = i.iter().fold(first, |mut result, &x| {result /= x; result});
213 return Unifiable::SInteger(result);
214 }
215} // evaluate_divide
216
217
218/// In Suiron, a number can be an SInteger (i64) or an SFloat (f64).
219#[derive(Debug)]
220pub enum SNumber {
221 SFloat(f64),
222 SInteger(i64),
223} // SNumeric
224
225/// Gets the numbers (integers and floats) from a vector of unifiable terms.
226///
227/// This function gets the ground terms from the given list of unifiable terms,
228/// and saves them in a vector of numbers (SNumber). It returns the vector of
229/// numbers, and a flag called has_float, which indicates that at least one of
230/// the numbers is a floating point number.
231///
232/// # Arguments
233/// * vector of Unifiable terms
234/// * SubstitutionSet
235/// # Return
236/// * (vector of SNumbers, has_float)
237/// # Panics
238/// * If a logic variable in the list of terms is not grounded.
239/// * If one of the terms is not an SInteger or SFloat.
240fn get_numbers<'a>(terms: &Vec<Unifiable>, ss: &'a Rc<SubstitutionSet<'a>>)
241 -> (Vec<SNumber>, bool) {
242
243 let mut numbers: Vec<SNumber> = vec![];
244 let mut has_float = false;
245
246 // Get ground terms.
247 for term in terms {
248 match get_ground_term(term, ss) {
249 Some(gt) => {
250 match gt {
251 Unifiable::SInteger(i) => {
252 numbers.push(SNumber::SInteger(*i));
253 },
254 Unifiable::SFloat(f) => {
255 has_float = true;
256 numbers.push(SNumber::SFloat(*f));
257 },
258 _ => {
259 number_panic("Argument is not a number", term);
260 },
261 } // match
262 },
263 None => { number_panic("Argument is not grounded", term); },
264 } // match
265 } // for
266 return (numbers, has_float);
267} // get_numbers()
268
269/// Gets the integers (i64) from a list of numbers.
270///
271/// # Argument
272/// * vector of numbers - (SNumber)
273/// # Return
274/// * vector of i64
275fn get_integers<'a>(numbers: &Vec<SNumber>) -> Vec<i64> {
276 let mut ints: Vec<i64> = vec![];
277 for n in numbers {
278 if let SNumber::SInteger(i) = n { ints.push(*i); }
279 }
280 return ints;
281} // get_integers()
282
283/// Gets the floating point numbers (f64) from a list of numbers.
284///
285/// # Argument
286/// * vector of numbers - (SNumber)
287/// # Return
288/// * vector of f64
289fn get_floats<'a>(numbers: &Vec<SNumber>) -> Vec<f64> {
290 let mut floats: Vec<f64> = vec![];
291 for n in numbers {
292 if let SNumber::SFloat(f) = n { floats.push(*f); }
293 else {
294 // Convert int to float.
295 if let SNumber::SInteger(i) = n { floats.push(*i as f64); }
296 }
297 }
298 return floats;
299} // get_floats()
300
301/// Creates an error message for get_numbers() and panics.
302///
303/// # Arguments
304/// * err - error description
305/// * term - term which caused the error
306fn number_panic(err: &str, term: &Unifiable) {
307 let msg = format!("get_numbers() - {}: {}", err, term);
308 panic!("{}", msg);
309}
310
311#[cfg(test)]
312mod test {
313
314 use std::rc::Rc;
315 use crate::*;
316 use super::*;
317
318 // Variables for testing.
319 fn w() -> Unifiable { logic_var!(1, "$W") }
320 fn x() -> Unifiable { logic_var!(2, "$X") }
321 fn y() -> Unifiable { logic_var!(3, "$Y") }
322 fn z() -> Unifiable { logic_var!(4, "$Z") }
323
324 // Set up substitution set.
325 fn get_ss<'a>() -> Rc<SubstitutionSet<'a>> {
326 let three = SInteger(3);
327 let four = SInteger(4);
328 let five_seven = SFloat(5.7);
329 let mut ss = empty_ss!();
330 ss = x().unify(&three, &ss).unwrap();
331 ss = y().unify(&four, &ss).unwrap();
332 ss = z().unify(&five_seven, &ss).unwrap();
333 ss
334 } // get_ss()
335
336 #[test]
337 fn test_evaluate_add() {
338
339 let ss = get_ss();
340
341 let arguments = vec![SInteger(2), x(), y()];
342 let result = evaluate_add(&arguments, &ss);
343 let result = format!("{:?}", result);
344 assert_eq!("SInteger(9)", result);
345
346 let arguments = vec![SInteger(2), x(), z()];
347 let result = evaluate_add(&arguments, &ss);
348 let result = format!("{:?}", result);
349 assert_eq!("SFloat(10.7)", result);
350
351 } // test_evaluate_add()
352
353 #[test]
354 fn test_evaluate_subtract() {
355
356 let ss = get_ss();
357
358 let arguments = vec![x(), SInteger(10), y()];
359 let result = evaluate_subtract(&arguments, &ss);
360 let result = format!("{:?}", result);
361 assert_eq!("SInteger(-11)", result);
362
363 let arguments = vec![z(), SInteger(10), y()];
364 let result = evaluate_subtract(&arguments, &ss);
365 let result = format!("{:?}", result);
366 assert_eq!("SFloat(-8.3)", result);
367
368 } // test_evaluate_subtract()
369
370 #[test]
371 fn test_evaluate_multiply() {
372
373 let ss = get_ss();
374
375 let arguments = vec![x(), y(), SInteger(-3)];
376 let result = evaluate_multiply(&arguments, &ss);
377 let result = format!("{:?}", result);
378 assert_eq!("SInteger(-36)", result);
379
380 let arguments = vec![x(), y(), SFloat(-3.0)];
381 let result = evaluate_multiply(&arguments, &ss);
382 let result = format!("{:?}", result);
383 assert_eq!("SFloat(-36.0)", result);
384
385 } // test_evaluate_multiply()
386
387 #[test]
388 fn test_evaluate_divide() {
389
390 let ss = get_ss();
391
392 let arguments = vec![SInteger(12), x(), y()];
393 let result = evaluate_divide(&arguments, &ss);
394 let result = format!("{:?}", result);
395 assert_eq!("SInteger(1)", result);
396
397 // Integer division truncates.
398 let arguments = vec![SInteger(13), x(), y()];
399 let result = evaluate_divide(&arguments, &ss);
400 let result = format!("{:?}", result);
401 assert_eq!("SInteger(1)", result);
402
403 let arguments = vec![SFloat(12.0), x(), y()];
404 let result = evaluate_divide(&arguments, &ss);
405 let result = format!("{:?}", result);
406 assert_eq!("SFloat(1.0)", result);
407
408 let arguments = vec![SFloat(13.0), x(), y()];
409 let result = evaluate_divide(&arguments, &ss);
410 let result = format!("{:?}", result);
411 assert_eq!("SFloat(1.0833333333333333)", result);
412
413 // Divide by zero.
414 let arguments = vec![SFloat(13.0), SInteger(0), y()];
415 let result = evaluate_divide(&arguments, &ss);
416 let result = format!("{:?}", result);
417 assert_eq!("SFloat(inf)", result);
418
419 } // test_evaluate_divide()
420
421 // Test with ungrounded variable in argument list.
422 #[test]
423 #[should_panic]
424 fn test_evaluate_ungrounded_panic() {
425 let ss = get_ss();
426 let arguments = vec![SInteger(12), x(), w()];
427 evaluate_add(&arguments, &ss);
428 }
429
430 // Test with non-number in argument list.
431 #[test]
432 #[should_panic]
433 fn test_evaluate_nonnumber_panic() {
434 let ss = get_ss();
435 let arguments = vec![SInteger(12), x(), atom!("Oh no.")];
436 evaluate_add(&arguments, &ss);
437 }
438
439} // test