Revision 11ae9ac5
Added by David Sorber over 4 years ago
| software/fixed/CMakeLists.txt | ||
|---|---|---|
|
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)
|
||
| software/fixed/fixed.h | ||
|---|---|---|
|
#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>
|
||
| ... | ... | |
|
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;
|
||
| ... | ... | |
|
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
|
||
|
{
|
||
| ... | ... | |
|
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>
|
||
| ... | ... | |
|
{
|
||
|
if (x.m_integer == y.m_integer)
|
||
|
{
|
||
|
return (x.m_fractional > y.m_fractional);
|
||
|
return (x.m_fractional >= y.m_fractional);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
| ... | ... | |
|
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;
|
||
|
|
||
| ... | ... | |
|
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_
|
||
| software/fixed/fixed_test.cc | ||
|---|---|---|
|
#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;
|
||
| ... | ... | |
|
|
||
|
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;
|
||
| software/fixed/parse_test.cc | ||
|---|---|---|
|
#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;
|
||
|
}
|
||
Additional improvements to the fixed point type. Also adding parse_test
which can be used to compare parse performance and a CMakeLists.txt.