///|
fn signed_nibble(value : Int) -> Int {
let nibble = value & 15
if nibble >= 8 {
nibble - 16
} else {
nibble
}
}
///|
/// Unit dimensions are signed nibble exponents, not converted physical values.
pub(all) struct UnitDimensions {
raw : Int64
system : Int
length : Int
mass : Int
time : Int
temperature : Int
current : Int
luminous_intensity : Int
reserved : Int
} derive(Debug, Eq)
///|
pub extend UnitDimensions with @debug.Debug::{to_repr}
///|
pub extend UnitDimensions with Eq::{equal, not_equal}
///|
pub fn decode_unit(raw : Int64) -> Result[UnitDimensions, Diagnostic] {
if raw < 0 || raw > 4294967295L {
return Err(
diagnostic(0, "unit_range", "Unit must fit an unsigned 32-bit item"),
)
}
Ok({
raw,
system: (raw & 15L).to_int(),
length: signed_nibble((raw >> 4).to_int()),
mass: signed_nibble((raw >> 8).to_int()),
time: signed_nibble((raw >> 12).to_int()),
temperature: signed_nibble((raw >> 16).to_int()),
current: signed_nibble((raw >> 20).to_int()),
luminous_intensity: signed_nibble((raw >> 24).to_int()),
reserved: ((raw >> 28) & 15L).to_int(),
})
}
///|
/// HID defaults to the logical range when either physical endpoint is absent,
/// or both declared physical endpoints are zero. Declared metadata stays intact.
pub fn effective_physical_range(field : Field) -> (Int64, Int64) {
match (field.physical_min, field.physical_max) {
(Some(minimum), Some(maximum)) if minimum != 0 || maximum != 0 =>
(minimum, maximum)
_ => (field.logical_min, field.logical_max)
}
}
///|
fn power_of_ten(exponent : Int) -> Double {
let mut result = 1.0
for i = 0; i < exponent.abs(); i = i + 1 {
result = result * 10.0
}
result
}
///|
/// Applies 10^exponent. Negative exponents divide by an exact power of ten.
fn scale_by_exponent(value : Double, exponent : Int) -> Double {
if exponent >= 0 {
value * power_of_ten(exponent)
} else {
value / power_of_ten(exponent)
}
}
///|
/// Converts a logical value to a physical quantity per HID 1.11 ยง6.2.2.7:
/// a linear map from the Logical range onto effective_physical_range, then
/// multiplied by 10^Unit Exponent (0 when undeclared). The result is a Double
/// in the Unit's base system units; decode_unit names the dimensions.
pub fn physical_value(
field : Field,
logical : Int64,
) -> Result[Double, Diagnostic] {
if (field.flags & 256) != 0 {
return Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Buffered Bytes have no numeric physical value",
),
)
}
if field.logical_max <= field.logical_min {
return Err(
diagnostic(
field.descriptor_offset,
"physical_range",
"Scaling needs Logical Maximum above Logical Minimum",
),
)
}
if logical < field.logical_min || logical > field.logical_max {
return Err(
if (field.flags & 64) != 0 {
diagnostic(
field.descriptor_offset,
"null_value",
"Value is the field's Null State",
)
} else {
diagnostic(
field.descriptor_offset,
"logical_range",
"Value is outside the field's logical range",
)
},
)
}
let (physical_min, physical_max) = effective_physical_range(field)
let scaled = physical_min.to_double() +
(logical - field.logical_min).to_double() *
(physical_max - physical_min).to_double() /
(field.logical_max - field.logical_min).to_double()
Ok(scale_by_exponent(scaled, field.unit_exponent.unwrap_or(0)))
}
///|
/// Inverse of physical_value, rounding half away from zero on every backend.
/// Results outside the Logical range are rejected rather than clamped.
pub fn logical_from_physical(
field : Field,
physical : Double,
) -> Result[Int64, Diagnostic] {
if (field.flags & 256) != 0 {
return Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Buffered Bytes have no numeric physical value",
),
)
}
let (physical_min, physical_max) = effective_physical_range(field)
if field.logical_max <= field.logical_min || physical_max == physical_min {
return Err(
diagnostic(
field.descriptor_offset,
"physical_range",
"Scaling needs distinct Logical and Physical endpoints",
),
)
}
if physical.is_nan() || physical.is_inf() {
return Err(
diagnostic(
field.descriptor_offset,
"value_range",
"Physical value must be finite",
),
)
}
let base = scale_by_exponent(physical, -field.unit_exponent.unwrap_or(0))
let logical = field.logical_min.to_double() +
(base - physical_min.to_double()) *
(field.logical_max - field.logical_min).to_double() /
(physical_max - physical_min).to_double()
// Core Double::round sends halves toward +infinity (-2.5 becomes -2), which
// would make negative quantities asymmetric. x - floor(x) is exact here.
let whole = logical.abs().floor()
let magnitude = if logical.abs() - whole >= 0.5 { whole + 1.0 } else { whole }
let rounded = if logical < 0.0 { -magnitude } else { magnitude }
if rounded < field.logical_min.to_double() ||
rounded > field.logical_max.to_double() {
return Err(
diagnostic(
field.descriptor_offset,
"logical_range",
"Physical value maps outside the field's logical range",
),
)
}
Ok(rounded.to_int64())
}