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}