Partially revert hypot arguments zero checks
Unfortunately, libc++'s 3-argument std::hypot implementation is
numerically unstable until LLVM 19.x. Therefore, checking the arguments
for zeros is insufficient since an underflow can still occur resulting
in a zero norm which requires another check. As such, division by zero
cannot be reliably avoided.
Change-Id: I189c8dc722aaec1ebc3ec8b1a177e1d8ac3b36db
diff --git a/include/ceres/rotation.h b/include/ceres/rotation.h
index dc04a16..261d3a0 100644
--- a/include/ceres/rotation.h
+++ b/include/ceres/rotation.h
@@ -320,11 +320,10 @@
const T& a2 = angle_axis[2];
T k;
+ const T theta = hypot(a0, a1, a2);
// For points not at the origin, the full conversion is numerically stable.
- if (fpclassify(a0) != FP_ZERO || fpclassify(a1) != FP_ZERO ||
- fpclassify(a2) != FP_ZERO) {
- const T theta = hypot(a0, a1, a2);
+ if (fpclassify(theta) != FP_ZERO) {
const T half_theta = theta * T(0.5);
k = sin(half_theta) / theta;
quaternion[0] = cos(half_theta);
@@ -351,12 +350,11 @@
const T& q3 = quaternion[3];
T k;
+ const T sin_theta = hypot(q1, q2, q3);
// For quaternions representing non-zero rotation, the conversion
// is numerically stable.
- if (fpclassify(q1) != FP_ZERO || fpclassify(q2) != FP_ZERO ||
- fpclassify(q3) != FP_ZERO) {
- const T sin_theta = hypot(q1, q2, q3);
+ if (fpclassify(sin_theta) != FP_ZERO) {
const T& cos_theta = quaternion[0];
// If cos_theta is negative, theta is greater than pi/2, which
diff --git a/internal/ceres/autodiff_manifold_test.cc b/internal/ceres/autodiff_manifold_test.cc
index f4b6156..c7a473d 100644
--- a/internal/ceres/autodiff_manifold_test.cc
+++ b/internal/ceres/autodiff_manifold_test.cc
@@ -143,9 +143,8 @@
template <typename T>
bool Plus(const T* x, const T* delta, T* x_plus_delta) const {
T q_delta[4];
- 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]);
+ T norm_delta = hypot(delta[0], delta[1], delta[2]);
+ if (fpclassify(norm_delta) != FP_ZERO) {
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];
@@ -171,11 +170,9 @@
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);
- 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 u_norm =
+ hypot(ambient_y_minus_x[1], ambient_y_minus_x[2], ambient_y_minus_x[3]);
+ if (fpclassify(u_norm) != FP_ZERO) {
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;