commit 92782e4a2446064991530b71bdd4a1f230dd0137
Author: David Sorber <david.sorber@gmail.com>
Date:   Sun Feb 27 14:59:03 2022 -0500

    Additional improvements to the fixed type. Also added a few good test
    cases.

diff --git a/software/fixed/fixed.h b/software/fixed/fixed.h
index ad2d4ff..bb6c24a 100644
--- a/software/fixed/fixed.h
+++ b/software/fixed/fixed.h
@@ -2,8 +2,12 @@
 #ifndef __FIXED_H_
 #define __FIXED_H_
 
+#define __STDC_FORMAT_MACROS
+
 #include <cctype>
 #include <cmath>
+#include <cinttypes>
+#include <cstdio>
 #include <cstdint>
 #include <cstdlib>
 #include <iomanip>
@@ -12,10 +16,8 @@
 #include <sstream>
 #include <stdexcept>
 #include <type_traits>
+#include <map>
 
-#ifdef USE_INT128
-#include "int128.h"
-#endif
 
 #if __GNUC__ >= 3
 #define __unlikely(cond) __builtin_expect((cond), 0)
@@ -72,6 +74,7 @@ public:
     static constexpr FractionalType MIN_FRACTIONAL_VALUE = 0;
 
     static const uint64_t SCALE_VALUES[20];
+    static const std::map<uint64_t, uint32_t> DIGIT_LOOKUP_TABLE;
 
     // Constructors
     fixed()
@@ -89,46 +92,53 @@ public:
           m_fractional(__checkFracOverflow(fractionalVal))
     {
         // Scale the fractional value appropriately
-        uint32_t idx = 0;
-        for (; idx < fractional_decimal_digits; ++idx)
-        {
-            if (SCALE_VALUES[idx] > m_fractional)
-            {
-                break;
-            }
-        }
-
-        m_fractional = __checkFracOverflow(
-            m_fractional * SCALE_VALUES[fractional_decimal_digits - idx]);
+        m_fractional = __checkFracOverflow(m_fractional * 
+                                           getFracScaleValue(m_fractional));
+    }
+    
+    fixed(IntegerType integerVal, FractionalType fractionalVal, uint32_t leadingZeros)
+        : m_integer(__checkIntOverflow(integerVal)),
+          m_fractional(__checkFracOverflow(fractionalVal))
+    {
+        // Scale the fractional value appropriately
+        m_fractional = __checkFracOverflow(m_fractional * 
+                                           getFracScaleValue(m_fractional,
+                                                             leadingZeros));
     }
 
     // Default copy constructor, and assignment operator
     fixed(const fixed&) = default;
     fixed& operator=(const fixed&) = default;
 
-    // Reassign value to type
+    // Reassign value to type with a prescaled fractional value.
     void assign(IntegerType integerVal, FractionalType fractionalVal)
     {
         m_integer = __checkIntOverflow(integerVal);
-        // Scale the fractional value appropriately
-        uint32_t idx = 0;
-        for (; idx < fractional_decimal_digits; ++idx)
-        {
-            if (SCALE_VALUES[idx] > fractionalVal)
-            {
-                break;
-            }
-        }
+        
+        // Fractional value is prescaled to fractional type precision
+        m_fractional = __checkFracOverflow(fractionalVal);
+    }
 
