deterministic_trigonometry/
lib.rs

1#![warn(missing_docs)]
2
3//! Deterministic trigonometry across architectures without using floating point arithmetic.
4//!
5//! - Uses (i32, i32) tuples to represent fractions.
6//! - Uses pre-baked arrays for trigonometry results.
7//! - Deterministic across compilers and computer architectures.
8//! - Introduces imprecision due to rounding errors.
9//! - Most likely to be useful for games that depend on lockstep determinism.
10//!
11//! # Example
12//!
13//! ```
14//! use deterministic_trigonometry::DTrig;
15//!
16//! fn main (){
17//!
18//! let d_trig = DTrig::initialize();
19//!
20//! let sine_of_pi_over_three = d_trig.sine((1047,1000));
21//!
22//! println!("The sine of 1047/1000 radians is {}/{}.", sine_of_pi_over_three.0, sine_of_pi_over_three.1);
23//!
24//! }
25//! ```
26
27/// Main struct through which trig functions are implemented.
28///
29/// Once this struct is initialized, it holds arrays with pre-baked trig functions.
30/// Trig functions are called as methods with the input as (i32 , i32) tuples with
31/// the first i32 representing the numerator an the second i32 representing the denominator.
32///
33/// The output is also a (i32 , i32) tuple with the first i32 representing the numerator
34/// and the second i32 representing the denominator. The output denominator will always be 1000.
35///
36/// # Example
37///
38/// ```
39/// use deterministic_trigonometry::DTrig;
40///
41/// fn main (){
42///
43/// let d_trig = DTrig::initialize();
44///
45/// let sine_of_pi_over_four = d_trig.sine((785,1000));
46///
47/// println!("The sine of 785/1000 radians is {}/{}.", sine_of_pi_over_four.0, sine_of_pi_over_four.1);
48///
49/// }
50/// ```
51
52pub struct DTrig {
53    
54    // Array sizes are set to balance accuracy with memory usage.
55    sine_array: [i16; 6283],
56    cosine_array: [i16; 6283],
57    tangent_array: [i32; 6283],
58    arcsine_array: [i16; 2001],
59    arccosine_array: [i16; 2001],
60    arctangent_thousandths: [i16; 8001],
61    arctangent_hundredths: [i16; 4001],
62    arctangent_tenths: [i16; 2001],
63    arctangent_ones: [i16; 2001],
64}
65
66/// This module contains the code that sets the values for the arrays from the pre-baked tables.
67pub mod initialize;
68
69/// This module contains utility functions.
70pub (self) mod utility;
71
72// These functions pull the appropriate results out of the arrays.
73impl DTrig {
74    /// Calculates the sine of an angle in radians.
75    ///
76    /// - The input tuple represents the angle as a numerator and denominator.
77    /// - The output tuple represents the sine result as a numerator and denominator.
78    /// - Most accurate between 0 and 2 PI with a factor of 1000 as denominator.
79    /// - See README for limitations on accuracy.
80    ///
81    /// # Panics
82    /// 
83    /// - A zero as the input for the denominator.
84    ///
85    /// # Example
86    ///
87    /// ```
88    /// use deterministic_trigonometry::DTrig;
89    ///
90    /// fn main (){
91    ///
92    /// let d_trig = DTrig::initialize();
93    ///
94    /// let sine_of_pi_over_four = d_trig.sine((785,1000));
95    ///
96    /// println!("The sine of 785/1000 radians is {}/{}.", sine_of_pi_over_four.0, sine_of_pi_over_four.1);
97    ///
98    /// }
99    /// ```
100
101    pub fn sine(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
102        return (
103            i32::from(
104                self.sine_array
105                    [
106                        utility::normalize_angle(
107                            utility::denominator_to_1000(argument_fraction)
108                        ) as usize
109                    ]
110            ),
111            1000,
112        );
113    }
114    /// Calculates the cosine of an angle in radians.
115    ///
116    /// - The input tuple represents the input angle as a numerator and denominator.
117    /// - The output tuple represents the cosine result as a numerator and denominator.
118    /// - Most accurate between 0 and 2 PI with a factor of 1000 as denominator.
119    /// - See README for limitations on accuracy.
120    /// 
121    /// # Panics
122    /// 
123    /// - A zero as the input for the denominator.
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use deterministic_trigonometry::DTrig;
129    ///
130    /// fn main (){
131    ///
132    /// let d_trig = DTrig::initialize();
133    ///
134    /// let cosine_of_pi_over_four = d_trig.cosine((785,1000));
135    ///
136    /// println!("The cosine of 785/1000 radians is {}/{}.", cosine_of_pi_over_four.0, cosine_of_pi_over_four.1);
137    ///
138    /// }
139    ///
140    /// ```
141
142    pub fn cosine(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
143        return (
144            i32::from(
145                self.cosine_array
146                    [
147                        utility::normalize_angle(
148                            utility::denominator_to_1000(argument_fraction)
149                        ) as usize
150                    ]
151            ),
152            1000,
153        );
154    }
155
156    /// Calculates the tangent of an angle in radians.
157    ///
158    /// - The input tuple represents the input angle as a numerator and denominator.
159    /// - The output tuple represents the tangent result as a numerator and denominator.
160    /// - Most accurate between 0 and 2 PI with a factor of 1000 as denominator.
161    /// - Can have large errors around asymptote lines for the tangent function.
162    /// - See README for limitations on accuracy.
163    /// 
164    /// # Panics
165    /// 
166    /// - A zero as the input for the denominator.
167    /// 
168    /// # Example
169    ///
170    /// ```
171    /// use deterministic_trigonometry::DTrig;
172    ///
173    /// fn main (){
174    ///
175    /// let d_trig = DTrig::initialize();
176    ///
177    /// let tangent_of_pi_over_four = d_trig.tangent((785,1000));
178    ///
179    /// println!("The tangent of 785/1000 radians is {}/{}.", tangent_of_pi_over_four.0, tangent_of_pi_over_four.1);
180    ///
181    /// }
182    /// ```
183
184    pub fn tangent(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
185        return (
186            self.tangent_array
187                [
188                    utility::normalize_angle(
189                        utility::denominator_to_1000(argument_fraction)
190                    ) as usize
191                ],
192            1000,
193        );
194    }
195
196    /// Performs arcsine on a value to produce the measure of the corresponding angle in radians.
197    ///
198    /// - The input tuple represents the input value as a numerator and denominator.
199    /// - The output tuple represents the angle result in radians as a numerator and denominator.
200    /// - Most accurate with a factor of 1000 as denominator.
201    /// - See README for detailed limitations on accuracy.
202    /// 
203    /// # Panics
204    /// 
205    /// - A zero as the input for the denominator.
206    /// - Inputs representing a fractions with a value greater than 1 or less than -1.
207    /// - This is out of the mathematically defined denominator the arcsine function.
208    /// 
209    /// # Example
210    ///
211    /// ```
212    /// use deterministic_trigonometry::DTrig;
213    ///
214    /// fn main (){
215    ///
216    /// let d_trig = DTrig::initialize();
217    ///
218    /// let arcsine_of_one_half = d_trig.arcsine((500,1000));
219    ///
220    /// println!("The arcsine of 500/1000 radians is {}/{}.", arcsine_of_one_half.0, arcsine_of_one_half.1);
221    ///
222    /// }
223    /// ```
224
225    pub fn arcsine(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
226        if utility::denominator_to_1000(argument_fraction) < -1000 {
227            panic!("Arcsine input less than 1.");
228        } else if utility::denominator_to_1000(argument_fraction) > 1000 {
229            panic!("Arcsine input greater than 1.");
230        } else {
231            return (
232                i32::from(
233                    self.arcsine_array
234                        [(utility::denominator_to_1000(argument_fraction) + 1000) as usize]
235                ),
236                1000,
237            );
238        }
239    }
240
241    /// Performs arccosine on a value to produce the measure of the corresponding angle in radians
242    ///
243    /// - The input tuple represents the input value as a numerator and denominator.
244    /// - The output tuple represents the angle result in radians as a numerator and denominator.
245    /// - Most accurate with a factor of 1000 as denominator.
246    /// - See README for detailed limitations on accuracy.
247    ///
248    /// # Panics
249    /// 
250    /// - A zero as the input for the denominator.
251    /// - Inputs representing a fractions with a value greater than 1 or less than -1.
252    /// - This is out of the mathematically defined domain for the arccosine function.
253    /// 
254    /// # Example
255    ///
256    /// ```
257    /// use deterministic_trigonometry::DTrig;
258    ///
259    /// fn main (){
260    ///
261    /// let d_trig = DTrig::initialize();
262    ///
263    /// let arccosine_of_one_half = d_trig.arccosine((500,1000));
264    ///
265    /// println!("The arccosine of 500/1000 radians is {}/{}.", arccosine_of_one_half.0, arccosine_of_one_half.1);
266    ///
267    /// }
268    /// ```
269
270    pub fn arccosine(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
271        if utility::denominator_to_1000(argument_fraction) < -1000 {
272            panic!("Arccosine input less than 1, which is undefined.");
273        } else if utility::denominator_to_1000(argument_fraction) > 1000 {
274            panic!("Arccosine input greater than 1, which is undefined.");
275        } else {
276            return (
277                i32::from(
278                    self.arccosine_array
279                        [(utility::denominator_to_1000(argument_fraction) + 1000) as usize]
280                ),
281                1000,
282            );
283        }
284    }
285
286    /// Performs arctangent on a value to produce the measure of the corresponding angle in radians
287    ///
288    /// - The input tuple represents the input value as a numerator and denominator.
289    /// - The output tuple represents the angle result in radians as a numerator and denominator.
290    /// - Most accurate with a factor of 1000 as denominator.
291    /// - See README for detailed limitations on accuracy.
292    ///
293    /// # Panics
294    /// 
295    /// - A zero as the input for the denominator.
296    /// 
297    /// # Example
298    ///
299    /// ```
300    /// use deterministic_trigonometry::DTrig;
301    ///
302    /// fn main (){
303    ///
304    /// let d_trig = DTrig::initialize();
305    ///
306    /// let arctangent_of_one_half = d_trig.arctangent((500,1000));
307    ///
308    /// println!("The arctangent of 500/1000 radians is {}/{}.", arctangent_of_one_half.0, arctangent_of_one_half.1);
309    ///
310    /// }
311    ///
312    /// ```
313
314    pub fn arctangent(&self, argument_fraction: (i32, i32)) -> (i32, i32) {
315        // Converts the numerator to what it would be out of 1000.
316        let numerator_out_of_1000 = utility::denominator_to_1000(argument_fraction);
317
318        if numerator_out_of_1000 >= -4000 && numerator_out_of_1000 <= 4000 {
319            // Handles from -4 to 4.
320            return (
321                i32::from(self.arctangent_thousandths[(numerator_out_of_1000 + 4000) as usize]),
322                1000,
323            );
324        } else if numerator_out_of_1000 >= -20000 && numerator_out_of_1000 <= 20000 {
325            // Handles from -20 to 20.
326            if (numerator_out_of_1000 % 10).abs() < 5 {
327                return (
328                    i32::from(
329                        self.arctangent_hundredths[(numerator_out_of_1000 / 10 + 2000) as usize]
330                    ),
331                    1000,
332                );
333            } else {
334                if numerator_out_of_1000 > 0 {
335                    return (
336                        i32::from(
337                            self.arctangent_hundredths
338                                [(numerator_out_of_1000 / 10 + 1 + 2000) as usize]
339                        ),
340                        1000,
341                    );
342                } else {
343                    return (
344                        i32::from(
345                            self.arctangent_hundredths
346                                [(numerator_out_of_1000 / 10 - 1 + 2000) as usize]
347                        ),
348                        1000,
349                    );
350                }
351            }
352        } else if numerator_out_of_1000 >= -100000 && numerator_out_of_1000 <= 100000 {
353            // Handles from -100 to 100
354            if (numerator_out_of_1000 % 100).abs() < 50 {
355                return (
356                    i32::from(
357                        self.arctangent_tenths[(numerator_out_of_1000 / 100 + 1000) as usize]
358                    ),
359                    1000,
360                );
361            } else {
362                if numerator_out_of_1000 > 0 {
363                    return (
364                        i32::from(
365                            self.arctangent_tenths
366                                [(numerator_out_of_1000 / 100 + 1 + 1000) as usize]
367                        ),
368                        1000,
369                    );
370                } else {
371                    return (
372                        i32::from(
373                            self.arctangent_tenths
374                                [(numerator_out_of_1000 / 100 - 1 + 1000) as usize]
375                        ),
376                        1000,
377                    );
378                }
379            }
380        } else if numerator_out_of_1000 >= -1000000 && numerator_out_of_1000 <= 1000000 {
381            // Handles from -1000 to 1000.
382            if numerator_out_of_1000 % 1000 < 500 {
383                return (
384                    i32::from(self.arctangent_ones[(numerator_out_of_1000 / 1000 + 1000) as usize]),
385                    1000,
386                );
387            } else {
388                if numerator_out_of_1000 > 0 {
389                    return (
390                        i32::from(
391                            self.arctangent_ones[(numerator_out_of_1000 / 1000 + 1 + 1000) as usize]
392                        ),
393                        1000,
394                    );
395                } else {
396                    return (
397                        i32::from(
398                            self.arctangent_ones[(numerator_out_of_1000 / 1000 - 1 + 1000) as usize]
399                        ),
400                        1000,
401                    );
402                }
403            }
404        } else {
405            // Handles lower than -1000 and higher than 1000.
406            if numerator_out_of_1000 < -1000000 && numerator_out_of_1000 > -3374653 {
407                return (-1570, 1000);
408            } else if numerator_out_of_1000 > 1000000 && numerator_out_of_1000 < 3374653 {
409                return (1570, 1000);
410            } else if numerator_out_of_1000 <= -3374653 {
411                return (-1571, 1000);
412            } else {
413                return (1571, 1000);
414            }
415        }
416    }
417}
418
419#[cfg(test)]
420mod tests {
421    use crate::DTrig;
422
423    #[test]
424    fn test_sine() {
425        let dtrig = DTrig::initialize();
426        let mut result: bool;
427
428        for a in 0..6283 {
429            if ((((a as f64) / 1000.0).sin() * 1000.0).round() as i32) == dtrig.sine((a, 1000)).0 {
430                result = true;
431            } else {
432                result = false;
433            }
434
435            assert_eq!(result, true);
436        }
437
438        for a in -1000000000..1000000001 {
439            if
440                (
441                    ((((a as f64) / 1000.0).sin() * 1000.0).round() as i32) -
442                    dtrig.sine((a, 1000)).0
443                ).abs() <= 1
444            {
445                result = true;
446            } else {
447                result = false;
448            }
449
450            assert_eq!(result, true);
451        }
452    }
453
454    #[test]
455    fn test_cosine() {
456        let dtrig = DTrig::initialize();
457        let mut result: bool;
458
459        for a in 0..6283 {
460            if ((((a as f64) / 1000.0).cos() * 1000.0).round() as i32) == dtrig.cosine((a, 1000)).0 {
461                result = true;
462            } else {
463                result = false;
464            }
465
466            assert_eq!(result, true);
467        }
468
469        for a in -1000000000..1000000001 {
470            if
471                (
472                    ((((a as f64) / 1000.0).cos() * 1000.0).round() as i32) -
473                    dtrig.cosine((a, 1000)).0
474                ).abs() <= 1
475            {
476                result = true;
477            } else {
478                result = false;
479            }
480
481            assert_eq!(result, true);
482        }
483    }
484
485    #[test]
486    fn test_tangent() {
487        let dtrig = DTrig::initialize();
488        let mut result: bool;
489
490        for a in 0..6283 {
491            if
492                ((((a as f64) / 1000.0).tan() * 1000.0).round() as i32) ==
493                dtrig.tangent((a, 1000)).0
494            {
495                result = true;
496            } else {
497                result = false;
498            }
499
500            assert_eq!(result, true);
501        }
502
503        for a in -1000000000..1000000001 {
504            if
505                // Off by no more than .01.
506                ((((a as f64) / 1000.0).tan() * 1000.0).round() as i64) -
507                    (dtrig.tangent((a, 1000)).0 as i64) <= 1 ||
508                // Or off by no more than 2%.
509                (
510                    (((a as f64) / 1000.0).tan() * 1000.0 - (dtrig.tangent((a, 1000)).0 as f64)) /
511                    (((a as f64) / 1000.0).tan() * 1000.0)
512                ).abs() <= 0.02 ||
513                // Or if greater than 10000 off by no more than 10%.
514                (((a as f64) / 1000.0).tan().abs() * 1000.0 > 10000.0 &&
515                    (
516                        (((a as f64) / 1000.0).tan() * 1000.0 -
517                            (dtrig.tangent((a, 1000)).0 as f64)) /
518                        (((a as f64) / 1000.0).tan() * 1000.0)
519                    ).abs() <= 0.1) ||
520                // Or if greater than 100000 just ignore it.
521                (((a as f64) / 1000.0).tan() * 1000.0).abs() > 100000.0
522            {
523                result = true;
524            } else {
525                result = false;
526            }
527
528            assert_eq!(result, true);
529        }
530    }
531
532    #[test]
533    fn test_arcsine() {
534        let dtrig = DTrig::initialize();
535        let mut result: bool;
536
537        for a in -1000..1001 {
538            if
539                ((((a as f64) / 1000.0).asin() * 1000.0).round() as i32) ==
540                dtrig.arcsine((a, 1000)).0
541            {
542                result = true;
543            } else {
544                result = false;
545            }
546
547            assert_eq!(result, true);
548        }
549    }
550
551    #[test]
552    fn test_arccosine() {
553        let dtrig = DTrig::initialize();
554        let mut result: bool;
555
556        for a in -1000..1001 {
557            if
558                ((((a as f64) / 1000.0).acos() * 1000.0).round() as i32) ==
559                dtrig.arccosine((a, 1000)).0
560            {
561                result = true;
562            } else {
563                result = false;
564            }
565
566            assert_eq!(result, true);
567        }
568    }
569
570    #[test]
571    fn test_arctangent() {
572        let dtrig = DTrig::initialize();
573        let mut result: bool;
574
575        for a in -2000..2001 {
576            if
577                ((((a as f64) / 1000.0).atan() * 1000.0).round() as i32) ==
578                dtrig.arctangent((a, 1000)).0
579            {
580                result = true;
581            } else {
582                result = false;
583            }
584
585            assert_eq!(result, true);
586        }
587
588        for a in -10000000..10000001 {
589            if
590                ((((a as f64) / 1000.0).atan() * 1000.0).round() as i32) -
591                    dtrig.arctangent((a, 1000)).0 <= 1
592            {
593                result = true;
594            } else {
595                result = false;
596            }
597
598            assert_eq!(result, true);
599        }
600
601        for a in -10000..10001 {
602            for b in 1..10001 {
603                if
604                    ((((a as f64) / (b as f64)).atan() * 1000.0).round() as i32) -
605                        dtrig.arctangent((a, b)).0 <= 1
606                {
607                    result = true;
608                } else {
609                    result = false;
610                }
611
612                assert_eq!(result, true);
613            }
614        }
615    }
616}