commit 11ae9ac5d399f95f6274ea23716768f9b6a6150e
Author: David Sorber <david.sorber@gmail.com>
Date:   Thu Feb 17 18:09:32 2022 -0500

    Additional improvements to the fixed point type. Also adding parse_test
    which can be used to compare parse performance and a CMakeLists.txt.

diff --git a/software/fixed/CMakeLists.txt b/software/fixed/CMakeLists.txt
new file mode 100644
index 0000000..03c314a
--- /dev/null
+++ b/software/fixed/CMakeLists.txt
@@ -0,0 +1,20 @@
+PROJECT("FixedPoint")
+CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
+
+SET(CMAKE_CXX_STANDARD 17)
+
+INCLUDE_DIRECTORIES(.)
+
+#~ SET(EXTERNAL_LIBS)
+#~ FIND_PACKAGE(Threads)
+#~ FIND_PACKAGE(Boost 1.58.0 COMPONENTS system regex program_options filesystem REQUIRED)
+#~ IF (Boost_FOUND)
+   #~ INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
+   #~ SET(EXTERNAL_LIBS ${EXTERNAL_LIBS} ${Boost_LIBRARIES})
+#~ ENDIF()
+
+
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3 -march=native -fuse-ld=gold -flto")
+
+ADD_EXECUTABLE(fixed_test ${CMAKE_CURRENT_SOURCE_DIR}/fixed_test.cc)
+ADD_EXECUTABLE(parse_test ${CMAKE_CURRENT_SOURCE_DIR}/parse_test.cc)
diff --git a/software/fixed/fixed.h b/software/fixed/fixed.h
index 24b5ae8..7327078 100644
--- a/software/fixed/fixed.h
+++ b/software/fixed/fixed.h
@@ -2,9 +2,15 @@
 #ifndef FIXED_H_
 #define FIXED_H_
 
+#include <cctype>
 #include <cmath>
+#include <cstdlib>
 #include <cstdint>
+#include <iomanip>
+#include <iostream>
 #include <limits>
+#include <stdexcept>
+#include <sstream>
 #include <type_traits>
 
 template <typename IntegerType, typename FractionalType>
@@ -30,22 +36,92 @@ public:
     static constexpr size_t MAX_FRACTIONAL_VALUE = (static_cast<uint64_t>(std::pow(10, fractional_decimal_digits)) - 1);
     static constexpr size_t MIN_FRACTIONAL_VALUE = 0;
     
+    static const uint64_t SCALE_VALUES[20];
+    
+    // Constructors
     fixed()
-        : m_integer(0), m_fractional(0) {}
+        : m_integer(0), 
+          m_fractional(0) 
+    {}
+    
+    fixed(IntegerType integerVal)
+        : m_integer(__checkIntOverflow(integerVal)), 
+          m_fractional(0) 
+    {}
         
     fixed(IntegerType integerVal, FractionalType fractionalVal)
-        : m_integer(integerVal), m_fractional(fractionalVal) {}
+        : m_integer(__checkIntOverflow(integerVal)), 
+          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]);
+    }
     
     // Default copy constructor, and assignment operator
 	fixed(const fixed&) = default;
 	fixed& operator=(const fixed&) = default;
     
+    // Reassign value to type
+    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;
+            }
+        }
+        
+        m_fractional = __checkFracOverflow(fractionalVal * 
+                                           SCALE_VALUES[fractional_decimal_digits - idx]);
+    }
+    
+    // Parse value from string input
     void parse(const char* input)
     {
+        char* endPtr = nullptr;
+        m_integer = __checkIntOverflow(strtoull(input, &endPtr, 10));
+        m_fractional = 0;
+        
+        if (std::isdigit(*endPtr))
+        {
+            throw std::out_of_range("Integer value is out of range.");
+        }
         
+        // If the ending char is a period we can now parse the fractional part
+        if (*endPtr == '.')
+        {
+            char* fracEndPtr = nullptr;
+            FractionalType fracTemp = __checkFracOverflow(strtoull(endPtr + 1, &fracEndPtr, 10));
+            uint32_t fracLen = (fracEndPtr - endPtr) - 1;
+            
+            // DEBUG
+            //~ std::cout << "frac len: " << fracLen << std::endl;
+            fracLen = fractional_decimal_digits - fracLen;
+            //~ std::cout << "invt len: " << fracLen << std::endl;
+            
+            m_fractional = __checkFracOverflow(fracTemp * SCALE_VALUES[fracLen]);
+        }
+        // TODO: could calculate and return overall length
+        //~ uint32_t length = endPtr - input;
     }
     
     constexpr inline fixed operator-() const noexcept = delete;
