A C++ library for performing accurate and efficient trigonometry calculations.
The standard trigonometry functions: sin, cos, tan, etc.
give unexpected results for well-known angles.
This is due to floating-point round-off errors
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
function to provide a sincos function to calculate more
accurate values than the standard sin and cos functions for angles in radians
and a sincosd function to calculate more accurate values
for angles in degrees.
The library also provides an Angle class which represents an angle by its sine and cosine as the coordinates of a unit circle, see Figure 1.
Figure 1 Unit circle formed by cos θ and sin θ
The Angle class enables more accurate calculations of angle rotations and
conversions to and from degrees or radians.
Degrees,RadiansandAngletypes;- functions for accurately calculating sines and cosines of angles in
DegreesorRadiansusing remquo; - functions for accurately calculating sines and cosines of differences of angles in
DegreesorRadiansusing the 2Sum algorithm; - functions for accurately calculating sums and differences of
Anglesusing trigonometric identities; - and some spherical trigonometry functions.
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.
#include "via/angle.hpp"
#include <boost/test/unit_test.hpp>
using namespace via;
namespace {
constexpr auto EPSILON{std::numeric_limits<double>::epsilon()};
constexpr auto CALCULATION_TOLERANCE{101 * EPSILON};
constexpr auto PI_3{ trig::PI_3 <double> };
constexpr auto COS_30_DEGREES{ COS_30_DEGREES <double> };
} // namespace
BOOST_AUTO_TEST_SUITE(Test_angle)
BOOST_AUTO_TEST_CASE(test_Angle_conversion) {
const Angle angle_60(Degrees(60.0));
BOOST_CHECK(angle_60.is_valid());
BOOST_CHECK_EQUAL(COS_30_DEGREES, angle_60.sin().v());
BOOST_CHECK_EQUAL(0.5, angle_60.cos().v());
BOOST_CHECK_EQUAL(Degrees(60.0), angle_60.to_degrees());
// Fails because PI is irrational
// BOOST_CHECK_EQUAL(Radians(PI_3), angle_60.to_radians());
BOOST_CHECK_CLOSE(PI_3, angle_60.to_radians().v(),
CALCULATION_TOLERANCE);
}
BOOST_AUTO_TEST_SUITE_END()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 constructor in the example above
with the difference in degrees.
It is particularly useful for implementing the
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.
#include "via/angle.hpp"
#include <boost/test/unit_test.hpp>
using namespace via;
namespace {
constexpr auto EPSILON{std::numeric_limits<double>::epsilon()};
constexpr auto CALCULATION_TOLERANCE{101 * EPSILON};
constexpr auto PI_6{ trig::PI_6 <double> };
constexpr auto COS_30_DEGREES{ COS_30_DEGREES <double> };
} // namespace
BOOST_AUTO_TEST_SUITE(Test_angle_difference)
BOOST_AUTO_TEST_CASE(test_Angle_difference_conversion) {
const Angle angle_d30(Degrees(-155.0), Degrees(175.0));
BOOST_CHECK(angle_d30.is_valid());
BOOST_CHECK_EQUAL(0.5, angle_d30.sin().v());
BOOST_CHECK_EQUAL(COS_30_DEGREES, angle_d30.cos().v());
BOOST_CHECK_EQUAL(Degrees(30.0), angle_d30.to_degrees());
BOOST_CHECK_EQUAL(Radians(PI_6), angle_d30.to_radians());
}
BOOST_AUTO_TEST_SUITE_END()The trig namespace contains accurate and efficient trigonometry functions.
The Angle struct represents an angle by its sine and cosine instead of in
degrees or radians, see Figure 2.
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
trigonometric identities,
while Angle double
and half methods use other
trigonometric identities.
The sin and cos fields of Angle are UnitNegRanges:,
a newtype
with values in the range -1.0 to +1.0 inclusive.
The C++ software depends on the Microsoft GSL library
to provide Contracts support.
The C++ tests use the boost.test
library, see Figure 3.
Figure 3 Angle Software Dependencies
Note: Python bindings do not require boost, just GSL.
The library is header only, so the library include directory just needs to be added to the include path.
Alternatively, when using cmake the environment variable ViaAngle_DIR just needs
to be set to the location of the via-angle-cpp directory; cmake will add it to the include path.
Note: CMakeLists.txt is setup to install python by default, so -DINSTALL_PYTHON=OFF
must be passed to cmake when building for C++.
cmake can also be used to install the library to the relevant include directory on Linux/macOS.
In the via-angle-cpp directory, run:
cmake -DINSTALL_PYTHON=OFF .
sudo make installNote: it will be necessary to delete the CMakeCache.txt file created by
running cmake above, before running cmake on this library again.
The C++ tests can be built and run using cmake by running:
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -DINSTALL_PYTHON=OFF -DCPP_UNIT_TESTS=ON <via-angle-cpp directory>
make
make testNote: -DCMAKE_EXPORT_COMPILE_COMMANDS=1 creates a compile_commands.json
file which can be copied back into the via-angle-cpp directory for
clangd tools.
The library uses pybind11 to provide C++ Python bindings and scikit-build to build a python package using cmake.
From the parent directory of via-angle-cpp, run:
pip install ./via-angle-cppIn Python code import the software as via_angle, e.g.:
from via_angle import Angle, Degrees, Radians See: test_Angle.py.
via-angle-cpp is provided under a MIT license, see LICENSE.