Precision-preserving number decode
diff --git a/inc/qcbor/qcbor_spiffy_decode.h b/inc/qcbor/qcbor_spiffy_decode.h
index 08e1eaa..d29ebb2 100644
--- a/inc/qcbor/qcbor_spiffy_decode.h
+++ b/inc/qcbor/qcbor_spiffy_decode.h
@@ -300,77 +300,79 @@
                                    int64_t            *pnValue);
 
 /**
- * @brief Decode next item into a signed 64-bit integer with basic conversions.
+ * @brief Decode next as a number, with precision-preserving conversions.
  *
  * @param[in] pCtx           The decode context.
- * @param[in] uConvertTypes  The integer conversion options.
  * @param[out] pNumber       The returned 64-bit signed integer.
  *
  * This will get the next item as a number and return it as a C
  * data type such that no precision is lost.
  *
- * Basic version takes type 0, type 1 and type 7 inputs and outputs
- * int64_t, uint64_t, double plus 65-bit negative. This gives 64-bit
- * precision for integers and 52-bit for fractions. This will try to
- * fit int64_t, then uint64_t then double, then 65-bit negative. Note that
- * this will convert doubles to ints if they are whole numbers.
+ * The CBOR input can be integers (major type 0 or 1) or floats (major type 7).
+ * If not these \ref QCBOR_ERR_UNEXPECTED_TYPE will be set.
  *
- * Advanced adds decoding of big nums and big floats and
- * can output bignums and big floats. This gives arbitrary
- * precision and range with exponents in an int64_t. This will try to
- * fit into int64_t, then uint64_t, then double, then big num,
- * then big float.
+ * The conversion is as follows.
  *
- * The precedence is:
- * - int64_t Whole numbers between INT64_MIN and INT64_MAX
- * - uint64_t Whole numbers between INT64_MAX and UINT64_MAX
- * - double Fractions; whole numbers from UINT64_MAX to
+ * Whole numbers between INT64_MIN and INT64_MAX will
+ * be returned as \ref QCBOR_TYPE_INT64. This includes
+ * conversion of float values that are whole number.
  *
- * - big num
- * - big float
+ * Whole numbers between INT64_MAX and UINT64_MAX will
+ * be returned as \ref QCBOR_TYPE_UINT64, again including
+ * conversion of whole floating-point numbers.
  *
- * The encoded number may be type 0 or type 1, half, single or double float,
- * big number or big float (probably should do decimal fraction too).
+ * Whole numbers between -2^63 - 1 to -2^64 that have more than
+ * 52-bit precision will be returned as \ref QCBOR_TYPE_65BIT_NEG_INT.
+ * That is to avoid returning QCBOR_TYPE_65BIT_NEG_INT, which is
+ * awkward to work with in C these far-from-zero negative whole
+ * numbers will be converted to double floating point. QCBOR_TYPE_65BIT_NEG_INT
+ * is only returned when the value can't convert to double because it
+ * has more than 53-bits of precision.
+ *
+ * All others are returned as a double.
  *
  * This is useful for dCBOR where floats are converted to ints when they are whole numbers.
  *
- * @c uConvertTypes controls what conversions this will perform and
- * thus what CBOR types will be decoded.  @c uConvertType is a bit map
- * listing the conversions to be allowed. This function supports
- * @ref QCBOR_CONVERT_TYPE_XINT64 and @ref QCBOR_CONVERT_TYPE_FLOAT
- * conversions.
- *
  * Please see @ref Decode-Errors-Overview "Decode Errors Overview".
  *
- * If the CBOR data type can never be convered by this function or the
- * conversion was not selected in @c uConversionTypes
- * @ref QCBOR_ERR_UNEXPECTED_TYPE is set.
- *
- * When converting floating-point values, the integer is rounded to
- * the nearest integer using llround(). By default, floating-point
- * suport is enabled for QCBOR.
- *
+ * TODO: is this right?
  * If floating-point HW use is disabled this will set
  * @ref QCBOR_ERR_HW_FLOAT_DISABLED if a single-precision number is
  * encountered. If half-precision support is disabled, this will set
  * @ref QCBOR_ERR_HALF_PRECISION_DISABLED if a half-precision number
  * is encountered.
- *
+ * * TODO: is this right?
  * If floating-point usage is disabled this will set
  * @ref QCBOR_ERR_ALL_FLOAT_DISABLED if a floating point value is
  * encountered.
  *
- * See also QCBORDecode_GetInt64ConvertAll() which will perform the
- * same conversions as this and a lot more at the cost of adding more
- * object code to your executable.
+ * See also QCBORDecode_GetNumberConvertPreciselyBig().
  */