+    constexpr inline fixed operator!() const noexcept = delete;
+    constexpr inline fixed operator~() const noexcept = delete;
     inline fixed& operator+=(const fixed& y) noexcept = delete;
     inline fixed& operator-=(const fixed& y) noexcept = delete;
     inline fixed& operator*=(const fixed& y) noexcept = delete;
@@ -70,15 +146,89 @@ public:
     template <typename I, typename F>
     friend constexpr inline bool operator>=(const fixed<I, F>& x, const fixed<I, F>& y) noexcept;
     
+    // Output stream operator
+    template <typename I, typename F>
+    friend inline std::ostream& operator<<(std::ostream& os, const fixed<I, F>& rhs);
+    
 private:
 
+    static inline IntegerType __checkIntOverflow(IntegerType integerVal)
+    {
+        if (integerVal > MAX_INTEGER_VALUE)
+        {
+            std::ostringstream msg;
+            msg << "Integer value: " << integerVal << " exceeds maximum "
+                   "integer range of type (" << MAX_INTEGER_VALUE << ")!";
+            throw std::out_of_range(msg.str());
+        }
+        return integerVal;
+    }
+    
+    static inline FractionalType __checkFracOverflow(FractionalType fractionalVal)
+    {
+        if (fractionalVal > MAX_FRACTIONAL_VALUE)
+        {
+            std::ostringstream msg;
+            msg << "Fractional value: " << fractionalVal << " exceeds maximum "
+                   "fractional range of type (" << MAX_FRACTIONAL_VALUE << ")!";
+            throw std::out_of_range(msg.str());
+        }
+        return fractionalVal;
+    }
+
     IntegerType m_integer;
     FractionalType m_fractional;
     
 };
 
+// Predefined types
+using fixed_8_8  = fixed<int8_t, uint8_t>;
+using fixed_8_16 = fixed<int8_t, uint16_t>;
+using fixed_8_32 = fixed<int8_t, uint32_t>;
+using fixed_8_64 = fixed<int8_t, uint64_t>;
+
+using fixed_16_8  = fixed<int16_t, uint8_t>;
+using fixed_16_16 = fixed<int16_t, uint16_t>;
+using fixed_16_32 = fixed<int16_t, uint32_t>;
+using fixed_16_64 = fixed<int16_t, uint64_t>;
+
+using fixed_32_8  = fixed<int32_t, uint8_t>;
+using fixed_32_16 = fixed<int32_t, uint16_t>;
+using fixed_32_32 = fixed<int32_t, uint32_t>;
+using fixed_32_64 = fixed<int32_t, uint64_t>;
+
+using fixed_64_8  = fixed<int64_t, uint8_t>;
+using fixed_64_16 = fixed<int64_t, uint16_t>;
+using fixed_64_32 = fixed<int64_t, uint32_t>;
 using fixed_64_64 = fixed<int64_t, uint64_t>;
 
