Avoid division by zero
Run cleanly under -fsanitize=float-divide-by-zero.
Change-Id: I99e92a50c60971c9f771774e58dbe65be7c675ec
diff --git a/include/ceres/rotation.h b/include/ceres/rotation.h
index 367a7d0..dc04a16 100644
--- a/include/ceres/rotation.h
+++ b/include/ceres/rotation.h
@@ -1,5 +1,5 @@
// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2023 Google Inc. All rights reserved.
+// Copyright 2025 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
@@ -318,27 +318,28 @@
const T& a0 = angle_axis[0];
const T& a1 = angle_axis[1];
const T& a2 = angle_axis[2];
- const T theta = hypot(a0, a1, a2);
+
+ T k;
// For points not at the origin, the full conversion is numerically stable.
- if (fpclassify(theta) != FP_ZERO) {
+ if (fpclassify(a0) != FP_ZERO || fpclassify(a1) != FP_ZERO ||
+ fpclassify(a2) != FP_ZERO) {
+ const T theta = hypot(a0, a1, a2);
const T half_theta = theta * T(0.5);
- const T k = sin(half_theta) / theta;
+ k = sin(half_theta) / theta;
quaternion[0] = cos(half_theta);
- quaternion[1] = a0 * k;
- quaternion[2] = a1 * k;
- quaternion[3] = a2 * k;
} else {
// At the origin, sqrt() will produce NaN in the derivative since
// the argument is zero. By approximating with a Taylor series,
// and truncating at one term, the value and first derivatives will be
// computed correctly when Jets are used.
- const T k(0.5);
+ k = T(0.5);
quaternion[0] = T(1.0);
- quaternion[1] = a0 * k;
- quaternion[2] = a1 * k;
- quaternion[3] = a2 * k;
}
+
+ quaternion[1] = a0 * k;
+ quaternion[2] = a1 * k;
+ quaternion[3] = a2 * k;
}
template <typename T>
@@ -348,11 +349,14 @@
const T& q1 = quaternion[1];
const T& q2 = quaternion[2];
const T& q3 = quaternion[3];
- const T sin_theta = hypot(q1, q2, q3);
+
+ T k;
// For quaternions representing non-zero rotation, the conversion
// is numerically stable.
- if (fpclassify(sin_theta) != FP_ZERO) {
+ if (fpclassify(q1) != FP_ZERO || fpclassify(q2) != FP_ZERO ||
+ fpclassify(q3) != FP_ZERO) {
+ const T sin_theta = hypot(q1, q2, q3);
const T& cos_theta = quaternion[0];
// If cos_theta is negative, theta is greater than pi/2, which
@@ -368,23 +372,20 @@
// theta - pi = atan(sin(theta - pi), cos(theta - pi))
// = atan(-sin(theta), -cos(theta))
//
- const T two_theta =
- T(2.0) * ((cos_theta < T(0.0)) ? atan2(-sin_theta, -cos_theta)
- : atan2(sin_theta, cos_theta));
- const T k = two_theta / sin_theta;
- angle_axis[0] = q1 * k;
- angle_axis[1] = q2 * k;
- angle_axis[2] = q3 * k;
+ const T sign = copysign(T(1), cos_theta);
+ const T two_theta = T(2.0) * atan2(sign * sin_theta, sign * cos_theta);
+ k = two_theta / sin_theta;
} else {
// For zero rotation, sqrt() will produce NaN in the derivative since
// the argument is zero. By approximating with a Taylor series,
// and truncating at one term, the value and first derivatives will be
// computed correctly when Jets are used.
- const T k(2.0);
- angle_axis[0] = q1 * k;
- angle_axis[1] = q2 * k;
- angle_axis[2] = q3 * k;
+ k = T(2.0);
}
+
+ angle_axis[0] = q1 * k;
+ angle_axis[1] = q2 * k;
+ angle_axis[2] = q3 * k;
}
template <typename T>
diff --git a/internal/ceres/autodiff_manifold_test.cc b/internal/ceres/autodiff_manifold_test.cc
index e691da6..f4b6156 100644
--- a/internal/ceres/autodiff_manifold_test.cc
+++ b/internal/ceres/autodiff_manifold_test.cc
@@ -142,12 +142,10 @@
struct QuaternionFunctor {
template <typename T>
bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
- const T squared_norm_delta =
- delta[0] * delta[0] + delta[1] * delta[1] + delta[2] * delta[2];
-
T q_delta[4];
- if (squared_norm_delta > T(0.0)) {
- T norm_delta = sqrt(squared_norm_delta);
+ if (fpclassify(delta[0]) != FP_ZERO || fpclassify(delta[1]) != FP_ZERO ||
+ fpclassify(delta[2]) != FP_ZERO) {
+ T norm_delta = hypot(delta[0], delta[1], delta[2]);
const T sin_delta_by_delta = sin(norm_delta) / norm_delta;
q_delta[0] = cos(norm_delta);
q_delta[1] = sin_delta_by_delta * delta[0];
@@ -173,10 +171,11 @@
T minus_x[4] = {x[0], -x[1], -x[2], -x[3]};
T ambient_y_minus_x[4];
QuaternionProduct(y, minus_x, ambient_y_minus_x);
- T u_norm = sqrt(ambient_y_minus_x[1] * ambient_y_minus_x[1] +
- ambient_y_minus_x[2] * ambient_y_minus_x[2] +
- ambient_y_minus_x[3] * ambient_y_minus_x[3]);
- if (u_norm > 0.0) {
+ if (fpclassify(ambient_y_minus_x[1]) != FP_ZERO ||
+ fpclassify(ambient_y_minus_x[2]) != FP_ZERO ||
+ fpclassify(ambient_y_minus_x[3]) != FP_ZERO) {
+ T u_norm = hypot(
+ ambient_y_minus_x[1], ambient_y_minus_x[2], ambient_y_minus_x[3]);
T theta = atan2(u_norm, ambient_y_minus_x[0]);
y_minus_x[0] = theta * ambient_y_minus_x[1] / u_norm;
y_minus_x[1] = theta * ambient_y_minus_x[2] / u_norm;
diff --git a/internal/ceres/is_close.cc b/internal/ceres/is_close.cc
index 575918b..c652b68 100644
--- a/internal/ceres/is_close.cc
+++ b/internal/ceres/is_close.cc
@@ -1,5 +1,5 @@
// Ceres Solver - A fast non-linear least squares minimizer
-// Copyright 2023 Google Inc. All rights reserved.
+// Copyright 2025 Google Inc. All rights reserved.
// http://ceres-solver.org/
//
// Redistribution and use in source and binary forms, with or without
@@ -41,18 +41,19 @@
double* absolute_error) {
double local_absolute_error;
double local_relative_error;
- if (!absolute_error) {
+ if (absolute_error == nullptr) {
absolute_error = &local_absolute_error;
}
- if (!relative_error) {
+ if (relative_error == nullptr) {
relative_error = &local_relative_error;
}
*absolute_error = std::fabs(x - y);
- *relative_error = *absolute_error / std::max(std::fabs(x), std::fabs(y));
- if (x == 0 || y == 0) {
+ if (std::fpclassify(x) == FP_ZERO || std::fpclassify(y) == FP_ZERO) {
// If x or y is exactly zero, then relative difference doesn't have any
// meaning. Take the absolute difference instead.
*relative_error = *absolute_error;
+ } else {
+ *relative_error = *absolute_error / std::max(std::fabs(x), std::fabs(y));
}
return *relative_error < std::fabs(relative_precision);
}
diff --git a/internal/ceres/jet_test.cc b/internal/ceres/jet_test.cc
index a9de40f..67e5580 100644
--- a/internal/ceres/jet_test.cc
+++ b/internal/ceres/jet_test.cc
@@ -103,13 +103,15 @@
Fenv env; // Do not leak floating-point exceptions to the caller
double absolute_difference = std::abs(x - y);
- double relative_difference =
- absolute_difference / std::max(std::abs(x), std::abs(y));
+ double relative_difference;
if (std::fpclassify(x) == FP_ZERO || std::fpclassify(y) == FP_ZERO) {
// If x or y is exactly zero, then relative difference doesn't have any
// meaning. Take the absolute difference instead.
relative_difference = absolute_difference;
+ } else {
+ relative_difference =
+ absolute_difference / std::max(std::abs(x), std::abs(y));
}
return std::islessequal(relative_difference, max_abs_relative_difference);
}
diff --git a/internal/ceres/polynomial.cc b/internal/ceres/polynomial.cc
index db3a404..a0596c6 100644
--- a/internal/ceres/polynomial.cc
+++ b/internal/ceres/polynomial.cc
@@ -70,26 +70,30 @@
scaling_has_changed = false;
for (int i = 0; i < degree; ++i) {
- const double row_norm = companion_matrix_offdiagonal.row(i).lpNorm<1>();
const double col_norm = companion_matrix_offdiagonal.col(i).lpNorm<1>();
- // Decompose row_norm/col_norm into mantissa * 2^exponent,
- // where 0.5 <= mantissa < 1. Discard mantissa (return value
- // of frexp), as only the exponent is needed.
- int exponent = 0;
- std::frexp(row_norm / col_norm, &exponent);
- exponent /= 2;
+ // Avoid division by zero
+ if (std::fpclassify(col_norm) != FP_ZERO) {
+ const double row_norm = companion_matrix_offdiagonal.row(i).lpNorm<1>();
+ // Decompose row_norm/col_norm into mantissa * 2^exponent,
+ // where 0.5 <= mantissa < 1. Discard mantissa (return value
+ // of frexp), as only the exponent is needed.
+ int exponent = 0;
+ std::frexp(row_norm / col_norm, &exponent);
+ exponent /= 2;
- if (exponent != 0) {
- const double scaled_col_norm = std::ldexp(col_norm, exponent);
- const double scaled_row_norm = std::ldexp(row_norm, -exponent);
- if (scaled_col_norm + scaled_row_norm < gamma * (col_norm + row_norm)) {
- // Accept the new scaling. (Multiplication by powers of 2 should not
- // introduce rounding errors (ignoring non-normalized numbers and
- // over- or underflow))
- scaling_has_changed = true;
- companion_matrix_offdiagonal.row(i) *= std::ldexp(1.0, -exponent);
- companion_matrix_offdiagonal.col(i) *= std::ldexp(1.0, exponent);
+ if (exponent != 0) {
+ const double scaled_col_norm = std::ldexp(col_norm, exponent);
+ const double scaled_row_norm = std::ldexp(row_norm, -exponent);
+ if (scaled_col_norm + scaled_row_norm <
+ gamma * (col_norm + row_norm)) {
+ // Accept the new scaling. (Multiplication by powers of 2 should not
+ // introduce rounding errors (ignoring non-normalized numbers and
+ // over- or underflow))
+ scaling_has_changed = true;
+ companion_matrix_offdiagonal.row(i) *= std::ldexp(1.0, -exponent);
+ companion_matrix_offdiagonal.col(i) *= std::ldexp(1.0, exponent);
+ }
}
}
}
diff --git a/internal/ceres/test_util.cc b/internal/ceres/test_util.cc
index fc73764..9ae661e 100644
--- a/internal/ceres/test_util.cc
+++ b/internal/ceres/test_util.cc
@@ -64,11 +64,14 @@
}
double absolute_difference = fabs(x - y);
- double relative_difference = absolute_difference / std::max(fabs(x), fabs(y));
- if (x == 0 || y == 0) {
+ double relative_difference;
+ if (std::fpclassify(x) == FP_ZERO || std::fpclassify(y) == FP_ZERO) {
// If x or y is exactly zero, then relative difference doesn't have any
// meaning. Take the absolute difference instead.
relative_difference = absolute_difference;
+ } else {
+ relative_difference =
+ absolute_difference / std::max(std::fabs(x), std::fabs(y));
}
if (relative_difference > max_abs_relative_difference) {
VLOG(1) << absl::StrFormat("x=%17g y=%17g abs=%17g rel=%17g",