-static void
+void
 QCBORDecode_GetNumberConvertPrecisely(QCBORDecodeContext *pCtx,
                                       QCBORItem          *pNumber);
 
-
-static void
+/**
+ * @brief Decode next item into a big number or big float.
+ *
+ * @param[in] pCtx           The decode context.
+ * @param[in] Buffer   The buffer for the big number or mantissa.
+ * @param[out] pNumber       The returned 64-bit signed integer.
+ *
+ * This outputs a big number QCBOR_TYPE_POSBIGNUM or QCBOR_TYPE_NEGBIGNUM when the input is a whole number
+ * and a big float QCBOR_TYPE_BIGFLOAT_POS_BIGNUM or QCBOR_TYPE_BIGFLOAT_NEG_BIGNUM when the input is not.  This can represent any
+ * number with no precision loss. The only limitation is that the
+ * exponent is a int64_t and the size of @c Buffer.
+ *
+ * Note that this will never return \ref QCBOR_TYPE_INT64 on the assumption
+ * that the caller is committed to supporting big numbers and big floats.
+ *
+ * The CBOR inputs to this can be integers (major types 0 and 1), floating-point,
+ * big numbers and big floats (eventually decimal fractions may also be supported).
+ */
+void
 QCBORDecode_GetNumberConvertPreciselyBig(QCBORDecodeContext *pCtx,
+                                         UsefulBuf           Buffer,
                                          QCBORItem          *pNumber);
 
 /**
diff --git a/src/qcbor_decode.c b/src/qcbor_decode.c
index 090eb38..bf97722 100644
--- a/src/qcbor_decode.c
+++ b/src/qcbor_decode.c
@@ -7037,11 +7037,29 @@
 #endif /* QCBOR_DISABLE_EXP_AND_MANTISSA */
 
 
+static double
+NegToD(uint64_t uNegInt)
+{
+   double d;
+   /* Improvement: do this with shifts and masks so it doesn't get disabled when float does */
+
+   /* Subtraction must be after the cast to avoid overflow. */
+   d = (double)uNegInt - 1.0;
+   if(d + 1 == (double)uNegInt) { /* Make sure it is a whole number */
+      return -d;
+   }
+
+   return NAN;
+}
+
+
 void
 QCBORDecode_GetNumberConvertPrecisely(QCBORDecodeContext *pMe,
                                       QCBORItem          *pNumber)
 {
    QCBORItem Item;
+   struct IEEE754_ToInt ToInt;
+   double d;
 
    if(pMe->uLastError != QCBOR_SUCCESS) {
       return;
@@ -7054,53 +7072,50 @@
    }
 
    switch(Item.uDataType) {
-
       case QCBOR_TYPE_INT64:
       case QCBOR_TYPE_UINT64:
          *pNumber = Item;
          break;
 
       case QCBOR_TYPE_DOUBLE:
-         /* TODO: Try to convert to int */
+         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) {
+            pNumber->uDataType = QCBOR_TYPE_UINT64;
+            pNumber->val.uint64 = ToInt.integer.un_signed;
+         } else {
+            *pNumber = Item;
+         }
          break;
 
       case QCBOR_TYPE_65BIT_NEG_INT:
-         /* TODO: Try to convert to double without precision loss */
+         d = NegToD(Item.val.uint64);
+         if(d == NAN) {
+            *pNumber = Item;
+         } else {
+            pNumber->uDataType = QCBOR_TYPE_DOUBLE;
+            pNumber->val.dfnum = d;
+         }
+         break;
 
       default:
          pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
+         pNumber->uDataType = QCBOR_TYPE_NONE;
          break;
-
-
    }
-
-
 }
 
 
 #if 0
-/*
-
-
- case QCBOR_TYPE_65BIT_NEG_INT:
-
- case QCBOR_TYPE_NEGBIGNUM:
- case QCBOR_TYPE_POSBIGNUM:
-    /* TODO: convert to int, uint, double??? */
-
- case QCBOR_TYPE_BIGFLOAT:
-
- case QCBOR_TYPE_BIGFLOAT_NEG_BIGNUM:
- case QCBOR_TYPE_BIGFLOAT_POS_BIGNUM:
- */
-#endif
 
 
 
 void