-        m_fractional = __checkFracOverflow(
-            fractionalVal * SCALE_VALUES[fractional_decimal_digits - idx]);
+    // Assign a new value with an *unscaled* fractional part (NOTE: use the 
+    // "leadingZeros" parameter to represent the number of leading zeros in the
+    // unscaled fractional part.
+    void assignUnscaledFrac(
+        IntegerType integerVal, 
+        FractionalType fractionalVal,
+        uint32_t leadingZeros=0)
+    {
+        m_integer = __checkIntOverflow(integerVal);
+        
+        // Scale the fractional value appropriately
+        m_fractional = __checkFracOverflow(fractionalVal * 
+                                           getFracScaleValue(fractionalVal, 
+                                                             leadingZeros));
     }
 
     // Parse value from string input
     uint32_t parse(const char* input)
     {
         char* endPtr = nullptr;
-        //~ m_integer = __checkIntOverflow(strtoll(input, &endPtr, 10));
         m_integer = __checkIntOverflow(strto_inttype(input, &endPtr, 10));
         m_fractional = 0;
 
@@ -142,10 +152,9 @@ public:
         {
             char* fracEndPtr = nullptr;
             FractionalType fracTemp =
-                //~ __checkFracOverflow(strtoull(endPtr + 1, &fracEndPtr, 10));
                 __checkFracOverflow(strto_fractype(endPtr + 1, &fracEndPtr, 10));
+                
             uint32_t fracLen = (fracEndPtr - endPtr) - 1;
-
             fracLen = fractional_decimal_digits - fracLen;
 
             m_fractional = __checkFracOverflow(fracTemp * SCALE_VALUES[fracLen]);
@@ -156,6 +165,11 @@ public:
         return (endPtr - input);
     }
 
+    void absval()
+    {
+        m_integer = abs(m_integer);
+    }
+
     constexpr inline fixed operator-() const noexcept = delete;
     constexpr inline fixed operator!() const noexcept = delete;
     constexpr inline fixed operator~() const noexcept = delete;
@@ -189,11 +203,17 @@ public:
     friend constexpr inline bool operator>=(
         const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept;
 
+    // Divide by unsigned int
+    template<typename I, typename F, typename STI, typename STF>
+    friend constexpr inline fixed<I, F, STI, STF> operator/(
+        const fixed<I, F, STI, STF>& x, const uint64_t& y);
+
     // Output stream operator
     template<typename I, typename F, typename STI, typename STF>
     friend inline std::ostream& operator<<(
         std::ostream& os, const fixed<I, F, STI, STF>& rhs);
 
+
 private:
 
     static StrToIntegerTypeFunc strto_inttype;
@@ -222,7 +242,21 @@ private:
         }
         return fractionalVal;
     }
+    
+public:
+    // Helper function to get the approprate scale value for an unscaled input
+    // value of the fractional type
+    static inline FractionalType getFracScaleValue(
+        FractionalType value,
+        uint32_t leadingZeros=0)
+    {
+        uint32_t index = fractional_decimal_digits - 
+                         (DIGIT_LOOKUP_TABLE.upper_bound(value)->second + 
+                          leadingZeros);
+        return SCALE_VALUES[index];
+    }
 
+public:
     IntegerType m_integer;
     FractionalType m_fractional;
 };
@@ -243,29 +277,6 @@ struct strtoull_ftor {
     }
 };
 
-#ifdef USE_INT128
-
-// Functor for calling strtoll_128()
-struct strtoll_128_ftor {
-    inline int64_t operator()(const char* str, char** str_end, int base) 
-    { 
-        //~ return strtoll_128(str, str_end, base); 
-        return strtoll_128_b10opt(str, str_end, base); 
-    }
-};
-
-// Functor for calling strtoull_128()
-struct strtoull_128_ftor {
-    inline uint64_t operator()(const char* str, char** str_end, int base) 
-    { 
-        //~ return strtoull_128(str, str_end, base); 
-        return strtoull_128_b10opt(str, str_end, base); 
-    }
-};
-
-#endif
-
-
 // Predefined types
 using fixed_8_8  = fixed<int8_t, uint8_t, strtoll_ftor, strtoull_ftor>;
 using fixed_8_16 = fixed<int8_t, uint16_t, strtoll_ftor, strtoull_ftor>;
@@ -287,10 +298,6 @@ using fixed_64_16 = fixed<int64_t, uint16_t, strtoll_ftor, strtoull_ftor>;
 using fixed_64_32 = fixed<int64_t, uint32_t, strtoll_ftor, strtoull_ftor>;
 using fixed_64_64 = fixed<int64_t, uint64_t, strtoll_ftor, strtoull_ftor>;
 
-#ifdef USE_INT128
-using fixed_128_128 = fixed<int128_t, uint128_t, strtoll_128_ftor, strtoull_128_ftor>;
-#endif
-
 // Precomputed scale value constants
 template<typename I, typename F, typename STI, typename STF>
 const uint64_t fixed<I, F, STI, STF>::SCALE_VALUES[20] = 
@@ -317,6 +324,30 @@ const uint64_t fixed<I, F, STI, STF>::SCALE_VALUES[20] =
     /* 19 */ 10000000000000000000ULL
 };
 
