Skip to content

Commit

Permalink
Add support for 16-bit floats
Browse files Browse the repository at this point in the history
  • Loading branch information
richard-viney committed Jun 22, 2024
1 parent 675bad4 commit 6ae3bed
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/ieee_float.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,58 @@ pub fn parse(s: String) -> IEEEFloat {
}
}

/// Converts an `IEEEFloat` to bytes for a little endian 16-bit IEEE 754 float.
///
@external(javascript, "./ieee_float_js.mjs", "to_bytes_16_le")
pub fn to_bytes_16_le(f: IEEEFloat) -> BitArray {
case f {
Finite(f) -> <<f:16-float-little>>
Infinite(Positive) -> <<0x7C00:16-little>>
Infinite(Negative) -> <<0xFC00:16-little>>
NaN -> <<0x7E00:16-little>>
}
}

/// Converts bytes for a little endian 16-bit IEEE 754 float to an `IEEEFloat`.
///
/// If the bit array doesn't contain exactly two bytes then NaN is returned.
///
@external(javascript, "./ieee_float_js.mjs", "from_bytes_16_le")
pub fn from_bytes_16_le(bytes: BitArray) -> IEEEFloat {
case bytes {
<<value:16-float-little>> -> Finite(value)
<<0x7C00:16-little>> -> Infinite(Positive)
<<0xFC00:16-little>> -> Infinite(Negative)
_ -> NaN
}
}

/// Converts an `IEEEFloat` to bytes for a big endian 16-bit IEEE 754 float.
///
@external(javascript, "./ieee_float_js.mjs", "to_bytes_16_be")
pub fn to_bytes_16_be(f: IEEEFloat) -> BitArray {
case f {
Finite(f) -> <<f:16-float>>
Infinite(Positive) -> <<0x7C00:16>>
Infinite(Negative) -> <<0xFC00:16>>
NaN -> <<0x7E00:16>>
}
}

/// Converts bytes for a big endian 16-bit IEEE 754 float to an `IEEEFloat`.
///
/// If the bit array doesn't contain exactly two bytes then NaN is returned.
///
@external(javascript, "./ieee_float_js.mjs", "from_bytes_16_be")
pub fn from_bytes_16_be(bytes: BitArray) -> IEEEFloat {
case bytes {
<<value:16-float>> -> Finite(value)
<<0x7C00:16>> -> Infinite(Positive)
<<0xFC00:16>> -> Infinite(Negative)
_ -> NaN
}
}

/// Converts an `IEEEFloat` to bytes for a little endian 32-bit IEEE 754 float.
///
@external(javascript, "./ieee_float_js.mjs", "to_bytes_32_le")
Expand Down
88 changes: 88 additions & 0 deletions src/ieee_float_js.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,78 @@ export function parse(s) {
}
}

function to_bytes_16(num, littleEndian) {
const u8Array = new Uint8Array(2);

if (isNaN(num)) {
u8Array[1] = 0x7e;
} else if (num === Infinity) {
u8Array[1] = 0x7c;
} else if (num === -Infinity) {
u8Array[1] = 0xfc;
} else if (num === 0) {
// Both values are already zero
} else {
const sign = num < 0 ? 1 : 0;
num = Math.abs(num);

let exponent = Math.floor(Math.log2(num));
let fraction = num / Math.pow(2, exponent) - 1;

exponent += 15;

if (exponent <= 0) {
exponent = 0;
fraction = num / Math.pow(2, -14);
} else if (exponent >= 31) {
exponent = 31;
fraction = 0;
}

fraction = Math.round(fraction * 1024);

u8Array[1] =
(sign << 7) | ((exponent & 0x1f) << 2) | ((fraction >> 8) & 0x03);
u8Array[0] = fraction & 0xff;
}

if (!littleEndian) {
const a = u8Array[0];
u8Array[0] = u8Array[1];
u8Array[1] = a;
}

return new BitArray(u8Array);
}

function from_bytes_16(bitArray, littleEndian) {
if (bitArray.length !== 2) {
return NaN;
}

let intValue;
if (littleEndian) {
intValue = bitArray.buffer[0] + bitArray.buffer[1] * 256;
} else {
intValue = bitArray.buffer[0] * 256 + bitArray.buffer[1];
}

const sign = intValue >= 0x8000 ? -1 : 1;
const exponent = (intValue & 0x7c00) >> 10;
const fraction = intValue & 0x03ff;

let value;
if (exponent === 0) {
value = 6.103515625e-5 * (fraction / 0x400);
} else if (exponent === 0x1f) {
value = fraction === 0 ? Infinity : NaN;
} else {
value = Math.pow(2, exponent - 15) * (1 + fraction / 0x400);
}

return sign * value;
}

function to_bytes_32(f, littleEndian) {
const u8Array = new Uint8Array(4);

Expand Down Expand Up @@ -100,6 +172,22 @@ function from_bytes_64(bitArray, littleEndian) {
return view.getFloat64(0, littleEndian);
}

export function to_bytes_16_le(f) {
return to_bytes_16(f, true);
}

export function from_bytes_16_le(f) {
return from_bytes_16(f, true);
}

export function to_bytes_16_be(f) {
return to_bytes_16(f, false);
}

export function from_bytes_16_be(f) {
return from_bytes_16(f, false);
}

export function to_bytes_32_le(f) {
return to_bytes_32(f, true);
}
Expand Down
29 changes: 29 additions & 0 deletions test/ieee_float_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,35 @@ pub fn parse_test() {
test_parse("1", nan())
}

pub fn fp16_bytes_serde_test() {
[
#([0x00, 0x00], finite(0.0)),
#([0x3C, 0x00], finite(1.0)),
#([0xBC, 0x00], finite(-1.0)),
#([0x3C, 0xF0], finite(1.234375)),
#([0xFB, 0xFF], finite(-65_504.0)),
#([0x80, 0xFF], finite(-0.00001519918441772461)),
#([0x7C, 0x00], positive_infinity()),
#([0xFC, 0x00], negative_infinity()),
#([0x7E, 0x00], nan()),
]
|> test_ieee_bytes_serde(
ieee_float.from_bytes_16_be,
ieee_float.to_bytes_16_be,
ieee_float.from_bytes_16_le,
ieee_float.to_bytes_16_le,
)

// Check overly large values round to infinity
finite(1_000_000.0)
|> ieee_float.to_bytes_16_be
|> expect.to_equal(<<0x7C, 0x00>>)

finite(-1_000_000.0)
|> ieee_float.to_bytes_16_be
|> expect.to_equal(<<0xFC, 0x00>>)
}

pub fn fp32_bytes_serde_test() {
[
#([0x00, 0x00, 0x00, 0x00], finite(0.0)),
Expand Down

0 comments on commit 6ae3bed

Please sign in to comment.