enhancement #149 implemented: access half-precision floating point data as single float

Motivation: half-precision floating point format is used to minimize storage
and traffic mostly.  Application level manipulates with single and double
precision usually. So, two routines added to public API to encode/decode given
single precision value in the half precision format

Signed-off-by: S.Phirsov
Signed-off-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
phirsov
2021-09-03 12:00:07 -07:00
committed by Thiago Macieira
parent cb372527df
commit 814ec02bb5
10 changed files with 370 additions and 13 deletions
+2
View File
@@ -26,7 +26,9 @@ TINYCBOR_FREESTANDING_SOURCES = \
src/cborerrorstrings.c \
src/cborencoder.c \
src/cborencoder_close_container_checked.c \
src/cborencoder_float.c \
src/cborparser.c \
src/cborparser_float.c \
src/cborpretty.c \
#
CBORDUMP_SOURCES = tools/cbordump/cbordump.c
+4
View File
@@ -5,8 +5,10 @@ TINYCBOR_SOURCES = \
src\cborerrorstrings.c \
src\cborencoder.c \
src\cborencoder_close_container_checked.c \
src\cborencoder_float.c \
src\cborparser.c \
src\cborparser_dup_string.c \
src\cborparser_float.c \
src\cborpretty.c \
src\cborpretty_stdio.c \
src\cborvalidation.c
@@ -14,8 +16,10 @@ TINYCBOR_OBJS = \
src\cborerrorstrings.obj \
src\cborencoder.obj \
src\cborencoder_close_container_checked.obj \
src\cborencoder_float.obj \
src\cborparser.obj \
src\cborparser_dup_string.obj \
src\cborparser_float.obj \
src\cborpretty.obj \
src\cborpretty_stdio.obj \
src\cborvalidation.obj
+3
View File
@@ -237,6 +237,7 @@ CBOR_INLINE_API CborError cbor_encode_undefined(CborEncoder *encoder)
CBOR_INLINE_API CborError cbor_encode_half_float(CborEncoder *encoder, const void *value)
{ return cbor_encode_floating_point(encoder, CborHalfFloatType, value); }
CBOR_API CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value);
CBOR_INLINE_API CborError cbor_encode_float(CborEncoder *encoder, float value)
{ return cbor_encode_floating_point(encoder, CborFloatType, &value); }
CBOR_INLINE_API CborError cbor_encode_double(CborEncoder *encoder, double value)
@@ -493,7 +494,9 @@ CBOR_API CborError cbor_value_map_find_value(const CborValue *map, const char *s
/* Floating point */
CBOR_INLINE_API bool cbor_value_is_half_float(const CborValue *value)
{ return value->type == CborHalfFloatType; }
CBOR_PRIVATE_API uint16_t _cbor_value_get_half_float_helper(const CborValue *value);
CBOR_API CborError cbor_value_get_half_float(const CborValue *value, void *result);
CBOR_API CborError cbor_value_get_half_float_as_float(const CborValue *value, float *result);
CBOR_INLINE_API bool cbor_value_is_float(const CborValue *value)
{ return value->type == CborFloatType; }
+15 -3
View File
@@ -382,7 +382,7 @@ CborError cbor_encode_simple_value(CborEncoder *encoder, uint8_t value)
* This function is useful for code that needs to pass through floating point
* values but does not wish to have the actual floating-point code.
*
* \sa cbor_encode_half_float, cbor_encode_float, cbor_encode_double
* \sa cbor_encode_half_float, cbor_encode_float_as_half_float, cbor_encode_float, cbor_encode_double
*/
CborError cbor_encode_floating_point(CborEncoder *encoder, CborType fpType, const void *value)
{
@@ -589,13 +589,25 @@ CborError cbor_encoder_close_container(CborEncoder *parentEncoder, const CborEnc
* \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double()
*/
/**
* \fn CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value)
*
* Convert the IEEE 754 single-precision (32-bit) floating point value \a value
* to the IEEE 754 half-precision (16-bit) floating point value and append it
* to the CBOR stream provided by \a encoder.
* The \a value should be in the range of the IEEE 754 half-precision floating point type,
* INFINITY, -INFINITY, or NAN, otherwise the behavior of this function is undefined.
*
* \sa cbor_encode_floating_point(), cbor_encode_float(), cbor_encode_double()
*/
/**
* \fn CborError cbor_encode_float(CborEncoder *encoder, float value)
*
* Appends the IEEE 754 single-precision (32-bit) floating point value \a value
* to the CBOR stream provided by \a encoder.
*
* \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_double()
* \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float_as_half_float(), cbor_encode_double()
*/
/**
@@ -604,7 +616,7 @@ CborError cbor_encoder_close_container(CborEncoder *parentEncoder, const CborEnc
* Appends the IEEE 754 double-precision (64-bit) floating point value \a value
* to the CBOR stream provided by \a encoder.
*
* \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float()
* \sa cbor_encode_floating_point(), cbor_encode_half_float(), cbor_encode_float_as_half_float(), cbor_encode_float()
*/
/**
+42
View File
@@ -0,0 +1,42 @@
/****************************************************************************
**
** Copyright (C) 2019 S.Phirsov
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
****************************************************************************/
#define _BSD_SOURCE 1
#define _DEFAULT_SOURCE 1
#ifndef __STDC_LIMIT_MACROS
# define __STDC_LIMIT_MACROS 1
#endif
#include "cbor.h"
#include "cborinternal_p.h"
#ifndef CBOR_NO_HALF_FLOAT_TYPE
CborError cbor_encode_float_as_half_float(CborEncoder *encoder, float value)
{
uint16_t v = (uint16_t)encode_half(value);
return cbor_encode_floating_point(encoder, CborHalfFloatType, &v);
}
#endif
+19 -4
View File
@@ -1433,17 +1433,32 @@ error:
* floating point, this function takes a \c{void *} as a parameter for the
* storage area, which must be at least 16 bits wide.
*
* \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_float()
* \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_half_float_as_float(), cbor_value_get_float()
*/
CborError cbor_value_get_half_float(const CborValue *value, void *result)
{
uint16_t v;
cbor_assert(cbor_value_is_half_float(value));
/* size has been computed already */
v = get16(value->ptr + 1);
v = _cbor_value_get_half_float_helper(value);
memcpy(result, &v, sizeof(v));
return CborNoError;
}
/** \internal
*
* Retrieves the CBOR half-precision floating point value binary
* representation as 16-bit unsigned integer.
* The result can be used as-is, e.g. to copy bitwise into the
* system-dependent half-precision floating point type, or it can be
* converted to the C language standard floating point type
* (float or double).
*/
CBOR_PRIVATE_API uint16_t _cbor_value_get_half_float_helper(const CborValue *value)
{
cbor_assert(cbor_value_is_half_float(value));
/* size has been computed already */
return get16(value->ptr + 1);
}
/** @} */
+54
View File
@@ -0,0 +1,54 @@
/****************************************************************************
**
** Copyright (C) 2019 S.Phirsov
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is
** furnished to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in
** all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
** THE SOFTWARE.
**
****************************************************************************/
#define _BSD_SOURCE 1
#define _DEFAULT_SOURCE 1
#ifndef __STDC_LIMIT_MACROS
# define __STDC_LIMIT_MACROS 1
#endif
#include "cbor.h"
#include "cborinternal_p.h"
#ifndef CBOR_NO_HALF_FLOAT_TYPE
/**
* Retrieves the CBOR half-precision floating point (16-bit) value that \a
* value points to, converts it to the float and store it in \a result.
* If the iterator \a value does not point to a half-precision floating
* point value, the behavior is undefined, so checking with \ref
* cbor_value_get_type or with \ref cbor_value_is_half_float is recommended.
* \sa cbor_value_get_type(), cbor_value_is_valid(), cbor_value_is_half_float(), cbor_value_get_half_float(), cbor_value_get_float()
*/
CborError cbor_value_get_half_float_as_float(const CborValue *value, float *result)
{
uint16_t v;
v = _cbor_value_get_half_float_helper(value);
*result = (float)decode_half((unsigned short)v);
return CborNoError;
}
#endif
+2
View File
@@ -23,9 +23,11 @@
****************************************************************************/
#include "../../src/cborencoder.c"
#include "../../src/cborencoder_float.c"
#include "../../src/cborerrorstrings.c"
#include "../../src/cborparser.c"
#include "../../src/cborparser_dup_string.c"
#include "../../src/cborparser_float.c"
#include "../../src/cborvalidation.c"
#include <QtTest>
+153 -6
View File
@@ -41,6 +41,13 @@ class tst_Encoder : public QObject
{
Q_OBJECT
private slots:
void floatAsHalfFloat_data();
void floatAsHalfFloat();
void halfFloat_data();
void halfFloat();
void floatAsHalfFloatCloseToZero_data();
void floatAsHalfFloatCloseToZero();
void floatAsHalfFloatNaN();
void fixed_data();
void fixed();
void strings_data();
@@ -272,21 +279,40 @@ CborError encodeVariant(CborEncoder *encoder, const QVariant &v)
return CborErrorUnknownType;
}
void compare(const QVariant &input, const QByteArray &output)
template <typename Input, typename FnUnderTest>
void encodeOne(Input input, FnUnderTest fn_under_test, QByteArray &buffer, CborError &error)
{
QByteArray buffer(output.length(), Qt::Uninitialized);
uint8_t *bufptr = reinterpret_cast<quint8 *>(buffer.data());
CborEncoder encoder;
cbor_encoder_init(&encoder, bufptr, buffer.length(), 0);
QCOMPARE(encodeVariant(&encoder, input), CborNoError);
QCOMPARE(encoder.remaining, size_t(1));
QCOMPARE(cbor_encoder_get_extra_bytes_needed(&encoder), size_t(0));
error = fn_under_test(&encoder, input);
buffer.resize(int(cbor_encoder_get_buffer_size(&encoder, bufptr)));
if (error == CborNoError) {
QCOMPARE(encoder.remaining, size_t(1));
QCOMPARE(cbor_encoder_get_extra_bytes_needed(&encoder), size_t(0));
buffer.resize(int(cbor_encoder_get_buffer_size(&encoder, bufptr)));
}
}
template <typename Input, typename FnUnderTest>
void compare(Input input, FnUnderTest fn_under_test, const QByteArray &output)
{
QByteArray buffer(output.length(), Qt::Uninitialized);
CborError error;
encodeOne(input, fn_under_test, buffer, error);
QCOMPARE(error, CborNoError);
QCOMPARE(buffer, output);
}
void compare(const QVariant &input, const QByteArray &output)
{
compare(input, encodeVariant, output);
}
void addColumns()
{
QTest::addColumn<QByteArray>("output");
@@ -472,6 +498,127 @@ void addArraysAndMaps()
QTest::newRow("map-1(2):3(4)") << raw("\xa1\xc1\2\xc3\4") << make_map({{QVariant::fromValue(Tag{1, 2}), QVariant::fromValue(Tag{3, 4})}});
}
static void addHalfFloat()
{
QTest::addColumn<QByteArray>("output");
QTest::addColumn<unsigned>("rawInput");
QTest::addColumn<double>("floatInput");
QTest::newRow("+0") << raw("\x00\x00") << 0U << 0.0;
QTest::newRow("-0") << raw("\x80\x00") << 0x8000U << 0.0;
QTest::newRow("min.denorm") << raw("\x00\x01") << 1U << ldexp(1.0, -14) * ldexp(1.0, -10);
QTest::newRow("-min.denorm") << raw("\x80\x01") << 0x8001U << ldexp(-1.0, -14) * ldexp(1.0, -10);
QTest::newRow("max.denorm") << raw("\x03\xff") << 0x03ffU << ldexp(1.0, -14) * (1.0 - ldexp(1.0, -10));
QTest::newRow("-max.denorm") << raw("\x83\xff") << 0x83ffU << ldexp(-1.0, -14) * (1.0 - ldexp(1.0, -10));
QTest::newRow("min.norm") << raw("\x04\x00") << 0x0400U << ldexp(1.0, -14);
QTest::newRow("-min.norm") << raw("\x84\x00") << 0x8400U << ldexp(-1.0, -14);
QTest::newRow("1.0") << raw("\x3c\x00") << 0x3c00U << 1.0;
QTest::newRow("-1.0") << raw("\xbc\x00") << 0xbc00U << -1.0;
QTest::newRow("1.5") << raw("\x3e\x00") << 0x3e00U << 1.5;
QTest::newRow("-1.5") << raw("\xbe\x00") << 0xbe00U << -1.5;
QTest::newRow("max") << raw("\x7b\xff") << 0x7bffU << ldexp(1.0, 15) * (2.0 - ldexp(1.0, -10));
QTest::newRow("-max") << raw("\xfb\xff") << 0xfbffU << ldexp(-1.0, 15) * (2.0 - ldexp(1.0, -10));
QTest::newRow("inf") << raw("\x7c\x00") << 0x7c00U << myInf();
QTest::newRow("-inf") << raw("\xfc\x00") << 0xfc00U << myNInf();
QTest::newRow("nan1") << raw("\x7c\x01") << 0x7c01U << myNaN();
QTest::newRow("nan2") << raw("\xfc\x01") << 0xfc01U << myNaN();
QTest::newRow("nan3") << raw("\x7e\x00") << 0x7e00U << myNaN();
QTest::newRow("nan4") << raw("\xfe\x00") << 0xfe00U << myNaN();
}
void tst_Encoder::floatAsHalfFloat_data()
{
addHalfFloat();
}
void tst_Encoder::floatAsHalfFloat()
{
QFETCH(unsigned, rawInput);
QFETCH(double, floatInput);
QFETCH(QByteArray, output);
if (rawInput == 0U || rawInput == 0x8000U)
QSKIP("zero values are out of scope of this test case", QTest::SkipSingle);
if (qIsNaN(floatInput))
QSKIP("NaN values are out of scope of this test case", QTest::SkipSingle);
output.prepend('\xf9');
compare((float)floatInput, cbor_encode_float_as_half_float, output);
}
void tst_Encoder::halfFloat_data()
{
addHalfFloat();
}
void tst_Encoder::halfFloat()
{
QFETCH(unsigned, rawInput);
QFETCH(QByteArray, output);
uint16_t v = (uint16_t)rawInput;
output.prepend('\xf9');
compare(&v, cbor_encode_half_float, output);
}
void tst_Encoder::floatAsHalfFloatCloseToZero_data()
{
QTest::addColumn<double>("floatInput");
QTest::newRow("+0") << 0.0;
QTest::newRow("-0") << -0.0;
QTest::newRow("below min.denorm") << ldexp(1.0, -14) * ldexp(1.0, -11);
QTest::newRow("above -min.denorm") << ldexp(-1.0, -14) * ldexp(1.0, -11);
}
void tst_Encoder::floatAsHalfFloatCloseToZero()
{
QFETCH(double, floatInput);
QByteArray buffer(4, Qt::Uninitialized);
CborError error;
encodeOne((float)floatInput, cbor_encode_float_as_half_float, buffer, error);
QCOMPARE(error, CborNoError);
QVERIFY2(
buffer == raw("\xf9\x00\x00") || buffer == raw("\xf9\x80\x00"),
"Got value " + QByteArray::number(floatInput) + " encoded to: " + buffer);
}
void tst_Encoder::floatAsHalfFloatNaN()
{
QByteArray buffer(4, Qt::Uninitialized);
CborError error;
encodeOne(myNaNf(), cbor_encode_float_as_half_float, buffer, error);
QCOMPARE(error, CborNoError);
QCOMPARE(buffer.size(), 3);
uint8_t ini_byte = (uint8_t)buffer[0],
exp = (uint8_t)buffer[1] & 0x7cU,
manth = (uint8_t)buffer[1] & 0x03U,
mantl = (uint8_t)buffer[2];
QCOMPARE((unsigned)ini_byte, 0xf9U);
QCOMPARE((unsigned)exp, 0x7cU);
QVERIFY((manth | mantl) != 0);
}
void tst_Encoder::fixed_data()
{
addColumns();
+76
View File
@@ -56,6 +56,8 @@ private slots:
// parsing API
void integers_data();
void integers();
void halfFloat_data();
void halfFloat();
void fixed_data();
void fixed();
void strings_data();
@@ -495,6 +497,80 @@ void tst_Parser::integers()
QCOMPARE(err, inIntRange ? CborNoError : CborErrorDataTooLarge);
}
static void addHalfFloat()
{
QTest::addColumn<QByteArray>("data");
QTest::addColumn<unsigned>("expectedRaw");
QTest::addColumn<double>("expectedValue");
QTest::newRow("+0") << raw("\x00\x00") << 0U << 0.0;
QTest::newRow("-0") << raw("\x80\x00") << 0x8000U << 0.0;
QTest::newRow("min.denorm") << raw("\x00\x01") << 1U << ldexp(1.0, -14) * ldexp(1.0, -10);
QTest::newRow("-min.denorm") << raw("\x80\x01") << 0x8001U << ldexp(-1.0, -14) * ldexp(1.0, -10);
QTest::newRow("max.denorm") << raw("\x03\xff") << 0x03ffU << ldexp(1.0, -14) * (1.0 - ldexp(1.0, -10));
QTest::newRow("-max.denorm") << raw("\x83\xff") << 0x83ffU << ldexp(-1.0, -14) * (1.0 - ldexp(1.0, -10));
QTest::newRow("min.norm") << raw("\x04\x00") << 0x0400U << ldexp(1.0, -14);
QTest::newRow("-min.norm") << raw("\x84\x00") << 0x8400U << ldexp(-1.0, -14);
QTest::newRow("1.0") << raw("\x3c\x00") << 0x3c00U << 1.0;
QTest::newRow("-1.0") << raw("\xbc\x00") << 0xbc00U << -1.0;
QTest::newRow("1.5") << raw("\x3e\x00") << 0x3e00U << 1.5;
QTest::newRow("-1.5") << raw("\xbe\x00") << 0xbe00U << -1.5;
QTest::newRow("max") << raw("\x7b\xff") << 0x7bffU << ldexp(1.0, 15) * (2.0 - ldexp(1.0, -10));
QTest::newRow("-max") << raw("\xfb\xff") << 0xfbffU << ldexp(-1.0, 15) * (2.0 - ldexp(1.0, -10));
QTest::newRow("inf") << raw("\x7c\x00") << 0x7c00U << double(INFINITY);
QTest::newRow("-inf") << raw("\xfc\x00") << 0xfc00U << double(-INFINITY);
QTest::newRow("nan") << raw("\x7c\x01") << 0x7c01U << double(NAN);
QTest::newRow("nan2") << raw("\xfc\x01") << 0xfc01U << double(NAN);
QTest::newRow("nan3") << raw("\x7e\x00") << 0x7e00U << double(NAN);
QTest::newRow("nan4") << raw("\xfe\x00") << 0xfe00U << double(NAN);
}
void tst_Parser::halfFloat_data()
{
addHalfFloat();
}
void tst_Parser::halfFloat()
{
QFETCH(QByteArray, data);
QFETCH(unsigned, expectedRaw);
QFETCH(double, expectedValue);
CborParser parser;
CborValue first;
data.prepend('\xf9');
CborError err = cbor_parser_init(reinterpret_cast<const quint8 *>(data.constData()), data.length(), 0, &parser, &first);
QVERIFY2(!err, QByteArray("Got error \"") + cbor_error_string(err) + "\"");
QVERIFY(cbor_value_is_half_float(&first));
uint16_t raw;
cbor_value_get_half_float(&first, &raw);
QCOMPARE(raw, uint16_t(expectedRaw));
float value;
cbor_value_get_half_float_as_float(&first, &value);
const double epsilon = ldexp(1.0, -25);
if (qIsNaN(expectedValue)) {
QVERIFY(qIsNaN(value));
} else if (qIsInf(expectedValue)) {
QVERIFY(value == (float)expectedValue);
} else {
QVERIFY(qAbs(value - (float)expectedValue) < epsilon);
}
}
void tst_Parser::fixed_data()
{
addColumns();