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 */