-Int_To_BigNum(int64_t     n,
+Int_To_BigNum(uint64_t     u,
               UsefulBuf   B,
-              UsefulBufC pBigNum)
+              UsefulBufC  *pBigNum)
 {
    /* This is just a memcpy, though watch out for endianess */
    /* Have to check size */
@@ -7109,10 +7124,11 @@
 }
 
 
+// TODO: error conditions
 void
-Double_To_BigNum(int64_t     n,
+Double_To_BigNum(double     d,
               UsefulBuf   B,
-              UsefulBufC pBigNum)
+              UsefulBufC *pBigNum)
 {
    /* Have to do shift thing to figure out if it is whole number */
    /* Then kind of a copy and shift, possibly with a lot of zeros */
@@ -7123,7 +7139,8 @@
 void
 QCBORDecode_GetNumberConvertBigNum(QCBORDecodeContext *pMe,
                                    UsefulBuf B,
-                                   UsefulBufC pBigNum)
+                                   UsefulBufC *pBigNum,
+                                   bool       *pbIsNegative)
 {
    QCBORItem Item;
 
@@ -7140,23 +7157,106 @@
    switch(Item.uDataType) {
 
       case QCBOR_TYPE_INT64:
+         if(Item.val.int64 < 0) {
+            *pbIsNegative = true;
+            Item.val.uint64 = (uint64_t)(-Item.val.int64);
+         } else {
+            Item.val.uint64 =  (uint64_t)Item.val.int64;
+         }
+         /* FALLTHROUGH */
+
       case QCBOR_TYPE_UINT64:
-         *pNumber = Item;
+         Int_To_BigNum(Item.val.uint64, B, pBigNum);
          break;
 
       case QCBOR_TYPE_DOUBLE:
-         /* TODO: Try to convert to int */
+         Double_To_BigNum(Item.val.dfnum, B, pBigNum);
+         /* TODO: sometimes this will fail */
          break;
 
       case QCBOR_TYPE_65BIT_NEG_INT:
          /* TODO: Try to convert to double without precision loss */
 
+      case QCBOR_TYPE_NEGBIGNUM:
+      case QCBOR_TYPE_POSBIGNUM:
+         *pBigNum = UsefulBuf_Copy(B, Item.val.bigNum);
+         break;
+         // TODO: sign
+
+      case QCBOR_TYPE_BIGFLOAT:
+         // TODO: lots of work
+         // This will fail sometimes
+
+
       default:
          pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
          break;
 
 
    }
-
-
 }
+
+
+/* This can represent a very large range of values! */
+void
+QCBORDecode_GetNumberConvertBigFloat(QCBORDecodeContext *pMe,
+                                   UsefulBuf B,
+                                   UsefulBufC *pBigNum,
+                                   bool       *pbIsNegative,
+                                     int64_t   *nExponent)
+{
+   QCBORItem Item;
+
+   if(pMe->uLastError != QCBOR_SUCCESS) {
+      return;
+   }
+
+   QCBORError uError = QCBORDecode_GetNext(pMe, &Item);
+   if(uError) {
+      pMe->uLastError = (uint8_t)uError;
+      return;
+   }
+
+   switch(Item.uDataType) {
+
+      case QCBOR_TYPE_INT64:
+         if(Item.val.int64 < 0) {
+            *pbIsNegative = true;
+            Item.val.uint64 = (uint64_t)(-Item.val.int64);
+         } else {
+            Item.val.uint64 =  (uint64_t)Item.val.int64;
+         }
+         /* FALLTHROUGH */
+
+      case QCBOR_TYPE_UINT64:
+         Int_To_BigNum(Item.val.uint64, B, pBigNum);
+         break;
+
+      case QCBOR_TYPE_DOUBLE:
+         Double_To_BigNum(Item.val.dfnum, B, pBigNum);
+         /* TODO: sometimes this will fail */
+         break;
+
+      case QCBOR_TYPE_65BIT_NEG_INT:
+         /* TODO: Try to convert to double without precision loss */
+
+      case QCBOR_TYPE_NEGBIGNUM:
+      case QCBOR_TYPE_POSBIGNUM:
+         *pBigNum = UsefulBuf_Copy(B, Item.val.bigNum);
+         break;
+         // TODO: sign
+
+      case QCBOR_TYPE_BIGFLOAT:
+         // TODO: lots of work
+         // This will fail sometimes
+
+
+      default:
+         pMe->uLastError = QCBOR_ERR_UNEXPECTED_TYPE;
+         break;
+
+
+   }
+}
+
+#endif
diff --git a/test/qcbor_decode_tests.c b/test/qcbor_decode_tests.c
index c8ef698..39a0f7e 100644
--- a/test/qcbor_decode_tests.c
+++ b/test/qcbor_decode_tests.c
@@ -8614,3 +8614,99 @@
 
    return 0;
 }