+
+// Precomputed scale value constants
+template <typename I, typename F>
+const uint64_t fixed<I, F>::SCALE_VALUES[20] =
+{
+    /*  0 */ 1ULL, 
+    /*  1 */ 10ULL, 
+    /*  2 */ 100ULL, 
+    /*  3 */ 1000ULL,
+    /*  4 */ 10000ULL,
+    /*  5 */ 100000ULL,
+    /*  6 */ 1000000ULL,
+    /*  7 */ 10000000ULL,
+    /*  8 */ 100000000ULL,
+    /*  9 */ 1000000000ULL,
+    /* 10 */ 10000000000ULL,
+    /* 11 */ 100000000000ULL,
+    /* 12 */ 1000000000000ULL,
+    /* 13 */ 10000000000000ULL,
+    /* 14 */ 100000000000000ULL,
+    /* 15 */ 1000000000000000ULL,
+    /* 16 */ 10000000000000000ULL,
+    /* 17 */ 100000000000000000ULL,
+    /* 18 */ 1000000000000000000ULL,
+    /* 19 */ 10000000000000000000ULL
+};
+
 template <typename I, typename F>
 constexpr inline bool operator==(const fixed<I, F>& x, const fixed<I, F>& y) noexcept
 {
@@ -88,7 +238,7 @@ constexpr inline bool operator==(const fixed<I, F>& x, const fixed<I, F>& y) noe
 template <typename I, typename F>
 constexpr inline bool operator!=(const fixed<I, F>& x, const fixed<I, F>& y) noexcept
 {
-    return (! x == y);
+    return (! (x == y));
 }
 
 template <typename I, typename F>
@@ -141,7 +291,7 @@ constexpr inline bool operator>=(const fixed<I, F>& x, const fixed<I, F>& y) noe
 {
     if (x.m_integer == y.m_integer)
     {
-        return (x.m_fractional > y.m_fractional);
+        return (x.m_fractional >= y.m_fractional);
     }
     else
     {
@@ -151,11 +301,11 @@ constexpr inline bool operator>=(const fixed<I, F>& x, const fixed<I, F>& y) noe
     return false;
 }
 
-// Addition
+// Addition -- not yet supported
 template <typename I, typename F>
 constexpr inline fixed<I, F> operator+(const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;
 
-// Subtraction
+// Subtraction -- not yet supported
 template <typename I, typename F>
 constexpr inline fixed<I, F> operator-(const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;
 
@@ -163,9 +313,18 @@ constexpr inline fixed<I, F> operator-(const fixed<I, F>& x, const fixed<I, F>&
 template <typename I, typename F>
 constexpr inline fixed<I, F> operator*(const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;
 
-// Division
+// Division -- not yet supported
 template <typename I, typename F>
 constexpr inline fixed<I, F> operator/(const fixed<I, F>& x, const fixed<I, F>& y) noexcept = delete;
 
+// Stream output
+template <typename I, typename F>
+inline std::ostream& operator<<(std::ostream& os, const fixed<I, F>& rhs)
+{
+    return os << std::fixed << rhs.m_integer << "." 
+              << std::setw(fixed<I, F>::fractional_decimal_digits) << std::setfill('0') 
+              << rhs.m_fractional;
+}
+
 
 #endif // FIXED_H_
diff --git a/software/fixed/fixed_test.cc b/software/fixed/fixed_test.cc
index ed1fe6c..5e2952a 100644
--- a/software/fixed/fixed_test.cc
+++ b/software/fixed/fixed_test.cc
@@ -2,10 +2,11 @@
 #include <cstdint>
 #include <iomanip>
 #include <iostream>
+#include <stdexcept>
 
 #include "fixed.h"
 
-// g++ fixed_text.cc -o fixed_test
+// g++ fixed_test.cc -o fixed_test
 int main(int argc, char** argv)
 {
     std::cout << "Fixed Type Test Util\n" << std::endl;
@@ -20,6 +21,57 @@ int main(int argc, char** argv)
     
     std::cout << "Max fractional value: " << fixed_64_64::MAX_FRACTIONAL_VALUE << std::endl;
     
+    std::cout << std::endl;
+    std::cout << "Value: " << foobar << std::endl;
+    
+    fixed_64_64 quxbar(3, 1415926353);
+    std::cout << "Value: " << quxbar << std::endl;
+    
+    fixed_64_64 intMax(999999999999999999ULL);
+    std::cout << "Value: " << intMax << std::endl;
+    
+    try
+    {
+        fixed_64_64 intOverflow(1999999999999999999ULL);
+        std::cout << "Value: " << intMax << std::endl;
+    }
+    catch (const std::out_of_range& excep)
+    {
+        std::cout << "Integer overflow caught" << std::endl;
+    }
+    
+    fixed_64_64 fracMax(0, 9999999999999999999ULL);
+    std::cout << "Value: " << fracMax << std::endl;
+    
+    try
+    {
+        fixed_64_64 fracOverflow(0, 10000000000000000001ULL);
+    }
+    catch (const std::out_of_range& excep)
+    {
+        std::cout << "Fractional overflow caught" << std::endl;
+    }
+    
+    
+    fixed_64_64 value1;
+    fixed_64_64 value2;
+    const char* strVal = "100.1";
+    value1.parse(strVal);
+    std::cout << "String value: " << strVal << std::endl;
+    std::cout << "Parsed value: " << value1 << std::endl;
+    
+    strVal = "100";
+    value2.parse(strVal);
+    std::cout << "String value: " << strVal << std::endl;
+    std::cout << "Parsed value: " << value2 << std::endl;
+    
+    std::cout << value1 << " == " << value2 << " --> " << (value1 == value2) << std::endl;
+    std::cout << value1 << " != " << value2 << " --> " << (value1 != value2) << std::endl;
+    std::cout << value1 << " > " << value2 << " --> " << (value1 > value2) << std::endl;
+    std::cout << value1 << " < " << value2 << " --> " << (value1 < value2) << std::endl;    
+    std::cout << value1 << " >= " << value2 << " --> " << (value1 >= value2) << std::endl;
+    std::cout << value1 << " <= " << value2 << " --> " << (value1 <= value2) << std::endl;
+    
     //~ double foo = std::pow(10, 18);
     //~ std::cout << "Foo: " << std::fixed << std::setprecision(0) << foo << std::endl;
     //~ std::cout << "Foo: " << std::fixed << std::setprecision(0) << (static_cast<uint64_t>(foo) - 1) << std::endl;
diff --git a/software/fixed/parse_test.cc b/software/fixed/parse_test.cc
new file mode 100644
index 0000000..fc37b7b
--- /dev/null
+++ b/software/fixed/parse_test.cc
@@ -0,0 +1,177 @@
+#include <charconv>
+#include <chrono>
+#include <cstdint>
+#include <cstdlib>
+#include <random>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "fixed.h"
+
+// g++ -std=c++17 -O3 parse.cc -o parse
+
+void genRandomNumStrings(
+    uint32_t number, 
+    std::vector<std::string>& values,
+    uint64_t seed)
+{
+    values.clear();
+    
+    std::random_device dev;
+    //~ std::mt19937 rng(dev());
+    std::mt19937 rng;
+    rng.seed(seed);
+    std::uniform_int_distribution<std::mt19937::result_type> numDigits(1, 20);
+    std::uniform_int_distribution<std::mt19937::result_type> digits(0, 9);
+    
+    for (uint32_t idx = 0; idx < number; ++idx)
+    {
+        std::ostringstream output;
+        // Generate random whole number digits
+        for (uint32_t wholeIdx = 0; wholeIdx < numDigits(rng); ++wholeIdx)
+        {
+            output << digits(rng);
+        }
+        
+        output << ".";
+        
+        // Generate random fractional number digits
+        for (uint32_t fracIdx = 0; fracIdx < numDigits(rng); ++fracIdx)
+        {
+            output << digits(rng);
+        }
+        
+        values.emplace_back(output.str());
+        //~ std::cout << "VAL: " << values.back() << std::endl;
+    }
+}
+
+int main(int argc, char** argv)
+{
+    std::cout << "Long Double Parse Test Util\n" << std::endl;
+    
+    if (argc < 3)
+    {
+        std::cerr << "ERROR: not enough arguments" << std::endl;
+        return -1;
+    }
+    
+    uint32_t parseType = std::strtoul(argv[1], nullptr, 10);
+    uint32_t numRandomStrings = std::strtoul(argv[2], nullptr, 10);
+    uint64_t seed = 0xDEADBEEF;
+    if (argc > 3)
+    {
+        seed = std::strtoull(argv[3], nullptr, 0);
+    }
+        
+    std::vector<std::string> values;
+    {
+        auto start = std::chrono::system_clock::now();
+        genRandomNumStrings(numRandomStrings, values, seed);
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Time to generate " << numRandomStrings << " values: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+    }
+    
+    long double value = 0.0;
+    fixed_64_64 fvalue;
+    
+    // 0) strtold() 
+    if (parseType == 0)
+    {
+        std::cout << "Parse type 0: std::strtold()" << std::endl;
+        auto start = std::chrono::system_clock::now();
+        for (auto& strValue : values)
+        {
+            value = std::strtold(strValue.c_str(), nullptr);
+            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
+        }
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Total parse time: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+    }
+    // 1) stold()
+    else if (parseType == 1)
+    {
+        std::cout << "Parse type 1: std::stold()" << std::endl;
+        auto start = std::chrono::system_clock::now();
+        for (auto& strValue : values)
+        {
+            value = std::stold(strValue.c_str(), nullptr);
+            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
+        }
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Total parse time: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+    }
+    // 2) istringstream
+    else if (parseType == 2)
+    {
+        std::cout << "Parse type 2: std::istringstream" << std::endl;
+        auto start = std::chrono::system_clock::now();
+        std::istringstream stream;
+        for (auto& strValue : values)
+        {
+            stream.clear();
+            stream.str(strValue);
+            stream >> value;
+            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
+        }
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Total parse time: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+    }
+    // 3) 
+    else if (parseType == 3)
+    {
+        std::cout << "Parse type 3: fixed_64_64" << std::endl;
+        auto start = std::chrono::system_clock::now();
+        for (auto& strValue : values)
+        {
+            fvalue.parse(strValue.c_str());
+            //~ std::cout << "In: " << strValue << " -- Out: " << fvalue << std::endl;
+        }
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Total parse time: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+        
+    }
+#if 0 // This needs >= gcc 11
+    // 4) from_chars()
+    else if (parseType == 4)
+    {
+        std::cout << "Parse type 4: std::from_chars()" << std::endl;
+        auto start = std::chrono::system_clock::now();
+        for (auto& strValue : values)
+        {
+            std::from_chars(strValue.begin(), strValue.end(),
+                            value, std::chars_format::general);
+            //~ std::cout << "In: " << strValue << " -- Out: " << value << std::endl;
+        }
+        auto end = std::chrono::system_clock::now();
+        auto diff = end - start;
+        std::cout << "Total parse time: "
+                  << std::chrono::duration <double, std::milli>(diff).count() 
+                  << " ms" << std::endl;
+    }
+#endif
+    else
+    {
+        std::cerr << "ERROR: invalid parse type: " << parseType << std::endl;
+        return -2;
+    }
+    
+    return 0;
+}    
