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;