# angle-sc

[![crates.io](https://img.shields.io/crates/v/angle-sc.svg)](https://crates.io/crates/angle-sc)
[![docs.io](https://docs.rs/angle-sc/badge.svg)](https://docs.rs/angle-sc/)
[![License](https://img.shields.io/badge/License-MIT-blue)](https://opensource.org/license/mit/)
[![Rust](https://github.com/kenba/angle-sc-rs/actions/workflows/rust.yml/badge.svg)](https://github.com/kenba/angle-sc-rs/actions)
[![codecov](https://codecov.io/gh/kenba/angle-sc-rs/graph/badge.svg?token=6DTOY9Y4BT)](https://codecov.io/gh/kenba/angle-sc-rs)

A Rust library for performing accurate and efficient trigonometry calculations.

## Description

The standard trigonometry functions: `sin`, `cos`, `tan`, etc.
[give unexpected results for well-known angles](https://stackoverflow.com/questions/31502120/sin-and-cos-give-unexpected-results-for-well-known-angles#answer-31525208).
This is due to floating-point [round-off errors](https://en.wikipedia.org/wiki/Round-off_error)
and the functions taking parameters in `radians` instead of `degrees`.
The conversion from `degrees` to `radians` (and vice-versa) suffers from `round-off error`
because `radians` is based on the irrational number π.

This library uses the [remquo](https://en.cppreference.com/w/cpp/numeric/math/remquo)
function to provide a [sincos](src/trig.rs#sincos) function to calculate more
accurate values than the standard `sin` and `cos` functions for angles in `radians`
and a [sincosd](src/trig.rs#sincosd) function to calculate more accurate values
for angles in `degrees`.

The library also provides an [Angle](#angle) struct which represents an angle
by its sine and cosine as the coordinates of a
[unit circle](https://en.wikipedia.org/wiki/Unit_circle), see *Figure 1*.

![Unit circle](https://upload.wikimedia.org/wikipedia/commons/thumb/7/72/Sinus_und_Kosinus_am_Einheitskreis_1.svg/250px-Sinus_und_Kosinus_am_Einheitskreis_1.svg.png)
*Figure 1 Unit circle formed by cos *θ* and sin *θ**

The `Angle` struct enables more accurate calculations of angle rotations and
conversions to and from `degrees` or `radians`.

## Features

* `Degrees`, `Radians` and `Angle` types;
* functions for accurately calculating sines and cosines of angles in `Degrees` or `Radians`
using [remquo](https://pubs.opengroup.org/onlinepubs/9699919799/functions/remquo.html);
* functions for accurately calculating sines and cosines of differences of angles in `Degrees` or `Radians`
using the [2Sum](https://en.wikipedia.org/wiki/2Sum) algorithm;
* functions for accurately calculating sums and differences of `Angles` using
[trigonometric identities](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities);
* and some [spherical trigonometry](https://en.wikipedia.org/wiki/Spherical_trigonometry) functions.
* The library is declared [no_std](https://docs.rust-embedded.org/book/intro/no-std.html).

## Examples

The following example shows the `round-off error` inherent in calculating angles in `radians`.
It calculates the correct sine and cosine for 60° and converts them back
precisely to 60°, but it fails to convert them to the precise angle in `radians`: π/3.

```rust
use angle_sc::{Angle, Degrees, Radians, is_within_tolerance, trig};

let angle_60 = Angle::from(Degrees(60.0));
assert_eq!(trig::COS_30_DEGREES, angle_60.sin().0);
assert_eq!(0.5, angle_60.cos().0);
assert_eq!(60.0, Degrees::from(angle_60).0);

// assert_eq!(core::f64::consts::FRAC_PI_3, Radians::from(angle_60).0); // Fails because PI is irrational
assert!(is_within_tolerance(
   core::f64::consts::FRAC_PI_3,
   Radians::from(angle_60).0,
   f64::EPSILON
));
```

The following example calculates the sine and cosine between the difference
of two angles in `degrees`: -155° - 175°.
It is more accurate than calling the `Angle` `From` trait in the example above
with the difference in `degrees`.
It is particularly useful for implementing the
[Haversine formula](https://en.wikipedia.org/wiki/Haversine_formula)
which requires sines and cosines of both longitude and latitude differences.
Note: in this example sine and cosine of 30° are converted precisely to π/6.

```rust
use angle_sc::{Angle, Degrees, Radians, trig};

// Difference of Degrees(-155.0) - Degrees(175.0)
let angle_30 = Angle::from((Degrees(-155.0), Degrees(175.0)));
assert_eq!(0.5, angle_30.sin().0);
assert_eq!(trig::COS_30_DEGREES, angle_30.cos().0);
assert_eq!(30.0, Degrees::from(angle_30).0);
assert_eq!(core::f64::consts::FRAC_PI_6, Radians::from(angle_30).0);
```

## Design

### Trigonometry Functions

The [trig](src/trig.rs) module contains accurate and efficient trigonometry functions.

### Angle

The `Angle` struct represents an angle by its sine and cosine instead of in
`degrees` or `radians`, see *Figure 2*.

![Angle Class Diagram](docs/images/angle_class_diagram.svg)  
*Figure 2 Angle Class Diagram*

This representation an angle makes functions such as
rotating an angle +/-90° around the unit circle or calculating the opposite angle;
simple, accurate and efficient since they just involve changing the signs
and/or positions of the `sin` and `cos` values.

`Angle` `Add` and `Sub` traits are implemented using
[angle sum and difference](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Angle_sum_and_difference_identities)
trigonometric identities,
while `Angle` [double](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Double-angle_formulae)
and [half](https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Half-angle_formulae) methods use other
trigonometric identities.

The `sin` and `cos` fields of `Angle` are `UnitNegRange`s:,
a [newtype](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
with values in the range -1.0 to +1.0 inclusive.

## Contribution

If you want to contribute through code or documentation, the [Contributing](CONTRIBUTING.md) guide is the best place to start. If you have any questions, please feel free to ask.
Just please abide by our [Code of Conduct](CODE_OF_CONDUCT.md).

## License

`angle-rs` is provided under a MIT license, see [LICENSE](LICENSE).
