// Value encoding: map PLC values (coils, 16/32-bit integers) onto the raw
// 16-bit words that travel on the Modbus wire, honouring byte order and word
// order. This is the layer a driver uses to translate a point-table register
// into the bytes sent to / received from the slave.
//
// Conventions
// -----------
// A "word" is a 16-bit value stored as an `Int` in 0..=65535.
// Byte order says which of a word's two bytes is transmitted first.
// Word order says which of a 32-bit value's two words occupies the lower
// register address.
//
// All arithmetic stays inside the 32-bit `Int` range, so the code behaves
// identically on every backend without depending on 64-bit-wide globals.
///|
/// How the two bytes of a 16-bit word are placed on the wire.
pub enum ByteOrder {
/// Big endian: high byte transmitted first.
BigEndian
/// Little endian: low byte transmitted first.
LittleEndian
}
///|
/// In which register the high word of a 32-bit value is mapped.
pub enum WordOrder {
/// Native order: the high word occupies the lower register address.
HighWordLowAddress
/// Byte-swapped order: the low word occupies the lower register address.
LowWordLowAddress
}
///|
/// The same value laid out according to the reverse of an order.
fn swap_byte_order(order : ByteOrder) -> ByteOrder {
match order {
BigEndian => LittleEndian
LittleEndian => BigEndian
}
}
///|
/// The reverse of a word order.
fn swap_word_order(order : WordOrder) -> WordOrder {
match order {
HighWordLowAddress => LowWordLowAddress
LowWordLowAddress => HighWordLowAddress
}
}
///|
/// Human keyword for a byte order, for reports and (de)serialization.
pub fn byte_order_name(order : ByteOrder) -> String {
match order {
BigEndian => "big-endian"
LittleEndian => "little-endian"
}
}
///|
/// Resolve a keyword ("big"/"little", case-insensitive) into a `ByteOrder`,
/// defaulting to big-endian for anything unrecognised.
pub fn byte_order_of(name : String) -> ByteOrder {
let n = lowercase(name)
if n == "little" || n == "le" || n == "littleendian" {
LittleEndian
} else {
BigEndian
}
}
///|
/// Resolve a keyword ("hi-lo"/"lo-hi", case-insensitive) into a `WordOrder`,
/// defaulting to native order (high word at the lower address).
pub fn word_order_of(name : String) -> WordOrder {
let n = lowercase(name)
if n == "lo-hi" || n == "lo" || n == "lowwordlowaddress" {
LowWordLowAddress
} else {
HighWordLowAddress
}
}
///|
/// Human keyword for a word order, for reports and (de)serialization.
pub fn word_order_name(order : WordOrder) -> String {
match order {
HighWordLowAddress => "hi-lo"
LowWordLowAddress => "lo-hi"
}
}
///|
/// The two bytes (ints in 0..=255) of a 16-bit word under `order`.
pub fn uint16_to_bytes(w : Int, order : ByteOrder) -> (Int, Int) {
let hi = (w >> 8) & 0xFF
let lo = w & 0xFF
match order {
BigEndian => (hi, lo)
LittleEndian => (lo, hi)
}
}
///|
/// Rebuild a 16-bit word from two bytes (ints in 0..=255) under `order`.
/// The pair is `(first_byte, second_byte)` in wire order.
pub fn bytes_to_uint16(b0 : Int, b1 : Int, order : ByteOrder) -> Int {
match order {
BigEndian => (b0 << 8) | b1
LittleEndian => (b1 << 8) | b0
}
}
///|
/// The unsigned 16-bit word for a coil bit (0 or 1).
pub fn encode_coil(value : Bool) -> Int {
if value {
1
} else {
0
}
}
///|
/// Decode a coil bit from a 16-bit word; any non-zero value counts as on.
pub fn decode_coil(word : Int) -> Bool {
(word & 0xFFFF) != 0
}
///|
/// The unsigned 16-bit word for a `uint16` value (0..=65535).
pub fn encode_uint16(v : Int) -> Int {
v & 0xFFFF
}
///|
/// Decode a `uint16` value from a word (0..=65535).
pub fn decode_uint16(word : Int) -> Int {
word & 0xFFFF
}
///|
/// Map a signed `int16` (-32768..=32767) onto its unsigned 16-bit word.
pub fn encode_int16(v : Int) -> Int {
v & 0xFFFF
}
///|
/// Map an unsigned 16-bit word back to a signed `int16` (two's complement).
pub fn decode_int16(word : Int) -> Int {
let u = word & 0xFFFF
if u >= 0x8000 {
u - 0x10000
} else {
u
}
}
///|
/// The two bytes, in wire order for `byteOrder`, of a 16-bit value. This is the
/// minimal Modbus payload a single register occupies on the bus.
pub fn int16_bytes(v : Int, byteOrder : ByteOrder) -> Array[Int] {
let (hi, lo) = uint16_to_bytes(v & 0xFFFF, byteOrder)
[hi, lo]
}
///|
/// Rebuild a 16-bit word from a two-byte wire payload (ints in 0..=255).
pub fn int16_from_bytes(bytes : Array[Int], byteOrder : ByteOrder) -> Int {
bytes_to_uint16(bytes[0] & 0xFF, bytes[1] & 0xFF, byteOrder)
}
///|
test "order keywords resolve case-insensitively" {
assert_eq(byte_order_name(byte_order_of("LITTLE")), "little-endian")
assert_eq(byte_order_name(byte_order_of("LittleEndian")), "little-endian")
assert_eq(byte_order_name(byte_order_of("Big")), "big-endian")
assert_eq(word_order_name(word_order_of("LO-HI")), "lo-hi")
assert_eq(word_order_name(word_order_of("LoWwOrDlOwAdDrEsS")), "lo-hi")
assert_eq(word_order_name(word_order_of("Hi-Lo")), "hi-lo")
}
///|
test "int16 byte payloads round trip and respect byte order" {
let be = int16_bytes(0x1234, BigEndian)
let le = int16_bytes(0x1234, LittleEndian)
assert_eq(be, [0x12, 0x34])
assert_eq(le, [0x34, 0x12])
assert_eq(int16_from_bytes(be, BigEndian), 0x1234)
assert_eq(int16_from_bytes(le, LittleEndian), 0x1234)
// A signed view of the same payload.
assert_eq(decode_int16(int16_from_bytes(be, BigEndian)), 4660)
}
///|
test "int16 wire round trip covers signed extremes in both byte orders" {
let vals = [0, 1, -1, 32767, -32768, -248]
for v in vals {
for bo in [BigEndian, LittleEndian] {
let bs = int16_bytes(v, bo)
// Reassemble the unsigned word, then pull out the two's-complement sign.
let w = int16_from_bytes(bs, bo)
assert_eq(w & 0xFFFF, encode_int16(v))
assert_eq(decode_int16(w & 0xFFFF), v)
}
}
}
///|
/// Split a 32-bit `int` into its high and low 16-bit words (unsigned).
pub fn int32_parts(v : Int) -> (Int, Int) {
let hi = (v >> 16) & 0xFFFF
let lo = v & 0xFFFF
(hi, lo)
}
///|
/// Fold two unsigned 16-bit words back into a signed 32-bit `int`.
/// Because `Int` is 32-bit two's complement, `(hi << 16) | lo` already yields
/// the correct signed value for both positive and negative inputs.
pub fn int32_fold(hi : Int, lo : Int) -> Int {
(hi << 16) | (lo & 0xFFFF)
}
///|
/// The two words of a 32-bit `int` in the register order they occupy on the
/// wire: the first element goes to the lower register address.
pub fn int32_words(v : Int, order : WordOrder) -> (Int, Int) {
let (hi, lo) = int32_parts(v)
match order {
HighWordLowAddress => (hi, lo)
LowWordLowAddress => (lo, hi)
}
}
///|
/// Rebuild a 32-bit `int` from the two words as transmitted, given their order.
/// The pair is `(lower_address_word, higher_address_word)`.
pub fn int32_fold_words(w0 : Int, w1 : Int, order : WordOrder) -> Int {
let atLowAddr = w0 & 0xFFFF
let atHighAddr = w1 & 0xFFFF
match order {
// With native order the high word sits at the lower address, so `w0` is hi.
HighWordLowAddress => int32_fold(atLowAddr, atHighAddr)
// With swapped order the low word sits at the lower address, so `w1` is hi.
LowWordLowAddress => int32_fold(atHighAddr, atLowAddr)
}
}
///|
/// Encode a 32-bit value into a Modbus byte payload given both byte and word
/// order. Returns 4 bytes in wire order, ints in 0..=255.
pub fn encode_int32_bytes(
v : Int,
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Array[Int] {
let (wAtLowAddr, wAtHighAddr) = int32_words(v, wordOrder)
let (b0, b1) = uint16_to_bytes(wAtLowAddr, byteOrder)
let (b2, b3) = uint16_to_bytes(wAtHighAddr, byteOrder)
[b0, b1, b2, b3]
}
///|
/// Decode a 4-byte Modbus payload back into a 32-bit `int`.
pub fn decode_int32_bytes(
bytes : Array[Int],
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Int {
let b0 = bytes[0] & 0xFF
let b1 = bytes[1] & 0xFF
let b2 = bytes[2] & 0xFF
let b3 = bytes[3] & 0xFF
let wAtLowAddr = bytes_to_uint16(b0, b1, byteOrder)
let wAtHighAddr = bytes_to_uint16(b2, b3, byteOrder)
int32_fold_words(wAtLowAddr, wAtHighAddr, wordOrder)
}
///|
test "uint16 bytes differ under big vs little endian" {
let be = uint16_to_bytes(0x1234, BigEndian)
let le = uint16_to_bytes(0x1234, LittleEndian)
assert_eq(be, (0x12, 0x34))
assert_eq(le, (0x34, 0x12))
}
///|
test "bytes and words round trip" {
assert_eq(bytes_to_uint16(0x12, 0x34, BigEndian), 0x1234)
assert_eq(bytes_to_uint16(0x34, 0x12, LittleEndian), 0x1234)
// The swap of a swap is identity (compared by name, since these types have
// no derived Eq and comparing enums directly triggers a deprecation warning).
assert_eq(byte_order_name(swap_byte_order(BigEndian)), "little-endian")
assert_eq(word_order_name(swap_word_order(LowWordLowAddress)), "hi-lo")
}
///|
test "int16 two's complement maps negative values" {
assert_eq(encode_int16(0), 0)
assert_eq(encode_int16(-1), 0xFFFF)
assert_eq(encode_int16(32767), 0x7FFF)
assert_eq(decode_int16(0xFFFF), -1)
assert_eq(decode_int16(0x8000), -32768)
assert_eq(decode_int16(0x7FFF), 32767)
assert_eq(decode_int16(encode_int16(-249)), -249)
}
///|
test "coil maps to 0 or 1 and decodes any non-zero as on" {
assert_eq(encode_coil(true), 1)
assert_eq(encode_coil(false), 0)
assert_eq(decode_coil(1), true)
assert_eq(decode_coil(0), false)
assert_eq(decode_coil(0x8000), true)
}
///|
test "int32_parts separates high and low words" {
assert_eq(int32_parts(0x12345678), (0x1234, 0x5678))
assert_eq(int32_fold(0x1234, 0x5678), 0x12345678)
// Negative values keep the two's-complement high word.
let (hi, lo) = int32_parts(-1)
assert_eq(hi, 0xFFFF)
assert_eq(lo, 0xFFFF)
assert_eq(int32_fold(0xFFFF, 0xFFFF), -1)
}
///|
test "int32_words respects word order" {
assert_eq(int32_words(0x12345678, HighWordLowAddress), (0x1234, 0x5678))
assert_eq(int32_words(0x12345678, LowWordLowAddress), (0x5678, 0x1234))
assert_eq(int32_fold_words(0x1234, 0x5678, HighWordLowAddress), 0x12345678)
assert_eq(int32_fold_words(0x5678, 0x1234, LowWordLowAddress), 0x12345678)
}
///|
test "int32 byte encoding round trips under every endianness" {
let values = [0, 1, -1, 32767, -32768, 0x12345678]
for v in values {
let be = encode_int32_bytes(v, BigEndian, HighWordLowAddress)
assert_eq(decode_int32_bytes(be, BigEndian, HighWordLowAddress), v)
let le = encode_int32_bytes(v, LittleEndian, LowWordLowAddress)
assert_eq(decode_int32_bytes(le, LittleEndian, LowWordLowAddress), v)
}
}
///|
test "swapping byte or word order changes the encoded payload" {
let a = encode_int32_bytes(0x12345678, BigEndian, HighWordLowAddress)
let b = encode_int32_bytes(0x12345678, LittleEndian, HighWordLowAddress)
let c = encode_int32_bytes(0x12345678, BigEndian, LowWordLowAddress)
assert_eq(a[0], 0x12)
assert_eq(c[0], 0x56) // low word moved to the lower address
let mut differ = 0
for k = 0; k < a.length(); k = k + 1 {
if a[k] != b[k] {
differ = differ + 1
}
}
assert_eq(differ > 0, true)
}
// ---------------------------------------------------------------------------
// Floating point.
//
// float64 is exact: the value's IEEE-754 bit pattern is reinterpreted directly
// (double_bits / double_from_bits), so normal, subnormal, ±0, ±Inf and NaN all
// round-trip bit-for-bit.
//
// float32 turns a double into single precision with the backend's IEEE-754
// round-to-nearest, ties-to-even demotion, then reads the resulting 32-bit
// pattern. That reproduces correct rounding at midpoints and at the
// normal/subnormal and overflow boundaries.
//
// The APIs use a fixed big-endian word order (most-significant word first).
// Per-word byte order is supported through the *bytes helpers, reusing
// `uint16_to_bytes` / `bytes_to_uint16`.
// ---------------------------------------------------------------------------
///|
/// The 16-bit WORD PACK that a double occupies, high word first.
fn split_words64(b : Int64) -> (Int, Int, Int, Int) {
let w0 = ((b >> 48) & 0xFFFFL).to_int()
let w1 = ((b >> 32) & 0xFFFFL).to_int()
let w2 = ((b >> 16) & 0xFFFFL).to_int()
let w3 = (b & 0xFFFFL).to_int()
(w0, w1, w2, w3)
}
///|
/// The reverse of `split_words64`.
fn compose_words64(w0 : Int, w1 : Int, w2 : Int, w3 : Int) -> Int64 {
(w0.to_int64() << 48) |
(w1.to_int64() << 32) |
(w2.to_int64() << 16) |
w3.to_int64()
}
///|
/// IEEE-754 double-precision bits for `v`. This is an exact bit-preserving
/// reinterpretation, so every pattern (normal, subnormal, ±0, ±Inf, NaN) is
/// represented faithfully.
fn double_bits(v : Double) -> Int64 {
v.reinterpret_as_int64()
}
///|
/// Rebuild the exact `Double` from its IEEE-754 bit pattern.
fn double_from_bits(b : Int64) -> Double {
b.reinterpret_as_double()
}
///|
/// The four 16-bit words (high word first) of a double-precision value.
pub fn float64_words(v : Double) -> (Int, Int, Int, Int) {
split_words64(double_bits(v))
}
///|
/// Rebuild a double-precision value from its four words (high word first).
pub fn float64_from_words(w0 : Int, w1 : Int, w2 : Int, w3 : Int) -> Double {
double_from_bits(compose_words64(w0, w1, w2, w3))
}
///|
/// The eight bytes (in wire order) of a double value, honouring both byte order
/// and word order. The four words are laid out most-significant word first under
/// `HighWordLowAddress`, and the whole sequence is reversed under
/// `LowWordLowAddress`.
pub fn float64_bytes(
v : Double,
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Array[Int] {
let (w0, w1, w2, w3) = float64_words(v)
let (a, b, c, d) = match wordOrder {
HighWordLowAddress => (w0, w1, w2, w3)
LowWordLowAddress => (w3, w2, w1, w0)
}
let (h0, l0) = uint16_to_bytes(a, byteOrder)
let (h1, l1) = uint16_to_bytes(b, byteOrder)
let (h2, l2) = uint16_to_bytes(c, byteOrder)
let (h3, l3) = uint16_to_bytes(d, byteOrder)
[h0, l0, h1, l1, h2, l2, h3, l3]
}
///|
/// Rebuild a double value from eight bytes produced by `float64_bytes`, given
/// their byte order and word order.
pub fn float64_from_bytes(
bytes : Array[Int],
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Double {
let a = bytes_to_uint16(bytes[0] & 0xFF, bytes[1] & 0xFF, byteOrder)
let b = bytes_to_uint16(bytes[2] & 0xFF, bytes[3] & 0xFF, byteOrder)
let c = bytes_to_uint16(bytes[4] & 0xFF, bytes[5] & 0xFF, byteOrder)
let d = bytes_to_uint16(bytes[6] & 0xFF, bytes[7] & 0xFF, byteOrder)
let (w0, w1, w2, w3) = match wordOrder {
HighWordLowAddress => (a, b, c, d)
LowWordLowAddress => (d, c, b, a)
}
float64_from_words(w0, w1, w2, w3)
}
///|
/// IEEE-754 single-precision bits for `v` (a `Double`, rounded to float32).
fn float32_bits(v : Double) -> Int {
if v != v {
// Canonical quiet NaN, kept deterministic across backends.
return 0x7FC00000
}
// Demote to single precision with IEEE-754 round-to-nearest, ties-to-even,
// then read the exact 32-bit pattern. This gets midpoints, overflow to
// infinity and the normal/subnormal boundary right by construction.
Float::from_double(v).reinterpret_as_int()
}
///|
/// Rebuild a single-precision value (as a `Double`) from its 32-bit bits.
fn float32_from_bits(bits : Int) -> Double {
let sign = (bits >> 31) & 1
let exp = (bits >> 23) & 0xFF
let mant = bits & 0x7FFFFF
if exp == 0 {
// Subnormal (or zero): value = mant * 2^-149, no implicit leading one.
let mag = if mant == 0 {
0.0
} else {
mant.to_double() * @math.pow(2.0, -149.0)
}
if sign == 1 {
-mag
} else {
mag
}
} else if exp == 0xFF {
// Special sentinel: all-ones exponent. Mantissa zero is ±Infinity, a
// non-zero mantissa is a NaN (any quiet NaN widens to a double NaN).
if mant == 0 {
if sign == 1 {
0xFFF0000000000000L.reinterpret_as_double()
} else {
0x7FF0000000000000L.reinterpret_as_double()
}
} else {
0x7FF8000000000001L.reinterpret_as_double()
}
} else {
let mag = (8388608 | mant).to_double() *
@math.pow(2.0, (exp - 150).to_double())
if sign == 1 {
-mag
} else {
mag
}
}
}
///|
/// The two 16-bit words (high word first) of a single-precision value.
pub fn float32_words(v : Double) -> (Int, Int) {
let b = float32_bits(v)
let hi = (b >> 16) & 0xFFFF
let lo = b & 0xFFFF
(hi, lo)
}
///|
/// Rebuild a single-precision value from its two words (high word first).
pub fn float32_from_words(hi : Int, lo : Int) -> Double {
float32_from_bits(((hi & 0xFFFF) << 16) | (lo & 0xFFFF))
}
///|
/// The four bytes (in wire order) of a single-precision value, honouring both
/// byte order and word order. The high word sits at the lower address under
/// `HighWordLowAddress`, and the low word does under `LowWordLowAddress`.
pub fn float32_bytes(
v : Double,
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Array[Int] {
let (hi, lo) = float32_words(v)
let (a, b) = match wordOrder {
HighWordLowAddress => (hi, lo)
LowWordLowAddress => (lo, hi)
}
let (b0, b1) = uint16_to_bytes(a, byteOrder)
let (b2, b3) = uint16_to_bytes(b, byteOrder)
[b0, b1, b2, b3]
}
///|
/// Rebuild a single-precision value from four bytes, given their byte order and
/// word order.
pub fn float32_from_bytes(
bytes : Array[Int],
byteOrder : ByteOrder,
wordOrder : WordOrder,
) -> Double {
let a = bytes_to_uint16(bytes[0] & 0xFF, bytes[1] & 0xFF, byteOrder)
let b = bytes_to_uint16(bytes[2] & 0xFF, bytes[3] & 0xFF, byteOrder)
let (hi, lo) = match wordOrder {
HighWordLowAddress => (a, b)
LowWordLowAddress => (b, a)
}
float32_from_words(hi, lo)
}
///|
test "float64 round trips 1.0 through words" {
let (w0, w1, w2, w3) = float64_words(1.0)
// 1.0 = 0x3FF00000_00000000, so w0=0x3FF0, w1=0, then zeros.
assert_eq(w0, 0x3FF0)
assert_eq(w1, 0)
assert_eq(float64_from_words(w0, w1, w2, w3), 1.0)
}
///|
test "float64 round trips a spread of values" {
let values = [1.0, -1.0, 0.0, -0.0, 3.0, 1234.5, 0.5, -2.25]
for v in values {
let (w0, w1, w2, w3) = float64_words(v)
assert_eq(float64_from_words(w0, w1, w2, w3), v)
for bo in [BigEndian, LittleEndian] {
for wo in [HighWordLowAddress, LowWordLowAddress] {
let bs = float64_bytes(v, bo, wo)
assert_eq(float64_from_bytes(bs, bo, wo), v)
}
}
}
}
///|
test "float64 bytes differ under big and little endian" {
let be = float64_bytes(1.0, BigEndian, HighWordLowAddress)
let le = float64_bytes(1.0, LittleEndian, HighWordLowAddress)
assert_eq(be[0], 0x3F)
assert_eq(be[1], 0xF0)
// Little endian flips each word's byte order (low byte first).
assert_eq(le[0], 0xF0)
assert_eq(le[1], 0x3F)
}
///|
test "float32/float64 byte order and word order round trip" {
// 1.5f = 0x3FC00000; with the low word at the lower address the words swap.
assert_eq(float32_bytes(1.5, BigEndian, HighWordLowAddress), [
0x3F, 0xC0, 0x00, 0x00,
])
assert_eq(float32_bytes(1.5, BigEndian, LowWordLowAddress), [
0x00, 0x00, 0x3F, 0xC0,
])
for bo in [BigEndian, LittleEndian] {
for wo in [HighWordLowAddress, LowWordLowAddress] {
assert_eq(float32_from_bytes(float32_bytes(1.5, bo, wo), bo, wo), 1.5)
assert_eq(
float64_from_bytes(float64_bytes(1234.5, bo, wo), bo, wo),
1234.5,
)
}
}
}
///|
test "float32 round trips values that are exactly representable" {
let values = [1.5, -0.25, 3.0, 0.0, -0.0, 1234.0, 0.5]
for v in values {
let (hi, lo) = float32_words(v)
assert_eq(float32_from_words(hi, lo), v)
let be = float32_bytes(v, BigEndian, HighWordLowAddress)
assert_eq(float32_from_bytes(be, BigEndian, HighWordLowAddress), v)
}
}
///|
test "float32 encodes 1.5 to the expected words" {
// 1.5 = sign 0, exp 127, mantissa 0x400000 => words hi=0x3FC0, lo=0x0000.
let (hi, lo) = float32_words(1.5)
assert_eq(hi, 0x3FC0)
assert_eq(lo, 0)
}
///|
test "float32 rounds midpoints to even (IEEE-754)" {
// 1 + 2^-24 is exactly halfway between 1.0 and 1.0 + 2^-23; ties-to-even
// selects the lower (even-mantissa) neighbour.
let mid = 1.0 + 1.0 / 16777216.0
let (hi, lo) = float32_words(mid)
assert_eq(hi, 0x3F80)
assert_eq(lo, 0x0000)
}
///|
test "float32 overflows to infinity instead of wrapping" {
let (hi1, lo1) = float32_words(1.0e39)
assert_eq(hi1, 0x7F80)
assert_eq(lo1, 0x0000)
let (hi2, lo2) = float32_words(@math.pow(2.0, 129.0))
assert_eq(hi2, 0x7F80)
assert_eq(lo2, 0x0000)
}
///|
test "float32 promotes the subnormal/normal midpoint to the smallest normal" {
let smallest_normal = @math.pow(2.0, -126.0)
// Exactly halfway between the largest subnormal and the smallest normal;
// ties-to-even selects the smallest normal (even mantissa).
let mid = smallest_normal - smallest_normal / 16777216.0
let (hi, lo) = float32_words(mid)
assert_eq(hi, 0x0080)
assert_eq(lo, 0x0000)
assert_eq(float32_from_words(hi, lo), smallest_normal)
}
// ---------------------------------------------------------------------------
// Integer boundary tests.
// ---------------------------------------------------------------------------
///|
test "int32 extremes round trip through words and bytes" {
let vals = [2147483647, -2147483648, 0, -1, 1]
for v in vals {
let (hi, lo) = int32_words(v, HighWordLowAddress)
assert_eq(int32_fold_words(hi, lo, HighWordLowAddress), v)
for bo in [BigEndian, LittleEndian] {
for wo in [HighWordLowAddress, LowWordLowAddress] {
let bs = encode_int32_bytes(v, bo, wo)
assert_eq(decode_int32_bytes(bs, bo, wo), v)
}
}
}
}
///|
test "uint32 bit patterns at the upper half of the range" {
// 0x7FFFFFFF.
let (hi1, lo1) = int32_parts(2147483647)
assert_eq(hi1, 0x7FFF)
assert_eq(lo1, 0xFFFF)
// 0x80000000 (bit 31 set) is -2147483648 in two's complement.
let (hi2, lo2) = int32_parts(-2147483648)
assert_eq(hi2, 0x8000)
assert_eq(lo2, 0x0000)
// 0xFFFFFFFF (all bits set) is -1 in two's complement.
let (hi3, lo3) = int32_parts(-1)
assert_eq(hi3, 0xFFFF)
assert_eq(lo3, 0xFFFF)
assert_eq(int32_fold(hi2, lo2), -2147483648)
assert_eq(int32_fold(hi3, lo3), -1)
}
// ---------------------------------------------------------------------------
// Floating-point boundary tests.
// ---------------------------------------------------------------------------
///|
test "float64 edge cases: largest, smallest normal, subnormal, inf, nan" {
let largest = 1.7976931348623157e308
let (l0, l1, l2, l3) = float64_words(largest)
assert_eq(float64_from_words(l0, l1, l2, l3), largest)
let smallest = 4.9406564584124654e-324
let (s0, s1, s2, s3) = float64_words(smallest)
assert_eq(float64_from_words(s0, s1, s2, s3), smallest)
// +Infinity produces the double pattern 0x7FF0 0000 0000 0000.
let (i0, i1, i2, i3) = float64_words(
0x7FF0000000000000L.reinterpret_as_double(),
)
assert_eq(i0, 0x7FF0)
assert_eq(i1, 0)
assert_eq(i2, 0)
assert_eq(i3, 0)
// NaN round trips: re-encoding its words still yields a NaN (self-unequal).
let nan = 0x7FF8000000000001L.reinterpret_as_double()
let (a, b, c, d) = float64_words(nan)
let nan2 = float64_from_words(a, b, c, d)
assert_eq(nan2 != nan2, true)
}
///|
test "float32 edge cases: normal bounds, subnormal, inf, and NaN pattern" {
let hi_normal = 3.4028234663852886e38
let (h0, h1) = float32_words(hi_normal)
assert_eq(float32_from_words(h0, h1), hi_normal)
let lo_normal = 1.1754943508222875e-38
let (n0, n1) = float32_words(lo_normal)
assert_eq(float32_from_words(n0, n1), lo_normal)
// Smallest positive subnormal float32 is 2^-149.
let sub = 1.401298464324817e-45
let (u0, u1) = float32_words(sub)
assert_eq(float32_from_words(u0, u1), sub)
// Decoding the float32 +inf pattern gives a positive infinity.
assert_eq(float32_from_words(0x7F80, 0x0000) > 3.402823466e38, true)
// Decoding a quiet-NaN pattern yields a NaN (self-unequal).
let nan = float32_from_words(0x7FC0, 0x0000)
assert_eq(nan != nan, true)
}