+template<typename I, typename F, typename STI, typename STF>
+const std::map<uint64_t, uint32_t> fixed<I, F, STI, STF>::DIGIT_LOOKUP_TABLE{
+    {1ULL, 0},
+    {10ULL, 1},
+    {100ULL, 2},
+    {1000ULL, 3},
+    {10000ULL, 4},
+    {100000ULL, 5},
+    {1000000ULL, 6},
+    {10000000ULL, 7},
+    {100000000ULL, 8},
+    {1000000000ULL, 9},
+    {10000000000ULL, 10},
+    {100000000000ULL, 11},
+    {1000000000000ULL, 12},
+    {10000000000000ULL, 13},
+    {100000000000000ULL, 14},
+    {1000000000000000ULL, 15},
+    {10000000000000000ULL, 16},
+    {100000000000000000ULL, 17},
+    {1000000000000000000ULL, 18},
+    {std::numeric_limits<uint64_t>::max(), 19}  // Special case for uint64_t max value
+};
+
 template<typename I, typename F, typename STI, typename STF>
 constexpr inline bool operator==(
     const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept
@@ -405,7 +436,7 @@ template<typename I, typename F, typename STI, typename STF>
 constexpr inline fixed<I, F, STI, STF> operator-(
     const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;
 
-// Multiplication
+// Multiplication -- not yet supported
 template<typename I, typename F, typename STI, typename STF>
 constexpr inline fixed<I, F, STI, STF> operator*(
     const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;
@@ -415,13 +446,63 @@ template<typename I, typename F, typename STI, typename STF>
 constexpr inline fixed<I, F, STI, STF> operator/(
     const fixed<I, F, STI, STF>& x, const fixed<I, F, STI, STF>& y) noexcept = delete;
 
+// Division by unsigned int
+template<typename I, typename F, typename STI, typename STF>
+constexpr inline fixed<I, F, STI, STF> operator/(
+     const fixed<I, F, STI, STF>& x, const uint64_t& y)
+    {
+        fixed<I, F, STI, STF> newVal;
+        newVal.m_integer = x.m_integer / y;
+        newVal.m_fractional = x.m_integer % y;
+        newVal.m_fractional *= x.getFracScaleValue(newVal.m_fractional);
+        newVal.m_fractional += x.m_fractional / y;
+        return newVal;
+    }
+
 // Stream output
 template<typename I, typename F, typename STI, typename STF>
 inline std::ostream& operator<<(std::ostream& os, const fixed<I, F, STI, STF>& rhs)
 {
+#if 0
+    // Print full fractional precision always (even with trailing zeros)
     return os << std::fixed << rhs.m_integer << "."
               << std::setw(fixed<I, F, STI, STF>::fractional_decimal_digits)
               << std::setfill('0') << rhs.m_fractional;
+#else
+    // Print fractional part without trailing zeros
+    char buffer[fixed<I, F, STI, STF>::fractional_decimal_digits + 1]{};
+    std::snprintf(buffer, fixed<I, F, STI, STF>::fractional_decimal_digits + 1,
+                  "%" PRIu64, rhs.m_fractional);
+    
+    // NOTE: if fractional part is zero always print at least one zero
+    int idx = fixed<I, F, STI, STF>::fractional_decimal_digits;
+    bool found = false;
+    for (; idx >= 0; --idx)
+    {
+        if (std::isdigit(buffer[idx]) && buffer[idx] != '0')
+        {
+            found = true;
+            break;
+        }
+    }
+    if (found)
+    {
+        buffer[idx + 1] = '\0';
+    }
+    
+    uint32_t leadingZeros = 
+        fixed<I, F, STI, STF>::fractional_decimal_digits - 
+        fixed<I, F, STI, STF>::DIGIT_LOOKUP_TABLE.upper_bound(rhs.m_fractional)->second;
+    
+    // Output any leading zeros
+    os << std::fixed << rhs.m_integer << ".";
+    for (uint32_t idx = 0; idx < leadingZeros; ++idx)
+    {
+        os << "0";
+    }
+    
+    return os << buffer;
+#endif
 }
 
 #endif  // FIXED_H_
diff --git a/software/fixed/fixed_test.cc b/software/fixed/fixed_test.cc
index 0a6d803..103806d 100644
--- a/software/fixed/fixed_test.cc
+++ b/software/fixed/fixed_test.cc
@@ -1,6 +1,10 @@
 
 #include <cstdint>
 #include <limits>
+#include <random>
+#include <sstream>
+#include <string>
+#include <vector>
 
 #define BOOST_TEST_MAIN  1
 #include <boost/test/unit_test.hpp>
@@ -9,7 +13,9 @@
 
 BOOST_AUTO_TEST_CASE(t0)
 {
-    // Check overall type sizes
+    /* TEST CASE 0: Check storage sizes (in bytes) of all predefined fixed 
+     * point types.
+     */
     fixed_8_8  f_8_8;
     fixed_8_32 f_8_32;
     fixed_8_16 f_8_16;
@@ -46,3 +52,220 @@ BOOST_AUTO_TEST_CASE(t0)
     BOOST_CHECK(sizeof(f_64_32) == 16);
     BOOST_CHECK(sizeof(f_64_64) == 16);
 }
+
+BOOST_AUTO_TEST_CASE(t1)
+{
+    /* TEST CASE 1: Test basic comparisons
+     */ 
+    fixed_64_64 val1(100, 1, 0);
+    fixed_64_64 val2(100, 1);
+    
+    BOOST_CHECK(  (val1 == val2));
+    BOOST_CHECK(! (val1 != val2));
+    BOOST_CHECK(! (val1 > val2));
+    BOOST_CHECK(! (val1 < val2));
+    BOOST_CHECK(  (val1 >= val2));
+    BOOST_CHECK(  (val1 <= val2));
+    
+    val2.assignUnscaledFrac(100, 1, 18);
+    
+    BOOST_CHECK(! (val1 == val2));
+    BOOST_CHECK(  (val1 != val2));
+    BOOST_CHECK(  (val1 > val2));
+    BOOST_CHECK(! (val1 < val2));
+    BOOST_CHECK(  (val1 >= val2));
+    BOOST_CHECK(! (val1 <= val2));
+}
+
+BOOST_AUTO_TEST_CASE(t2)
+{
+    /* TEST CASE 2: Test parsing with leading zeros.
+     */ 
+    fixed_64_64 val1;
+    fixed_64_64 val2;
+
+    // 1
+    val1.assign(1, 2000000000000000000);
+    val2.parse("1.2");
+    BOOST_CHECK(val1 == val2);
+
+    // 2
+    val1.assign(1, 200000000000000000);
+    val2.parse("1.02");
+    BOOST_CHECK(val1 == val2);
+
+    // 3
+    val1.assign(1, 20000000000000000);
+    val2.parse("1.002");
+    BOOST_CHECK(val1 == val2);
+
+    // 4
+    val1.assign(1, 2000000000000000);
+    val2.parse("1.0002");
+    BOOST_CHECK(val1 == val2);
+
+    // 5
+    val1.assign(1, 200000000000000);
+    val2.parse("1.00002");
+    BOOST_CHECK(val1 == val2);
+
+    // 6
+    val1.assign(1, 20000000000000);
+    val2.parse("1.000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 7
+    val1.assign(1, 2000000000000);
+    val2.parse("1.0000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 8
+    val1.assign(1, 200000000000);
+    val2.parse("1.00000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 9
+    val1.assign(1, 20000000000);
+    val2.parse("1.000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 10
+    val1.assign(1, 2000000000);
+    val2.parse("1.0000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 11
+    val1.assign(1, 200000000);
+    val2.parse("1.00000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 12
+    val1.assign(1, 20000000);
+    val2.parse("1.000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 13
+    val1.assign(1, 2000000);
+    val2.parse("1.0000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 14
+    val1.assign(1, 200000);
+    val2.parse("1.00000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 15
+    val1.assign(1, 20000);
+    val2.parse("1.000000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 16
+    val1.assign(1, 2000);
+    val2.parse("1.0000000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 17
+    val1.assign(1, 200);
+    val2.parse("1.00000000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 18
+    val1.assign(1, 20);
+    val2.parse("1.000000000000000002");
+    BOOST_CHECK(val1 == val2);
+
+    // 19
+    val1.assign(1, 2);
+    val2.parse("1.0000000000000000002");
+    BOOST_CHECK(val1 == val2);
+}
+
+BOOST_AUTO_TEST_CASE(t3)
+{
+    /* TEST CASE 3: Test parsing and printing randomly generated values
+     */ 
+    using theType = fixed_64_64;
+
+    uint32_t NUMBER = 1000000;
+    uint64_t SEED = 0xDEADBEEF123;
+    
+    std::random_device dev;
+    std::mt19937 rng;
+    rng.seed(SEED);
+    
+    std::uniform_int_distribution<std::mt19937::result_type> intDigits(1, theType::integer_decimal_digits);
+    std::uniform_int_distribution<std::mt19937::result_type> fracDigits(1, theType::fractional_decimal_digits);
+    std::uniform_int_distribution<std::mt19937::result_type> digits(0, 9);
+    
+    // Generate NUMBER worth of parsable decimal number strings
+    std::vector<std::string> values;
+    for (uint32_t idx = 0; idx < NUMBER; ++idx)
+    {
+        std::ostringstream output;
+        // Generate random integer number digits
+        for (uint32_t integerIdx = 0; integerIdx < intDigits(rng); ++integerIdx)
+        {
+            int digit = digits(rng);
+            
+            // Integer part cannot start with a zero
+            if (integerIdx == 0)
+            {
+                while (digit == 0)
+                {
+                    digit = digits(rng);
+                }
+            }
+            
+            output << digit;
+        }
+        
+        output << ".";
+        
+        // Generate random fractional number digits
+        int numFracDigits = fracDigits(rng);
+        for (uint32_t fracIdx = 0; fracIdx < numFracDigits; ++fracIdx)
+        {
+            int digit = digits(rng);
+            
+            // Fractional part cannot end with a zero
+            if (fracIdx == (numFracDigits - 1))
+            {
+                while (digit == 0)
+                {
+                    digit = digits(rng);
+                }
+            }
+            
+            output << digit;
+        }
+        
+        values.emplace_back(output.str());
+        //~ std::cout << "VAL: " << values.back() << std::endl;
+    }
+    
+    // Now validate that the output of the parsed 
+    theType fixedValue;
+    std::ostringstream output;
+    for (auto& value : values)
+    {
+        fixedValue.parse(value.c_str());
+        output << fixedValue;
+        
+        //~ std::cout << "TEST: " << output.str() << " -- " << value << std::endl;
+        BOOST_CHECK(output.str() == value);
+        output.str("");
+    }
+}
+
+BOOST_AUTO_TEST_CASE(t4)
+{
+    /* TEST CASE 4: Basic test division by unsigned integer
+     */ 
+    fixed_64_64 val1(18, 55, 0);
+    fixed_64_64 val2(0, 1855, 0);
+    
+    fixed_64_64 answer = val1 / 100;
+    BOOST_CHECK(answer == val2);
+}
+
+
diff --git a/software/fixed/parse_test.cc b/software/fixed/parse_test.cc
index caa421f..9c43bca 100644
--- a/software/fixed/parse_test.cc
+++ b/software/fixed/parse_test.cc
@@ -79,6 +79,7 @@ int main(int argc, char** argv)
     
     long double value = 0.0;
     fixed_64_64 fvalue;
+    //~ fixed_128_128 f128value;
     
     // 0) strtold() 
     if (parseType == 0)
@@ -167,6 +168,7 @@ int main(int argc, char** argv)
                   << " ms" << std::endl;
     }
 #endif
+#if 0
     // 5) 
     else if (parseType == 5)
     {
@@ -174,8 +176,8 @@ int main(int argc, char** argv)
         auto start = std::chrono::system_clock::now();
         for (auto& strValue : values)
         {
-            fvalue.parse(strValue.c_str());
-            //~ std::cout << "In: " << strValue << " -- Out: " << fvalue << std::endl;
+            f128value.parse(strValue.c_str());
+            std::cout << "In: " << strValue << " -- Out: " << f128value << std::endl;
         }
         auto end = std::chrono::system_clock::now();
         auto diff = end - start;
@@ -184,6 +186,7 @@ int main(int argc, char** argv)
                   << " ms" << std::endl;
         
     }
+#endif
     else
     {
         std::cerr << "ERROR: invalid parse type: " << parseType << std::endl;
diff --git a/software/fixed/util.py b/software/fixed/util.py
new file mode 100755
index 0000000..b42233c
--- /dev/null
+++ b/software/fixed/util.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python3
+import sys
+
+def generate_t1():
+    """ Generate test case t1.
+    """
+    num = 18
+    int_digit = '1'
+    frac_digit = '2'
+    
+    for idx in range(num + 1):
+        print(f'    // {idx + 1}')        
+        frac_val = f"{frac_digit}{'0' * (num - idx)}"
+        print(f"    val1.assign({int_digit}, {frac_val});")
+        frac_val = f"{'0' * idx}{frac_digit}"
+        print(f"    val2.parse(\"{int_digit}.{frac_val}\");")
+        print('    BOOST_CHECK(val1 == val2);\n')
+
+def main():
+    print('Gen Util')
+    
+
+    
+if __name__ == '__main__':
+    sys.exit(main())
