fuchsia_triage/metrics/
arithmetic.rsuse super::{MathFunction, MetricValue, Problem};
enum PromotedOperands {
Float(Vec<f64>),
Int(Vec<i64>),
}
pub fn calculate(function: &MathFunction, operands: &[MetricValue]) -> MetricValue {
match function {
MathFunction::Min | MathFunction::Max if !operands.is_empty() => {}
MathFunction::Min | MathFunction::Max => {
return super::syntax_error("No operands in math expression");
}
MathFunction::Abs if operands.len() == 1 => {}
MathFunction::Abs => return super::syntax_error("Abs requires exactly one operand."),
_ if operands.len() == 2 => {}
_ => return super::internal_bug("Internal bug. Function needs 2 arguments."),
}
let operands = match promote_type(operands) {
Ok(operands) => operands,
Err(value) => return value,
};
match operands {
PromotedOperands::Float(operands) => MetricValue::Float(match function {
MathFunction::Add => operands[0] + operands[1],
MathFunction::Sub => operands[0] - operands[1],
MathFunction::Mul => operands[0] * operands[1],
MathFunction::FloatDiv | MathFunction::IntDiv if operands[1] == 0.0 => {
return super::value_error("Division by zero")
}
MathFunction::FloatDivChecked | MathFunction::IntDivChecked if operands[1] == 0.0 => {
return MetricValue::Problem(Problem::Ignore(vec![Problem::ValueError(
"Division by zero".to_string(),
)]))
}
MathFunction::FloatDiv | MathFunction::FloatDivChecked => operands[0] / operands[1],
MathFunction::IntDiv | MathFunction::IntDivChecked => {
return match super::safe_float_to_int(operands[0] / operands[1]) {
Some(int) => MetricValue::Int(int),
None => super::value_error("Non-numeric division result"),
}
}
MathFunction::Greater => return MetricValue::Bool(operands[0] > operands[1]),
MathFunction::Less => return MetricValue::Bool(operands[0] < operands[1]),
MathFunction::GreaterEq => return MetricValue::Bool(operands[0] >= operands[1]),
MathFunction::LessEq => return MetricValue::Bool(operands[0] <= operands[1]),
MathFunction::Min => fold(operands, &f64::min),
MathFunction::Max => fold(operands, &f64::max),
MathFunction::Abs => operands[0].abs(),
}),
PromotedOperands::Int(operands) => MetricValue::Int(match function {
MathFunction::Add => operands[0] + operands[1],
MathFunction::Sub => operands[0] - operands[1],
MathFunction::Mul => operands[0] * operands[1],
MathFunction::FloatDiv | MathFunction::IntDiv if operands[1] == 0 => {
return super::value_error("Division by zero")
}
MathFunction::FloatDivChecked | MathFunction::IntDivChecked if operands[1] == 0 => {
return MetricValue::Problem(Problem::Ignore(vec![Problem::ValueError(
"Division by zero".to_string(),
)]))
}
MathFunction::FloatDiv | MathFunction::FloatDivChecked => {
return MetricValue::Float(operands[0] as f64 / operands[1] as f64)
}
MathFunction::IntDiv | MathFunction::IntDivChecked => operands[0] / operands[1],
MathFunction::Greater => return MetricValue::Bool(operands[0] > operands[1]),
MathFunction::Less => return MetricValue::Bool(operands[0] < operands[1]),
MathFunction::GreaterEq => return MetricValue::Bool(operands[0] >= operands[1]),
MathFunction::LessEq => return MetricValue::Bool(operands[0] <= operands[1]),
MathFunction::Min => fold(operands, &i64::min),
MathFunction::Max => fold(operands, &i64::max),
MathFunction::Abs => operands[0].abs(),
}),
}
}
fn fold<T: num_traits::Num + Copy>(operands: Vec<T>, function: &dyn (Fn(T, T) -> T)) -> T {
let mut iter = operands.iter();
let mut result = *iter.next().unwrap(); loop {
match iter.next() {
Some(next) => result = function(result, *next),
None => return result,
}
}
}
fn promote_type(operands: &[MetricValue]) -> Result<PromotedOperands, MetricValue> {
let mut int_vec = Vec::with_capacity(operands.len());
let mut float_vec = Vec::with_capacity(operands.len());
let mut error_vec = Vec::with_capacity(operands.len());
let mut non_numeric_error = None;
for o in operands.iter() {
match super::unwrap_for_math(o) {
MetricValue::Int(value) => {
int_vec.push(*value);
float_vec.push(*value as f64);
}
MetricValue::Float(value) => {
float_vec.push(*value);
}
MetricValue::Problem(problem) => {
error_vec.push(problem);
}
bad_type => {
non_numeric_error = Some(Problem::Missing(format!("{} not numeric", bad_type)));
}
}
}
if int_vec.len() == operands.len() {
return Ok(PromotedOperands::Int(int_vec));
}
if float_vec.len() == operands.len() {
return Ok(PromotedOperands::Float(float_vec));
}
if let Some(ref err) = non_numeric_error {
error_vec.push(err);
}
Err(MetricValue::Problem(super::MetricState::important_problem(error_vec)))
}