Functional Weave
Code in TypeScript

construction.roof-pitch@1.0.0

impl/rust.rs

2,556 bytes · the Rust implementation · view raw

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_atan::atan;  ← from math.atan ^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

// 180 / pi as a double, written out so no language computes it differently.
const DEGREES_PER_RADIAN: f64 = 57.29577951308232;
const LIMIT: f64 = 10000.0;

fn check_length(name: &str, value: f64, allow_zero: bool) {
    let ok = value.is_finite() && value <= LIMIT && if allow_zero { value >= 0.0 } else { value > 0.0 };
    if !ok {
        let want = if allow_zero { "zero or more" } else { "greater than zero" };
        panic!(
            "{} must be a finite number of metres, {} and at most 10000, received {}",
            name, want, value
        );
    }
}

/// Pitch, rafter length and slope area of a duo- or mono-pitch roof.
///
/// The area is worked from the unrounded rafter, not the one rounded to a
/// millimetre for display: over a long roof the difference shows in the
/// second decimal place.
///
/// # Panics
/// Panics on a span or roof length that is not above zero, a negative rise,
/// any dimension over 10,000 m, or an unknown roof type.
pub fn roof_pitch(span: f64, rise: f64, roof_length: f64, roof_type: &str) -> RoofPitch {
    check_length("span", span, false);
    check_length("rise", rise, true);
    check_length("roofLength", roof_length, false);
    let (run, slopes) = match roof_type {
        "duo" => (span / 2.0, 2.0),
        "mono" => (span, 1.0),
        other => panic!("unknown roof type \"{}\"", other),
    };
    let radians = atan(rise / run);
    // sqrt is one of the operations IEEE 754 requires to be correctly rounded,
    // so f64::sqrt is the same double in every language.
    let rafter = (run * run + rise * rise).sqrt();
    RoofPitch {
        pitch_degrees: round_float(radians * DEGREES_PER_RADIAN, 2),
        rafter_length: round_float(rafter, 3),
        roof_area: round_float(rafter * roof_length * slopes, 2),
    }
}

pub fn roof_pitch_to_value(r: &RoofPitch) -> Value {
    Value::obj(vec![
        ("pitchDegrees", Value::Float(r.pitch_degrees)),
        ("rafterLength", Value::Float(r.rafter_length)),
        ("roofArea", Value::Float(r.roof_area)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    for (i, name) in ["span", "rise", "roofLength"].iter().enumerate() {
        if let Value::Str(s) = &args[i] {
            panic!("{} must be a finite number of metres, received \"{}\"", name, s);
        }
    }
    roof_pitch_to_value(&roof_pitch(
        args[0].as_f64(),
        args[1].as_f64(),
        args[2].as_f64(),
        args[3].as_str(),
    ))
}