use super::funejson::Value; use super::math_round_div::round_div; use super::math_round_float::round_float; 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())) }