Skip to content

Commit dd9ed14

Browse files
authored
Merge pull request #426 from YoYoGames/gmb-4442-buffer-funcs-f16-support
Gmb 4442 buffer funcs f16 support
2 parents 3521c41 + 7f50309 commit dd9ed14

File tree

1 file changed

+152
-0
lines changed

1 file changed

+152
-0
lines changed

scripts/yyBuffer.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,141 @@ function yyBuffer( _size, _type, _alignment, _srcbytebuff ) {
244244
}
245245
}
246246

247+
248+
// #################################################################################################
249+
/// Function: yygetFloat16
250+
/// Description:
251+
/// Retrieve and decode a 16-bit floating-point number from a DataView.
252+
///
253+
/// Parameters:
254+
/// byteOffset - The offset where the 16-bit float is stored.
255+
/// littleEndian - Specifies the endianness of the data (true for little-endian, false for big-endian).
256+
///
257+
/// Returns:
258+
/// The decoded 16-bit floating-point number.
259+
/// If the input data is invalid or out of range, NaN is returned.
260+
// #################################################################################################
261+
DataView.prototype.yygetFloat16 = function (byteOffset, littleEndian) {
262+
// byteOffset: The offset where the 16-bit float is stored
263+
// littleEndian: Specifies the endianness of the data
264+
265+
// Used references: https://en.wikipedia.org/wiki/Half-precision_floating-point_format
266+
267+
// Read the 16-bit float as an unsigned integer
268+
const uint16 = this.getUint16(byteOffset, littleEndian);
269+
270+
// Extract the sign bit (bit 15)
271+
const sign = (uint16 & 0x8000) ? -1 : 1;
272+
273+
// Extract the exponent bits (bits 10-14)
274+
const exponent = (uint16 >> 10) & 0x1F;
275+
276+
// Extract the mantissa bits (bits 0-9)
277+
const mantissa = uint16 & 0x3FF;
278+
279+
if (exponent === 0) {
280+
// If exponent is 0, it's a denormalized number or zero
281+
if (mantissa === 0) {
282+
// Zero
283+
return sign * 0.0;
284+
} else {
285+
// Denormalized number
286+
// Calculate the value using the formula for denormalized numbers
287+
return sign * Math.pow(2, -14) * (mantissa / 1024);
288+
}
289+
} else if (exponent === 31) {
290+
// If exponent is 31, it's infinity or NaN
291+
if (mantissa === 0) {
292+
// Positive or negative infinity
293+
return (sign === 1) ? Infinity : -Infinity;
294+
} else {
295+
// NaN
296+
return NaN;
297+
}
298+
} else {
299+
// Normalized number
300+
// Calculate the value using the formula for normalized numbers
301+
return sign * Math.pow(2, exponent - 15) * (1 + mantissa / 1024);
302+
}
303+
};
304+
305+
// #################################################################################################
306+
/// Function: yysetFloat16
307+
/// Description:
308+
/// Encode and write a 16-bit floating-point number (float16) to a DataView.
309+
///
310+
/// Parameters:
311+
/// offset - The offset where the float16 will be written in the DataView.
312+
/// value - The float16 value to encode and write.
313+
/// littleEndian - Optional. Specifies the endianness of the data (true for little-endian, false for big-endian).
314+
///
315+
/// Returns:
316+
/// None.
317+
///
318+
/// Details:
319+
/// The function encodes the provided float16 value as per IEEE 754-2008 standard for half-precision
320+
/// floating-point numbers and writes it to the specified offset in the DataView. If the value is
321+
/// outside the representable range for float16, a RangeError is thrown.
322+
///
323+
/// Special cases:
324+
/// - If the value is 0, it is written as a positive zero.
325+
/// - If the value is NaN, it is written as the NaN representation.
326+
///
327+
/// Example Usage:
328+
/// const dataView = new DataView(new ArrayBuffer(2));
329+
/// dataView.yysetFloat16(0, 1.0, true); // Write float16 value 1.0 in little-endian
330+
// #################################################################################################
331+
DataView.prototype.yysetFloat16 = function(offset, value, littleEndian = true) {
332+
let sign = 0;
333+
let exponent = 0;
334+
let mantissa = 0;
335+
336+
// Used references: https://en.wikipedia.org/wiki/Half-precision_floating-point_format
337+
if (isNaN(value)) {
338+
// If the value is NaN, use the standard NaN representation for float16.
339+
mantissa = 0x200;
340+
exponent = 0x1F;
341+
} else if (value === Infinity || value === -Infinity) {
342+
// Handling Infinity.
343+
exponent = 0x1F;
344+
} else if (value === 0) {
345+
// Handling zero (both positive and negative).
346+
sign = (1 / value === -Infinity) ? 0x8000 : 0;
347+
} else {
348+
sign = value < 0 ? 0x8000 : 0;
349+
value = Math.abs(value);
350+
351+
if (value >= Math.pow(2, -14)) {
352+
// Handle normal numbers.
353+
let exponentAndMantissa = Math.floor(Math.log2(value) + 15);
354+
exponent = exponentAndMantissa;
355+
mantissa = Math.floor((value / Math.pow(2, exponent - 15) - 1) * 1024);
356+
357+
if (mantissa === 1024) {
358+
// We might end in an exponent overflow state.
359+
exponentAndMantissa += 1;
360+
exponent = exponentAndMantissa;
361+
mantissa = 0;
362+
}
363+
364+
// Check if we overflow into Infinity.
365+
if (exponentAndMantissa > 30) {
366+
// Handle overflow to Infinity.
367+
exponent = 0x1F;
368+
mantissa = 0;
369+
}
370+
} else {
371+
// Handle subnormal numbers.
372+
mantissa = Math.floor(value / Math.pow(2, -24));
373+
}
374+
}
375+
376+
const float16 = sign | (exponent << 10) | mantissa;
377+
this.setUint16(offset, float16, littleEndian);
378+
};
379+
380+
381+
247382
// #############################################################################################
248383
/// Function:<summary>
249384
/// Return the size of a "type"
@@ -470,6 +605,11 @@ yyBuffer.prototype.yyb_read = function(_type) {
470605
res = new Long( this.m_DataView.getUint32(this.m_BufferIndex, true), 0);
471606
this.m_BufferIndex += 4;
472607
break;
608+
609+
case eBuffer_F16:
610+
res = this.m_DataView.yygetFloat16(this.m_BufferIndex, true);
611+
this.m_BufferIndex += 2;
612+
break;
473613
case eBuffer_F32:
474614
res = this.m_DataView.getFloat32(this.m_BufferIndex, true);
475615
this.m_BufferIndex += 4;
@@ -478,6 +618,7 @@ yyBuffer.prototype.yyb_read = function(_type) {
478618
res = this.m_DataView.getFloat64(this.m_BufferIndex, true);
479619
this.m_BufferIndex += 8;
480620
break;
621+
481622
case eBuffer_U64:
482623
var low = this.m_DataView.getUint32(this.m_BufferIndex, true);
483624
this.m_BufferIndex += 4;
@@ -1260,6 +1401,11 @@ yyBuffer.prototype.yyb_write = function(_type, _value) {
12601401
this.m_DataView.setUint32(this.m_BufferIndex, _value, true);
12611402
this.m_BufferIndex += 4;
12621403
break;
1404+
1405+
case eBuffer_F16:
1406+
this.m_DataView.yysetFloat16(this.m_BufferIndex, _value, true);
1407+
this.m_BufferIndex += 2;
1408+
break;
12631409
case eBuffer_F32:
12641410
this.m_DataView.setFloat32(this.m_BufferIndex, _value, true);
12651411
this.m_BufferIndex += 4;
@@ -1268,6 +1414,7 @@ yyBuffer.prototype.yyb_write = function(_type, _value) {
12681414
this.m_DataView.setFloat64(this.m_BufferIndex, _value, true);
12691415
this.m_BufferIndex += 8;
12701416
break;
1417+
12711418
case eBuffer_U64:
12721419
var int64Val = yyGetInt64(_value);
12731420
this.m_DataView.setUint32(this.m_BufferIndex, int64Val.low, true);
@@ -1336,12 +1483,17 @@ yyBuffer.prototype.yyb_peek = function(_type, _offset) {
13361483
case eBuffer_U32:
13371484
res = this.m_DataView.getUint32(_offset, true);
13381485
break;
1486+
1487+
case eBuffer_F16:
1488+
res = this.m_DataView.yygetFloat16(_offset, true);
1489+
break;
13391490
case eBuffer_F32:
13401491
res = this.m_DataView.getFloat32(_offset, true);
13411492
break;
13421493
case eBuffer_F64:
13431494
res = this.m_DataView.getFloat64(_offset, true);
13441495
break;
1496+
13451497
case eBuffer_U64:
13461498
var low = this.m_DataView.getUint32(_offset, true);
13471499
var high = this.m_DataView.getUint32(_offset + 4, true);

0 commit comments

Comments
 (0)