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
const CM2_PER_M2: f64 = 10000.0;
// The nearest whole square centimetre to the double given, so 2.88 is 28800,
// not the 28799.999... that 2.88 * 10000 might suggest.
fn square_centimetres(m2: f64) -> i64 {
(round_float(m2, 4) * CM2_PER_M2).round() as i64
}
/// Packs needed to cover an area, rounded up to whole packs.
///
/// Everything is converted to whole square centimetres first and the division
/// is done in integers. Dividing floats and taking the ceiling is the naive
/// way and it is wrong: 8.64 / 2.88 is 3.0000000000000004, which buys a fourth
/// sheet of plasterboard nobody needs.
///
/// # Panics
/// Panics on an area, coverage, coat count or wastage outside its range.
pub fn materials_area(area_square_metres: f64, coverage_per_pack: f64, coats: i64, wastage_basis_points: i64) -> MaterialsQuantity {
if !area_square_metres.is_finite() || area_square_metres < 0.0 || area_square_metres > 1000000.0 {
panic!("areaSquareMetres must be a finite number from 0 to 1000000, received {}", area_square_metres);
}
if !coverage_per_pack.is_finite() || coverage_per_pack > 100000.0 {
panic!("coveragePerPack must be at least 0.0001 and at most 100000 square metres, received {}", coverage_per_pack);
}
let cover = square_centimetres(coverage_per_pack);
if cover < 1 {
panic!("coveragePerPack must be at least 0.0001 and at most 100000 square metres, received {}", coverage_per_pack);
}
if !(1..=10).contains(&coats) {
panic!("coats must be a whole number from 1 to 10, received {}", coats);
}
if !(0..=10000).contains(&wastage_basis_points) {
panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", wastage_basis_points);
}
let area = square_centimetres(area_square_metres);
let needed = round_div(area * coats * (10000 + wastage_basis_points), 10000, "up");
let packs = round_div(needed, cover, "up");
MaterialsQuantity {
area_to_cover: needed as f64 / CM2_PER_M2,
packs,
surplus: (packs * cover - needed) as f64 / CM2_PER_M2,
}
}
pub fn materials_quantity_to_value(q: &MaterialsQuantity) -> Value {
Value::obj(vec![
("areaToCover", Value::Float(q.area_to_cover)),
("packs", Value::Int(q.packs)),
("surplus", Value::Float(q.surplus)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
if let Value::Float(f) = &args[2] {
panic!("coats must be a whole number from 1 to 10, received {}", f);
}
if let Value::Float(f) = &args[3] {
panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", f);
}
materials_quantity_to_value(&materials_area(args[0].as_f64(), args[1].as_f64(), args[2].as_i64(), args[3].as_i64()))
}