This project provides a C++ implementation of a udouble class for handling values with uncertainties, similar to Python's uncertainties package. The udouble class supports automatic error propagation with correlation tracking, so expressions like x - x correctly yield 0 ± 0.
- Correlation tracking: Variables are tracked through expressions so that
x - x = 0 ± 0andx + x = 2x ± 2σ(notσ√2). - Create objects with nominal values and standard deviations.
- Implicit conversion from
double(zero uncertainty). - Arithmetic operations (
+,-,*,/) with automatic error propagation. - Division by zero returns
0 ± infinstead of throwing (see Division by Zero). - Unary operators (
+,-). - Compound assignment operators (
+=,-=,*=,/=). - Comparison operators (
==,!=,<,>,<=,>=) based on nominal values. - Mixed-type operations between
udoubleanddouble. - Exponentiation with
pow(). - Mathematical functions:
- Trigonometric:
sin(),cos(),tan() - Inverse trigonometric:
asin(),acos(),atan(),atan2() - Hyperbolic:
sinh(),cosh(),tanh() - Inverse hyperbolic:
asinh(),acosh(),atanh() - Exponential/logarithmic:
exp(),log(),log10(),sqrt() - Other:
abs(),hypot()
- Trigonometric:
- Multiple output formats: default, scientific notation, compact notation.
- Eigen matrix library integration (optional).
- Includes unit tests and examples.
- C++17 or later
- CMake 3.10 or later
- Optional: Google Test (GTest) for unit testing.
-
Clone the repository:
git clone <repository_url> cd uncertainties-cpp
-
Create a build directory and configure the project:
mkdir build && cd build cmake ..
-
Build the project:
cmake --build . -
Optionally, install the library:
cmake --build . --target install
-
Ensure tests are enabled in the build configuration:
cmake -DUNCERTAINTIES_BUILD_TESTS=ON ..
-
Build and run tests:
cmake --build . ctest --verbose
Alternatively, run tests automatically during the build process by running:
cmake --build . --target run_testsHere’s an example of how to use the udouble class:
#include <iostream>
#include "uncertainties/udouble.hpp"
int main() {
uncertainties::udouble a(1.0, 0.1); // 1.0 ± 0.1
uncertainties::udouble b(2.0, 0.2); // 2.0 ± 0.2
uncertainties::udouble c = a + b; // 3.0 ± sqrt(0.1^2 + 0.2^2)
std::cout << "c = " << c << std::endl; // Output: 3.0 ± 0.223606
return 0;
}The library tracks correlations between variables automatically. When the same variable appears multiple times in an expression, the uncertainties are correctly correlated:
#include <iostream>
#include "uncertainties/udouble.hpp"
#include "uncertainties/umath.hpp"
int main() {
uncertainties::udouble x(10.0, 0.5);
uncertainties::udouble y(20.0, 1.0);
// Correlated: x - x = 0 with zero uncertainty
uncertainties::udouble zero = x - x;
std::cout << "x - x = " << zero << std::endl; // 0 ± 0
// Correlated: x + x = 2x with doubled uncertainty
uncertainties::udouble doubled = x + x;
std::cout << "x + x = " << doubled << std::endl; // 20 ± 1
// Mixed: (x + y) - x = y (x cancels out)
uncertainties::udouble result = (x + y) - x;
std::cout << "(x + y) - x = " << result << std::endl; // 20 ± 1
// Math functions preserve correlations
uncertainties::udouble s = uncertainties::sin(x);
std::cout << "sin(x) - sin(x) = " << (s - s) << std::endl; // 0 ± 0
// Trig identity: sin²(x) + cos²(x) = 1 with zero uncertainty
uncertainties::udouble x2(0.5, 0.1);
uncertainties::udouble identity =
uncertainties::sin(x2) * uncertainties::sin(x2) +
uncertainties::cos(x2) * uncertainties::cos(x2);
std::cout << "sin²+cos² = " << identity << std::endl; // 1 ± 0
// Create an independent copy (different variable, same value/stddev)
uncertainties::udouble x_copy = x.independent_copy();
std::cout << "x - x_copy = " << (x - x_copy) << std::endl; // 0 ± 0.707
return 0;
}The library provides mathematical functions with automatic error propagation:
#include <iostream>
#include "uncertainties/udouble.hpp"
#include "uncertainties/umath.hpp"
int main() {
uncertainties::udouble x(1.0, 0.1);
// Trigonometric functions
uncertainties::udouble s = uncertainties::sin(x);
uncertainties::udouble c = uncertainties::cos(x);
uncertainties::udouble t = uncertainties::tan(x);
// Inverse trigonometric functions
uncertainties::udouble as = uncertainties::asin(uncertainties::udouble(0.5, 0.01));
uncertainties::udouble at = uncertainties::atan(x);
// Hyperbolic functions
uncertainties::udouble sh = uncertainties::sinh(x);
uncertainties::udouble ch = uncertainties::cosh(x);
// Exponential and logarithmic functions
uncertainties::udouble e = uncertainties::exp(x);
uncertainties::udouble l = uncertainties::log(x);
uncertainties::udouble sq = uncertainties::sqrt(uncertainties::udouble(4.0, 0.1));
// Exponentiation
uncertainties::udouble p = uncertainties::pow(x, uncertainties::udouble(2.0, 0.0));
std::cout << "sin(x) = " << s << std::endl;
std::cout << "sqrt(4) = " << sq << std::endl;
return 0;
}The library supports various operators for convenient calculations:
#include <iostream>
#include "uncertainties/udouble.hpp"
int main() {
uncertainties::udouble a(10.0, 0.5);
uncertainties::udouble b(3.0, 0.2);
// Unary operators
uncertainties::udouble neg = -a; // -10.0 ± 0.5
// Compound assignment
uncertainties::udouble c = a;
c += b; // 13.0 ± sqrt(0.5² + 0.2²)
c *= 2.0; // Scale by constant
// Comparison (based on nominal values)
if (a > b) {
std::cout << "a is greater than b" << std::endl;
}
std::cout << "c = " << c << std::endl;
return 0;
}Dividing by zero does not throw. The quotient is unconstrained, so it is
reported as 0 ± inf — a nominal value of zero carrying an infinite
uncertainty:
#include <iostream>
#include "uncertainties/udouble.hpp"
int main() {
uncertainties::udouble a(1.0, 0.1);
uncertainties::udouble zero(0.0, 0.1);
std::cout << a / zero << std::endl; // 0 ± inf
std::cout << a / 0.0 << std::endl; // 0 ± inf
std::cout << 1.0 / zero << std::endl; // 0 ± inf
// The infinite uncertainty keeps propagating:
uncertainties::udouble b = (a / zero) + uncertainties::udouble(5.0, 0.2);
std::cout << b << std::endl; // 5 ± inf
return 0;
}This applies to every division form — udouble / udouble, udouble / double,
double / udouble, and /= — and triggers whenever the divisor's nominal
value is zero, regardless of its uncertainty.
Each division by zero allocates a fresh uncertainty source, so two of them are
treated as independent undefined quantities: (a / zero) - (a / zero) remains
0 ± inf rather than cancelling to 0 ± 0.
Note: This deliberately differs from the Python
uncertaintiespackage, which raisesZeroDivisionErroron division by zero.
Plain double values are automatically converted to udouble with zero uncertainty:
#include <iostream>
#include "uncertainties/udouble.hpp"
int main() {
uncertainties::udouble a(5.0, 0.1);
// Implicit conversion from double
uncertainties::udouble b = 3.0; // 3.0 ± 0.0
// Works in arithmetic too
uncertainties::udouble c = a + 2.0; // Adds constant with no additional uncertainty
// Can pass doubles to functions expecting udouble
auto square = [](const uncertainties::udouble& x) { return x * x; };
uncertainties::udouble d = square(4.0); // 16.0 ± 0.0
return 0;
}The library provides multiple ways to format output:
#include <iostream>
#include "uncertainties/udouble.hpp"
int main() {
uncertainties::udouble x(1.23456, 0.00789);
// Default stream output
std::cout << x << std::endl; // 1.23456 ± 0.00789
// Custom precision
std::cout << x.to_string(3) << std::endl; // 1.23 ± 0.00789
// Scientific notation
std::cout << x.to_scientific(2) << std::endl; // 1.23e+00 ± 7.89e-03
// Compact notation (uncertainty in parentheses)
std::cout << x.to_compact() << std::endl; // 1.235(79)
return 0;
}The library integrates with the Eigen matrix library for uncertainty propagation through matrix operations:
#include <Eigen/Dense>
#include "uncertainties/eigen_support.hpp"
int main() {
// Type aliases for convenience
using Matrix2u = Eigen::Matrix<uncertainties::udouble, 2, 2>;
using Vector2u = Eigen::Vector<uncertainties::udouble, 2>;
// Create a matrix with uncertainties
Matrix2u A;
A << uncertainties::udouble(1.0, 0.1), uncertainties::udouble(2.0, 0.2),
uncertainties::udouble(3.0, 0.3), uncertainties::udouble(4.0, 0.4);
// Create a vector with uncertainties
Vector2u b;
b << uncertainties::udouble(5.0, 0.5), uncertainties::udouble(6.0, 0.6);
// Matrix-vector multiplication propagates uncertainties
Vector2u result = A * b;
// Other operations: transpose, dot product, cross product, determinant, etc.
Matrix2u At = A.transpose();
uncertainties::udouble det = A.determinant();
return 0;
}To enable Eigen support, ensure Eigen3 is installed and detected by CMake. The eigen_support.hpp header provides the necessary NumTraits specialization for Eigen to work with udouble.
-
Enable examples in the build configuration:
cmake -DUNCERTAINTIES_BUILD_EXAMPLES=ON ..
-
Build and run the example:
cmake --build . ./example_basic
API documentation can be generated using Doxygen:
-
Install Doxygen (if not already installed):
# Ubuntu/Debian sudo apt-get install doxygen # macOS brew install doxygen
-
Generate documentation:
# From the project root directory doxygen DoxyfileOr using CMake:
cmake -DUNCERTAINTIES_BUILD_DOCS=ON .. cmake --build . --target docs -
Open
docs/html/index.htmlin your browser.
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create a new branch.
- Commit your changes.
- Open a pull request.
This project is licensed under the BSD 3-Clause License. See the LICENSE file for details.
Happy coding!