+
+
+struct PreciseNumberConversion {
+   char       *szDescription;
+   UsefulBufC  CBOR;
+   QCBORError  uError;
+   uint8_t     qcborType;
+   struct foo {
+      int64_t  int64;
+      uint64_t  uint64;
+      double   d;
+   } number;
+};
+
+
+
+static const struct PreciseNumberConversion PreciseNumberConversions[] = {
+   {
+      "Zero",
+      {"\x00", 1},
+      QCBOR_SUCCESS,
+      QCBOR_TYPE_INT64,
+      {0, 0, 0}
+   },
+   {
+      "Pi",
+      {"\xFB\x40\x09\x2A\xDB\x40\x2D\x16\xB9", 9},
+      QCBOR_SUCCESS,
+      QCBOR_TYPE_DOUBLE,
+      {0, 0, 3.145926}
+   },
+   {
+      "String",
+      {"\x60", 1},
+      QCBOR_ERR_UNEXPECTED_TYPE,
+      QCBOR_TYPE_NONE,
+      {0, 0, 0}
+   }
+
+};
+
+int32_t
+PreciseTest(void)
+{
+   int i;
+   const struct PreciseNumberConversion *pTest;
+   QCBORError uErr;
+   QCBORItem Item;
+   QCBORDecodeContext DCtx;
+
+   const int count = (int)C_ARRAY_COUNT(PreciseNumberConversions, struct PreciseNumberConversion);
+
+   for(i = 0; i < count; i++) {
+      pTest = &PreciseNumberConversions[i];
+
+      QCBORDecode_Init(&DCtx, pTest->CBOR, 0);
+
+      QCBORDecode_GetNumberConvertPrecisely(&DCtx, &Item);
+
+      uErr = QCBORDecode_GetError(&DCtx);
+
+      if(uErr != pTest->uError) {
+         return i * 1000 + (int)uErr;
+      }
+
+      if(pTest->qcborType != Item.uDataType) {
+         return i * 1000 + 200;
+      }
+
+      if(pTest->qcborType == QCBOR_TYPE_NONE) {
+         continue;
+      }
+
+      switch(pTest->qcborType) {
+         case QCBOR_TYPE_INT64:
+            if(Item.val.int64 != pTest->number.int64) {
+               return i * 1000 + 300;
+            }
+            break;
+
+         case QCBOR_TYPE_UINT64:
+            if(Item.val.uint64 != pTest->number.uint64) {
+               return i * 1000 + 400;
+            }
+            break;
+
+         case QCBOR_TYPE_DOUBLE:
+            if(Item.val.dfnum != pTest->number.d) {
+               return i * 1000 + 500;
+            }
+            break;
+      }
+   }
+
+   return 0;
+}
diff --git a/test/qcbor_decode_tests.h b/test/qcbor_decode_tests.h
index 11fdc94..77f0bfc 100644
--- a/test/qcbor_decode_tests.h
+++ b/test/qcbor_decode_tests.h
@@ -318,4 +318,8 @@
 */
 int32_t CBORTestIssue134(void);
 
+
+int32_t
+PreciseTest(void);
+
 #endif /* defined(__QCBOR__qcbort_decode_tests__) */
diff --git a/test/run_tests.c b/test/run_tests.c
index bfc9dd9..1b064af 100644
--- a/test/run_tests.c
+++ b/test/run_tests.c
@@ -153,6 +153,7 @@
    TEST_ENTRY(CDETest),
    TEST_ENTRY(DCBORTest),
 #endif /* ! USEFULBUF_DISABLE_ALL_FLOAT && ! QCBOR_DISABLE_PREFERRED_FLOAT */
+   TEST_ENTRY(PreciseTest),
    TEST_ENTRY(ParseEmptyMapInMapTest),
 
 };