///|
/// Decode an exact signed or unsigned integer from a checked DBC layout.
pub fn decode_signal_raw(
signal : Signal,
payload : Array[Byte],
) -> RawValue raise DbcError {
let positions = signal_bits(signal, payload.length())
raw_from_positions(signal, payload, positions)
}
///|
fn extract_intel(payload : Array[Byte], positions : Array[Int]) -> UInt64 {
let mut bits = 0UL
for weight, position in positions {
let bit = (payload[position / 8].to_int() >> (position % 8)) & 1
bits = bits | (bit.to_uint64() << weight)
}
bits
}
///|
fn extract_motorola(payload : Array[Byte], positions : Array[Int]) -> UInt64 {
let mut bits = 0UL
for position in positions {
let bit = (payload[position / 8].to_int() >> (position % 8)) & 1
bits = (bits << 1) | bit.to_uint64()
}
bits
}
///|
fn checked_raw_bits(signal : Signal, raw : RawValue) -> UInt64 raise DbcError {
if signal.bit_length < 1 || signal.bit_length > 64 {
fail("signal.length", "integer signals must be 1..64 bits")
}
match (signal.signed, raw) {
(true, Signed(n)) => {
if !integer_fits(signal, n) {
fail("codec.raw_range", "signed raw value does not fit signal")
}
n.reinterpret_as_uint64()
}
(false, Unsigned(n)) => {
if signal.bit_length < 64 && n >= 1UL << signal.bit_length {
fail("codec.raw_range", "unsigned raw value does not fit signal")
}
n
}
_ => {
fail("codec.raw_type", "raw signedness must match signal")
0UL
}
}
}
///|
/// Write only the signal's occupied bits. Validate all inputs before mutating.
pub fn encode_signal_raw(
signal : Signal,
payload : Array[Byte],
raw : RawValue,
) -> Unit raise DbcError {
let positions = signal_bits(signal, payload.length())
let bits = checked_raw_bits(signal, raw)
write_bits(signal, payload, positions, bits)
}
///|
fn write_bits(
signal : Signal,
payload : Array[Byte],
positions : Array[Int],
bits : UInt64,
) -> Unit {
for i, position in positions {
let weight = match signal.byte_order {
Intel => i
Motorola => signal.bit_length - 1 - i
}
let bit = ((bits >> weight) & 1UL).to_int()
let byte_index = position / 8
let mask = 1 << (position % 8)
payload[byte_index] = ((payload[byte_index].to_int() & (255 ^ mask)) |
(bit << (position % 8))).to_byte()
}
}
///|
/// Checked linear conversion; exact raw values remain available separately.
pub fn raw_to_physical(
signal : Signal,
raw : RawValue,
) -> Double raise DbcError {
ignore(checked_raw_bits(signal, raw))
if !finite(signal.factor) || signal.factor == 0.0 || !finite(signal.offset) {
fail("signal.factor", "invalid signal scaling")
}
let n = match raw {
Unsigned(v) => v.to_double()
Signed(v) => v.to_double()
}
let result = n * signal.factor + signal.offset
if !finite(result) {
fail("codec.physical_overflow", "physical value is nonfinite")
}
result
}
///|
/// Physical input quantizes to nearest raw integer, ties away from zero.
/// Values beyond Double's exact integer range require the raw API.
pub fn physical_to_raw(
signal : Signal,
physical : Double,
range_policy? : RangePolicy = Reject,
) -> RawValue raise DbcError {
if !finite(physical) {
fail("codec.nonfinite", "physical value must be finite")
}
if !finite(signal.factor) || signal.factor == 0.0 || !finite(signal.offset) {
fail("signal.factor", "invalid signal scaling")
}
if !finite(signal.minimum) ||
!finite(signal.maximum) ||
signal.minimum > signal.maximum {
fail("signal.range", "invalid physical range")
}
let mut value = physical
if value < signal.minimum || value > signal.maximum {
match range_policy {
Reject =>
fail("codec.physical_range", "physical value exceeds declared range")
Clamp =>
value = if value < signal.minimum {
signal.minimum
} else {
signal.maximum
}
Ignore | Warn => ()
}
}
let unrounded = (value - signal.offset) / signal.factor
if !finite(unrounded) || unrounded.abs() > 9007199254740991.0 {
fail("codec.precision", "use raw API outside exact Double integer range")
}
// Adding 0.5 can change already integral values above 2^52 through IEEE rounding.
let magnitude = unrounded.abs()
let integral = @double.floor(magnitude)
let nearest = if magnitude - integral >= 0.5 {
integral + 1.0
} else {
integral
}
let rounded = if unrounded < 0.0 { -nearest } else { nearest }
let raw = if signal.signed {
Signed(rounded.to_int64())
} else {
if rounded < 0.0 {
fail("codec.raw_range", "negative raw for unsigned signal")
}
Unsigned(rounded.to_uint64())
}
ignore(checked_raw_bits(signal, raw))
let quantized = raw_to_physical(signal, raw)
if (range_policy == Reject || range_policy == Clamp) &&
(quantized < signal.minimum || quantized > signal.maximum) {
fail(
"codec.quantized_range", "nearest raw integer lies outside physical range",
)
}
raw
}