Precision-preserving number decode and conversion (#211)
This is part of dCBOR support. It decodes a number (int or float) into the best C representation. It's good for more than just dCBOR.
* Remove more stuff related to QCBOREncode_AddBytesLenOnly
* Commit beginnings so dev can be merged in
* Precision-preserving number decode
* Fix ifdefs for precise number decoding
* blank lines
* blank lines
* 65-bit negs without float HW
---------
Co-authored-by: Laurence Lundblade <lgl@securitytheory.com>
diff --git a/src/ieee754.c b/src/ieee754.c
index 002ca40..69bf113 100644
--- a/src/ieee754.c
+++ b/src/ieee754.c
@@ -191,6 +191,7 @@
* This returns the bits for a single-precision float, a binary64
* as specified in IEEE754.
*/
+// TODO: make the sign and exponent type int?
static double
IEEE754_AssembleDouble(uint64_t uDoubleSign,
uint64_t uDoubleSignificand,
@@ -644,27 +645,40 @@
}
+
+/* This returns 64 minus the number of zero bits on the right. It is
+ * is the amount of precision in the 64-bit significand passed in.
+ * When used for 52 and 23-bit significands, subtract 12 and 41
+ * to get their precision.
+ *
+ * The value returned is for a *normalized* number like the
+ * significand of a double. When used for precision for a non-normalized
+ * number like a uint64_t, further computation is required.
+ *
+ * If the significand is 0, then 0 is returned as the precision.*/
static int
-IEEE754_Private_CountNonZeroBits(int nMax, uint64_t uTarget)
+IEEE754_Private_CountPrecisionBits(uint64_t uSignigicand)
{
int nNonZeroBitsCount;
uint64_t uMask;
- for(nNonZeroBitsCount = nMax; nNonZeroBitsCount > 0; nNonZeroBitsCount--) {
- uMask = (0x01UL << nMax) >> nNonZeroBitsCount;
- if(uMask & uTarget) {
+ for(nNonZeroBitsCount = 64; nNonZeroBitsCount > 0; nNonZeroBitsCount--) {
+ uMask = 0x01UL << (64 - nNonZeroBitsCount);
+ if(uMask & uSignigicand) {
break;
}
}
+
return nNonZeroBitsCount;
}
+
/* Public function; see ieee754.h */
struct IEEE754_ToInt
IEEE754_DoubleToInt(const double d)
{
- int64_t nNonZeroBitsCount;
+ int64_t nPrecisionBits;
struct IEEE754_ToInt Result;
uint64_t uInteger;
@@ -700,19 +714,15 @@
/* --- Exponent out of range --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
} else {
- /* Count down from 52 to the number of bits that are not zero in
- * the significand. This counts from the least significant bit
- * until a non-zero bit is found to know if it is a whole
- * number.
- *
- * Conversion only fails when the input is too large or is not a
+ /* Conversion only fails when the input is too large or is not a
* whole number, never because of lack of precision because
* 64-bit integers always have more precision than the 52-bits
* of a double.
*/
- nNonZeroBitsCount = IEEE754_Private_CountNonZeroBits(DOUBLE_NUM_SIGNIFICAND_BITS, uDoubleSignificand);
+ nPrecisionBits = IEEE754_Private_CountPrecisionBits(uDoubleSignificand) -
+ (64-DOUBLE_NUM_SIGNIFICAND_BITS);
- if(nNonZeroBitsCount && nNonZeroBitsCount > nDoubleUnbiasedExponent) {
+ if(nPrecisionBits && nPrecisionBits > nDoubleUnbiasedExponent) {
/* --- Not a whole number --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
} else {
@@ -746,7 +756,7 @@
struct IEEE754_ToInt
IEEE754_SingleToInt(const float f)
{
- int32_t nNonZeroBitsCount;
+ int32_t nPrecisionBits;
struct IEEE754_ToInt Result;
uint64_t uInteger;
@@ -781,18 +791,15 @@
/* --- Exponent out of range --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
} else {
- /* Count down from 23 to the number of bits that are not zero in
- * the significand. This counts from the least significant bit
- * until a non-zero bit is found.
- *
- * Conversion only fails when the input is too large or is not a
+ /* Conversion only fails when the input is too large or is not a
* whole number, never because of lack of precision because
- * 64-bit integers always have more precision than the 52-bits
- * of a double.
+ * 64-bit integers always have more precision than the 23 bits
+ * of a single.
*/
- nNonZeroBitsCount = IEEE754_Private_CountNonZeroBits(SINGLE_NUM_SIGNIFICAND_BITS, uSingleleSignificand);
+ nPrecisionBits = IEEE754_Private_CountPrecisionBits(uSingleleSignificand) -
+ (64 - SINGLE_NUM_SIGNIFICAND_BITS);
- if(nNonZeroBitsCount && nNonZeroBitsCount > nSingleUnbiasedExponent) {
+ if(nPrecisionBits && nPrecisionBits > nSingleUnbiasedExponent) {
/* --- Not a whole number --- */
Result.type = IEEE754_ToInt_NO_CONVERSION;
} else {
@@ -820,6 +827,48 @@
return Result;
}
+
+
+/* Public function; see ieee754.h */
+double
+IEEE754_UintToDouble(const uint64_t uInt, const int uIsNegative)
+{
+ int nDoubleUnbiasedExponent;
+ uint64_t uDoubleSignificand;
+ int nPrecisionBits;
+
+ /* Figure out the exponent and normalize the significand. This is
+ * done by shifting out all leading zero bits and counting them. If
+ * none are shifted out, the exponent is 63. */
+ uDoubleSignificand = uInt;
+ nDoubleUnbiasedExponent = 63;
+ while(1) {
+ if(uDoubleSignificand & 0x8000000000000000UL) {
+ break;
+ }
+ uDoubleSignificand <<= 1;
+ nDoubleUnbiasedExponent--;
+ };
+
+ /* Position significand correctly for a double. Only shift 63 bits
+ * because of the 1 that is present by implication in IEEE 754.*/
+ uDoubleSignificand >>= 63 - DOUBLE_NUM_SIGNIFICAND_BITS;
+
+ /* Subtract 1 which is present by implication in IEEE 754 */
+ uDoubleSignificand -= 1ULL << (DOUBLE_NUM_SIGNIFICAND_BITS);
+
+ nPrecisionBits = IEEE754_Private_CountPrecisionBits(uInt) - (64 - nDoubleUnbiasedExponent);
+
+ if(nPrecisionBits > DOUBLE_NUM_SIGNIFICAND_BITS) {
+ /* Will lose precision if converted */
+ return IEEE754_UINT_TO_DOUBLE_OOB;
+ }
+
+ return IEEE754_AssembleDouble((uint64_t)uIsNegative,
+ uDoubleSignificand,
+ nDoubleUnbiasedExponent);
+}
+
#endif /* QCBOR_DISABLE_PREFERRED_FLOAT */
diff --git a/src/ieee754.h b/src/ieee754.h
index 53ab3eb..c893e6f 100644
--- a/src/ieee754.h
+++ b/src/ieee754.h
@@ -184,6 +184,25 @@
struct IEEE754_ToInt
IEEE754_SingleToInt(float f);
+
+/**
+ * @brief Convert an unsigned integer to a double with no precision loss.
+ *
+ * @param[in] uInt The value to convert.
+ * @param[in] uIsNegative 0 if postive, 1 if negative.
+ *
+ * @returns Either the converted number or 0.5 if no conversion.
+ *
+ * The conversion will fail if the input can not be represented in the
+ * 52 bits or precision that a double has. 0.5 is returned to indicate
+ * no conversion. It is out-of-band from non-error results, because
+ * all non-error results are whole integers.
+ */
+#define IEEE754_UINT_TO_DOUBLE_OOB 0.5
+double
+IEEE754_UintToDouble(uint64_t uInt, int uIsNegative);
+
+
#endif /* ! QCBOR_DISABLE_PREFERRED_FLOAT */
diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c
index bb8d4d4..64818df 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -7042,3 +7042,92 @@
}
#endif /* QCBOR_DISABLE_EXP_AND_MANTISSA */
+
+
+#if !defined(USEFULBUF_DISABLE_ALL_FLOAT) && !defined(QCBOR_DISABLE_PREFERRED_FLOAT)
+/*
+ * Public function, see header qcbor/qcbor_spiffy_decode.h file
+ */
+void
+QCBORDecode_GetNumberConvertPrecisely(QCBORDecodeContext *pMe,
+ QCBORItem *pNumber)
+{
+ QCBORItem Item;
+ struct IEEE754_ToInt ToInt;
+ double d;
+ QCBORError uError;
+
+ if(pMe->uLastError != QCBOR_SUCCESS) {
+ return;
+ }
+
+ uError = QCBORDecode_GetNext(pMe, &Item);
+ if(uError != QCBOR_SUCCESS) {
+ pMe->uLastError = (uint8_t)uError;
+ return;
+ }
+
+ switch(Item.uDataType) {
+ case QCBOR_TYPE_INT64:
+ case QCBOR_TYPE_UINT64:
+ *pNumber = Item;
+ break;
+
+ case QCBOR_TYPE_DOUBLE:
+ ToInt = IEEE754_DoubleToInt(Item.val.dfnum);
+ if(ToInt.type == IEEE754_ToInt_IS_INT) {
+ pNumber->uDataType = QCBOR_TYPE_INT64;
+ pNumber->val.int64 = ToInt.integer.is_signed;
+ } else if(ToInt.type == IEEE754_ToInt_IS_UINT) {
+ if(ToInt.integer.un_signed <= INT64_MAX) {
+ /* Do the same as base QCBOR integer decoding */
+ pNumber->uDataType = QCBOR_TYPE_INT64;
+ pNumber->val.int64 = (int64_t)ToInt.integer.un_signed;
+ } else {
+ pNumber->uDataType = QCBOR_TYPE_UINT64;
+ pNumber->val.uint64 = ToInt.integer.un_signed;
+ }
+ } else {
+ *pNumber = Item;
+ }
+ break;
+
+ case QCBOR_TYPE_FLOAT:
+ ToInt = IEEE754_SingleToInt(Item.val.fnum);
+ if(ToInt.type == IEEE754_ToInt_IS_INT) {
+ pNumber->uDataType = QCBOR_TYPE_INT64;
+ pNumber->val.int64 = ToInt.integer.is_signed;
+ } else if(ToInt.type == IEEE754_ToInt_IS_UINT) {
+ if(ToInt.integer.un_signed <= INT64_MAX) {
+ /* Do the same as base QCBOR integer decoding */
+ pNumber->uDataType = QCBOR_TYPE_INT64;
+ pNumber->val.int64 = (int64_t)ToInt.integer.un_signed;
+ } else {
+ pNumber->uDataType = QCBOR_TYPE_UINT64;
+ pNumber->val.uint64 = ToInt.integer.un_signed;
+ }
+ } else {
+ *pNumber = Item;
+ }
+ break;
+
+
+ case QCBOR_TYPE_65BIT_NEG_INT:
+ d = IEEE754_UintToDouble(Item.val.uint64, 1);
+ if(d == IEEE754_UINT_TO_DOUBLE_OOB) {
+ *pNumber = Item;
+ } else {
+ pNumber->uDataType = QCBOR_TYPE_DOUBLE;
+ /* -1 is because of CBOR offset of negative numbers */
+ pNumber->val.dfnum = d - 1;
+ }
+ break;
+
+ default:
+ pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
+ pNumber->uDataType = QCBOR_TYPE_NONE;
+ break;
+ }
+}
+
+#endif /* ! USEFULBUF_DISABLE_ALL_FLOAT && ! QCBOR_DISABLE_PREFERRED_FLOAT */