Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
fn tenths(value: f64, message: &str) -> i64 {
if !value.is_finite() || value < -100.0 || value > 100.0 {
panic!("{}", message);
}
(round_float(value, 1) * 10.0).round() as i64
}
/// Growing degree days, McMaster and Wilhelm (1997), in tenths of a degree.
///
/// Each day is (max + min) / 2 - base with the temperatures taken to 0.1 °C,
/// so it is an exact multiple of 0.05 and the total is summed exactly and
/// rounded once. Rounding each day to 0.1 first and adding drifts: three days
/// of 0.05 are 0.15 (0.2 to one place), not 0.3.
///
/// # Panics
/// Panics on a temperature out of range, a day whose min is above its max,
/// an upper cap not above the base, or an unknown method.
pub fn growing_degree_days(days: &[DailyTemperature], base_celsius: f64, upper_celsius: Option<f64>, method: &str) -> DegreeDays {
let base = tenths(base_celsius, "baseCelsius must be a finite number from -100 to 100");
let upper = upper_celsius.map(|u| tenths(u, "upperCelsius must be a finite number from -100 to 100"));
if let Some(u) = upper {
if u <= base {
panic!("upperCelsius must be above baseCelsius");
}
}
if method != "average" && method != "clamped" {
panic!("unknown method \"{}\": use average or clamped", method);
}
let cap = |t: i64| match upper {
Some(u) if t > u => u,
_ => t,
};
let mut daily: Vec<f64> = Vec::with_capacity(days.len());
let mut total: i64 = 0;
for (i, day) in days.iter().enumerate() {
let message = format!("day {}: temperatures must be finite numbers from -100 to 100 °C", i + 1);
let lo = tenths(day.min, &message);
let hi = tenths(day.max, &message);
if lo > hi {
panic!("day {}: min must not be above max", i + 1);
}
let (lo, hi) = (cap(lo), cap(hi));
let twice = if method == "clamped" {
lo.max(base) + hi.max(base) - 2 * base
} else {
(lo + hi - 2 * base).max(0)
};
// twice is 2 x (mean - base) in tenths, so the day is twice / 20 degrees.
daily.push(twice as f64 / 20.0);
total += twice;
}
DegreeDays {
total: round_div(total, 2, "half-up") as f64 / 10.0,
daily,
}
}
pub fn degree_days_to_value(d: &DegreeDays) -> Value {
Value::obj(vec![
("total", Value::Float(d.total)),
("daily", Value::Arr(d.daily.iter().map(|&v| Value::Float(v)).collect())),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
let days: Vec<DailyTemperature> = args[0]
.as_arr()
.iter()
.map(|v| DailyTemperature {
min: v.get("min").as_f64(),
max: v.get("max").as_f64(),
})
.collect();
let upper = if args[2].is_null() { None } else { Some(args[2].as_f64()) };
degree_days_to_value(&growing_degree_days(&days, args[1].as_f64(), upper, args[3].as_str()))
}