#![forbid(unsafe_code)]
use crate::rounding::two_sum_error;
use crate::{
ArithmeticOperation, LaError, NonFiniteLocation, NonFiniteOrigin, Vector, VectorOperand,
};
pub trait VectorAngle {
fn angle(&self, other: &Self) -> Result<f64, LaError>;
}
impl VectorAngle for [f64] {
#[inline]
fn angle(&self, other: &Self) -> Result<f64, LaError> {
if self.len() != other.len() {
return Err(LaError::DimensionMismatch {
left: self.len(),
right: other.len(),
});
}
let left_scale = input_scale(self, VectorOperand::Left)?;
let right_scale = input_scale(other, VectorOperand::Right)?;
angle_with_scales(self, other, left_scale, right_scale)
}
}
fn input_scale(values: &[f64], operand: VectorOperand) -> Result<f64, LaError> {
let mut scale = 0.0_f64;
for (index, &value) in values.iter().enumerate() {
if !value.is_finite() {
return Err(LaError::NonFinite {
location: NonFiniteLocation::VectorOperandEntry { operand, index },
origin: NonFiniteOrigin::Input,
});
}
scale = scale.max(value.abs());
}
Ok(scale)
}
#[inline]
pub(crate) fn angle_finite<const D: usize>(
left: &Vector<D>,
right: &Vector<D>,
) -> Result<f64, LaError> {
let left = left.as_array();
let right = right.as_array();
let maximum = |values: &[f64; D]| values.iter().fold(0.0_f64, |s, x| s.max(x.abs()));
angle_with_scales(left, right, maximum(left), maximum(right))
}
struct DirectionScale {
first: f64,
second: f64,
}
impl DirectionScale {
fn new(mut maximum: f64) -> Self {
let adjustment = if maximum < f64::MIN_POSITIVE {
maximum *= f64::from_bits((1023 + 52) << 52);
52
} else {
0
};
let bits = maximum.to_bits();
let exponent = (bits >> 52).cast_signed() - 1023 - adjustment;
let shift = 500 - exponent;
let first_shift = shift.min(1023);
Self {
first: f64::from_bits((first_shift + 1023).cast_unsigned() << 52),
second: f64::from_bits((shift - first_shift + 1023).cast_unsigned() << 52),
}
}
#[inline]
fn apply(&self, value: f64) -> f64 {
(value * self.first) * self.second
}
}
#[inline]
fn difference_of_products(a: f64, b: f64, c: f64, d: f64) -> f64 {
let left = a * b;
let right = c * d;
let left_error = a.mul_add(b, -left);
let right_error = c.mul_add(d, -right);
let difference = left - right;
let difference_error = two_sum_error(left, -right, difference);
difference + ((left_error - right_error) + difference_error)
}
fn angle_with_scales(
left: &[f64],
right: &[f64],
left_scale: f64,
right_scale: f64,
) -> Result<f64, LaError> {
const REDUCE: f64 = f64::from_bits((1023 - 500) << 52);
const SMALL_RATIO: f64 = f64::from_bits((1023 - 27) << 52);
if left.is_empty() {
return Err(LaError::EmptyVector);
}
for (scale, operand) in [
(left_scale, VectorOperand::Left),
(right_scale, VectorOperand::Right),
] {
if scale == 0.0 {
return Err(LaError::ZeroVector { operand });
}
}
let left_scale = DirectionScale::new(left_scale);
let right_scale = DirectionScale::new(right_scale);
let mut dot = 0.0_f64;
let mut dot_error = 0.0_f64;
let mut exterior_norm = 0.0_f64;
for (i, (&left_i, &right_i)) in left.iter().zip(right).enumerate() {
let left_i = left_scale.apply(left_i);
let right_i = right_scale.apply(right_i);
let product = (left_i * right_i) * REDUCE;
let next_dot = dot + product;
dot_error += two_sum_error(dot, product, next_dot);
dot = next_dot;
for (&left_j, &right_j) in left[i + 1..].iter().zip(&right[i + 1..]) {
let left_j = left_scale.apply(left_j);
let right_j = right_scale.apply(right_j);
let minor = difference_of_products(left_i, right_j, left_j, right_i);
exterior_norm = exterior_norm.hypot(minor * REDUCE);
}
}
let dot = dot + dot_error;
let angle = if dot > 0.0 && exterior_norm <= dot * SMALL_RATIO {
exterior_norm / dot
} else {
exterior_norm.atan2(dot)
};
if angle.is_finite() {
Ok(angle)
} else {
Err(LaError::non_finite_computation_scalar(
ArithmeticOperation::VectorAngle,
))
}
}