///|
/// Clamp a floating-point value to a closed interval.
pub fn clamp_double(value : Double, low~ : Double, high~ : Double) -> Double {
  if low > high {
    clamp_double(value, low=high, high=low)
  } else if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}

///|
/// Return a fallback when a value is NaN or infinite.
pub fn finite_or(value : Double, fallback~ : Double) -> Double {
  if value.is_nan() || value.is_inf() {
    fallback
  } else {
    value
  }
}

///|
/// A division that returns a caller-selected value for a zero denominator.
pub fn safe_divide(
  numerator : Double,
  denominator : Double,
  fallback~ : Double,
) -> Double {
  if denominator == 0.0 {
    fallback
  } else {
    numerator / denominator
  }
}

///|
/// Sum a vector without changing its order.
pub fn sum_values(values : ArrayView[Double]) -> Double {
  let mut total = 0.0
  for value in values {
    total += value
  }
  total
}

///|
/// Arithmetic mean, returning zero for an empty vector.
pub fn mean_value(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    sum_values(values) / values.length().to_double()
  }
}

///|
/// Population variance, returning zero for fewer than two values.
pub fn variance_value(values : ArrayView[Double]) -> Double {
  if values.length() < 2 {
    0.0
  } else {
    let mean = mean_value(values)
    let mut total = 0.0
    for value in values {
      let delta = value - mean
      total += delta * delta
    }
    total / values.length().to_double()
  }
}

///|
/// Root-mean-square magnitude.
pub fn rms_value(values : ArrayView[Double]) -> Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut total = 0.0
    for value in values {
      total += value * value
    }
    (total / values.length().to_double()).sqrt()
  }
}

///|
/// Absolute L2 error normalized by the number of samples.
pub fn l2_error(
  expected : ArrayView[Double],
  actual : ArrayView[Double],
) -> Double {
  let n = expected.length().min(actual.length())
  if n == 0 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0.. Double {
  let n = expected.length().min(actual.length())
  let mut result = 0.0
  for i in 0.. Double {
  let denominator = rms_value(expected)
  if denominator == 0.0 {
    l2_error(expected, actual)
  } else {
    l2_error(expected, actual) / denominator
  }
}

///|
/// Linear interpolation between two values.
pub fn lerp(a : Double, b : Double, t : Double) -> Double {
  a + (b - a) * t
}

///|
/// Cubic smoothstep interpolation on an unclamped parameter.
pub fn smoothstep(edge0~ : Double, edge1~ : Double, value~ : Double) -> Double {
  let t = clamp_double(
    safe_divide(value - edge0, edge1 - edge0, fallback=0.0),
    low=0.0,
    high=1.0,
  )
  t * t * (3.0 - 2.0 * t)
}

///|
/// Return -1, 0, or 1 according to the sign of a value.
pub fn signum(value : Double) -> Int {
  if value < 0.0 {
    -1
  } else if value > 0.0 {
    1
  } else {
    0
  }
}

///|
/// True when a value is close to zero under an absolute tolerance.
pub fn nearly_zero(value : Double, tolerance? : Double = 0.000000001) -> Bool {
  let absolute = if value < 0.0 { -value } else { value }
  absolute <= tolerance
}

///|
/// Return the smallest and largest finite values in a vector.
pub fn min_max(values : ArrayView[Double]) -> (Double, Double) {
  if values.length() == 0 {
    (0.0, 0.0)
  } else {
    let mut minimum = values[0]
    let mut maximum = values[0]
    for value in values[1:] {
      minimum = minimum.min(value)
      maximum = maximum.max(value)
    }
    (minimum, maximum)
  }
}

///|
/// A small online accumulator for streaming diagnostics.
pub(all) struct StatisticsAccumulator {
  mut count : Int
  mut sum : Double
  mut sum_squares : Double
  mut minimum : Double
  mut maximum : Double
} derive(Debug)

///|
/// Create an empty streaming accumulator.
pub fn StatisticsAccumulator::new() -> StatisticsAccumulator {
  { count: 0, sum: 0.0, sum_squares: 0.0, minimum: 0.0, maximum: 0.0 }
}

///|
/// Add one value to a streaming accumulator.
pub fn StatisticsAccumulator::push(
  self : StatisticsAccumulator,
  value : Double,
) -> Unit {
  if self.count == 0 {
    self.minimum = value
    self.maximum = value
  } else {
    self.minimum = self.minimum.min(value)
    self.maximum = self.maximum.max(value)
  }
  self.count += 1
  self.sum += value
  self.sum_squares += value * value
}

///|
/// Convert a streaming accumulator into descriptive statistics.
pub fn StatisticsAccumulator::finish(
  self : StatisticsAccumulator,
) -> FieldStatistics {
  if self.count == 0 {
    {
      count: 0,
      sum: 0.0,
      mean: 0.0,
      minimum: 0.0,
      maximum: 0.0,
      variance: 0.0,
      l2_norm: 0.0,
    }
  } else {
    let mean = self.sum / self.count.to_double()
    let variance = (self.sum_squares / self.count.to_double() - mean * mean).max(
      0.0,
    )
    {
      count: self.count,
      sum: self.sum,
      mean,
      minimum: self.minimum,
      maximum: self.maximum,
      variance,
      l2_norm: self.sum_squares.sqrt(),
    }
  }
}