Base: Use std::numeric_limits and std::numbers instead of defines

This commit is contained in:
Benjamin Nauck
2025-03-27 18:59:37 +01:00
parent 81e0b408fa
commit ae686942a7
18 changed files with 97 additions and 140 deletions

View File

@@ -32,7 +32,6 @@
#if defined(FC_OS_LINUX) || defined(FC_OS_CYGWIN) || defined(FC_OS_MACOSX) || defined(FC_OS_BSD)
#include <dirent.h>
#include <unistd.h>
#include <limits.h>
#elif defined(FC_OS_WIN32)
#include <io.h>
#include <Windows.h>

View File

@@ -429,7 +429,7 @@ bool Matrix4D::toAxisAngle(Vector3d& rclBase,
rfAngle = acos(fCos); // in [0,PI]
if (rfAngle > 0.0) {
if (rfAngle < D_PI) {
if (rfAngle < std::numbers::pi) {
rclDir.x = (dMtrx4D[2][1] - dMtrx4D[1][2]);
rclDir.y = (dMtrx4D[0][2] - dMtrx4D[2][0]);
rclDir.z = (dMtrx4D[1][0] - dMtrx4D[0][1]);
@@ -1013,8 +1013,8 @@ std::array<Matrix4D, 4> Matrix4D::decompose() const
residualMatrix = rotationMatrix * residualMatrix;
// To keep signs of the scale factors equal
if (residualMatrix.determinant() < 0) {
rotationMatrix.rotZ(D_PI);
residualMatrix.rotZ(D_PI);
rotationMatrix.rotZ(std::numbers::pi);
residualMatrix.rotZ(std::numbers::pi);
}
rotationMatrix.inverseGauss();
// extract scale

View File

@@ -218,7 +218,7 @@ PyObject* MatrixPy::number_power_handler(PyObject* self, PyObject* other, PyObje
}
if (b < 0) {
if (fabs(a.determinant()) > DBL_EPSILON) {
if (fabs(a.determinant()) > std::numeric_limits<double>::epsilon()) {
a.inverseGauss();
}
else {
@@ -667,7 +667,7 @@ PyObject* MatrixPy::invert()
{
PY_TRY
{
if (fabs(getMatrixPtr()->determinant()) > DBL_EPSILON) {
if (fabs(getMatrixPtr()->determinant()) > std::numeric_limits<double>::epsilon()) {
getMatrixPtr()->inverseGauss();
Py_Return;
}
@@ -682,7 +682,7 @@ PyObject* MatrixPy::inverse()
{
PY_TRY
{
if (fabs(getMatrixPtr()->determinant()) > DBL_EPSILON) {
if (fabs(getMatrixPtr()->determinant()) > std::numeric_limits<double>::epsilon()) {
Base::Matrix4D m = *getMatrixPtr();
m.inverseGauss();
return new MatrixPy(m);

View File

@@ -99,7 +99,8 @@ int PlacementPy::PyInit(PyObject* args, PyObject* /*kwd*/)
&angle)) {
// NOTE: The first parameter defines the translation, the second the rotation axis
// and the last parameter defines the rotation angle in degree.
Base::Rotation rot(static_cast<Base::VectorPy*>(d)->value(), angle / 180.0 * D_PI);
Base::Rotation rot(static_cast<Base::VectorPy*>(d)->value(),
angle / 180.0 * std::numbers::pi);
*getPlacementPtr() = Base::Placement(static_cast<Base::VectorPy*>(o)->value(), rot);
return 0;
}

View File

@@ -36,13 +36,8 @@
#include <cstdio>
#include <cassert>
#include <ctime>
#include <cfloat>
#include <chrono>
#ifdef FC_OS_WIN32
#define _USE_MATH_DEFINES
#endif // FC_OS_WIN32
#include <cmath>
#include <climits>
#include <codecvt>
#ifdef FC_OS_WIN32
@@ -61,7 +56,6 @@
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <limits.h>
#endif
// STL
@@ -69,6 +63,7 @@
#include <string_view>
#include <list>
#include <map>
#include <numbers>
#include <unordered_map>
#include <vector>
#include <set>

View File

@@ -22,9 +22,9 @@
#include "PreCompiled.h"
#ifndef _PreComp_
#define _USE_MATH_DEFINES
#include <cmath>
#include <array>
#include <numbers>
#endif
#include <fmt/format.h>
@@ -443,8 +443,8 @@ const Quantity Quantity::AngSecond(1.0 / 3600.0, Unit(0, 0, 0, 0, 0, 0, 0, 1));
const Quantity
Quantity::Degree(1.0,
Unit(0, 0, 0, 0, 0, 0, 0, 1)); // degree (internal standard angle)
const Quantity Quantity::Radian(180 / M_PI, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // radian
const Quantity Quantity::Gon(360.0 / 400.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // gon
const Quantity Quantity::Radian(180 / std::numbers::pi, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // radian
const Quantity Quantity::Gon(360.0 / 400.0, Unit(0, 0, 0, 0, 0, 0, 0, 1)); // gon
// === Parser & Scanner stuff ===============================================
@@ -568,7 +568,7 @@ Quantity Quantity::parse(const std::string& string)
QuantityParser::yy_scan_string(string.c_str());
QuantityParser::StringBufferCleaner cleaner(my_string_buffer);
// set the global return variables
QuantResult = Quantity(DOUBLE_MIN);
QuantResult = Quantity(std::numeric_limits<double>::min());
// run the parser
QuantityParser::yyparse();

View File

@@ -27,15 +27,6 @@
#include "Unit.h"
#include <string>
// NOLINTBEGIN
#ifndef DOUBLE_MAX
#define DOUBLE_MAX 1.7976931348623157E+308 /* max decimal value of a "double"*/
#endif
#ifndef DOUBLE_MIN
#define DOUBLE_MIN 2.2250738585072014E-308 /* min decimal value of a "double"*/
#endif
// NOLINTEND
namespace Base
{
class UnitsSchema;

View File

@@ -1613,12 +1613,12 @@ YY_RULE_SETUP
case 135:
YY_RULE_SETUP
#line 228 "QuantityParser.l"
{yylval = Quantity(M_PI) ; return NUM;} // constant pi
{yylval = Quantity(std::numbers::pi) ; return NUM;} // constant pi
YY_BREAK
case 136:
YY_RULE_SETUP
#line 229 "QuantityParser.l"
{yylval = Quantity(M_E) ; return NUM;} // constant e
{yylval = Quantity(std::numbers::e) ; return NUM;} // constant e
YY_BREAK
case 137:
YY_RULE_SETUP

View File

@@ -68,12 +68,12 @@
#define YYSTYPE Quantity
#define yyparse Quantity_yyparse
#define yyerror Quantity_yyerror
#ifndef DOUBLE_MAX
# define DOUBLE_MAX 1.7976931348623157E+308 /* max decimal value of a "double"*/
#endif
#ifndef DOUBLE_MIN
# define DOUBLE_MIN 2.2250738585072014E-308 /* min decimal value of a "double"*/
#endif
#line 79 "QuantityParser.c" /* yacc.c:339 */
@@ -1301,7 +1301,7 @@ yyreduce:
{
case 2:
#line 34 "QuantityParser.y" /* yacc.c:1646 */
{ QuantResult = Quantity(DOUBLE_MIN); /* empty input */ }
{ QuantResult = Quantity(std::numeric_limits<double>::min()); /* empty input */ }
#line 1305 "QuantityParser.c" /* yacc.c:1646 */
break;

View File

@@ -225,8 +225,8 @@ CGRP '\,'[0-9][0-9][0-9]
","?{DIGIT}+{EXPO}? { yylval = Quantity(num_change(yytext,',','.'));return NUM; }
"pi" {yylval = Quantity(M_PI) ; return NUM;} // constant pi
"e" {yylval = Quantity(M_E) ; return NUM;} // constant e
"pi" {yylval = Quantity(std::numbers::pi) ; return NUM;} // constant pi
"e" {yylval = Quantity(std::numbers::e) ; return NUM;} // constant e
"acos" return ACOS;
"asin" return ASIN;

View File

@@ -25,12 +25,12 @@
#define YYSTYPE Quantity
#define yyparse Quantity_yyparse
#define yyerror Quantity_yyerror
#ifndef DOUBLE_MAX
# define DOUBLE_MAX 1.7976931348623157E+308 /* max decimal value of a "double"*/
#endif
#ifndef DOUBLE_MIN
# define DOUBLE_MIN 2.2250738585072014E-308 /* min decimal value of a "double"*/
#endif
%}
@@ -49,7 +49,7 @@
%%
input: { QuantResult = Quantity(DOUBLE_MIN); /* empty input */ }
input: { QuantResult = Quantity(std::numeric_limits<double>::min()); /* empty input */ }
| num { QuantResult = $1 ; }
| unit { QuantResult = $1 ; }
| quantity { QuantResult = $1 ; }

View File

@@ -88,7 +88,7 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/)
}
PyErr_Clear(); // set by PyArg_ParseTuple()
double f = DOUBLE_MAX;
double f = std::numeric_limits<double>::max();
if (PyArg_ParseTuple(args, "dO!", &f, &(Base::UnitPy::Type), &object)) {
*self = Quantity(f, *(static_cast<Base::UnitPy*>(object)->getUnitPtr()));
return 0;
@@ -110,7 +110,7 @@ int QuantityPy::PyInit(PyObject* args, PyObject* /*kwd*/)
int i8 = 0;
PyErr_Clear(); // set by PyArg_ParseTuple()
if (PyArg_ParseTuple(args, "|diiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) {
if (f < DOUBLE_MAX) {
if (f < std::numeric_limits<double>::max()) {
*self = Quantity(f,
Unit {static_cast<int8_t>(i1),
static_cast<int8_t>(i2),
@@ -207,7 +207,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args)
}
if (!quant.isValid()) {
double f = DOUBLE_MAX;
double f = std::numeric_limits<double>::max();
int i1 = 0;
int i2 = 0;
int i3 = 0;
@@ -218,7 +218,7 @@ PyObject* QuantityPy::getValueAs(PyObject* args)
int i8 = 0;
PyErr_Clear();
if (PyArg_ParseTuple(args, "d|iiiiiiii", &f, &i1, &i2, &i3, &i4, &i5, &i6, &i7, &i8)) {
if (f < DOUBLE_MAX) {
if (f < std::numeric_limits<double>::max()) {
quant = Quantity(f,
Unit {static_cast<int8_t>(i1),
static_cast<int8_t>(i2),

View File

@@ -245,11 +245,12 @@ void Rotation::setValue(const Matrix4D& m)
void Rotation::setValue(const Vector3d& axis, double fAngle)
{
using std::numbers::pi;
// Taken from <http://de.wikipedia.org/wiki/Quaternionen>
//
// normalization of the angle to be in [0, 2pi[
_angle = fAngle;
double theAngle = fAngle - floor(fAngle / (2.0 * D_PI)) * (2.0 * D_PI);
double theAngle = fAngle - floor(fAngle / (2.0 * pi)) * (2.0 * pi);
this->quat[3] = cos(theAngle / 2.0);
Vector3d norm = axis;
@@ -691,9 +692,9 @@ void Rotation::setYawPitchRoll(double y, double p, double r)
{
// The Euler angles (yaw,pitch,roll) are in XY'Z''-notation
// convert to radians
y = (y / 180.0) * D_PI;
p = (p / 180.0) * D_PI;
r = (r / 180.0) * D_PI;
y = (y / 180.0) * std::numbers::pi;
p = (p / 180.0) * std::numbers::pi;
r = (r / 180.0) * std::numbers::pi;
double c1 = cos(y / 2.0);
double s1 = sin(y / 2.0);
@@ -710,6 +711,8 @@ void Rotation::setYawPitchRoll(double y, double p, double r)
void Rotation::getYawPitchRoll(double& y, double& p, double& r) const
{
using std::numbers::pi;
double q00 = quat[0] * quat[0];
double q11 = quat[1] * quat[1];
double q22 = quat[2] * quat[2];
@@ -722,30 +725,31 @@ void Rotation::getYawPitchRoll(double& y, double& p, double& r) const
double q23 = quat[2] * quat[3];
double qd2 = 2.0 * (q13 - q02);
// Tolerance copied from OCC "gp_Quaternion.cxx"
constexpr double tolerance = 16 * std::numeric_limits<double>::epsilon();
// handle gimbal lock
if (fabs(qd2 - 1.0) <= 16 * DBL_EPSILON) { // Tolerance copied from OCC "gp_Quaternion.cxx"
if (fabs(qd2 - 1.0) <= tolerance) {
// north pole
y = 0.0;
p = D_PI / 2.0;
p = pi / 2.0;
r = 2.0 * atan2(quat[0], quat[3]);
}
else if (fabs(qd2 + 1.0)
<= 16 * DBL_EPSILON) { // Tolerance copied from OCC "gp_Quaternion.cxx"
else if (fabs(qd2 + 1.0) <= tolerance) {
// south pole
y = 0.0;
p = -D_PI / 2.0;
p = -pi / 2.0;
r = 2.0 * atan2(quat[0], quat[3]);
}
else {
y = atan2(2.0 * (q01 + q23), (q00 + q33) - (q11 + q22));
p = qd2 > 1.0 ? D_PI / 2.0 : (qd2 < -1.0 ? -D_PI / 2.0 : asin(qd2));
p = qd2 > 1.0 ? pi / 2.0 : (qd2 < -1.0 ? -pi / 2.0 : asin(qd2));
r = atan2(2.0 * (q12 + q03), (q22 + q33) - (q00 + q11));
}
// convert to degree
y = (y / D_PI) * 180;
p = (p / D_PI) * 180;
r = (r / D_PI) * 180;
y = (y / pi) * 180;
p = (p / pi) * 180;
r = (r / pi) * 180;
}
bool Rotation::isSame(const Rotation& q) const
@@ -978,15 +982,17 @@ void Rotation::setEulerAngles(EulerSequence theOrder,
double theBeta,
double theGamma)
{
using std::numbers::pi;
if (theOrder == Invalid || theOrder >= EulerSequenceLast) {
throw Base::ValueError("invalid euler sequence");
}
EulerSequence_Parameters o = translateEulerSequence(theOrder);
theAlpha *= D_PI / 180.0;
theBeta *= D_PI / 180.0;
theGamma *= D_PI / 180.0;
theAlpha *= pi / 180.0;
theBeta *= pi / 180.0;
theGamma *= pi / 180.0;
double a = theAlpha;
double b = theBeta;
@@ -1048,7 +1054,7 @@ void Rotation::getEulerAngles(EulerSequence theOrder,
EulerSequence_Parameters o = translateEulerSequence(theOrder);
if (o.isTwoAxes) {
double sy = sqrt(M(o.i, o.j) * M(o.i, o.j) + M(o.i, o.k) * M(o.i, o.k));
if (sy > 16 * DBL_EPSILON) {
if (sy > 16 * std::numeric_limits<double>::epsilon()) {
theAlpha = atan2(M(o.i, o.j), M(o.i, o.k));
theGamma = atan2(M(o.j, o.i), -M(o.k, o.i));
}
@@ -1060,7 +1066,7 @@ void Rotation::getEulerAngles(EulerSequence theOrder,
}
else {
double cy = sqrt(M(o.i, o.i) * M(o.i, o.i) + M(o.j, o.i) * M(o.j, o.i));
if (cy > 16 * DBL_EPSILON) {
if (cy > 16 * std::numeric_limits<double>::epsilon()) {
theAlpha = atan2(M(o.k, o.j), M(o.k, o.k));
theGamma = atan2(M(o.j, o.i), M(o.i, o.i));
}
@@ -1081,7 +1087,7 @@ void Rotation::getEulerAngles(EulerSequence theOrder,
theGamma = aFirst;
}
theAlpha *= 180.0 / D_PI;
theBeta *= 180.0 / D_PI;
theGamma *= 180.0 / D_PI;
theAlpha *= 180.0 / std::numbers::pi;
theBeta *= 180.0 / std::numbers::pi;
theGamma *= 180.0 / std::numbers::pi;
}

View File

@@ -28,6 +28,7 @@
#include <FCGlobal.h>
#endif
#include <cmath>
#include <numbers>
#include <ostream>
#include <string>
#include <vector>
@@ -127,20 +128,16 @@ inline T sgn(T t)
return (t > 0) ? T(1) : T(-1);
}
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
template<class T>
inline T toRadians(T d)
{
return static_cast<T>((d * M_PI) / 180.0);
return static_cast<T>((d * std::numbers::pi) / 180.0);
}
template<class T>
inline T toDegrees(T r)
{
return static_cast<T>((r / M_PI) * 180.0);
return static_cast<T>((r / std::numbers::pi) * 180.0);
}
inline float fromPercent(const long value)

View File

@@ -43,7 +43,7 @@ double Vector2d::GetAngle(const Vector2d& vec) const
if ((fDivid < -1e-10) || (fDivid > 1e-10)) {
fNum = (*this * vec) / fDivid;
if (fNum < -1) {
return D_PI;
return std::numbers::pi;
}
if (fNum > 1) {
return 0.0;
@@ -52,7 +52,7 @@ double Vector2d::GetAngle(const Vector2d& vec) const
return acos(fNum);
}
return -FLOAT_MAX; // division by zero
return -std::numeric_limits<double>::max(); // division by zero
}
void Vector2d::ProjectToLine(const Vector2d& point, const Vector2d& line)
@@ -173,13 +173,13 @@ bool Line2d::Intersect(const Line2d& rclLine, Vector2d& rclV) const
m1 = (clV2.y - clV1.y) / (clV2.x - clV1.x);
}
else {
m1 = DOUBLE_MAX;
m1 = std::numeric_limits<double>::max();
}
if (fabs(rclLine.clV2.x - rclLine.clV1.x) > 1e-10) {
m2 = (rclLine.clV2.y - rclLine.clV1.y) / (rclLine.clV2.x - rclLine.clV1.x);
}
else {
m2 = DOUBLE_MAX;
m2 = std::numeric_limits<double>::max();
}
if (m1 == m2) { /****** RETURN ERR (parallel lines) *************/
return false;
@@ -189,11 +189,11 @@ bool Line2d::Intersect(const Line2d& rclLine, Vector2d& rclV) const
b2 = rclLine.clV1.y - m2 * rclLine.clV1.x;
// calc intersection
if (m1 == DOUBLE_MAX) {
if (m1 == std::numeric_limits<double>::max()) {
rclV.x = clV1.x;
rclV.y = m2 * rclV.x + b2;
}
else if (m2 == DOUBLE_MAX) {
else if (m2 == std::numeric_limits<double>::max()) {
rclV.x = rclLine.clV1.x;
rclV.y = m1 * rclV.x + b1;
}

View File

@@ -32,15 +32,6 @@
#include <FCGlobal.h>
#endif
// NOLINTBEGIN
#ifndef DOUBLE_MAX
#define DOUBLE_MAX 1.7976931348623157E+308 /* max decimal value of a "double"*/
#endif
#ifndef DOUBLE_MIN
#define DOUBLE_MIN 2.2250738585072014E-308 /* min decimal value of a "double"*/
#endif
// NOLINTEND
namespace Base
{
@@ -462,8 +453,8 @@ inline bool Line2d::Contains(const Vector2d& rclV) const
inline BoundBox2d::BoundBox2d()
{
MinX = MinY = DOUBLE_MAX;
MaxX = MaxY = -DOUBLE_MAX;
MinX = MinY = std::numeric_limits<double>::max();
MaxX = MaxY = -std::numeric_limits<double>::max();
}
inline BoundBox2d::BoundBox2d(double fX1, double fY1, double fX2, double fY2)
@@ -480,7 +471,8 @@ inline bool BoundBox2d::IsValid() const
inline bool BoundBox2d::IsInfinite() const
{
return MaxX >= DOUBLE_MAX && MaxY >= DOUBLE_MAX && MinX <= -DOUBLE_MAX && MinY <= -DOUBLE_MAX;
constexpr double max = std::numeric_limits<double>::max();
return MaxX >= max && MaxY >= max && MinX <= -max && MinY <= -max;
}
inline bool BoundBox2d::IsEqual(const BoundBox2d& bbox, double tolerance) const
@@ -525,8 +517,8 @@ inline Vector2d BoundBox2d::GetCenter() const
inline void BoundBox2d::SetVoid()
{
MinX = MinY = DOUBLE_MAX;
MaxX = MaxY = -DOUBLE_MAX;
MinX = MinY = std::numeric_limits<double>::max();
MaxX = MaxY = -std::numeric_limits<double>::max();
}
inline void BoundBox2d::Add(const Vector2d& v)

View File

@@ -24,34 +24,9 @@
#ifndef BASE_VECTOR3D_H
#define BASE_VECTOR3D_H
#include <limits>
#include <cmath>
#include <cfloat>
#ifndef F_PI
#define F_PI 3.1415926f
#endif
#ifndef D_PI
#define D_PI 3.141592653589793
#endif
#ifndef FLOAT_MAX
#define FLOAT_MAX 3.402823466E+38F
#endif
#ifndef FLOAT_MIN
#define FLOAT_MIN 1.175494351E-38F
#endif
#ifndef DOUBLE_MAX
#define DOUBLE_MAX 1.7976931348623157E+308 /* max decimal value of a "double"*/
#endif
#ifndef DOUBLE_MIN
#define DOUBLE_MIN 2.2250738585072014E-308 /* min decimal value of a "double"*/
#endif
#include <numbers>
namespace Base
{
@@ -60,21 +35,22 @@ struct float_traits
{
};
// TODO: Remove these specializations and use the default implementation for all types.
template<>
struct float_traits<float>
{
using float_type = float;
[[nodiscard]] static constexpr float_type pi()
[[nodiscard]] static consteval float_type pi()
{
return F_PI;
return std::numbers::pi_v<float_type>;
}
[[nodiscard]] static constexpr float_type epsilon()
[[nodiscard]] static consteval float_type epsilon()
{
return FLT_EPSILON;
return std::numeric_limits<float_type>::epsilon();
}
[[nodiscard]] static constexpr float_type maximum()
[[nodiscard]] static consteval float_type maximum()
{
return FLT_MAX;
return std::numeric_limits<float_type>::max();
}
};
@@ -82,17 +58,17 @@ template<>
struct float_traits<double>
{
using float_type = double;
[[nodiscard]] static constexpr float_type pi()
[[nodiscard]] static consteval float_type pi()
{
return D_PI;
return std::numbers::pi_v<float_type>;
}
[[nodiscard]] static constexpr float_type epsilon()
[[nodiscard]] static consteval float_type epsilon()
{
return DBL_EPSILON;
return std::numeric_limits<float_type>::epsilon();
}
[[nodiscard]] static constexpr float_type maximum()
[[nodiscard]] static consteval float_type maximum()
{
return DBL_MAX;
return std::numeric_limits<float_type>::max();
}
};
@@ -275,7 +251,7 @@ template<class float_type>
float_type x = v1.x - v2.x;
float_type y = v1.y - v2.y;
float_type z = v1.z - v2.z;
return static_cast<float_type>(sqrt((x * x) + (y * y) + (z * z)));
return static_cast<float_type>(std::sqrt((x * x) + (y * y) + (z * z)));
}
/// Returns the squared distance between two points

View File

@@ -39,16 +39,16 @@ TEST(Tools, TestSignum)
TEST(Tools, TestRadian)
{
EXPECT_EQ(Base::toRadians<int>(90), 1);
EXPECT_DOUBLE_EQ(Base::toRadians<double>(180), M_PI);
EXPECT_DOUBLE_EQ(Base::toRadians<double>(90.0), M_PI / 2.0);
EXPECT_DOUBLE_EQ(Base::toRadians<double>(180), std::numbers::pi);
EXPECT_DOUBLE_EQ(Base::toRadians<double>(90.0), std::numbers::pi / 2.0);
EXPECT_DOUBLE_EQ(Base::toRadians<double>(0.0), 0.0);
}
TEST(Tools, TestDegree)
{
EXPECT_EQ(Base::toDegrees<int>(3), 171);
EXPECT_DOUBLE_EQ(Base::toDegrees<double>(M_PI), 180.0);
EXPECT_DOUBLE_EQ(Base::toDegrees<double>(M_PI / 2.0), 90.0);
EXPECT_DOUBLE_EQ(Base::toDegrees<double>(std::numbers::pi), 180.0);
EXPECT_DOUBLE_EQ(Base::toDegrees<double>(std::numbers::pi / 2.0), 90.0);
EXPECT_DOUBLE_EQ(Base::toDegrees<double>(0.0), 0.0);
}