From 214a805fb22fae414d67bc32a2b14f408b25e41d Mon Sep 17 00:00:00 2001 From: Tomasz Kozak Date: Mon, 10 Nov 2025 11:31:11 +0100 Subject: [PATCH 1/9] fix: Add test for 32 bit raw and fix for it. --- include/FixedPointConverter.h | 20 +++++++-- .../testFixedPointConverter.cpp | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/include/FixedPointConverter.h b/include/FixedPointConverter.h index 4b0d5989c..2304569b3 100644 --- a/include/FixedPointConverter.h +++ b/include/FixedPointConverter.h @@ -386,7 +386,6 @@ namespace ChimeraTK { if(_isSigned && isNegative) { rawValue = ~rawValue; } - // return with bit mask applied return rawValue & _usedBitsMask; } @@ -436,9 +435,13 @@ namespace ChimeraTK { catch(boost::numeric::positive_overflow& e) { raw = _maxRawValue; } - - // apply bit mask - // NOLINTNEXTLINE(hicpp-signed-bitwise) + // when cookedValue is not zero and caculated raw is not zero, but still when _usedBitsMask is applied, + // the result is the zero - fix test testInt16_fraction16 for 32bits Raw value - cases ToRaw13 and ToRaw16 + if(cookedValue && raw && !(raw & _usedBitsMask)) { + return _minRawValue; + } + // apply bit mask + // NOLINTNEXTLINE(hicpp-signed-bitwise) return raw & _usedBitsMask; } } @@ -548,6 +551,15 @@ namespace ChimeraTK { // fractional bit coefficients note: we loop over one of the maps only, but // initCoefficients() will fill all maps! boost::fusion::for_each(_minCookedValues, initCoefficients(this)); + /* + std::cout << std::dec << "RAW BYTES: " << sizeof(RawType) << ", signed: " << _isSigned << ", nBits: " << nBits + << ", _fractionalBits: " << _fractionalBits << std::endl; + std::cout << "_signBitMask: " << std::hex << _signBitMask << std::endl; + std::cout << "_usedBitsMask: " << std::hex << _usedBitsMask << std::endl; + std::cout << "_unusedBitsMask: " << std::hex << _unusedBitsMask << std::endl; + std::cout << "_maxRawValue: " << std::hex << _maxRawValue << std::endl; + std::cout << "_minRawValue: " << std::hex << _minRawValue << std::endl; + */ } /********************************************************************************************************************/ diff --git a/tests/executables_src/testFixedPointConverter.cpp b/tests/executables_src/testFixedPointConverter.cpp index 5b11de4b2..2ba2ec48f 100644 --- a/tests/executables_src/testFixedPointConverter.cpp +++ b/tests/executables_src/testFixedPointConverter.cpp @@ -25,6 +25,9 @@ using namespace boost::unit_test_framework; #define SIGNED_HEX_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) #define SIGNED_HEX_TO_INT64(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX16_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX16_TO_INT64(INPUT) static_cast(static_cast(INPUT)) + #define CHECK_SIGNED_FIXED_POINT_NO_FRACTIONAL \ checkToFixedPoint(converter, 0.25, 0); \ checkToFixedPoint(converter, -0.25, 0); \ @@ -946,6 +949,46 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction32) { // checkToRaw( converter, (unsigned short)0x5555, 0x7FFFFFFF ); } +BOOST_AUTO_TEST_CASE(testInt16_fraction16) { + FixedPointConverter converter("Variable16plus16signed", 16, + 16); // 16 bits, 16 fractional bits, signed + + checkToCooked(converter, 0xAAAA, SIGNED_HEX16_TO_DOUBLE(0xAAAA) * pow(2, -16), "ToCoocked1"); + checkToCooked(converter, 0x5555, SIGNED_HEX16_TO_DOUBLE(0x5555) * pow(2, -16), "ToCoocked2"); + checkToCooked(converter, 0xAAAA, (int)0); + checkToCooked(converter, 0x5555, (int)0); + checkToCooked(converter, 0xAAAA, (unsigned int)0); + checkToCooked(converter, 0x5555, (unsigned int)0); + checkToCooked(converter, 0xAAAA, (short)0); + checkToCooked(converter, 0x5555, (short)0); + checkToCooked(converter, 0xAAAA, (unsigned short)0); + checkToCooked(converter, 0x5555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x4000, "ToRaw1"); + checkToRaw(converter, -0.25, 0xC000, "ToRaw2"); + + // these values are out of range + checkToRaw(converter, 0.75, 0x7FFF, "ToRaw3"); + checkToRaw(converter, -0.75, 0x8000, "ToRaw4"); + checkToRaw(converter, 3.25, 0x7FFF, "ToRaw5"); + checkToRaw(converter, -3.25, 0x8000, "ToRaw6"); + checkToRaw(converter, 5.75, 0x7FFF, "ToRaw7"); + checkToRaw(converter, -5.75, 0x8000, "ToRaw8"); + + checkToCooked(converter, 0x4000, 0.25); + checkToCooked(converter, 0xC000, -0.25); + + checkToRaw(converter, (int)0x55555555, 0x7FFF, "ToRaw9"); + checkToRaw(converter, (int)0xAAAAAAAA, 0x8000, "ToRaw10"); + checkToRaw(converter, (int)0, 0, "ToRaw11"); + checkToRaw(converter, (int)1, 0x7FFF, "ToRaw12"); + checkToRaw(converter, (int)-1, 0x8000, "ToRaw13"); + + checkToRaw(converter, (short)0x5555, 0x7FFF, "ToRaw14"); + checkToRaw(converter, (short)0xAAAA, 0x8000, "ToRaw15"); + checkToRaw(converter, (short)-1, 0x8000, "ToRaw16"); +} + BOOST_AUTO_TEST_CASE(testUInt32_fraction32) { FixedPointConverter converter("Variable32plus32unsigned", 32, 32, false); // 32 bits, 32 fractional bits, not signed From ba74da6dc4617bc5314e62ce5c4fe43cf241d49d Mon Sep 17 00:00:00 2001 From: Tomasz Kozak Date: Fri, 21 Nov 2025 11:59:40 +0100 Subject: [PATCH 2/9] feat: wip on testing the problem with convetion from -1 for signed, the same fractional bit as size of the converter --- include/FixedPointConverter.h | 31 +- .../testFixedPointConverter.cpp | 89 + .../testFixedPointConverterRaw64.cpp | 1687 +++++++++++++++++ 3 files changed, 1801 insertions(+), 6 deletions(-) create mode 100644 tests/executables_src/testFixedPointConverterRaw64.cpp diff --git a/include/FixedPointConverter.h b/include/FixedPointConverter.h index 2304569b3..0dccdb9af 100644 --- a/include/FixedPointConverter.h +++ b/include/FixedPointConverter.h @@ -359,12 +359,20 @@ namespace ChimeraTK { return 0; } else { + std::cout << "Cooked for conv to raw: " << std::dec << cookedValue + << ", min: " << boost::fusion::at_key(_minCookedValues) + << ", max: " << boost::fusion::at_key(_maxCookedValues) << std::endl; + // Do a range check first. The later overflow check in the conversion is not // sufficient, since we can have non-standard word sizes like 12 bits. if(cookedValue < boost::fusion::at_key(_minCookedValues)) { + std::cout << "Neg OF . min: " << boost::fusion::at_key(_minCookedValues) << " raw: 0x" << std::hex + << _minRawValue << std::endl; return _minRawValue; } if(cookedValue > boost::fusion::at_key(_maxCookedValues)) { + std::cout << "Pos OF . max: " << boost::fusion::at_key(_maxCookedValues) << " raw: 0x" << std::hex + << _maxRawValue << std::endl; return _maxRawValue; } @@ -430,18 +438,29 @@ namespace ChimeraTK { } } catch(boost::numeric::negative_overflow& e) { + std::cout << "BOOST Neg OF . Cooked: " << cookedValue << " raw: 0x" << std::hex << _minRawValue << std::endl; raw = _minRawValue; } catch(boost::numeric::positive_overflow& e) { + std::cout << "BOOST Pos OF . Cooked: " << cookedValue << " raw: 0x" << std::hex << _minRawValue << std::endl; raw = _maxRawValue; } + std::cout << "ToRawFrac . Cooked: " << cookedValue << " d_cooked: " << d_cooked << " raw: 0x" << std::hex << raw + << std::endl; // when cookedValue is not zero and caculated raw is not zero, but still when _usedBitsMask is applied, // the result is the zero - fix test testInt16_fraction16 for 32bits Raw value - cases ToRaw13 and ToRaw16 - if(cookedValue && raw && !(raw & _usedBitsMask)) { - return _minRawValue; - } + // if(cookedValue && raw && !(raw & _usedBitsMask)) { + // return _minRawValue; + //} // apply bit mask // NOLINTNEXTLINE(hicpp-signed-bitwise) + + // negative overflow not detected by boost in case when converter is signed and number of bits in converter is + // smaller than in raw type + if(cookedValue && raw && (raw & _usedBitsMask) == 0) { + return _minRawValue; + } + return raw & _usedBitsMask; } } @@ -551,15 +570,15 @@ namespace ChimeraTK { // fractional bit coefficients note: we loop over one of the maps only, but // initCoefficients() will fill all maps! boost::fusion::for_each(_minCookedValues, initCoefficients(this)); - /* - std::cout << std::dec << "RAW BYTES: " << sizeof(RawType) << ", signed: " << _isSigned << ", nBits: " << nBits + + std::cout << "\n\n" + << std::dec << "RAW BYTES: " << sizeof(RawType) << ", signed: " << _isSigned << ", nBits: " << nBits << ", _fractionalBits: " << _fractionalBits << std::endl; std::cout << "_signBitMask: " << std::hex << _signBitMask << std::endl; std::cout << "_usedBitsMask: " << std::hex << _usedBitsMask << std::endl; std::cout << "_unusedBitsMask: " << std::hex << _unusedBitsMask << std::endl; std::cout << "_maxRawValue: " << std::hex << _maxRawValue << std::endl; std::cout << "_minRawValue: " << std::hex << _minRawValue << std::endl; - */ } /********************************************************************************************************************/ diff --git a/tests/executables_src/testFixedPointConverter.cpp b/tests/executables_src/testFixedPointConverter.cpp index 2ba2ec48f..06ebc8fef 100644 --- a/tests/executables_src/testFixedPointConverter.cpp +++ b/tests/executables_src/testFixedPointConverter.cpp @@ -854,6 +854,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction31) { checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF); checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000); + checkToRaw(converter, (int)1, 0x7FFFFFFF); + checkToRaw(converter, (short)1, 0x7FFFFFFF); checkToRaw(converter, (int)0, 0); checkToRaw(converter, (int)-1, 0x80000000); checkToRaw(converter, (unsigned int)0x55555555, 0x7FFFFFFF); @@ -946,9 +948,53 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction32) { checkToRaw(converter, (short)0x5555, 0x7FFFFFFF); checkToRaw(converter, (short)0xAAAA, 0x80000000, "ToRaw14"); checkToRaw(converter, (short)-1, 0x80000000, "ToRaw15"); + checkToRaw(converter, (short)1, 0x7FFFFFFF, "ToRaw16"); // checkToRaw( converter, (unsigned short)0x5555, 0x7FFFFFFF ); } +BOOST_AUTO_TEST_CASE(testInt27_fraction27) { + FixedPointConverter converter("Variable27plus27signed", 27, + 27); // 27 bits, 27 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0x02aaaaaa) * pow(2, -27)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0xFD555555) * pow(2, -27)); + + checkToCooked(converter, 0xAAAAAAAA, (int)0); + checkToCooked(converter, 0x55555555, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); + checkToCooked(converter, 0x55555555, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)0); + checkToCooked(converter, 0x55555555, (short)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)0); + checkToCooked(converter, 0x55555555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x2000000, "ToRaw1"); + checkToRaw(converter, -0.25, 0x6000000, "ToRaw2"); + + // these values are out of range + checkToRaw(converter, 0.75, 0x3ffffff, "ToRaw3"); + checkToRaw(converter, -0.75, 0x4000000, "ToRaw4"); + checkToRaw(converter, 3.25, 0x3ffffff, "ToRaw5"); + checkToRaw(converter, -3.25, 0x4000000, "ToRaw6"); + checkToRaw(converter, 5.75, 0x3ffffff, "ToRaw7"); + checkToRaw(converter, -5.75, 0x4000000, "ToRaw8"); + + checkToCooked(converter, 0x2000000, 0.25); + checkToCooked(converter, 0x6000000, -0.25); + + checkToRaw(converter, (int)0x55555555, 0x3ffffff, "ToRaw9"); + checkToRaw(converter, (int)0xAAAAAAAA, 0x4000000, "ToRaw10"); + checkToRaw(converter, (int)0, 0, "ToRaw11"); + checkToRaw(converter, (int)1, 0x3ffffff, "ToRaw12"); + checkToRaw(converter, (int)-1, 0x4000000, "ToRaw13"); + checkToRaw(converter, (short)0x5555, 0x3ffffff, "ToRaw14"); + checkToRaw(converter, (short)0xAAAA, 0x4000000, "ToRaw15"); + checkToRaw(converter, (short)-1, 0x4000000, "ToRaw16"); + checkToRaw(converter, (short)1, 0x3ffffff, "ToRaw17"); + checkToRaw(converter, (short)2, 0x3ffffff, "ToRaw18"); + checkToRaw(converter, (short)-2, 0x4000000, "ToRaw19"); +} + BOOST_AUTO_TEST_CASE(testInt16_fraction16) { FixedPointConverter converter("Variable16plus16signed", 16, 16); // 16 bits, 16 fractional bits, signed @@ -987,6 +1033,49 @@ BOOST_AUTO_TEST_CASE(testInt16_fraction16) { checkToRaw(converter, (short)0x5555, 0x7FFF, "ToRaw14"); checkToRaw(converter, (short)0xAAAA, 0x8000, "ToRaw15"); checkToRaw(converter, (short)-1, 0x8000, "ToRaw16"); + checkToRaw(converter, (short)-2, 0x8000, "ToRaw17"); +} + +BOOST_AUTO_TEST_CASE(testInt14_fraction14) { + FixedPointConverter converter("Variable14plus14signed", 14, + 14); // 16 bits, 1 6 fractional bits, signed + + checkToCooked(converter, 0xAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFFEAAA) * pow(2, -14)); + checkToCooked(converter, 0x5555, SIGNED_HEX_TO_DOUBLE(0x1555) * pow(2, -14)); + + checkToCooked(converter, 0xAAAA, (int)0); + checkToCooked(converter, 0x5555, (int)0); + checkToCooked(converter, 0xAAAA, (unsigned int)0); + checkToCooked(converter, 0x5555, (unsigned int)0); + checkToCooked(converter, 0xAAAA, (short)0); + checkToCooked(converter, 0x5555, (short)0); + checkToCooked(converter, 0xAAAA, (unsigned short)0); + checkToCooked(converter, 0x5555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x1000, "ToRaw1"); + checkToRaw(converter, -0.25, 0x3000, "ToRaw2"); + + // these values are out of range + checkToRaw(converter, 0.75, 0x1FFF, "ToRaw3"); + checkToRaw(converter, -0.75, 0x2000, "ToRaw4"); + checkToRaw(converter, 3.25, 0x1FFF, "ToRaw5"); + checkToRaw(converter, -3.25, 0x2000, "ToRaw6"); + checkToRaw(converter, 5.75, 0x1FFF, "ToRaw7"); + checkToRaw(converter, -5.75, 0x2000, "ToRaw8"); + + checkToCooked(converter, 0x1000, 0.25); + checkToCooked(converter, 0x3000, -0.25); + + checkToRaw(converter, (int)0x55555555, 0x1FFF, "ToRaw9"); + checkToRaw(converter, (int)0xAAAAAAAA, 0x2000, "ToRaw10"); + checkToRaw(converter, (int)0, 0, "ToRaw11"); + checkToRaw(converter, (int)1, 0x1FFF, "ToRaw12"); + checkToRaw(converter, (int)-1, 0x2000, "ToRaw13"); + + checkToRaw(converter, (short)0x5555, 0x1FFF, "ToRaw14"); + checkToRaw(converter, (short)0xAAAA, 0x2000, "ToRaw15"); + checkToRaw(converter, (short)-1, 0x2000, "ToRaw16"); + checkToRaw(converter, (short)-2, 0x2000, "ToRaw17"); } BOOST_AUTO_TEST_CASE(testUInt32_fraction32) { diff --git a/tests/executables_src/testFixedPointConverterRaw64.cpp b/tests/executables_src/testFixedPointConverterRaw64.cpp new file mode 100644 index 000000000..db353d2e8 --- /dev/null +++ b/tests/executables_src/testFixedPointConverterRaw64.cpp @@ -0,0 +1,1687 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE FixedPointConverterTest +#include +using namespace boost::unit_test_framework; + +/* Test requirements: + * Test to and from double for the follwing cases: + * int64, uint64, int32, uint32, int16, uint16, int8, uint8. No fractional bits (standard data + * types) 32 bits with -12 (negative), -1 (test rounding), 1 (test rounding), 7 + * (somewhere in the middle), 31, 32 (resolution edge) and 43 (larger than 32 + * bits), fractional bits, signed and unsigned 18 bits with -12, 0, 7, 17, 18, + * 43 fractional bits, signed and unsigned + * + * All tests are run with the bit sequence 0xAAAAAAAA (negative when signed) + * and 0x55555555 (positive when signed) to float, + * and with +-0.25, +-0.75, +-3.25 +-5.75 to fixed + * to test correct rounding. + * + */ + +using FIXEDPOINT_RAW_BIT64 = int64_t; + +#define HEX_TO_DOUBLE(INPUT) static_cast(INPUT) +#define SIGNED_HEX_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX_TO_INT64(INPUT) static_cast(static_cast(INPUT)) + +#define SIGNED_HEX16_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX16_TO_INT64(INPUT) static_cast(static_cast(INPUT)) + +#define CHECK_SIGNED_FIXED_POINT_NO_FRACTIONAL \ + checkToFixedPoint(converter, 0.25, 0); \ + checkToFixedPoint(converter, -0.25, 0); \ + checkToFixedPoint(converter, 0.75, 1); \ + checkToFixedPoint(converter, -0.75, -1); \ + checkToFixedPoint(converter, 3.25, 3); \ + checkToFixedPoint(converter, -3.25, -3); \ + checkToFixedPoint(converter, 5.75, 6); \ + checkToFixedPoint(converter, -5.75, -6); + +#include "Exception.h" +#include "FixedPointConverter.h" + +#include +namespace ChimeraTK { + using namespace ChimeraTK; +} +using namespace ChimeraTK; + +// get name of a type for better readible error messages +template +inline const char* typeName(void) { + return "unknown"; +} +#define DEF_TYPENAME(name) \ + template<> \ + inline const char* typeName(void) { \ + return #name; \ + } +DEF_TYPENAME(int32_t) +DEF_TYPENAME(uint32_t) +DEF_TYPENAME(int16_t) +DEF_TYPENAME(uint16_t) +DEF_TYPENAME(int8_t) +DEF_TYPENAME(uint8_t) +DEF_TYPENAME(int64_t) +DEF_TYPENAME(uint64_t) +DEF_TYPENAME(float) +DEF_TYPENAME(double) +DEF_TYPENAME(std::string) +DEF_TYPENAME(Boolean) + +template +void checkToCookedOverflowNeg(FixedPointConverter const& converter, uint32_t input) { + BOOST_CHECK_EQUAL(converter.scalarToCooked(input), std::numeric_limits::min()); +} + +template +void checkToCookedOverflowPos(FixedPointConverter const& converter, uint32_t input) { + BOOST_CHECK_EQUAL(converter.scalarToCooked(input), std::numeric_limits::max()); +} + +template +void checkToCooked(FixedPointConverter const& converter, uint32_t input, T expectedValue, + const std::string& msg = std::string("")) { + std::stringstream message; + message << "testToCooked failed for type " << typeName() << " with input " << std::hex << "0x" << input << std::hex + << ", expected 0x" << expectedValue << std::dec; + + BOOST_TEST_CHECKPOINT(message.str()); + + T output = converter.scalarToCooked(input); + if(msg.empty()) { + message << std::hex << ", output 0x" << output << std::dec; + } + else { + message << std::hex << ", output 0x" << output << std::dec << ", " << msg; + } + + BOOST_CHECK_MESSAGE(output == expectedValue, message.str()); +} + +template +void checkToRaw(FixedPointConverter const& converter, T input, uint32_t expectedValue, + const std::string& msg = std::string("")) { + std::stringstream message; + message << "testToRaw failed for type " << typeName() << " with input 0x" << std::hex << input << ", expected 0x" + << expectedValue << std::dec; + + BOOST_TEST_CHECKPOINT(message.str()); + + uint32_t result = converter.toRaw(input); + if(msg.empty()) { + message << std::hex << ", output 0x" << result << std::dec; + } + else { + message << std::hex << ", output 0x" << result << std::dec << ", " << msg; + } + + BOOST_CHECK_MESSAGE(result == expectedValue, message.str()); +} + +BOOST_AUTO_TEST_SUITE(FixedPointConverterTestSuite) + +BOOST_AUTO_TEST_CASE(testConstructor) { + BOOST_CHECK_NO_THROW(FixedPointConverter("UnknownVariable")); + BOOST_CHECK_NO_THROW(FixedPointConverter("UnknownVariable", 16, 42, false)); + + // number of significant bits + BOOST_CHECK_THROW(FixedPointConverter("UnknownVariable", 65), ChimeraTK::logic_error); + + // dynamic range of sufficient for bit shift + BOOST_CHECK_THROW(FixedPointConverter("UnknownVariable", 2, 1021 - 1), ChimeraTK::logic_error); + BOOST_CHECK_THROW(FixedPointConverter("UnknownVariable", 2, -1024 + 1), ChimeraTK::logic_error); + BOOST_CHECK_NO_THROW(FixedPointConverter("UnknownVariable", 2, 1021 - 2)); + BOOST_CHECK_NO_THROW(FixedPointConverter("UnknownVariable", 2, -1024 + 2)); +} + +BOOST_AUTO_TEST_CASE(testInt32) { + FixedPointConverter converter("Variable32signed"); // default parameters are signed 32 bit + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555)); + checkToCooked(converter, 0xAAAAAAAA, (int)0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (int)0x55555555); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_INT64(0xAAAAAAAA)); + checkToCooked(converter, 0x55555555, (uint64_t)0x55555555); + + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (unsigned int)0x55555555); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, -1); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, -3); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, -6); + + checkToRaw(converter, (int)0x55555555, 0x55555555); + checkToRaw(converter, (int)0xAAAAAAAA, 0xAAAAAAAA); + checkToRaw(converter, (unsigned int)0x55555555, 0x55555555); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x7FFFFFFF); + checkToRaw(converter, (short)0x5555, 0x5555); + checkToRaw(converter, (short)0xAAAA, 0xFFFFAAAA); + checkToRaw(converter, (unsigned short)0x5555, 0x5555); + checkToRaw(converter, (unsigned short)0xAAAA, 0xAAAA); + checkToRaw(converter, (int64_t)0x5555, 0x5555); + checkToRaw(converter, (int64_t)0xFFFFFFFFFFFFAAAA, 0xFFFFAAAA); + checkToRaw(converter, (int64_t)0xFFFFFFFAAAAAAAAA, + 0x80000000); // Smallest signed representation possible + checkToRaw(converter, (int64_t)0xFFFFFFFFF, 0x7FFFFFFF); + checkToRaw(converter, (uint64_t)0xFFFFFFFFF, + 0x7FFFFFFF); // max signed representation possible + + checkToCooked(converter, 0x55555555, std::string("1431655765")); + checkToRaw(converter, std::string("1431655765"), 0x55555555); + + // Boolean check + checkToCooked(converter, 0x55555555, Boolean(true)); +} + +BOOST_AUTO_TEST_CASE(testUInt32) { + FixedPointConverter converter("Variable32unsigned", 32, 0, + false); // 32 bits, 0 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555)); + checkToCookedOverflowPos(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (int)0x55555555); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (unsigned int)0x55555555); + checkToCookedOverflowPos(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (uint64_t)0x55555555); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0x55555555); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0x55555555); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0xAAAAAAAA); + checkToRaw(converter, (short)0x5555, 0x5555); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x5555); + checkToRaw(converter, (unsigned short)0xAAAA, 0xAAAA); + checkToRaw(converter, (int64_t)0x5555, 0x5555); + checkToRaw(converter, SIGNED_HEX_TO_INT64(0xAAAAAAAA), + 0x0); // Lowest range of 32 bit wide unsigned register + checkToRaw(converter, (int64_t)0x100000000, 0xFFFFFFFF); + checkToRaw(converter, (uint64_t)0x100000000, + 0xFFFFFFFF); // max signed representation possible + + checkToCooked(converter, 0x55555555, std::string("1431655765")); + checkToRaw(converter, std::string("1431655765"), 0x55555555); + + checkToCooked(converter, 0xAAAAAAAA, std::string("2863311530")); + checkToRaw(converter, std::string("2863311530"), 0xAAAAAAAA); + + checkToCooked(converter, 0xAAAAAAAA, Boolean(true)); +} + +BOOST_AUTO_TEST_CASE(testInt16) { + FixedPointConverter converter("Variable16signed", + 16); // 16 bits, 0 fractional bits, signed + + checkToCooked(converter, 0xAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFFAAAA)); + checkToCooked(converter, 0x5555, HEX_TO_DOUBLE(0x5555)); + checkToCooked(converter, 0xAAAA, (int)0xFFFFAAAA); + checkToCooked(converter, 0x5555, (int)0x5555); + checkToCookedOverflowNeg(converter, 0xAAAA); + checkToCooked(converter, 0x5555, (unsigned int)0x5555); + checkToCooked(converter, 0xAAAA, (short)0xAAAA); + checkToCooked(converter, 0x5555, (short)0x5555); + checkToCookedOverflowNeg(converter, 0xAAAA); + checkToCooked(converter, 0x5555, (unsigned short)0x5555); + checkToCooked(converter, 0x5555, (int64_t)0x5555); + checkToCooked(converter, 0xAAAA, static_cast(static_cast(0xAAAA))); + + checkToCooked(converter, 0x5555, (uint64_t)0x5555); + checkToCookedOverflowNeg(converter, 0xAAAA); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0xFFFF); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0xFFFD); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0xFFFA); + + checkToRaw(converter, (int)0x55555555, 0x7FFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0x8000); + checkToRaw(converter, (unsigned int)0x55555555, 0x7FFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x7FFF); + checkToRaw(converter, (short)0x5555, 0x5555); + checkToRaw(converter, (short)0xAAAA, 0xAAAA); + checkToRaw(converter, (unsigned short)0x5555, 0x5555); + checkToRaw(converter, (unsigned short)0xAAAA, 0x7FFF); + checkToRaw(converter, (int64_t)0x5555, 0x5555); + checkToRaw(converter, static_cast(static_cast(0xAAAA)), 0xAAAA); + checkToRaw(converter, (int64_t)0x555555, 0x7FFF); + checkToRaw(converter, static_cast(static_cast(0xAAAAAAAA)), 0x8000); + checkToRaw(converter, (uint64_t)0x5555, 0x5555); + checkToRaw(converter, (uint64_t)0x0, 0x0); + checkToRaw(converter, (uint64_t)0xF555, 0x7FFF); +} + +BOOST_AUTO_TEST_CASE(testUInt16) { + FixedPointConverter converter("Variable16unsigned", 16, 0, + false); // 16 bits, 0 fractional bits, not signed + + checkToCooked(converter, 0xAAAA, HEX_TO_DOUBLE(0xAAAA)); + checkToCooked(converter, 0x5555, HEX_TO_DOUBLE(0x5555)); + checkToCooked(converter, 0xAAAA, (int)0xAAAA); + checkToCooked(converter, 0x5555, (int)0x5555); + checkToCooked(converter, 0xAAAA, (unsigned int)0xAAAA); + checkToCooked(converter, 0x5555, (unsigned int)0x5555); + checkToCookedOverflowPos(converter, 0xAAAA); + checkToCooked(converter, 0x5555, (short)0x5555); + checkToCooked(converter, 0xAAAA, (unsigned short)0xAAAA); + checkToCooked(converter, 0x5555, (unsigned short)0x5555); + checkToCooked(converter, 0x5555, (int64_t)0x5555); + checkToCooked(converter, 0xAAAA, (int64_t)0xAAAA); + checkToCooked(converter, 0x5555, (uint64_t)0x5555); + checkToCooked(converter, 0xAAAA, (uint64_t)0xAAAA); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0xFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xFFFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0xFFFF); + checkToRaw(converter, (short)0x5555, 0x5555); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x5555); + checkToRaw(converter, (unsigned short)0xAAAA, 0xAAAA); + checkToRaw(converter, (int64_t)0x5555, 0x5555); + checkToRaw(converter, static_cast(static_cast(0xAAAA)), 0); + checkToRaw(converter, (int64_t)0x555555, 0xFFFF); + checkToRaw(converter, (uint64_t)0x5555, 0x5555); + checkToRaw(converter, (uint64_t)0x0, 0x0); + checkToRaw(converter, (uint64_t)0xFF555, 0xFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt8) { + FixedPointConverter converter("Variable8signed", + 8); // 8 bits, 0 fractional bits, signed + + checkToCooked(converter, 0xAA, SIGNED_HEX_TO_DOUBLE(0xFFFFFFAA)); + checkToCooked(converter, 0x55, HEX_TO_DOUBLE(0x55)); + checkToCooked(converter, 0xAA, (int)0xFFFFFFAA); + checkToCooked(converter, 0x55, (int)0x55); + checkToCookedOverflowNeg(converter, 0xAA); + checkToCooked(converter, 0x55, (unsigned int)0x55); + checkToCooked(converter, 0xAA, (short)0xFFAA); + checkToCooked(converter, 0x55, (short)0x55); + checkToCookedOverflowNeg(converter, 0xAA); + checkToCooked(converter, 0x55, (unsigned short)0x55); + checkToCooked(converter, 0x55, (int64_t)0x55); + checkToCooked(converter, 0xAA, static_cast(static_cast(0xAA))); + checkToCooked(converter, 0x55, (uint64_t)0x55); + checkToCookedOverflowNeg(converter, 0xAA); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0xFF); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0xFD); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0xFA); + + checkToRaw(converter, (int)0x55555555, 0x7F); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80); + checkToRaw(converter, (unsigned int)0x55555555, 0x7F); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x7F); + checkToRaw(converter, (short)0x5555, 0x7F); + checkToRaw(converter, (short)0xAAAA, 0x80); + checkToRaw(converter, (unsigned short)0x5555, 0x7F); + checkToRaw(converter, (unsigned short)0xAAAA, 0x7F); + + checkToRaw(converter, (int64_t)0x55, 0x55); + checkToRaw(converter, static_cast(static_cast(0xAA)), 0xAA); + checkToRaw(converter, (int64_t)0x5555, 0x7F); + checkToRaw(converter, static_cast(static_cast(0xAAAAAAAA)), 0x80); + checkToRaw(converter, (uint64_t)0x55, 0x55); + checkToRaw(converter, (uint64_t)0xF5, 0x7F); +} + +BOOST_AUTO_TEST_CASE(testUInt8) { + FixedPointConverter converter("Variable8unsigned", 8, 0, + false); // 8 bits, 0 fractional bits, not signed + + checkToCooked(converter, 0xAA, HEX_TO_DOUBLE(0xAA)); + checkToCooked(converter, 0x55, HEX_TO_DOUBLE(0x55)); + checkToCooked(converter, 0xAA, (int)0xAA); + checkToCooked(converter, 0x55, (int)0x55); + checkToCooked(converter, 0xAA, (unsigned int)0xAA); + checkToCooked(converter, 0x55, (unsigned int)0x55); + checkToCooked(converter, 0xAA, (short)0xAA); + checkToCooked(converter, 0x55, (short)0x55); + checkToCooked(converter, 0xAA, (unsigned short)0xAA); + checkToCooked(converter, 0x55, (unsigned short)0x55); + checkToCooked(converter, 0x55, (int64_t)0x55); + checkToCooked(converter, 0xAA, (int64_t)0xAA); + checkToCooked(converter, 0x55, (uint64_t)0x55); + checkToCooked(converter, 0xAA, (uint64_t)0xAA); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0); + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0xFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0xFF); + checkToRaw(converter, (short)0x5555, 0xFF); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0xFF); + checkToRaw(converter, (unsigned short)0xAAAA, 0xFF); + checkToRaw(converter, (int64_t)0x55, 0x55); + checkToRaw(converter, static_cast(static_cast(0xAAAA)), 0); + checkToRaw(converter, (int64_t)0x555555, 0xFF); + checkToRaw(converter, (uint64_t)0x55, 0x55); + checkToRaw(converter, (uint64_t)0x0, 0x0); + checkToRaw(converter, (uint64_t)0xFF555, 0xFF); +} + +BOOST_AUTO_TEST_CASE(testInt32_fractionMinus12) { + FixedPointConverter converter("Variable32minus12signed", 32, + -12); // 32 bits, -12 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, 12)); // Basically a left shift 12 + // places + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, 12)); + checkToCookedOverflowPos(converter, 0x000AAAAA); + checkToCooked(converter, 0xFFFAAAAA, (int)0xAAAAA000); + checkToCooked(converter, 0x00055555, (int)0x55555000); + checkToCookedOverflowNeg(converter, 0xFFFAAAAA); + checkToCooked(converter, 0x00055555, (unsigned int)0x55555000); + checkToCooked(converter, 0x000AAAAA, (unsigned int)0xAAAAA000); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xFFFFFAAAAAAAA000); + checkToCooked(converter, 0x55555555, (int64_t)0x55555555000); + checkToCooked(converter, 0x55555555, (uint64_t)0x55555555000); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + checkToRaw(converter, 3.25, 0); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0x00055555); + checkToRaw(converter, (int)0xAAAAAAAA, 0xFFFAAAAB); + checkToRaw(converter, (unsigned int)0x55555555, 0x00055555); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x000AAAAB); + checkToRaw(converter, (short)0x5555, 0x00000005); + checkToRaw(converter, (short)0xAAAA, 0xFFFFFFFB); + checkToRaw(converter, (unsigned short)0x5555, 0x00000005); + checkToRaw(converter, (unsigned short)0xAAAA, 0x0000000B); + checkToRaw(converter, (int64_t)0x55555555, 0x00055555); + checkToRaw(converter, static_cast(static_cast(0xAAAAAAAA)), 0XFFFAAAAB); + checkToRaw(converter, (int64_t)0x5555555555555, 0x7fffffff); // full range + checkToRaw(converter, (int64_t)0xFFFFA55555555555, 0x80000000); + checkToRaw(converter, (uint64_t)0x55555, 0x00000055); + checkToRaw(converter, (uint64_t)0x5555555555555, 0X7FFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fractionMinus12) { + FixedPointConverter converter("Variable32minus12unsigned", 32, -12, + false); // 32 bits, -12 fractional bits, not + // signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, 12)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * pow(2, 12)); + checkToCookedOverflowPos(converter, 0x000AAAAA); + checkToCooked(converter, 0x00055555, (int)0x55555000); + checkToCooked(converter, 0x00055555, (unsigned int)0x55555000); + checkToCooked(converter, 0x000AAAAA, (unsigned int)0xAAAAA000); + checkToCookedOverflowPos(converter, 0x000AAAAA); + checkToCooked(converter, 0x00055555, (int64_t)0x55555000); + checkToCooked(converter, 0x000AAAAA, (int64_t)0xAAAAA000); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xAAAAAAAA000); + checkToCooked(converter, 0x00055555, (uint64_t)0x55555000); + checkToCooked(converter, 0xAAAAAAAA, (uint64_t)0xAAAAAAAA000); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + checkToRaw(converter, 3.25, 0); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0x00055555); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0x00055555); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x000AAAAB); + checkToRaw(converter, (short)0x5555, 0x00000005); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x00000005); + checkToRaw(converter, (unsigned short)0xAAAA, 0x0000000B); + checkToRaw(converter, (int64_t)0x55555555, 0x00055555); + checkToRaw(converter, static_cast(static_cast(0xAAAAAAAA)), 0x0); + checkToRaw(converter, (int64_t)0x5555555555555, 0xFFFFFFFF); // full range + checkToRaw(converter, (uint64_t)0x55555, 0x00000055); + checkToRaw(converter, (uint64_t)0x5555555555555, 0XFFFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt32_fractionMinus1) { + FixedPointConverter converter("Variable32minus1signed", 32, + -1); // 32 bits, -1 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 2); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + checkToCooked(converter, 0x22222202, (int)0x44444404); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (unsigned int)0xAAAAAAAA); + checkToCooked(converter, 0x22222202, (unsigned int)0x44444404); + checkToCooked(converter, 0x7FFFFFFF, (unsigned int)0xFFFFFFFE); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xFFFFFFFF55555554); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + // bit pattern of 3 is 11, where the last digit is rounded up, and afterwards + // one bit is shifted. So the actual value is 4 + checkToRaw(converter, 3.25, 0x2); + checkToRaw(converter, -3.25, 0xFFFFFFFE); // (-2) + checkToRaw(converter, 5.75, 0x3); + checkToRaw(converter, -5.75, 0xFFFFFFFD); // (-3) + + checkToRaw(converter, (int)0x55555554, 0x2AAAAAAA); + checkToRaw(converter, (int)0x55555555, 0x2AAAAAAB); + checkToRaw(converter, (int)0x55555556, 0x2AAAAAAB); + checkToRaw(converter, (int)0xAAAAAAAA, 0xD5555555); + checkToRaw(converter, (unsigned int)0x55555555, 0x2AAAAAAB); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x55555555); + checkToRaw(converter, (short)0x5555, 0x00002AAB); + checkToRaw(converter, (short)0xAAAA, 0xFFFFD555); + checkToRaw(converter, (unsigned short)0x5555, 0x00002AAB); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00005555); + checkToRaw(converter, static_cast(static_cast(0xAAAAAAAA)), 0xD5555555); + checkToRaw(converter, static_cast(0xAAAAAAAA), 0x55555555); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fractionMinus1) { + FixedPointConverter converter("Variable32minus1unsigned", 32, -1, + false); // 32 bits, -1 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * 2); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 2); + checkToCooked(converter, 0x22222202, (int)0x44444404); + checkToCooked(converter, 0x55555555, (unsigned int)0xAAAAAAAA); + checkToCooked(converter, 0x22222202, (unsigned int)0x44444404); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + // bit pattern of 3 is 11, where the last digit is rounded up, and afterwards + // one bit is shifted. So the actual value is 4 + checkToRaw(converter, 3.25, 0x2); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0x3); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x55555555, 0x2AAAAAAB); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0x2AAAAAAB); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x55555555); + checkToRaw(converter, (short)0x5555, 0x00002AAB); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x00002AAB); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00005555); +} + +BOOST_AUTO_TEST_CASE(testInt16_fractionMinus1) { + FixedPointConverter converter( + "Variable16minus1signed", 16, -1); // 16 bits, -1 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFFAAAA) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x5555) * 2); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + checkToCooked(converter, 0x22222202, (int)0x4404); + checkToCookedOverflowNeg(converter, 0xAAAA); + checkToCooked(converter, 0x55555555, (unsigned int)0xAAAA); + checkToCooked(converter, 0x22222202, (unsigned int)0x4404); + checkToCooked(converter, 0x00007FFF, (unsigned int)0xFFFE); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xFFFFFFFFFFFF5554); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + // bit pattern of 3 is 11, where the last digit is rounded up, and afterwards + // one bit is shifted. So the actual value is 4 + checkToRaw(converter, 3.25, 0x2); + checkToRaw(converter, -3.25, 0xFFFE); // (-2) + checkToRaw(converter, 5.75, 0x3); + checkToRaw(converter, -5.75, 0xFFFD); // (-3) + + checkToRaw(converter, (int)0x5554, 0x2AAA); + checkToRaw(converter, (int)0x5555, 0x2AAB); + checkToRaw(converter, (int)0x5556, 0x2AAB); + checkToRaw(converter, (int)0xFFFFAAAA, 0xD555); + checkToRaw(converter, (unsigned int)0x5555, 0x2AAB); + checkToRaw(converter, (unsigned int)0xAAAA, 0x5555); + checkToRaw(converter, (short)0x5555, 0x00002AAB); + checkToRaw(converter, (short)0xAAAA, 0xD555); + checkToRaw(converter, (unsigned short)0x5555, 0x00002AAB); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00005555); + checkToRaw(converter, static_cast(static_cast(0xFFFFAAAA)), 0xD555); + checkToRaw(converter, static_cast(0xAAAA), 0x5555); +} + +BOOST_AUTO_TEST_CASE(testUInt16_fractionMinus1) { + FixedPointConverter converter( + "Variable16minus1unsigned", 16, -1, false); // 16 bits, -1 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAA) * 2); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x5555) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x5555) * 2); + checkToCooked(converter, 0x22222202, (int)0x4404); + checkToCooked(converter, 0x55555555, (unsigned int)0xAAAA); + checkToCooked(converter, 0x22222202, (unsigned int)0x4404); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + // bit pattern of 3 is 11, where the last digit is rounded up, and afterwards + // one bit is shifted. So the actual value is 4 + checkToRaw(converter, 3.25, 0x2); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0x3); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0x5555, 0x2AAB); + checkToRaw(converter, (int)0xFFFFAAAA, 0); + checkToRaw(converter, (unsigned int)0x5555, 0x2AAB); + checkToRaw(converter, (unsigned int)0xAAAA, 0x5555); + checkToRaw(converter, (short)0x5555, 0x00002AAB); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x00002AAB); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00005555); +} + +BOOST_AUTO_TEST_CASE(testInt32_fraction1) { + FixedPointConverter converter("Variable32plus1signed", 32, + 1); // 32 bits, 1 fractional bit, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * 0.5); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 0.5); + checkToCooked(converter, 0xAAAAAAA9, (int)0xD5555554); + checkToCooked(converter, 0xAAAAAAAA, (int)0xD5555555); + checkToCooked(converter, 0xAAAAAAAB, (int)0xD5555555); + checkToCooked(converter, 0xFFFFFE11, (int)0xFFFFFF08); + checkToCooked(converter, 0x55555554, (int)0x2AAAAAAA); + checkToCooked(converter, 0x55555555, (int)0x2AAAAAAB); + checkToCooked(converter, 0x55555556, (int)0x2AAAAAAB); + checkToCooked(converter, 0x22222202, (int)0x11111101); + checkToCooked(converter, 0x55555555, (unsigned int)0x2AAAAAAB); + checkToCooked(converter, 0x22222202, (unsigned int)0x11111101); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0xFFFFFFFFD5555555); + checkToCooked(converter, 0x55555555, (int64_t)0x2AAAAAAB); + + checkToRaw(converter, 0.25, 0x1); + checkToRaw(converter, -0.25, 0xFFFFFFFF); + checkToRaw(converter, 0.75, 0x2); + checkToRaw(converter, -0.75, 0xFFFFFFFE); + + checkToRaw(converter, 3.25, 0x7); + checkToRaw(converter, -3.25, 0xFFFFFFF9); // (-7) + checkToRaw(converter, 5.75, 0xC); + checkToRaw(converter, -5.75, 0xFFFFFFF4); // (-12) + + checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000); + checkToRaw(converter, (int)0x22222202, 0x44444404); + checkToRaw(converter, (int)0xE2222202, 0xC4444404); + checkToRaw(converter, (unsigned int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x7FFFFFFF); + checkToRaw(converter, (unsigned int)0x22222202, 0x44444404); + checkToRaw(converter, (unsigned int)0xE2222202, 0x7FFFFFFF); + checkToRaw(converter, (short)0x5555, 0x0000AAAA); + checkToRaw(converter, (short)0xAAAA, 0xFFFF5554); + checkToRaw(converter, (unsigned short)0x5555, 0x0000AAAA); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00015554); + checkToRaw(converter, static_cast(static_cast(0xFAAAAAAA)), 0XF5555554); + checkToRaw(converter, static_cast(0xAAAAAAA), 0X15555554); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fraction1) { + FixedPointConverter converter("Variable32plus1unsigned", 32, 1, + false); // 32 bits, 1 fractional bit, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * 0.5); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * 0.5); + checkToCooked(converter, 0xAAAAAAAA, (int)0x55555555); + checkToCooked(converter, 0x55555555, (int)0x2AAAAAAB); + checkToCooked(converter, 0x22222202, (int)0x11111101); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0x55555555); + checkToCooked(converter, 0x55555555, (unsigned int)0x2AAAAAAB); + checkToCooked(converter, 0x22222202, (unsigned int)0x11111101); + checkToCooked(converter, 0xAAAAAAAA, (int64_t)0x55555555); + checkToCooked(converter, 0x55555555, (int64_t)0x2AAAAAAB); + + checkToRaw(converter, 0.25, 0x1); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x2); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 0x7); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0xC); + checkToRaw(converter, -5.75, 0x0); + + checkToRaw(converter, (int)0x55555555, 0xAAAAAAAA); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (int)0x22222202, 0x44444404); + checkToRaw(converter, (int)0xE2222202, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xAAAAAAAA); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0xFFFFFFFF); + checkToRaw(converter, (unsigned int)0x22222202, 0x44444404); + checkToRaw(converter, (unsigned int)0xE2222202, 0xFFFFFFFF); + checkToRaw(converter, (short)0x5555, 0x0000AAAA); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x0000AAAA); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00015554); + checkToRaw(converter, static_cast(static_cast(0xFAAAAAAA)), 0x0); + checkToRaw(converter, static_cast(0xFAAAAAAA), 0XFFFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt32_fraction7) { + FixedPointConverter converter("Variable32plus7signed", 32, + 7); // 32 bits, 7 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -7)); + checkToCooked(converter, 0xAAAAAA20, (int)0xFF555554); + checkToCooked(converter, 0xAAAAAA60, (int)0xFF555555); + checkToCooked(converter, 0xAAAAAA80, (int)0xFF555555); + checkToCooked(converter, 0xAAAAAAAA, (int)0xFF555555); + checkToCooked(converter, 0xAAAAAAC0, (int)0xFF555555); + checkToCooked(converter, 0xAAAAAAD0, (int)0xFF555556); + checkToCooked(converter, 0xAAAAAAFF, (int)0xFF555556); + checkToCooked(converter, 0x5555553F, (int)0x00AAAAAA); + checkToCooked(converter, 0x55555540, (int)0x00AAAAAB); + checkToCooked(converter, 0x555555BF, (int)0x00AAAAAB); + checkToCooked(converter, 0x555555C0, (int)0x00AAAAAC); + checkToCooked(converter, 0x22220222, (int)0x00444404); + checkToCooked(converter, 0x55555555, (unsigned int)0x00AAAAAB); + checkToCooked(converter, 0x22220222, (unsigned int)0x00444404); + + checkToRaw(converter, 0.25, 0x20); + checkToRaw(converter, -0.25, 0xFFFFFFE0); + checkToRaw(converter, 0.75, 0x60); + checkToRaw(converter, -0.75, 0xFFFFFFA0); + + checkToRaw(converter, 3.25, 0x1A0); + checkToRaw(converter, -3.25, 0xFFFFFE60); + checkToRaw(converter, 5.75, 0x2E0); + checkToRaw(converter, -5.75, 0xFFFFFD20); + + checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000); + checkToRaw(converter, (int)0x00888808, 0x44440400); + checkToRaw(converter, (int)0xFF888808, 0xC4440400); + checkToRaw(converter, (unsigned int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0x7FFFFFFF); + checkToRaw(converter, (unsigned int)0x00888808, 0x44440400); + checkToRaw(converter, (unsigned int)0xFF888808, 0x7FFFFFFF); + checkToRaw(converter, (short)0x5555, 0x002AAA80); + checkToRaw(converter, (short)0xAAAA, 0xFFD55500); + checkToRaw(converter, (unsigned short)0x5555, 0x002AAA80); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00555500); + + checkToCooked(converter, 0x20, std::string("0.250000")); + checkToRaw(converter, std::string("0.25"), 0x20); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fraction7) { + FixedPointConverter converter("Variable32plus7unsigned", 32, 7, + false); // 32 bits, -7 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * pow(2, -7)); + checkToCooked(converter, 0xAAAAAAAA, (int)0x01555555); + checkToCooked(converter, 0x55555555, (int)0x00AAAAAB); + checkToCooked(converter, 0x22220222, (int)0x00444404); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0x01555555); + checkToCooked(converter, 0x55555555, (unsigned int)0x00AAAAAB); + checkToCooked(converter, 0x22220222, (unsigned int)0x00444404); + + checkToRaw(converter, 0.25, 0x20); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x60); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 0x1A0); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0x2E0); + checkToRaw(converter, -5.75, 0x0); + + checkToRaw(converter, (int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (int)0x00888808, 0x44440400); + checkToRaw(converter, (int)0xFF888808, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (unsigned int)0xAAAAAAAA, 0xFFFFFFFF); + checkToRaw(converter, (unsigned int)0x00888808, 0x44440400); + checkToRaw(converter, (unsigned int)0xFF888808, 0xFFFFFFFF); + checkToRaw(converter, (short)0x5555, 0x002AAA80); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (unsigned short)0x5555, 0x002AAA80); + checkToRaw(converter, (unsigned short)0xAAAA, 0x00555500); +} + +BOOST_AUTO_TEST_CASE(testInt32_fraction31) { + FixedPointConverter converter("Variable32plus31signed", 32, + 31); // 32 bits, 31 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -31)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -31)); + checkToCooked(converter, 0xAAAAAAAA, (int)-1); + checkToCooked(converter, 0x55555555, (int)1); + checkToCooked(converter, 0x22220222, (int)0); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (unsigned int)1); + checkToCooked(converter, 0x22220222, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)-1); + checkToCooked(converter, 0x55555555, (short)1); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (unsigned short)1); + + checkToRaw(converter, 0.25, 0x20000000); + checkToRaw(converter, -0.25, 0xE0000000); + checkToRaw(converter, 0.75, 0x60000000); + checkToRaw(converter, -0.75, 0xA0000000); + + // these values are out of range + checkToRaw(converter, 3.25, 0x7FFFFFFF); + checkToRaw(converter, -3.25, 0x80000000); + checkToRaw(converter, 5.75, 0x7FFFFFFF); + checkToRaw(converter, -5.75, 0x80000000); + + checkToCooked(converter, 0xA0000000, -0.75); + checkToCooked(converter, 0x60000000, 0.75); + checkToCooked(converter, 0xE0000000, -0.25); + checkToCooked(converter, 0x20000000, 0.25); + + checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000); + checkToRaw(converter, (int)0, 0); + checkToRaw(converter, (int)-1, 0x80000000); + checkToRaw(converter, (unsigned int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (short)0x5555, 0x7FFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0x80000000); + checkToRaw(converter, (short)-1, 0x80000000); + checkToRaw(converter, (unsigned short)0x5555, 0x7FFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fraction31) { + FixedPointConverter converter("Variable32plus31unsigned", 32, 31, + false); // 32 bits, 31 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -31)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * pow(2, -31)); + checkToCooked(converter, 0xAAAAAAAA, (int)1); + checkToCooked(converter, 0x55555555, (int)1); + checkToCooked(converter, 0x22220222, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)1); + checkToCooked(converter, 0x55555555, (unsigned int)1); + checkToCooked(converter, 0x22220222, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)1); + checkToCooked(converter, 0x55555555, (short)1); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)1); + checkToCooked(converter, 0x55555555, (unsigned short)1); + + checkToRaw(converter, 0.25, 0x20000000); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x60000000); + checkToRaw(converter, -0.75, 0x0); + + // these values are out of range + checkToRaw(converter, 3.25, 0xFFFFFFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0xFFFFFFFF); + checkToRaw(converter, -5.75, 0x0); + + checkToCooked(converter, 0xA0000000, 1.25); + checkToCooked(converter, 0x60000000, 0.75); + checkToCooked(converter, 0xE0000000, 1.75); + checkToCooked(converter, 0x20000000, 0.25); + + checkToRaw(converter, (int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (int)1, 0x80000000); + checkToRaw(converter, (unsigned int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (unsigned int)1, 0x80000000); + checkToRaw(converter, (short)0x5555, 0xFFFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (short)1, 0x80000000); + checkToRaw(converter, (unsigned short)0x5555, 0xFFFFFFFF); + checkToRaw(converter, (unsigned short)1, 0x80000000); +} + +BOOST_AUTO_TEST_CASE(testInt32_fraction32) { + FixedPointConverter converter("Variable32plus32signed", 32, + 32); // 32 bits, 32 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -32)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -32)); + checkToCooked(converter, 0xAAAAAAAA, (int)0); + checkToCooked(converter, 0x55555555, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); + checkToCooked(converter, 0x55555555, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)0); + checkToCooked(converter, 0x55555555, (short)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)0); + checkToCooked(converter, 0x55555555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x40000000, "ToRaw1"); + checkToRaw(converter, -0.25, 0xC0000000, "ToRaw2"); + + // these values are out of range + checkToRaw(converter, 0.75, 0x7FFFFFFF, "ToRaw3"); + checkToRaw(converter, -0.75, 0x80000000, "ToRaw4"); + checkToRaw(converter, 3.25, 0x7FFFFFFF, "ToRaw5"); + checkToRaw(converter, -3.25, 0x80000000, "ToRaw6"); + checkToRaw(converter, 5.75, 0x7FFFFFFF, "ToRaw7"); + checkToRaw(converter, -5.75, 0x80000000, "ToRaw8"); + + checkToCooked(converter, 0x40000000, 0.25); + checkToCooked(converter, 0xC0000000, -0.25); + + checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF, "ToRaw9"); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000, "ToRaw10"); + checkToRaw(converter, (int)0, 0, "ToRaw11"); + checkToRaw(converter, (int)1, 0x7FFFFFFF, "ToRaw12"); + checkToRaw(converter, (int)-1, 0x80000000, "ToRaw13"); + // checkToRaw( converter, (unsigned int)0x55555555, 0x7FFFFFFF ); + checkToRaw(converter, (short)0x5555, 0x7FFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0x80000000, "ToRaw14"); + checkToRaw(converter, (short)-1, 0x80000000, "ToRaw15"); + // checkToRaw( converter, (unsigned short)0x5555, 0x7FFFFFFF ); +} + +BOOST_AUTO_TEST_CASE(testInt16_fraction16) { + FixedPointConverter converter("Variable16plus16signed", 16, + 16); // 16 bits, 16 fractional bits, signed + + checkToCooked(converter, 0xAAAA, SIGNED_HEX16_TO_DOUBLE(0xAAAA) * pow(2, -16), "ToCoocked1"); + checkToCooked(converter, 0x5555, SIGNED_HEX16_TO_DOUBLE(0x5555) * pow(2, -16), "ToCoocked2"); + checkToCooked(converter, 0xAAAA, (int)0); + checkToCooked(converter, 0x5555, (int)0); + checkToCooked(converter, 0xAAAA, (unsigned int)0); + checkToCooked(converter, 0x5555, (unsigned int)0); + checkToCooked(converter, 0xAAAA, (short)0); + checkToCooked(converter, 0x5555, (short)0); + checkToCooked(converter, 0xAAAA, (unsigned short)0); + checkToCooked(converter, 0x5555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x4000, "ToRaw1"); + checkToRaw(converter, -0.25, 0xC000, "ToRaw2"); + + // these values are out of range + checkToRaw(converter, 0.75, 0x7FFF, "ToRaw3"); + checkToRaw(converter, -0.75, 0x8000, "ToRaw4"); + checkToRaw(converter, 3.25, 0x7FFF, "ToRaw5"); + checkToRaw(converter, -3.25, 0x8000, "ToRaw6"); + checkToRaw(converter, 5.75, 0x7FFF, "ToRaw7"); + checkToRaw(converter, -5.75, 0x8000, "ToRaw8"); + + checkToCooked(converter, 0x4000, 0.25); + checkToCooked(converter, 0xC000, -0.25); + + checkToRaw(converter, (int)0x55555555, 0x7FFF, "ToRaw9"); + checkToRaw(converter, (int)0xAAAAAAAA, 0x8000, "ToRaw10"); + checkToRaw(converter, (int)0, 0, "ToRaw11"); + checkToRaw(converter, (int)1, 0x7FFF, "ToRaw12"); + checkToRaw(converter, (int)-1, 0x8000, "ToRaw13"); + + checkToRaw(converter, (short)0x5555, 0x7FFF, "ToRaw14"); + checkToRaw(converter, (short)0xAAAA, 0x8000, "ToRaw15"); + checkToRaw(converter, (short)-1, 0x8000, "ToRaw16"); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fraction32) { + FixedPointConverter converter("Variable32plus32unsigned", 32, 32, + false); // 32 bits, 32 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -32)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * pow(2, -32)); + checkToCooked(converter, 0xAAAAAAAA, (int)1); + checkToCooked(converter, 0x55555555, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)1); + checkToCooked(converter, 0x55555555, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)1); + checkToCooked(converter, 0x55555555, (short)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)1); + checkToCooked(converter, 0x55555555, (unsigned short)0); + + checkToRaw(converter, 0.25, 0x40000000); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0xC0000000); + checkToRaw(converter, -0.75, 0x0); + + // these values are out of range + checkToRaw(converter, 3.25, 0xFFFFFFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0xFFFFFFFF); + checkToRaw(converter, -5.75, 0x0); + + checkToCooked(converter, 0x40000000, 0.25); + checkToCooked(converter, 0xC0000000, 0.75); + + checkToRaw(converter, (int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (int)0, 0); + checkToRaw(converter, (int)-1, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (short)0x5555, 0xFFFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (short)-1, 0); + checkToRaw(converter, (unsigned short)0x5555, 0xFFFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt32_fraction43) { + FixedPointConverter converter("Variable32plus43signed", 32, + 43); // 32 bits, 43 fractional bits, signed + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -43)); + checkToCooked(converter, 0xAAAAAAAA, (int)0); + checkToCooked(converter, 0x55555555, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); + checkToCooked(converter, 0x55555555, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)0); + checkToCooked(converter, 0x55555555, (short)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)0); + checkToCooked(converter, 0x55555555, (unsigned short)0); + checkToCooked(converter, 0x555, (int64_t)0); + checkToCooked(converter, 0x555, (uint64_t)0); + + // all out of range + checkToRaw(converter, 0.25, 0x7FFFFFFF); + checkToRaw(converter, -0.25, 0x80000000); + checkToRaw(converter, 0.75, 0x7FFFFFFF); + checkToRaw(converter, -0.75, 0x80000000); + + checkToRaw(converter, 3.25, 0x7FFFFFFF); + checkToRaw(converter, -3.25, 0x80000000); + checkToRaw(converter, 5.75, 0x7FFFFFFF); + checkToRaw(converter, -5.75, 0x80000000); + + checkToRaw(converter, (int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0x80000000); + checkToRaw(converter, (int)0, 0); + checkToRaw(converter, (int)-1, 0x80000000); + checkToRaw(converter, (unsigned int)0x55555555, 0x7FFFFFFF); + checkToRaw(converter, (short)0x5555, 0x7FFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0x80000000); + checkToRaw(converter, (short)-1, 0x80000000); + checkToRaw(converter, (unsigned short)0x5555, 0x7FFFFFFF); + checkToRaw(converter, (int64_t)0xFFFFFFFAAAAAAAAA, 0x80000000); + checkToRaw(converter, (uint64_t)0xAAAAAAAAA, 0x7FFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testUInt32_fraction43) { + FixedPointConverter converter("Variable32plus43unsigned", 32, 43, + false); // 32 bits, 43 fractional bits, not signed + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * pow(2, -43)); + checkToCooked(converter, 0xAAAAAAAA, (int)0); + checkToCooked(converter, 0x55555555, (int)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); + checkToCooked(converter, 0x55555555, (unsigned int)0); + checkToCooked(converter, 0xAAAAAAAA, (short)0); + checkToCooked(converter, 0x55555555, (short)0); + checkToCooked(converter, 0xAAAAAAAA, (unsigned short)0); + checkToCooked(converter, 0x55555555, (unsigned short)0); + + // all out of range + checkToRaw(converter, 0.25, 0xFFFFFFFF); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0xFFFFFFFF); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 0xFFFFFFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0xFFFFFFFF); + checkToRaw(converter, -5.75, 0x0); + + checkToRaw(converter, (int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (int)0xAAAAAAAA, 0); + checkToRaw(converter, (int)0, 0); + checkToRaw(converter, (int)-1, 0); + checkToRaw(converter, (unsigned int)0x55555555, 0xFFFFFFFF); + checkToRaw(converter, (short)0x5555, 0xFFFFFFFF); + checkToRaw(converter, (short)0xAAAA, 0); + checkToRaw(converter, (short)-1, 0); + checkToRaw(converter, (unsigned short)0x5555, 0xFFFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt18_fractionMinus12) { + FixedPointConverter converter("int18_fractionMinus12", 18, + -12); // 10 bits, -12 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + + // the converter should ignore bits which are not in the spec + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + + checkToCooked(converter, 0x2AAAA, (int)0xEAAAA000); + checkToCooked(converter, 0x15555, (int)0x15555000); + checkToCooked(converter, 0x15555, (unsigned int)0x15555000); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + checkToRaw(converter, 3.25, 0); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0xEAAAA000, 0x2AAAA); + checkToRaw(converter, (int)0x15555000, 0x15555); + checkToRaw(converter, (unsigned int)0x15555000, 0x15555); + checkToRaw(converter, (short)0xA000, 0x3FFFA); + checkToRaw(converter, (short)0x5000, 0x00005); + checkToRaw(converter, (unsigned short)0xA000, 0x0000A); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fractionMinus12) { + FixedPointConverter converter("Variable18minus12unsigned", 18, -12, + false); // 10 bits, -12 fractional bits, not + // signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, 12)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + + // the converter should ignore bits which are not in the spec + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, 12)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + + checkToCooked(converter, 0x2AAAA, (int)0x2AAAA000); + checkToCooked(converter, 0x15555, (int)0x15555000); + checkToCooked(converter, 0x2AAAA, (unsigned int)0x2AAAA000); + checkToCooked(converter, 0x15555, (unsigned int)0x15555000); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 0); + checkToRaw(converter, -0.75, 0); + + checkToRaw(converter, 3.25, 0); + checkToRaw(converter, -3.25, 0); + checkToRaw(converter, 5.75, 0); + checkToRaw(converter, -5.75, 0); + + checkToRaw(converter, (int)0xEAAAA000, 0); + checkToRaw(converter, (int)0x15555000, 0x15555); + checkToRaw(converter, (unsigned int)0x15555000, 0x15555); + checkToRaw(converter, (short)0xA000, 0); + checkToRaw(converter, (short)0x5000, 0x00005); + checkToRaw(converter, (unsigned short)0xA000, 0x0000A); +} + +BOOST_AUTO_TEST_CASE(testInt18_fraction0) { + FixedPointConverter converter("Variable18minus12signed", + 18); // 18 bits, 0 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555)); + + // the converter should ignore bits which are not in the spec + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555)); + + checkToCooked(converter, 0x2AAAA, (int)0xFFFEAAAA); + checkToCooked(converter, 0x15555, (int)0x15555); + checkToCooked(converter, 0x15555, (unsigned int)0x15555); + + checkToCooked(converter, 0x2AAAA, (int64_t)0xFFFFFFFFFFFEAAAA); + checkToCooked(converter, 0x15555, (int64_t)0x15555); + checkToCooked(converter, 0x15555, (uint64_t)0x15555); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0x3FFFF); + + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0x3FFFD); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0x3FFFA); + + checkToRaw(converter, (int)0xFFFEAAAA, 0x2AAAA); + checkToRaw(converter, (int)0x00015555, 0x15555); + checkToRaw(converter, (unsigned int)0x00015555, 0x15555); + checkToRaw(converter, (short)0xA000, 0x3A000); + checkToRaw(converter, (short)0x5000, 0x05000); + checkToRaw(converter, (unsigned short)0xA000, 0x0A000); + + checkToRaw(converter, (int64_t)0xFFFFFFFFFFFFA000, 0x3A000); + checkToRaw(converter, (int64_t)0xA000, 0xA000); + checkToRaw(converter, (uint64_t)0xA000, 0x0A000); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fraction0) { + FixedPointConverter converter("Variable18unsigned", 18, 0, + false); // 10 bits, -12 fractional bits, not signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555)); + + // the converter should ignore bits which are not in the spec + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555)); + + checkToCooked(converter, 0x2AAAA, (int)0x2AAAA); + checkToCooked(converter, 0x15555, (int)0x15555); + checkToCooked(converter, 0x2AAAA, (unsigned int)0x2AAAA); + checkToCooked(converter, 0x15555, (unsigned int)0x15555); + + checkToRaw(converter, 0.25, 0); + checkToRaw(converter, -0.25, 0); + checkToRaw(converter, 0.75, 1); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 3); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 6); + checkToRaw(converter, -5.75, 0x0); + + checkToRaw(converter, (int)0xFFFEAAAA, 0); + checkToRaw(converter, (int)0x00015555, 0x15555); + checkToRaw(converter, (unsigned int)0x00015555, 0x15555); + checkToRaw(converter, (short)0xA000, 0); + checkToRaw(converter, (short)0x5000, 0x05000); + checkToRaw(converter, (unsigned short)0xA000, 0x0A000); +} + +BOOST_AUTO_TEST_CASE(testInt18_fraction7) { + FixedPointConverter converter("Variable18plus7signed", 18, + 7); // 18 bits, 7 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + + checkToCooked(converter, 0x2AAAA, (int)0xFFFFFD55); + checkToCooked(converter, 0x15555, (int)0x02AB); + checkToCooked(converter, 0x15555, (unsigned int)0x2AB); + + checkToRaw(converter, 0.25, 0x20); + checkToRaw(converter, -0.25, 0x3FFE0); + checkToRaw(converter, 0.75, 0x60); + checkToRaw(converter, -0.75, 0x3FFA0); + + checkToRaw(converter, 3.25, 0x1A0); + checkToRaw(converter, -3.25, 0x3FE60); + checkToRaw(converter, 5.75, 0x2E0); + checkToRaw(converter, -5.75, 0x3FD20); + + checkToRaw(converter, (int)0xFFFFFD55, 0x2AA80); + checkToRaw(converter, (int)0x02AA, 0x15500); + checkToRaw(converter, (unsigned int)0x2AA, 0x15500); + checkToRaw(converter, (short)0xFFAA, 0x3D500); + checkToRaw(converter, (short)0x0055, 0x02A80); + checkToRaw(converter, (unsigned short)0x0055, 0x02A80); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fraction7) { + FixedPointConverter converter("Variable18plus7unsigned", 18, 7, + false); // 10 bits, -12 fractional bits, not signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -7)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + + checkToCooked(converter, 0x2AAAA, (int)0x0555); + checkToCooked(converter, 0x15555, (int)0x02AB); + checkToCooked(converter, 0x2AAAA, (unsigned int)0x0555); + checkToCooked(converter, 0x15555, (unsigned int)0x02AB); + checkToCooked(converter, 0x2AAAA, (short)0x0555); + checkToCooked(converter, 0x15555, (short)0x02AB); + checkToCooked(converter, 0x2AAAA, (unsigned short)0x0555); + checkToCooked(converter, 0x15555, (unsigned short)0x02AB); + + checkToRaw(converter, 0.25, 0x20); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x60); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 0x1A0); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0x2E0); + checkToRaw(converter, -5.75, 0x0); + + checkToRaw(converter, (int)0x0555, 0x2AA80); + checkToRaw(converter, (int)0x02AA, 0x15500); + checkToRaw(converter, (unsigned int)0x02AA, 0x15500); + checkToRaw(converter, (short)0xFFAA, 0); + checkToRaw(converter, (short)0x0055, 0x02A80); + checkToRaw(converter, (unsigned short)0x0055, 0x02A80); +} + +BOOST_AUTO_TEST_CASE(testInt18_fraction17) { + FixedPointConverter converter("Variable18plus17signed", 18, + 17); // 18 bits, 17 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + + checkToRaw(converter, 0.25, 0x8000); + checkToRaw(converter, -0.25, 0x38000); + checkToRaw(converter, 0.75, 0x18000); + checkToRaw(converter, -0.75, 0x28000); + + // these values are out of range + checkToRaw(converter, 3.25, 0x1FFFF); + checkToRaw(converter, -3.25, 0x20000); + checkToRaw(converter, 5.75, 0x1FFFF); + checkToRaw(converter, -5.75, 0x20000); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fraction17) { + FixedPointConverter converter("Variable18plus17unsigned", 18, 17, + false); // 18 bits, 17 fractional bits, not signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -17)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -17)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + + checkToRaw(converter, 0.25, 0x8000); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x18000); + checkToRaw(converter, -0.75, 0x0); + + // these values are out of range + checkToRaw(converter, 3.25, 0x3FFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0x3FFFF); + checkToRaw(converter, -5.75, 0x0); +} + +BOOST_AUTO_TEST_CASE(testInt18_fraction18) { + FixedPointConverter converter("Variable18plus18signed", 18, + 18); // 18 bits, 18 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + + checkToRaw(converter, 0.25, 0x10000); + checkToRaw(converter, -0.25, 0x30000); + // +- 0.75 are out of range + checkToRaw(converter, 0.75, + 0x1FFFF); // the largest possible value (0.5 - 1e-18) + checkToRaw(converter, -0.75, 0x20000); // -0.5, the smallest possible value + + checkToCooked(converter, 0x10000, 0.25); + checkToCooked(converter, 0x30000, -0.25); + + // these values are out of range + checkToRaw(converter, 3.25, 0x1FFFF); + checkToRaw(converter, -3.25, 0x20000); + checkToRaw(converter, 5.75, 0x1FFFF); + checkToRaw(converter, -5.75, 0x20000); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fraction18) { + FixedPointConverter converter("Variable18plus18unsigned", 18, 18, + false); // 10 bits, -12 fractional bits, not signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -18)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -18)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + + checkToRaw(converter, 0.25, 0x10000); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x30000); + checkToRaw(converter, -0.75, 0x0); + + checkToCooked(converter, 0x10000, 0.25); + checkToCooked(converter, 0x30000, 0.75); + + checkToRaw(converter, 3.25, 0x3FFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0x3FFFF); + checkToRaw(converter, -5.75, 0x0); +} + +BOOST_AUTO_TEST_CASE(testInt18_fraction43) { + FixedPointConverter converter("int18_fraction43", 18, + 43); // 18 bits, 43 fractional bits, signed + + checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); + checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + + // all out of range + checkToRaw(converter, 0.25, 0x1FFFF); + checkToRaw(converter, -0.25, 0x20000); + checkToRaw(converter, 0.75, 0x1FFFF); + checkToRaw(converter, -0.75, 0x20000); + + checkToRaw(converter, 3.25, 0x1FFFF); + checkToRaw(converter, -3.25, 0x20000); + checkToRaw(converter, 5.75, 0x1FFFF); + checkToRaw(converter, -5.75, 0x20000); +} + +BOOST_AUTO_TEST_CASE(testUInt18_fraction43) { + FixedPointConverter converter("Variable18plus43unsigned", 18, 43, + false); // 18 bits, -43 fractional bits, not signed + + checkToCooked(converter, 0x2AAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -43)); + checkToCooked(converter, 0x15555, HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + + checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0x2AAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + + // all out of range + checkToRaw(converter, 0.25, 0x3FFFF); + checkToRaw(converter, -0.25, 0x0); + checkToRaw(converter, 0.75, 0x3FFFF); + checkToRaw(converter, -0.75, 0x0); + + checkToRaw(converter, 3.25, 0x3FFFF); + checkToRaw(converter, -3.25, 0x0); + checkToRaw(converter, 5.75, 0x3FFFF); + checkToRaw(converter, -5.75, 0x0); +} + +BOOST_AUTO_TEST_CASE(testGetters) { + FixedPointConverter defaultConverter("default"); + BOOST_CHECK(defaultConverter.getNBits() == 32); + BOOST_CHECK(defaultConverter.getFractionalBits() == 0); + BOOST_CHECK(defaultConverter.isSigned() == true); + + FixedPointConverter customConverter("custom", 13, 7, false); + BOOST_CHECK(customConverter.getNBits() == 13); + BOOST_CHECK(customConverter.getFractionalBits() == 7); + BOOST_CHECK(customConverter.isSigned() == false); +} + +BOOST_AUTO_TEST_CASE(testInt32ToInt32) { + FixedPointConverter converter("int32toInt32"); // default parameters are signed 32 bit + + checkToCooked(converter, 0, (int32_t)0); + checkToCooked(converter, 1, (int32_t)1); + checkToCooked(converter, 0xFFFFFFFF, (int32_t)-1); + checkToCooked(converter, 3, (int32_t)3); + checkToCooked(converter, 0xFFFFFFFD, (int32_t)-3); + checkToCooked(converter, 6, (int32_t)6); + checkToCooked(converter, 0xFFFFFFFA, (int32_t)-6); + checkToCooked(converter, 0xAAAAAAAA, (int32_t)0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (int32_t)0x55555555); + + checkToRaw(converter, 0, 0); + checkToRaw(converter, 1, 1); + checkToRaw(converter, -1, -1); + checkToRaw(converter, 3, 3); + checkToRaw(converter, -3, -3); + checkToRaw(converter, 6, 6); + checkToRaw(converter, -6, -6); +} + +BOOST_AUTO_TEST_CASE(testInt32ToInt16) { + FixedPointConverter converter("int32ToInt16"); // default constructor is signed 32 bit + + checkToCooked(converter, 0, (int16_t)0); + checkToCooked(converter, 1, (int16_t)1); + checkToCooked(converter, 0xFFFFFFFF, (int16_t)-1); + checkToCooked(converter, 3, (int16_t)3); + checkToCooked(converter, 0xFFFFFFFD, (int16_t)-3); + checkToCooked(converter, 6, (int16_t)6); + checkToCooked(converter, 0xFFFFFFFA, (int16_t)-6); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCookedOverflowPos(converter, 0x55555555); + + checkToRaw(converter, (int16_t)0, 0); + checkToRaw(converter, (int16_t)1, 1); + checkToRaw(converter, (int16_t)-1, -1); + checkToRaw(converter, (int16_t)3, 3); + checkToRaw(converter, (int16_t)-3, -3); + checkToRaw(converter, (int16_t)6, 6); + checkToRaw(converter, (int16_t)-6, -6); + checkToRaw(converter, (int16_t)0x7FFF, 0x7FFF); + checkToRaw(converter, (int16_t)0x8000, 0xFFFF8000); + checkToRaw(converter, (int16_t)0xFFFF, 0xFFFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt18ToInt32) { + FixedPointConverter converter("int18ToInt32", 18, 0, true); + + checkToCooked(converter, 0, 0); + checkToCooked(converter, 1, 1); + checkToCooked(converter, 0x3FFFF, -1); + checkToCooked(converter, 3, 3); + checkToCooked(converter, 0x3FFFD, -3); + checkToCooked(converter, 6, 6); + checkToCooked(converter, 0x3FFFA, -6); + checkToCooked(converter, 0xFFFFFFFF, (int32_t)0xFFFFFFFF); + checkToCooked(converter, 0xFFFFFFFE, (int32_t)0xFFFFFFFE); + checkToCooked(converter, 0x55555555, (int32_t)0x15555); + + checkToRaw(converter, (int32_t)0, 0); + checkToRaw(converter, (int32_t)1, 1); + checkToRaw(converter, (int32_t)-1, 0x3FFFF); + checkToRaw(converter, (int32_t)3, 3); + checkToRaw(converter, (int32_t)-3, 0x3FFFD); + checkToRaw(converter, (int32_t)6, 6); + checkToRaw(converter, (int32_t)-6, 0x3FFFA); + checkToRaw(converter, (int32_t)0x1FFFF, 0x1FFFF); + checkToRaw(converter, (int32_t)0x20000, 0x1FFFF); + checkToRaw(converter, (int32_t)-1, 0x3FFFF); + checkToRaw(converter, (int32_t)-0x20000, 0x20000); +} + +BOOST_AUTO_TEST_CASE(testIntSignedToUnsigned) { + FixedPointConverter converter("signedToUnsigned"); // default parameters are signed 32 bit + + checkToCooked(converter, 0, (uint32_t)0); + checkToCooked(converter, 1, (uint32_t)1); + checkToCooked(converter, 3, (uint32_t)3); + checkToCooked(converter, 6, (uint32_t)6); + checkToCookedOverflowNeg(converter, 0xFFFFFFFF); + checkToCookedOverflowNeg(converter, 0xFFFFFFFA); + checkToCookedOverflowNeg(converter, 0xAAAAAAAA); + checkToCooked(converter, 0x55555555, (uint32_t)0x55555555); + + checkToRaw(converter, (uint32_t)0, 0); + checkToRaw(converter, (uint32_t)1, 1); + checkToRaw(converter, (uint32_t)3, 3); + checkToRaw(converter, (uint32_t)6, 6); + checkToRaw(converter, (uint32_t)0x7FFFFFFF, 0x7FFFFFFF); + checkToRaw(converter, (uint32_t)0x80000000, 0x7FFFFFFF); + checkToRaw(converter, (uint32_t)0xFFFFFFFF, 0x7FFFFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt17SignedToInt16Unsigned) { + FixedPointConverter converter("int17SignedToInt16Unsigned", 17, 0, true); + + checkToCooked(converter, 0, (uint16_t)0); + checkToCooked(converter, 1, (uint16_t)1); + checkToCookedOverflowNeg(converter, 0xFFFFFFFF); + checkToCooked(converter, 3, (uint16_t)3); + checkToCooked(converter, 6, (uint16_t)6); + checkToCooked(converter, 0xAAAAAAAA, (uint16_t)0xAAAA); + checkToCookedOverflowNeg(converter, 0x55555555); + + checkToRaw(converter, (uint16_t)0, 0); + checkToRaw(converter, (uint16_t)1, 1); + checkToRaw(converter, (uint16_t)3, 3); + checkToRaw(converter, (uint16_t)6, 6); + checkToRaw(converter, (uint16_t)0x7FFF, 0x7FFF); + checkToRaw(converter, (uint16_t)0x8000, 0x8000); + checkToRaw(converter, (uint16_t)0xFFFF, 0xFFFF); +} + +BOOST_AUTO_TEST_CASE(testInt0unsigned) { // test with 0 significant bits + // (unsigned, no fractional bits) + FixedPointConverter converter("int0unsigned", 0, 0, false); + + checkToCooked(converter, 0, 0); + checkToCooked(converter, 1, 0); + checkToCooked(converter, 0x0000FFFF, 0); + checkToCooked(converter, 0xFFFF0000, 0); + checkToCooked(converter, 0xFFFFFFFF, 0); + + checkToRaw(converter, 0, 0); + checkToRaw(converter, 1, 0); + checkToRaw(converter, 0xFFFF, 0); + checkToRaw(converter, -1, 0); +} + +BOOST_AUTO_TEST_CASE(testInt0signed) { // test with 0 significant bits (signed, + // no fractional bits) + FixedPointConverter converter("int0signed", 0, 0, true); + + checkToCooked(converter, 0, 0); + checkToCooked(converter, 1, 0); + checkToCooked(converter, 0x0000FFFF, 0); + checkToCooked(converter, 0xFFFF0000, 0); + checkToCooked(converter, 0xFFFFFFFF, 0); + + checkToRaw(converter, 0, 0); + checkToRaw(converter, 1, 0); + checkToRaw(converter, 0xFFFF, 0); + checkToRaw(converter, -1, 0); +} + +BOOST_AUTO_TEST_CASE(testInt0unsignedFractional) { // test with 0 significant bits (unsigned, + // with fractional bits) + FixedPointConverter converter("int0unsignedFractional", 0, 5, false); + + checkToCooked(converter, 0, 0); + checkToCooked(converter, 1, 0); + checkToCooked(converter, 0x0000FFFF, 0); + checkToCooked(converter, 0xFFFF0000, 0); + checkToCooked(converter, 0xFFFFFFFF, 0); + + checkToRaw(converter, 0, 0); + checkToRaw(converter, 1, 0); + checkToRaw(converter, 0xFFFF, 0); + checkToRaw(converter, -1, 0); +} + +BOOST_AUTO_TEST_CASE(testInt0signedFractional) { // test with 0 significant bits (signed, with + // negative fractional bits) + FixedPointConverter converter("int0signedFractional", 0, -5, true); + + checkToCooked(converter, 0, 0); + checkToCooked(converter, 1, 0); + checkToCooked(converter, 0x0000FFFF, 0); + checkToCooked(converter, 0xFFFF0000, 0); + checkToCooked(converter, 0xFFFFFFFF, 0); + + checkToRaw(converter, 0, 0); + checkToRaw(converter, 1, 0); + checkToRaw(converter, 0xFFFF, 0); + checkToRaw(converter, -1, 0); +} + +BOOST_AUTO_TEST_CASE(testDynamicRangePos) { + FixedPointConverter converter("dynamicRangePos", 16, 1021 - 16, false); + + checkToCooked(converter, 0, 0.); + checkToCooked(converter, 1, pow(2., -(1021 - 16))); + checkToCooked(converter, 0xFFFF, 65535. * pow(2., -(1021 - 16))); + + // check that our comparison values are not already exceeding the dynamic + // range + BOOST_CHECK(!std::isinf(65535. * pow(2., -(1021 - 16)))); +} + +BOOST_AUTO_TEST_CASE(testDynamicRangeNeg) { + FixedPointConverter converter("dynamicRangeNeg", 16, -1024 + 16, false); + + checkToCooked(converter, 0, 0.); + checkToCooked(converter, 1, pow(2., 1024 - 16)); + checkToCooked(converter, 0xFFFF, 65535. * pow(2., 1024 - 16)); + + // check that our comparison values are not already exceeding the dynamic + // range + BOOST_CHECK(pow(2., -(1024 - 16)) > 0.); +} + +BOOST_AUTO_TEST_CASE(testBoolean0) { + FixedPointConverter converter("Variable32signed"); // default parameters are signed 32 bit + + checkToCooked(converter, 0x00000000, Boolean(false)); +} + +BOOST_AUTO_TEST_CASE(testVoid) { + FixedPointConverter converter("Variable32signed"); // default parameters are signed 32 bit + + Void output = converter.scalarToCooked(23); + (void)output; +} + +BOOST_AUTO_TEST_SUITE_END() From b00f68677404e999353cb314bd8117754f6df6f6 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer Date: Thu, 5 Feb 2026 09:18:23 +0100 Subject: [PATCH 3/9] fix: wrong include of own header --- tests/executables_src/testRawDataTypeInfo.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/executables_src/testRawDataTypeInfo.cpp b/tests/executables_src/testRawDataTypeInfo.cpp index 57e7775de..e45e80a75 100644 --- a/tests/executables_src/testRawDataTypeInfo.cpp +++ b/tests/executables_src/testRawDataTypeInfo.cpp @@ -6,7 +6,8 @@ #include using namespace boost::unit_test_framework; -#include +#include "Device.h" + using namespace ChimeraTK; BOOST_AUTO_TEST_CASE(testRawAccessor) { From d15533cdbbc13678148025ea7d7494c4dfdf5aa1 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer Date: Thu, 5 Feb 2026 12:02:42 +0100 Subject: [PATCH 4/9] chore: move Boolean and Void to separate headers --- include/Boolean.h | 78 ++++++++++++++++++++++++++++++++ include/SupportedUserTypes.h | 86 +++--------------------------------- include/Void.h | 37 ++++++++++++++++ 3 files changed, 120 insertions(+), 81 deletions(-) create mode 100644 include/Boolean.h create mode 100644 include/Void.h diff --git a/include/Boolean.h b/include/Boolean.h new file mode 100644 index 000000000..450b6e340 --- /dev/null +++ b/include/Boolean.h @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +#include +#include +#include + +namespace ChimeraTK { + + /********************************************************************************************************************/ + + /** + * Wrapper Class to avoid vector problems + */ + class Boolean { + public: + constexpr Boolean() : _value() {} + // We want implicit construction and conversion. Turn off the linter warnings. + // NOLINTBEGIN(hicpp-explicit-conversions, google-explicit-constructor) + constexpr Boolean(bool value) : _value(value) {} + + constexpr operator const bool&() const { return _value; } + constexpr operator bool&() { return _value; } + // NOLINTEND(hicpp-explicit-conversions, google-explicit-constructor) + // TODO: test user types to numeric etc + + private: + bool _value; + }; + + /********************************************************************************************************************/ + + inline std::istream& operator>>(std::istream& is, Boolean& value) { + std::string data; + is >> data; + + std::transform(data.begin(), data.end(), data.begin(), [](unsigned char c) { return std::tolower(c); }); + + if(data == "false" || data == "0" || data.empty()) { + value = false; + } + else { + value = true; + } + return is; + } + + /********************************************************************************************************************/ + + template + constexpr bool isBoolean = std::is_same_v || std::is_same_v; + + /********************************************************************************************************************/ + + // Define ChimeraTK::to_string to convert Boolean into string. We cannot define std::to_string(Boolean), as it would + // violate the C++ standard. The right definition can be autoselected with + // "using std::to_string; using ChimeraTK::to_string;". + // NOLINTNEXTLINE(readability-identifier-naming) - name is fixed by C++ standard + inline std::string to_string(Boolean& value) { + if(value) { + return "true"; + } + return "false"; + } + + /********************************************************************************************************************/ + +} // namespace ChimeraTK + +/********************************************************************************************************************/ + +namespace std { + template<> + class numeric_limits : public numeric_limits {}; +} // namespace std + +/********************************************************************************************************************/ diff --git a/include/SupportedUserTypes.h b/include/SupportedUserTypes.h index 221094e6f..4e049a952 100644 --- a/include/SupportedUserTypes.h +++ b/include/SupportedUserTypes.h @@ -2,11 +2,13 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #pragma once +#include "Boolean.h" +#include "Void.h" + #include #include #include -#include #include #include #include @@ -16,82 +18,6 @@ namespace ChimeraTK { /********************************************************************************************************************/ /********************************************************************************************************************/ - /** - * Wrapper Class to avoid vector problems - */ - class Boolean { - public: - Boolean() : m_value() {} - // We want implicit construction and conversion. Turn off the linter warnings. - // NOLINTBEGIN(hicpp-explicit-conversions, google-explicit-constructor) - Boolean(bool value) : m_value(value) {} - - operator const bool&() const { return m_value; } - operator bool&() { return m_value; } - // NOLINTEND(hicpp-explicit-conversions, google-explicit-constructor) - // TODO: test user types to numeric etc - - private: - bool m_value; - }; - - /********************************************************************************************************************/ - - inline std::istream& operator>>(std::istream& is, Boolean& value) { - std::string data; - is >> data; - - std::transform(data.begin(), data.end(), data.begin(), [](unsigned char c) { return std::tolower(c); }); - - if(data == "false" || data == "0" || data.empty()) { - value = false; - } - else { - value = true; - } - return is; - } - - /********************************************************************************************************************/ - - // Define ChimeraTK::to_string to convert Boolean into string. We cannot define std::to_string(Boolean), as it would - // violate the C++ standard. The right definition can be autoselected with - // "using std::to_string; using ChimeraTK::to_string;". - inline std::string to_string(Boolean& value) { - if(value) { - return "true"; - } - return "false"; - } - - /********************************************************************************************************************/ - - /** - * Wrapper Class for void. return is always 0. - */ - class Void { - public: - Void() = default; - Void& operator=(const Void&) = default; - Void(const Void&) = default; - }; - - /********************************************************************************************************************/ - - inline std::istream& operator>>(std::istream& is, __attribute__((unused)) Void& value) { - return is; - } - - /********************************************************************************************************************/ - - inline std::ostream& operator<<(std::ostream& os, __attribute__((unused)) Void& value) { - os << 0; - return os; - } - - /********************************************************************************************************************/ - /********************************************************************************************************************/ - /** * Concept requiring a type to be one of the supported ChimeraTK UserTypes. */ @@ -208,8 +134,7 @@ namespace ChimeraTK { /********************************************************************************************************************/ /** Helper function to convert numeric data into any UserType (even if it is a string etc.). The conversion is done - * with proper rounding and range checking. It will throw boost::numeric::positive_overflow resp. - * boost::numeric::negative_overflow if the data is out of range. */ + * with proper rounding and range checking (clamping). */ template UserType numericToUserType(NUMERIC value) { return detail::numericToUserType_impl::impl(value); @@ -218,8 +143,7 @@ namespace ChimeraTK { /********************************************************************************************************************/ /** Helper function to convert numeric data into any UserType (even if it is a string etc.). The conversion is done - * with proper rounding and range checking. It will throw boost::numeric::positive_overflow resp. - * boost::numeric::negative_overflow if the data is out of range. */ + * with proper rounding and range checking (clamping). */ template NUMERIC userTypeToNumeric(UserType value) { return detail::userTypeToNumeric_impl::impl(value); diff --git a/include/Void.h b/include/Void.h new file mode 100644 index 000000000..aae470de3 --- /dev/null +++ b/include/Void.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +#include +#include + +namespace ChimeraTK { + + /********************************************************************************************************************/ + + /** + * Wrapper Class for void. return is always 0. + */ + class Void { + public: + Void() = default; + Void& operator=(const Void&) = default; + Void(const Void&) = default; + }; + + /********************************************************************************************************************/ + + inline std::istream& operator>>(std::istream& is, __attribute__((unused)) Void& value) { + return is; + } + + /********************************************************************************************************************/ + + inline std::ostream& operator<<(std::ostream& os, __attribute__((unused)) Void& value) { + os << 0; + return os; + } + + /********************************************************************************************************************/ + +} // namespace ChimeraTK From b3e55cc4a8f06dfa6beafeb0f4d3b371ed795396 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer Date: Thu, 5 Feb 2026 15:22:50 +0100 Subject: [PATCH 5/9] chore: remove option to throw boost::numeric::bad_numeric_cast This is not allowed according to the TransferElement specification. Seems to be unused anyway. --- tests/include/TransferElementTestAccessor.h | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/include/TransferElementTestAccessor.h b/tests/include/TransferElementTestAccessor.h index 1b3c7f5e1..c66315a26 100644 --- a/tests/include/TransferElementTestAccessor.h +++ b/tests/include/TransferElementTestAccessor.h @@ -46,7 +46,6 @@ namespace ChimeraTK { try { if(_throwLogicErr) throw ChimeraTK::logic_error("Test"); if(_throwRuntimeErrInPre) throw ChimeraTK::runtime_error("Test"); - if(_throwNumericCast) throw boost::numeric::bad_numeric_cast(); if(!_writeable) { throw ChimeraTK::logic_error("Not writeable!"); } @@ -116,14 +115,6 @@ namespace ChimeraTK { } this->buffer_2D[0][0] = _setPostReadData; - - try { - if(_throwNumericCast) throw boost::numeric::bad_numeric_cast(); - } - catch(...) { - _thrownException = std::current_exception(); - throw; - } } void doPostWrite(TransferType type, VersionNumber versionNumber) override { From 0060539ab7ec064e95055acfac7f011ea0689dda Mon Sep 17 00:00:00 2001 From: Martin Hierholzer Date: Thu, 5 Feb 2026 09:07:29 +0100 Subject: [PATCH 6/9] feat: add numeric converter --- include/NumericConverter.h | 182 +++++++++++++ .../testNDRegisterAccessorDecorator.cpp | 12 +- .../executables_src/testNumericConverter.cpp | 254 ++++++++++++++++++ 3 files changed, 440 insertions(+), 8 deletions(-) create mode 100644 include/NumericConverter.h create mode 100644 tests/executables_src/testNumericConverter.cpp diff --git a/include/NumericConverter.h b/include/NumericConverter.h new file mode 100644 index 000000000..1015f8e34 --- /dev/null +++ b/include/NumericConverter.h @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +#include "Boolean.h" +#include "Void.h" + +#include +#include +#include +#include + +namespace ChimeraTK::numeric { + + /********************************************************************************************************************/ + + /** + * Concept to require an integral data type incl. ChimeraTK::Boolean and ChimeraTK::Void in templates + */ + template + concept Integral = std::integral || std::is_same_v; + + /** + * Concept to require a numeric data type in templates + */ + template + concept Arithmetic = Integral || std::floating_point; + + /** + * Concept to require a numeric data type in templates, or ChimeraTK::Void + */ + template + concept ArithmeticOrVoid = Integral || std::floating_point || std::is_same_v; + + /********************************************************************************************************************/ + + /** + * Replacement for std::round() which also works constexpr and also casts to the target type in the same step. + */ + namespace detail { + template + constexpr TO roundAndCast(FROM x) { + if constexpr(!isBoolean) { + if(std::isnan(x)) { + if constexpr(std::is_signed_v) { + return std::numeric_limits::lowest(); + } + return std::numeric_limits::max(); + } + // NOLINTNEXTLINE(bugprone-incorrect-roundings) + return (x >= FROM(0)) ? TO(x + FROM(0.5)) : TO(x - FROM(0.5)); + } + + // Note: NaN will yield false + return x >= 0.5 || x <= -0.5; + } + + /******************************************************************************************************************/ + + // Helper to check wether T_LEFT has a greater maximum value than T_RIGHT, without generating warnings due to + // unsigned/signed comparisons and precision loss in casts. + template + constexpr bool greaterMaximum() { + if constexpr(std::is_same_v || isBoolean) { + return false; + } + else if constexpr(isBoolean) { + static_assert(!isBoolean); + return true; + } + else if constexpr(std::is_floating_point_v) { + return std::is_same_v || std::is_integral_v; + } + else if constexpr(std::is_floating_point_v) { + static_assert(std::is_integral_v); + return false; + } + else { + static_assert(std::is_integral_v && std::is_integral_v); + return std::numeric_limits::max() > std::numeric_limits::max(); + } + } + + /******************************************************************************************************************/ + + // Helper to check wether T_LEFT has a lesser (more negative) minimum value than T_RIGHT, without generating + // warnings due to precision loss in casts. + // This function only works for signed data types (unsigned is handled differently) + template + constexpr bool lesserMinimum() { + static_assert(std::is_signed_v && std::is_signed_v); + if constexpr(std::is_same_v) { + return false; + } + else if constexpr(std::is_floating_point_v) { + return std::is_same_v || std::is_integral_v; + } + else if constexpr(std::is_floating_point_v) { + static_assert(std::is_integral_v); + return false; + } + else { + static_assert(std::is_integral_v && std::is_integral_v); + return std::numeric_limits::lowest() < std::numeric_limits::lowest(); + } + } + } // namespace detail + + /********************************************************************************************************************/ + + /** + * Convert numeric data types with proper rounding and clamping to the target value range. + * + * The exact behaviour is defined as follows: + * + * - integer target types: both positive and negative overflows clamp to the closest value of the target type. + * - unsigned integer targets: negative input values give always 0 + * - double-to-single floats: overflows clamp, Inf and NaN is kept as is + * - float-to-integer: rounding to nearest integer + * - bool targets: any non-zero value (after rounding to nearest integer) is true (also negative values) + */ + template + constexpr TO convert(FROM value) { + if constexpr(std::is_same_v || (isBoolean && isBoolean)) { + // fastpath for equal types + return value; + } + if constexpr(std::is_same_v || std::is_same_v) { + // fastpath for Void, also preventing compilation errors in the true numeric part below + return {}; + } + else { + if constexpr(!std::is_floating_point_v || std::is_floating_point_v) { + // Conversion into floating point works (almost) always as expected, so we need special handling for integer + // targets only + + if constexpr(detail::greaterMaximum()) { + // clamp to maximum value + if(value >= FROM(std::numeric_limits::max())) { + if constexpr(std::is_floating_point_v && std::is_floating_point_v) { + if(std::isinf(value)) { + return std::numeric_limits::infinity(); + } + } + return std::numeric_limits::max(); + } + } + if constexpr(std::is_signed_v && !std::is_signed_v && !isBoolean) { + // clamp to 0 for unsigned target types + if(value < 0) { + return TO(0); + } + } + if constexpr(std::is_signed_v && std::is_signed_v) { + if constexpr(detail::lesserMinimum()) { + // clamp to lowest for signed source and target + if(value <= FROM(std::numeric_limits::lowest())) { + if constexpr(std::is_floating_point_v && std::is_floating_point_v) { + if(std::isinf(value)) { + return -std::numeric_limits::infinity(); + } + } + return std::numeric_limits::lowest(); + } + } + } + + // Hint: no need for any clamping on minimum side for unsigned source! + + if constexpr(std::is_floating_point_v && !std::is_floating_point_v) { + // conversion from floating point into integer: need to round + return detail::roundAndCast(value); + } + } + + return TO(value); + } + } + + /********************************************************************************************************************/ + +} // namespace ChimeraTK::numeric diff --git a/tests/executables_src/testNDRegisterAccessorDecorator.cpp b/tests/executables_src/testNDRegisterAccessorDecorator.cpp index d8c2fbb50..0cca0ccc6 100644 --- a/tests/executables_src/testNDRegisterAccessorDecorator.cpp +++ b/tests/executables_src/testNDRegisterAccessorDecorator.cpp @@ -67,7 +67,6 @@ class DecoratorTestAccessor : public NDRegisterAccessor { _newVersion = versionNumber; if(_throwLogicErr) throw ChimeraTK::logic_error("Test"); if(_throwRuntimeErrInPre) throw ChimeraTK::runtime_error("Test"); - if(_throwNumericCast) throw boost::numeric::bad_numeric_cast(); if(_throwThreadInterruptedInPre) throw boost::thread_interrupted(); } @@ -99,7 +98,7 @@ class DecoratorTestAccessor : public NDRegisterAccessor { BOOST_CHECK(_postWrite_counter == 0); BOOST_CHECK(_transferType == TransferType::write); BOOST_CHECK(versionNumber == _newVersion); - if(_throwLogicErr || _throwRuntimeErrInPre || _throwThreadInterruptedInPre || _throwNumericCast) { + if(_throwLogicErr || _throwRuntimeErrInPre || _throwThreadInterruptedInPre) { BOOST_CHECK_MESSAGE(false, "doWriteTransfer() must not be called if doPreWrite() has thrown an exception."); } ++_writeTransfer_counter; @@ -117,7 +116,7 @@ class DecoratorTestAccessor : public NDRegisterAccessor { BOOST_CHECK(_postWrite_counter == 0); BOOST_CHECK(_transferType == TransferType::writeDestructively); BOOST_CHECK(versionNumber == _newVersion); - if(_throwLogicErr || _throwRuntimeErrInPre || _throwThreadInterruptedInPre || _throwNumericCast) { + if(_throwLogicErr || _throwRuntimeErrInPre || _throwThreadInterruptedInPre) { BOOST_CHECK_MESSAGE( false, "doWriteTransferDestructively() must not be called if doPreWrite() has thrown an exception."); } @@ -165,7 +164,6 @@ class DecoratorTestAccessor : public NDRegisterAccessor { BOOST_CHECK(_transferType == type); ++_postRead_counter; _hasNewData = updateDataBuffer; - if(_throwNumericCast) throw boost::numeric::bad_numeric_cast(); if(_throwThreadInterruptedInPost) throw boost::thread_interrupted(); } @@ -181,7 +179,7 @@ class DecoratorTestAccessor : public NDRegisterAccessor { BOOST_CHECK(_preRead_counter == 0); BOOST_CHECK(_preWrite_counter == 1); BOOST_CHECK_EQUAL(_readTransfer_counter, 0); - if(!_throwLogicErr && !_throwRuntimeErrInPre && !_throwNumericCast && !_throwThreadInterruptedInPre) { + if(!_throwLogicErr && !_throwRuntimeErrInPre && !_throwThreadInterruptedInPre) { BOOST_CHECK(_writeTransfer_counter == 1); } else { @@ -191,7 +189,7 @@ class DecoratorTestAccessor : public NDRegisterAccessor { // Exceptions must be passed on to the level which is throwing it (B.6.3 This actually tests the // NDRegisterAccessorDecorator) if(_throwLogicErr || _throwRuntimeErrInPre || _throwThreadInterruptedInPre || _throwRuntimeErrInTransfer || - _throwThreadInterruptedInTransfer || _throwNumericCast) { + _throwThreadInterruptedInTransfer) { BOOST_CHECK(this->_activeException != nullptr); } BOOST_CHECK(versionNumber == _newVersion); @@ -242,7 +240,6 @@ class DecoratorTestAccessor : public NDRegisterAccessor { _throwLogicErr = false; _throwRuntimeErrInPre = false; _throwRuntimeErrInTransfer = false; - _throwNumericCast = false; _throwThreadInterruptedInPre = false; _throwThreadInterruptedInTransfer = false; _throwThreadInterruptedInPost = false; @@ -251,7 +248,6 @@ class DecoratorTestAccessor : public NDRegisterAccessor { bool _throwLogicErr{false}; // always in doPreXxx() bool _throwRuntimeErrInTransfer{false}; bool _throwRuntimeErrInPre{false}; - bool _throwNumericCast{false}; // in doPreWrite() or doPreRead() depending on operation bool _throwThreadInterruptedInPre{false}; bool _throwThreadInterruptedInTransfer{false}; bool _throwThreadInterruptedInPost{false}; diff --git a/tests/executables_src/testNumericConverter.cpp b/tests/executables_src/testNumericConverter.cpp new file mode 100644 index 000000000..086ec7af6 --- /dev/null +++ b/tests/executables_src/testNumericConverter.cpp @@ -0,0 +1,254 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE TestNumericConverter +#include +using namespace boost::unit_test_framework; + +#include + +using namespace ChimeraTK::numeric; + +/**********************************************************************************************************************/ + +// List of types to test with. ChimeraTK::Void is tested separately. +using IntTypes = + std::tuple; +using FloatTypes = std::tuple; + +/**********************************************************************************************************************/ + +// Helper function to iterate over the types of an std::tuple +template +void forEachType(F&& f) { + [](F&& g, std::index_sequence) { + (g.template operator()>(), ...); + }(std::forward(f), std::make_index_sequence>{}); +}; + +/**********************************************************************************************************************/ + +// Note: This test does almost nothing at runtime, almost all checks are implemented as static_asserts at compile time. +BOOST_AUTO_TEST_CASE(TestNumericConverter) { + // Conversion from int to float + forEachType([]() { + forEachType([]() { + // Note: we might not always distinguish "std::numeric_limits::max()" from "std::numeric_limits::max() - 1" + // etc. in the floating point type, but the same imprecision must happen here on both sides of the ==. + static_assert(convert(std::numeric_limits::max()) == F(std::numeric_limits::max())); + static_assert(convert(std::numeric_limits::lowest()) == F(std::numeric_limits::lowest())); + static_assert(convert(std::numeric_limits::max() - 1) == F(std::numeric_limits::max() - 1)); + static_assert(convert(std::numeric_limits::lowest() + 1) == F(std::numeric_limits::lowest() + 1)); + + static_assert(convert(I(1)) == F(1)); + static_assert(convert(I(0)) == F(0)); + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(I(42)) == F(42)); + } + + if constexpr(std::is_signed_v) { + static_assert(convert(I(-1)) == F(-1)); + static_assert(convert(I(-120)) == F(-120)); + } + }); + }); + + // Conversion from float to int + forEachType([]() { + forEachType([]() { + if constexpr(!std::is_same_v && !std::is_same_v && + (std::is_same_v || (!std::is_same_v && !std::is_same_v))) { + // these checks work only if lowest() and max() of the I type can be exactly represented in the F type + static_assert(convert(F(std::numeric_limits::max())) == std::numeric_limits::max()); + static_assert(convert(F(std::numeric_limits::max() - 1)) == std::numeric_limits::max() - 1); + + static_assert(convert(F(std::numeric_limits::lowest())) == std::numeric_limits::lowest()); + static_assert(convert(F(std::numeric_limits::lowest() + 1)) == std::numeric_limits::lowest() + 1); + + // check proper rounding for big values + static_assert(convert(F(std::numeric_limits::max()) - F(0.51)) == std::numeric_limits::max() - 1); + static_assert(convert(F(std::numeric_limits::max()) - F(0.49)) == std::numeric_limits::max()); + + static_assert( + convert(F(std::numeric_limits::lowest()) + F(0.51)) == std::numeric_limits::lowest() + 1); + static_assert(convert(F(std::numeric_limits::lowest()) + F(0.49)) == std::numeric_limits::lowest()); + } + // the next two checks might be insensitive for some types due to limited floating point precision + static_assert(convert(F(std::numeric_limits::max()) + F(0.49)) == std::numeric_limits::max()); + static_assert(convert(F(std::numeric_limits::max()) + F(0.51)) == std::numeric_limits::max()); + // make sure the next one does become insensitive due to limited precision + static_assert(F(std::numeric_limits::max()) + F(1.E13) != F(std::numeric_limits::max())); + static_assert(convert(F(std::numeric_limits::max()) + F(1.E13)) == std::numeric_limits::max()); + + static_assert(convert(F(std::numeric_limits::lowest()) - F(0.49)) == std::numeric_limits::lowest()); + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(F(std::numeric_limits::lowest()) - F(0.51)) == std::numeric_limits::lowest()); + static_assert(convert(F(std::numeric_limits::lowest()) - F(100000.)) == std::numeric_limits::lowest()); + } + else { + static_assert(convert(-F(0.51)) == true); + static_assert(convert(-F(100000.)) == true); + } + + static_assert(convert(F(1)) == I(1)); + static_assert(convert(F(0)) == I(0)); + static_assert(convert(F(42)) == I(42)); + if constexpr(std::is_signed_v) { + static_assert(convert(F(-1)) == I(-1)); + static_assert(convert(F(-120)) == I(-120)); + } + else { + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(F(-1)) == 0); + static_assert(convert(F(-120)) == 0); + } + else { + static_assert(convert(F(-1)) == true); + static_assert(convert(F(-120)) == true); + } + } + + // check proper rounding + static_assert(convert(F(0.49999)) == 0); + static_assert(convert(F(0.50001)) == 1); + static_assert(convert(F(1.49999)) == 1); + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(F(1.50001)) == 2); + } + static_assert(convert(F(-0.49999)) == 0); + if constexpr(std::is_signed_v) { + static_assert(convert(F(-0.50001)) == -1); + static_assert(convert(F(-1.49999)) == -1); + static_assert(convert(F(-1.50001)) == -2); + } + else { + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(F(-0.50001)) == 0); + static_assert(convert(F(-1.49999)) == 0); + static_assert(convert(F(-1.50001)) == 0); + } + else { + static_assert(convert(F(-0.50001)) == true); + static_assert(convert(F(-1.49999)) == true); + static_assert(convert(F(-1.50001)) == true); + } + } + + // check Inf and NaN + static_assert(convert(std::numeric_limits::infinity()) == std::numeric_limits::max()); + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(-std::numeric_limits::infinity()) == std::numeric_limits::lowest()); + } + else { + static_assert(convert(-std::numeric_limits::infinity()) == true); + } + + if constexpr(std::is_signed_v) { + static_assert(convert(std::numeric_limits::quiet_NaN()) == std::numeric_limits::lowest()); + } + else { + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(std::numeric_limits::quiet_NaN()) == std::numeric_limits::max()); + } + else { + static_assert(convert(std::numeric_limits::quiet_NaN()) == false); + } + } + }); + }); + + // Conversion from int to int + forEachType([]() { + forEachType([]() { + if constexpr(detail::greaterMaximum()) { + // I2 can represent bigger values than I1 + static_assert(convert(std::numeric_limits::max()) == std::numeric_limits::max()); + static_assert(convert(std::numeric_limits::max() - 1) == std::numeric_limits::max() - 1); + + static_assert(convert(I2(std::numeric_limits::max()) + 1) == std::numeric_limits::max()); + static_assert(convert(std::numeric_limits::max()) == std::numeric_limits::max()); + } + + static_assert(convert(I2(1)) == 1); + static_assert(convert(I2(0)) == 0); + + if constexpr(std::is_signed_v && std::is_signed_v) { + // both are signed + if constexpr(std::numeric_limits::lowest() < std::numeric_limits::lowest()) { + // I2 can represent more negative values than I1 + static_assert(convert(std::numeric_limits::lowest()) == std::numeric_limits::lowest()); + static_assert(convert(std::numeric_limits::lowest() + 1) == std::numeric_limits::lowest() + 1); + + static_assert(convert(I2(std::numeric_limits::lowest()) - 1) == std::numeric_limits::lowest()); + static_assert(convert(std::numeric_limits::lowest()) == std::numeric_limits::lowest()); + } + } + + if constexpr(std::is_signed_v && !std::is_signed_v) { + // only I1 is signed + if constexpr(!ChimeraTK::isBoolean) { + static_assert(convert(std::numeric_limits::lowest()) == 0); + static_assert(convert(std::numeric_limits::lowest() + 1) == 0); + static_assert(convert(-1) == 0); + } + else { + // any non-zero value, including negative values are considered "true" + static_assert(convert(std::numeric_limits::lowest()) == true); + static_assert(convert(std::numeric_limits::lowest() + 1) == true); + static_assert(convert(-1) == true); + } + } + }); + }); + + // conversion from float to float + forEachType([]() { + forEachType([]() { + if constexpr(detail::greaterMaximum()) { + // F2 = double, F1 = float + static_assert(convert(std::numeric_limits::max()) == std::numeric_limits::max()); + static_assert(convert(std::numeric_limits::lowest()) == std::numeric_limits::lowest()); + + static_assert(convert(std::numeric_limits::max()) == F2(std::numeric_limits::max())); + static_assert(convert(std::numeric_limits::lowest()) == F2(std::numeric_limits::lowest())); + } + + static_assert(convert(F1(0.)) == F2(0.)); + static_assert(convert(F1(1.)) == F2(1.)); + static_assert(convert(F1(-1.)) == F2(-1.)); + static_assert(convert(F1(0.12345)) == F2(F1(0.12345))); + + // check retention of sign bit for zero + // (Note: std::signbit isn't yet constexpr with clang on Ubuntu 24) + BOOST_TEST(std::signbit(convert(F1(0.))) == 0); // signbit 0 means "positive" + constexpr F2 result = convert(F1(0.) / F1(-1.)); // "-0." will not give us a negative 0 in C++ + static_assert(result == 0); // negative and positive zero compare equal in C++ + BOOST_TEST(std::signbit(result) == 1); // signbit 1 means "negative" + + static_assert(std::isnan(convert(std::numeric_limits::quiet_NaN()))); + static_assert(std::isinf(convert(std::numeric_limits::infinity()))); + static_assert(convert(std::numeric_limits::infinity()) == std::numeric_limits::infinity()); + static_assert(convert(-std::numeric_limits::infinity()) == -std::numeric_limits::infinity()); + }); + }); + + // conversion from/to Void + forEachType([]() { + static_assert(convert(ChimeraTK::Void{}) == F(0)); + constexpr auto result1 = convert(F(0.0)); + static_assert(std::is_same_v); + constexpr auto result2 = convert(F(123.456)); + static_assert(std::is_same_v); + }); + forEachType([]() { + static_assert(convert(ChimeraTK::Void{}) == I(0)); + constexpr auto result1 = convert(I(0)); + static_assert(std::is_same_v); + constexpr auto result2 = convert(I(123)); + static_assert(std::is_same_v); + }); +} + +/**********************************************************************************************************************/ From 9bb86f5f5aa43402f4a645225ed23642cc027561 Mon Sep 17 00:00:00 2001 From: Martin Hierholzer Date: Thu, 5 Feb 2026 15:28:06 +0100 Subject: [PATCH 7/9] chore: replace boost::numeric::converter --- include/FixedPointConverter.h | 70 ++++--------------- include/IEEE754_SingleConverter.h | 61 ++++------------ include/SupportedUserTypes.h | 33 ++------- include/TypeChangingDecorator.h | 52 ++------------ .../testIEEE754_SingleConverter.cpp | 36 +++++----- .../testTypeChangingDecorator.cpp | 6 +- .../testUnifiedTypeChangingDecorator.cpp | 10 +-- 7 files changed, 59 insertions(+), 209 deletions(-) diff --git a/include/FixedPointConverter.h b/include/FixedPointConverter.h index 0dccdb9af..709300b6a 100644 --- a/include/FixedPointConverter.h +++ b/include/FixedPointConverter.h @@ -3,6 +3,7 @@ #pragma once #include "Exception.h" +#include "NumericConverter.h" #include "SupportedUserTypes.h" #include @@ -17,7 +18,6 @@ #include #include #include -#include #include #include @@ -189,32 +189,14 @@ namespace ChimeraTK { } // compute minimum and maximum values in cooked representation - try { - boost::fusion::at_key(_fpc->_minCookedValues) = _fpc->scalarToCooked(_fpc->_minRawValue); - } - catch(boost::numeric::negative_overflow& e) { - boost::fusion::at_key(_fpc->_minCookedValues) = std::numeric_limits::min(); - } - try { - boost::fusion::at_key(_fpc->_maxCookedValues) = _fpc->scalarToCooked(_fpc->_maxRawValue); - } - catch(boost::numeric::positive_overflow& e) { - boost::fusion::at_key(_fpc->_maxCookedValues) = std::numeric_limits::max(); - } + boost::fusion::at_key(_fpc->_minCookedValues) = _fpc->scalarToCooked(_fpc->_minRawValue); + boost::fusion::at_key(_fpc->_maxCookedValues) = _fpc->scalarToCooked(_fpc->_maxRawValue); } private: FixedPointConverter* _fpc; }; - /** define round type for the boost::numeric::converter */ - template - struct Round { - static S nearbyint(S s) { return round(s); } - - using round_style = boost::mpl::integral_c; - }; - /** helper function to test if UserTyped value is negative without triggering a * compiler warning for unsigned user types */ template{}, int>::type = 0> @@ -407,43 +389,21 @@ namespace ChimeraTK { // needed. Negative and positive overflow exceptions need to be caught for some corner // cases (e.g. number of fractional bits >= number of bits in total). RawType raw; - try { - if(_isSigned) { - if constexpr(sizeof(RawType) == 4) { - using converter_signed = - boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - raw = static_cast(converter_signed::convert(d_cooked)); - } - else if constexpr(sizeof(RawType) == 8) { - using converter_signed = - boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - raw = static_cast(converter_signed::convert(d_cooked)); - } + if(_isSigned) { + if constexpr(sizeof(RawType) == 4) { + raw = RawType(numeric::convert(d_cooked)); } - else { - if constexpr(sizeof(RawType) == 4) { - using converter_unsigned = - boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - raw = static_cast(converter_unsigned::convert(d_cooked)); - } - else if constexpr(sizeof(RawType) == 8) { - using converter_unsigned = - boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - raw = static_cast(converter_unsigned::convert(d_cooked)); - } + else if constexpr(sizeof(RawType) == 8) { + raw = RawType(numeric::convert(d_cooked)); } } - catch(boost::numeric::negative_overflow& e) { - std::cout << "BOOST Neg OF . Cooked: " << cookedValue << " raw: 0x" << std::hex << _minRawValue << std::endl; - raw = _minRawValue; - } - catch(boost::numeric::positive_overflow& e) { - std::cout << "BOOST Pos OF . Cooked: " << cookedValue << " raw: 0x" << std::hex << _minRawValue << std::endl; - raw = _maxRawValue; + else { + if constexpr(sizeof(RawType) == 4) { + raw = RawType(numeric::convert(d_cooked)); + } + else if constexpr(sizeof(RawType) == 8) { + raw = RawType(numeric::convert(d_cooked)); + } } std::cout << "ToRawFrac . Cooked: " << cookedValue << " d_cooked: " << d_cooked << " raw: 0x" << std::hex << raw << std::endl; diff --git a/include/IEEE754_SingleConverter.h b/include/IEEE754_SingleConverter.h index 050f1f2a8..5b022093e 100644 --- a/include/IEEE754_SingleConverter.h +++ b/include/IEEE754_SingleConverter.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later #pragma once -#include "SupportedUserTypes.h" +#include "NumericConverter.h" #include @@ -12,37 +12,9 @@ namespace ChimeraTK { - template - struct RoundingRangeCheckingDataConverter { - /** define round type for the boost::numeric::converter */ - template - struct Round { - static S nearbyint(S s) { return round(s); } - - using round_style = boost::mpl::integral_c; - }; - - using converter = - boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - }; - - template - struct RoundingRangeCheckingDataConverter { - struct converter { - static Void convert(__attribute__((unused)) SourceType s) { return {}; } - }; - }; - - template - struct RoundingRangeCheckingDataConverter { - struct converter { - static DestType convert(__attribute__((unused)) Void s) { return 0.0; } - }; - }; - - /** Needs to have the same interface as FixedPointConverter, except for the - * constructor. Converter for IEEE754 single precision (32bit) floating point. + /** + * Needs to have the same interface as FixedPointConverter, except for the constructor. Converter for IEEE754 single + * precision (32bit) floating point. */ struct IEEE754_SingleConverter { template @@ -50,13 +22,16 @@ namespace ChimeraTK { const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end, const COOKED_ITERATOR& cooked_begin) const { // Note: IEEE754_SingleConverter must be instantiable for all raw user types but can only be used for int32_t assert((std::is_same::value_type, int32_t>::value)); + static_assert(std::is_same::value_type, int8_t>::value || std::is_same::value_type, int16_t>::value || std::is_same::value_type, int32_t>::value, "RAW_ITERATOR template argument must be an iterator with value type equal to int8_t, int16_t or int32_t."); + static_assert(std::is_same::value_type, CookedType>::value, "COOKED_ITERATOR template argument must be an iterator with value type equal to the CookedType template " "argument."); + vectorToCooked_impl::impl(raw_begin, raw_end, cooked_begin); } @@ -92,7 +67,7 @@ namespace ChimeraTK { memcpy(&genericRepresentation, &(*it), sizeof(float)); // Step 2: convert the float to the cooked type - *cooked_begin = RoundingRangeCheckingDataConverter::converter::convert(genericRepresentation); + *cooked_begin = numeric::convert(genericRepresentation); ++cooked_begin; } } @@ -101,21 +76,11 @@ namespace ChimeraTK { uint32_t IEEE754_SingleConverter::toRaw(CookedType cookedValue) const { // step 1: convert from cooked to the generic representation in the CPU // (float) - float genericRepresentation; - try { - genericRepresentation = RoundingRangeCheckingDataConverter::converter::convert(cookedValue); - } - catch(boost::numeric::positive_overflow&) { - genericRepresentation = FLT_MAX; - } - catch(boost::numeric::negative_overflow&) { - genericRepresentation = -FLT_MAX; - } + float genericRepresentation = numeric::convert(cookedValue); // step 2: reinterpret float to int32 to send it to the device - void* warningAvoider = &genericRepresentation; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - int32_t rawValue = *(reinterpret_cast(warningAvoider)); + int32_t rawValue; + memcpy(&rawValue, &genericRepresentation, sizeof(float)); return rawValue; } @@ -125,8 +90,8 @@ namespace ChimeraTK { static void impl(const RAW_ITERATOR& raw_begin, const RAW_ITERATOR& raw_end, COOKED_ITERATOR cooked_begin) { for(auto it = raw_begin; it != raw_end; ++it) { // Step 1: convert the raw data to the "generic" representation in the CPU: float - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - float genericRepresentation = *(reinterpret_cast(&(*it))); + float genericRepresentation; + memcpy(&genericRepresentation, &(*it), sizeof(float)); // Step 2: convert the float to the cooked type *cooked_begin = std::to_string(genericRepresentation); diff --git a/include/SupportedUserTypes.h b/include/SupportedUserTypes.h index 4e049a952..37a6c7028 100644 --- a/include/SupportedUserTypes.h +++ b/include/SupportedUserTypes.h @@ -3,13 +3,14 @@ #pragma once #include "Boolean.h" +#include "NumericConverter.h" #include "Void.h" #include #include -#include #include +#include #include #include @@ -153,40 +154,14 @@ namespace ChimeraTK { template UserType detail::numericToUserType_impl::impl(NUMERIC value) { - using converter = boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - // There seems to be no other way to alter the value on negative/positive overflow than using a try-catch-block - // here. The overflow_handler is a stateless class and hence can either throw another exception or do nothing. - // It does not have any influence on the converted value, and it cannot transport the information out differently. - try { - return converter::convert(value); - } - catch(boost::numeric::negative_overflow&) { - return std::numeric_limits::min(); - } - catch(boost::numeric::positive_overflow&) { - return std::numeric_limits::max(); - } + return numeric::convert(value); } /********************************************************************************************************************/ template NUMERIC detail::userTypeToNumeric_impl::impl(UserType value) { - using converter = boost::numeric::converter, - boost::numeric::def_overflow_handler, Round>; - // There seems to be no other way to alter the value on negative/positive overflow than using a try-catch-block - // here. The overflow_handler is a stateless class and hence can either throw another exception or do nothing. - // It does not have any influence on the converted value, and it cannot transport the information out differently. - try { - return converter::convert(value); - } - catch(boost::numeric::negative_overflow&) { - return std::numeric_limits::min(); - } - catch(boost::numeric::positive_overflow&) { - return std::numeric_limits::max(); - } + return numeric::convert(value); } /********************************************************************************************************************/ diff --git a/include/TypeChangingDecorator.h b/include/TypeChangingDecorator.h index 74b7b03b4..b3336e795 100644 --- a/include/TypeChangingDecorator.h +++ b/include/TypeChangingDecorator.h @@ -3,11 +3,11 @@ #pragma once #include "NDRegisterAccessorDecorator.h" +#include "NumericConverter.h" #include "SupportedUserTypes.h" #include "TransferElementAbstractor.h" #include -#include namespace ChimeraTK { /** There are two types of TypeChanging decorators which do different data @@ -315,11 +315,8 @@ namespace ChimeraTK { void convertAndCopyToImpl() override {} }; - /** This decorator uses the boost numeric converter which performs two tasks: - * - * - a range check (throws boost::numeric::bad_numeric_cast if out of range) - * - the rounding floating point -> int is done mathematically (not just cut - * off the bits) + /** + * This decorator uses ChimeraTK::numeric::convert() for proper conversion with clamping and rounding. */ template class TypeChangingRangeCheckingDecorator : public TypeChangingStringImplDecorator { @@ -329,22 +326,6 @@ namespace ChimeraTK { void convertAndCopyToImpl() override; DecoratorType getDecoratorType() const override { return DecoratorType::limiting; } - private: - /** Internal exceptions to overload the what() function of the boost - * exceptions in order to fill in the variable name. These exceptions are not - * part of the external interface and cannot be caught explicitly because they - * are protected. Catch boost::numeric::bad_numeric_cast and derrivatives if - * you want to do error handling. - */ - template - struct BadNumericCastException : public BOOST_EXCEPTION_T { - BadNumericCastException(std::string variableName) - : errorMessage( - "Exception during type changing conversion in " + variableName + ": " + BOOST_EXCEPTION_T().what()) {} - std::string errorMessage; - const char* what() const throw() override { return errorMessage.c_str(); } - }; - using ChimeraTK::NDRegisterAccessorDecorator::_target; }; @@ -454,41 +435,18 @@ namespace ChimeraTK { template void TypeChangingRangeCheckingDecorator::convertAndCopyFromImpl() { - typedef boost::numeric::converter, - boost::numeric::def_overflow_handler, csa_helpers::Round> - FromImplConverter; - // fixme: are iterartors more efficient? for(size_t i = 0; i < this->buffer_2D.size(); ++i) { for(size_t j = 0; j < this->buffer_2D[i].size(); ++j) { - try { - this->buffer_2D[i][j] = FromImplConverter::convert(_target->accessChannel(i)[j]); - } - catch(boost::numeric::positive_overflow&) { - this->buffer_2D[i][j] = std::numeric_limits::max(); - } - catch(boost::numeric::negative_overflow&) { - this->buffer_2D[i][j] = std::numeric_limits::min(); - } + this->buffer_2D[i][j] = numeric::convert(_target->accessChannel(i)[j]); } } } template void TypeChangingRangeCheckingDecorator::convertAndCopyToImpl() { - typedef boost::numeric::converter, - boost::numeric::def_overflow_handler, csa_helpers::Round> - ToImplConverter; for(size_t i = 0; i < this->buffer_2D.size(); ++i) { for(size_t j = 0; j < this->buffer_2D[i].size(); ++j) { - try { - _target->accessChannel(i)[j] = ToImplConverter::convert(this->buffer_2D[i][j]); - } - catch(boost::numeric::positive_overflow&) { - _target->accessChannel(i)[j] = std::numeric_limits::max(); - } - catch(boost::numeric::negative_overflow&) { - _target->accessChannel(i)[j] = std::numeric_limits::min(); - } + _target->accessChannel(i)[j] = numeric::convert(this->buffer_2D[i][j]); } } } diff --git a/tests/executables_src/testIEEE754_SingleConverter.cpp b/tests/executables_src/testIEEE754_SingleConverter.cpp index c99e925a0..45b550f38 100644 --- a/tests/executables_src/testIEEE754_SingleConverter.cpp +++ b/tests/executables_src/testIEEE754_SingleConverter.cpp @@ -16,8 +16,8 @@ BOOST_AUTO_TEST_CASE(test_toCooked_3_25) { IEEE754_SingleConverter converter; float testValue = 3.25; - void* warningAvoider = &testValue; - int32_t rawValue = *(reinterpret_cast(warningAvoider)); + int32_t rawValue; + memcpy(&rawValue, &testValue, sizeof(float)); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), 3.25, 0.0001); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), 3.25, 0.0001); @@ -39,14 +39,14 @@ BOOST_AUTO_TEST_CASE(test_toCooked_60k) { // tests two functionalities: range check of the target (value too large for // int8 and int16) float testValue = 60000.7; - void* warningAvoider = &testValue; - int32_t rawValue = *(reinterpret_cast(warningAvoider)); + int32_t rawValue; + memcpy(&rawValue, &testValue, sizeof(float)); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), 60000.7, 0.0001); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), 60000.7, 0.0001); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::positive_overflow); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::positive_overflow); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::positive_overflow); // +- 32k + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 127); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 255); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 32767); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 60001); // unsigned 16 bit is up to 65k BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 60001); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 60001); @@ -60,26 +60,26 @@ BOOST_AUTO_TEST_CASE(test_toCooked_minus240) { IEEE754_SingleConverter converter; float testValue = -240.6; - void* warningAvoider = &testValue; - int32_t rawValue = *(reinterpret_cast(warningAvoider)); + int32_t rawValue; + memcpy(&rawValue, &testValue, sizeof(float)); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), -240.6, 0.0001); BOOST_CHECK_CLOSE(converter.scalarToCooked(rawValue), -240.6, 0.0001); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::negative_overflow); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::negative_overflow); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), -128); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 0); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), -241); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::negative_overflow); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 0); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), -241); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::negative_overflow); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 0); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), -241); - BOOST_CHECK_THROW(converter.scalarToCooked(rawValue), boost::numeric::negative_overflow); + BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), 0); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), std::to_string(testValue)); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), true); } void checkAsRaw(int32_t rawValue, float expectedValue) { - void* warningAvoider = &rawValue; - float testValue = *(reinterpret_cast(warningAvoider)); + float testValue; + memcpy(&testValue, &rawValue, sizeof(float)); BOOST_CHECK_CLOSE(testValue, expectedValue, 0.0001); } @@ -116,8 +116,8 @@ BOOST_AUTO_TEST_CASE(test_toCooked_00) { // tests if Boolean turns 0.0 into false float testValue = 0.0; - void* warningAvoider = &testValue; - int32_t rawValue = *(reinterpret_cast(warningAvoider)); + int32_t rawValue; + memcpy(&rawValue, &testValue, sizeof(float)); BOOST_CHECK_EQUAL(converter.scalarToCooked(rawValue), false); } diff --git a/tests/executables_src/testTypeChangingDecorator.cpp b/tests/executables_src/testTypeChangingDecorator.cpp index e033628b7..f8978d68d 100644 --- a/tests/executables_src/testTypeChangingDecorator.cpp +++ b/tests/executables_src/testTypeChangingDecorator.cpp @@ -21,7 +21,7 @@ using namespace ChimeraTK; /**********************************************************************************************************************/ // helper function to convert numeric or string to double -template +template double toDouble(UserType input) { return userTypeToNumeric(input); } @@ -163,7 +163,7 @@ void testDecorator(double startReadValue, T expectedReadValue, T startWriteValue decoratedScalar.accessData(0) = startWriteValue; decoratedScalar.write(); anotherScalarAccessor.read(); - BOOST_CHECK_CLOSE(toDouble(anotherScalarAccessor), expectedWriteValue, 0.0001); + BOOST_CHECK_CLOSE(double(anotherScalarAccessor), expectedWriteValue, 0.0001); // repeat the read / write tests with all different functions @@ -211,7 +211,7 @@ void testDecorator(double startReadValue, T expectedReadValue, T startWriteValue decoratedScalarInGroup->accessData(0) = Adder::add(startWriteValue, 1); transferGroup.write(); anotherScalarAccessor.read(); - BOOST_CHECK_CLOSE(toDouble(anotherScalarAccessor), expectedWriteValue + 1, 0.0001); + BOOST_CHECK_CLOSE(double(anotherScalarAccessor), expectedWriteValue + 1, 0.0001); // Test pre/postRead anotherScalarAccessor = startReadValue + 4; diff --git a/tests/executables_src/testUnifiedTypeChangingDecorator.cpp b/tests/executables_src/testUnifiedTypeChangingDecorator.cpp index f67c2fec4..de75e9603 100644 --- a/tests/executables_src/testUnifiedTypeChangingDecorator.cpp +++ b/tests/executables_src/testUnifiedTypeChangingDecorator.cpp @@ -134,15 +134,7 @@ struct TestRegister { std::vector> generateValue() { UserType val; // wrap around when we overflow - try { - val = numericToUserType(acc + 3); - } - catch(boost::numeric::positive_overflow&) { - val = std::numeric_limits::min(); - } - catch(boost::numeric::negative_overflow&) { - val = std::numeric_limits::max(); - } + val = numericToUserType(acc + 3); return {{val}}; } From 197b95ac49148ac27ffea3e079363b4911fbcd78 Mon Sep 17 00:00:00 2001 From: Dietrich Rothe Date: Thu, 12 Feb 2026 15:03:26 +0100 Subject: [PATCH 8/9] fix: MatchingMode=historized might use initial values When the provided initial values were not yet consistent, but a data update coming in turned them into a consistent set, the data made available to the user was corrupt, due to unnecessary buffer switch happening --- .../DataConsistencyGroupHistorizedMatcher.h | 1 + include/NDRegisterAccessorDecorator.h | 4 ++ src/DataConsistencyDecorator.cc | 11 +++- src/DataConsistencyGroup.cc | 3 +- src/DataConsistencyGroupHistorizedMatcher.cc | 29 ++++++++- .../testHistorizedDataMatching.cpp | 64 +++++++++++++++++++ 6 files changed, 104 insertions(+), 8 deletions(-) diff --git a/include/DataConsistencyGroupHistorizedMatcher.h b/include/DataConsistencyGroupHistorizedMatcher.h index d8a946c93..968c64e9d 100644 --- a/include/DataConsistencyGroupHistorizedMatcher.h +++ b/include/DataConsistencyGroupHistorizedMatcher.h @@ -28,6 +28,7 @@ namespace ChimeraTK::DataConsistencyGroupDetail { * reason, we do not provide add function with TransferElement. */ void add(TransferElementAbstractor& acc, unsigned histLen); + [[nodiscard]] bool isConsistent(); void updateCalled(const TransferElementID& transferElementID) { _updateCalled = transferElementID; } diff --git a/include/NDRegisterAccessorDecorator.h b/include/NDRegisterAccessorDecorator.h index 7426b2556..2b3f8239e 100644 --- a/include/NDRegisterAccessorDecorator.h +++ b/include/NDRegisterAccessorDecorator.h @@ -287,6 +287,10 @@ namespace ChimeraTK { for(size_t i = 0; i < _target->getNumberOfChannels(); ++i) { buffer_2D[i].resize(_target->getNumberOfSamples()); } + // Here we do not take over the buffer contents of the target, since specific implementations + // of NDRegisterAccessorDecorator will handle this differently. + // In case UserType=TargetUserType a buffer swap would be efficient, but the specific implementation should + // have that under control. if(target->isReadTransactionInProgress()) { // In case accessor was already used from ReadAnyGroup, it has readTransactionInProgress set. We must copy diff --git a/src/DataConsistencyDecorator.cc b/src/DataConsistencyDecorator.cc index 7489391db..049f0b83d 100644 --- a/src/DataConsistencyDecorator.cc +++ b/src/DataConsistencyDecorator.cc @@ -11,7 +11,12 @@ namespace ChimeraTK { const boost::shared_ptr>& target, DataConsistencyGroupDetail::HistorizedMatcher* dGroup) : NDRegisterAccessorDecorator(target), _hGroup(dGroup) { - this->_inReadAnyGroup = target->getReadAnyGroup(); + // meta data is already copied in base implemention, so only take over actual data + for(size_t i = 0; i < _target->getNumberOfChannels(); ++i) { + buffer_2D[i].swap(_target->accessChannel(i)); + } + + this->setInReadAnyGroup(target->getReadAnyGroup()); this->_readQueue = target->getReadQueue().template then([this]() { readCallback(); }, std::launch::deferred); } @@ -65,7 +70,7 @@ namespace ChimeraTK { template void DataConsistencyDecorator::readCallback() { // While ReadAnyGroup.waitAny() already calls preRead at the beginning, waitAnyNonBlocking() does not, - // and fist peeks into the readQueue, causing readCallback() to be executed first. + // and first peeks into the readQueue, causing readCallback() to be executed first. // We call preRead just to be sure; if called twice no harm done. // Using TransferType::read should be fine even in non-blocking case, since // readCallback is only executed when we know something is in target's readQueue. @@ -80,7 +85,7 @@ namespace ChimeraTK { // because we need user buffer content to judge data consistency. _target->postRead(TransferType::read, true); - // check data consistency, including history, and update user buffers if necessary + // check data consistency, including history bool consistent = _hGroup->checkUpdate(_target->getId()); if(!consistent) { // if not consistent, delay call to postRead (called from ReadAnyGroup), by throwing DiscardValueException. diff --git a/src/DataConsistencyGroup.cc b/src/DataConsistencyGroup.cc index cadb0240f..06c34b482 100644 --- a/src/DataConsistencyGroup.cc +++ b/src/DataConsistencyGroup.cc @@ -173,8 +173,7 @@ namespace ChimeraTK { case MatchingMode::exact: return dynamic_cast(_impl.get())->isConsistent(); case MatchingMode::historized: - // no need to call HistorizedMatcher::findMatch; it would return true - return true; + return dynamic_cast(_impl.get())->isConsistent(); } assert(false); return false; diff --git a/src/DataConsistencyGroupHistorizedMatcher.cc b/src/DataConsistencyGroupHistorizedMatcher.cc index 6e44cafea..d21d45c6e 100644 --- a/src/DataConsistencyGroupHistorizedMatcher.cc +++ b/src/DataConsistencyGroupHistorizedMatcher.cc @@ -100,11 +100,17 @@ namespace ChimeraTK::DataConsistencyGroupDetail { // To update other user buffers, call postRead on the other involved decorators. // This concerns all pushElements, except when a push element was already on right version num and received // another update, which can only happen if the new datum has the same version number (handled as special case). + // The other exception is when the initial value of some pushElements already had the matching version number. // Note, in case of an exception thrown by some postRead, it might happen that postRead is // called more than once in a row, for the other elements. This is allowed. for(auto& e : _pushElements) { - if(e.first != callerId) { - auto& acc = e.second; + if(e.first == callerId) { + continue; + } + auto& acc = e.second; + // in case the inital value of the accessor already had the matching version number, we must skip the postRead + // since otherwise it would swap away the value + if(lastMatchingVersionNumber() > acc.getVersionNumber()) { acc.getHighLevelImplElement()->postRead(TransferType::read, updateBuffer); } } @@ -195,6 +201,24 @@ namespace ChimeraTK::DataConsistencyGroupDetail { /********************************************************************************************************************/ + bool HistorizedMatcher::isConsistent() { + // we cannot always assume that user buffers are consistent: + // - before receiving data updates through ReadAnyGroup, state of initial values is unknown + // - also, user might have added extra elements to DataConsistencyGroup + if(_pushElements.size() == 0) { + return true; + } + auto firstVn = _pushElements.begin()->second.getVersionNumber(); + for(auto x : _pushElements) { + if(x.second.getVersionNumber() != firstVn) { + return false; + } + } + return true; + } + + /********************************************************************************************************************/ + bool HistorizedMatcher::findMatch(TransferElementID transferElementID) { auto it = _targetElements.find(transferElementID); if(it == _targetElements.end()) { @@ -248,7 +272,6 @@ namespace ChimeraTK::DataConsistencyGroupDetail { using UserType = decltype(arg); using UserBufferType = std::vector>; auto& buf = getUserBuffer(transferElementID); - // update history of currentBunchPattern so we can use it in findBunchPattern() auto& bufferVector = *getBufferVector(transferElementID); unsigned histLen = bufferVector.size(); diff --git a/tests/executables_src/testHistorizedDataMatching.cpp b/tests/executables_src/testHistorizedDataMatching.cpp index 09838fd53..47efee968 100644 --- a/tests/executables_src/testHistorizedDataMatching.cpp +++ b/tests/executables_src/testHistorizedDataMatching.cpp @@ -9,6 +9,7 @@ using namespace boost::unit_test_framework; #include "DataConsistencyGroup.h" #include "Device.h" +#include "NDRegisterAccessorDecorator.h" #include "ReadAnyGroup.h" using namespace ChimeraTK; @@ -355,6 +356,69 @@ BOOST_FIXTURE_TEST_CASE(testInitialValues, Fixture) { BOOST_TEST(readAccB.getVersionNumber() != VersionNumber(nullptr)); } +BOOST_AUTO_TEST_CASE(testInitialValuesConsistency) { + // in all the previous tests, we simply discarded the initial values. + // However in real use, e.g. with ApplicationCore, it often makes sense to keep the initial values and complete + // them with some data update that turns them into a consistent set. + // Test that MatchingMode::historized supports this use case. + + Device dev; + dev.open("(logicalNameMap?map=historizedDataMatching.xlmap)"); + dev.activateAsyncRead(); + + for(bool extraDecorators : {false, true}) { + // prepare initial values + VersionNumber vs0; + VersionNumber vs1; + auto accA = dev.getScalarRegisterAccessor("/A"); + auto accB = dev.getScalarRegisterAccessor("/B"); + accA.setAndWrite(100, vs1); + accB.setAndWrite(99, vs0); + + // use 'fresh' read accessors not yet tainted by ReadAnyGroup or DataConsistencyGroup + auto readAccA = dev.getScalarRegisterAccessor("/A", 0, {AccessMode::wait_for_new_data}); + auto readAccB = dev.getScalarRegisterAccessor("/B", 0, {AccessMode::wait_for_new_data}); + + if(extraDecorators) { + // In order to mimic ApplicationCore behaviour, where a MetaDataPropagatingRegisterDecorator is placed around + // every accessor, we add a decoration layer via otherwise useless NDRegisterAccessorDecorator. + // We want to test that DataConsistencyDecorator swaps the right buffers even then. + auto da = boost::make_shared>(readAccA.getImpl()); + readAccA.replace(da); + auto db = boost::make_shared>(readAccB.getImpl()); + readAccB.replace(db); + } + + // read and check them + readAccA.readLatest(); + readAccB.readLatest(); + BOOST_TEST(readAccA == 100); + BOOST_TEST(readAccA.getVersionNumber() == vs1); + BOOST_TEST(readAccB == 99); + BOOST_TEST(readAccB.getVersionNumber() == vs0); + + ReadAnyGroup rag{readAccA, readAccB}; + DataConsistencyGroup dg({readAccA, readAccB}, DataConsistencyGroup::MatchingMode::historized); + // check user buffer again - because of DataConsistencyDecorator + BOOST_TEST(readAccA.getVersionNumber() == vs1); + BOOST_TEST(readAccB.getVersionNumber() == vs0); + BOOST_TEST(dg.isConsistent() == false); + // note, following 2 tests fail unless DataConsistencyDecorator takes over initial data on construction + BOOST_TEST(readAccA == 100); + BOOST_TEST(readAccB == 99); + + // provide data update for B that completes consistent set + accB.setAndWrite(100, vs1); + auto id = rag.readAny(); + BOOST_TEST(id == readAccB.getId()); + BOOST_TEST(readAccA == 100); + BOOST_TEST(readAccB == 100); + BOOST_TEST(readAccA.getVersionNumber() == vs1); + BOOST_TEST(readAccB.getVersionNumber() == vs1); + BOOST_TEST(dg.isConsistent() == true); + } +} + BOOST_FIXTURE_TEST_CASE(testIllegalUse, Fixture) { std::cout << "testIllegalUse" << std::endl; From 72a3485f7d334cd09906b072e75f0a309d091b01 Mon Sep 17 00:00:00 2001 From: Tomasz Kozak Date: Wed, 25 Feb 2026 10:02:17 +0100 Subject: [PATCH 9/9] feat: add tests for 64 bits width for converter --- .../testFixedPointConverterRaw64.cpp | 454 ++++++++++++++++-- 1 file changed, 403 insertions(+), 51 deletions(-) diff --git a/tests/executables_src/testFixedPointConverterRaw64.cpp b/tests/executables_src/testFixedPointConverterRaw64.cpp index db353d2e8..b0fa4f95f 100644 --- a/tests/executables_src/testFixedPointConverterRaw64.cpp +++ b/tests/executables_src/testFixedPointConverterRaw64.cpp @@ -24,8 +24,10 @@ using namespace boost::unit_test_framework; using FIXEDPOINT_RAW_BIT64 = int64_t; #define HEX_TO_DOUBLE(INPUT) static_cast(INPUT) -#define SIGNED_HEX_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) -#define SIGNED_HEX_TO_INT64(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX64_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) + +#define SIGNED_HEX32_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) +#define SIGNED_HEX32_TO_INT64(INPUT) static_cast(static_cast(INPUT)) #define SIGNED_HEX16_TO_DOUBLE(INPUT) static_cast(static_cast(INPUT)) #define SIGNED_HEX16_TO_INT64(INPUT) static_cast(static_cast(INPUT)) @@ -122,6 +124,56 @@ void checkToRaw(FixedPointConverter const& converter, T in BOOST_CHECK_MESSAGE(result == expectedValue, message.str()); } +template +void checkToCookedOverflowNeg64(FixedPointConverter const& converter, uint64_t input) { + BOOST_CHECK_EQUAL(converter.scalarToCooked(input), std::numeric_limits::min()); +} + +template +void checkToCookedOverflowPos64(FixedPointConverter const& converter, uint64_t input) { + BOOST_CHECK_EQUAL(converter.scalarToCooked(input), std::numeric_limits::max()); +} + +template +void checkToCooked64(FixedPointConverter const& converter, uint64_t input, T expectedValue, + const std::string& msg = std::string("")) { + std::stringstream message; + message << "testToCooked failed for type " << typeName() << " with input " << std::hex << "0x" << input << std::hex + << ", expected 0x" << expectedValue << std::dec; + + BOOST_TEST_CHECKPOINT(message.str()); + + T output = converter.scalarToCooked(input); + if(msg.empty()) { + message << std::hex << ", output 0x" << output << std::dec; + } + else { + message << std::hex << ", output 0x" << output << std::dec << ", " << msg; + } + + BOOST_CHECK_MESSAGE(output == expectedValue, message.str()); +} + +template +void checkToRaw64(FixedPointConverter const& converter, T input, uint64_t expectedValue, + const std::string& msg = std::string("")) { + std::stringstream message; + message << "testToRaw failed for type " << typeName() << " with input 0x" << std::hex << input << ", expected 0x" + << expectedValue << std::dec; + + BOOST_TEST_CHECKPOINT(message.str()); + + uint64_t result = converter.toRaw(input); + if(msg.empty()) { + message << std::hex << ", output 0x" << result << std::dec; + } + else { + message << std::hex << ", output 0x" << result << std::dec << ", " << msg; + } + + BOOST_CHECK_MESSAGE(result == expectedValue, message.str()); +} + BOOST_AUTO_TEST_SUITE(FixedPointConverterTestSuite) BOOST_AUTO_TEST_CASE(testConstructor) { @@ -138,13 +190,313 @@ BOOST_AUTO_TEST_CASE(testConstructor) { BOOST_CHECK_NO_THROW(FixedPointConverter("UnknownVariable", 2, -1024 + 2)); } +BOOST_AUTO_TEST_CASE(testInt64) { + FixedPointConverter converter("Variable64signed", 64); + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, SIGNED_HEX64_TO_DOUBLE(0xAAAAAAAAAAAAAAAA)); + checkToCooked64(converter, 0x5555555555555555, HEX_TO_DOUBLE(0x5555555555555555)); + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, (int64_t)0xAAAAAAAAAAAAAAAA); + checkToCooked64(converter, 0x5555555555555555, (int64_t)0x5555555555555555); + checkToCookedOverflowNeg64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCooked64(converter, 0x5555555555555555, (uint64_t)0x5555555555555555); + checkToCookedOverflowNeg64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x5555555555555555); + checkToCookedOverflowNeg64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x5555555555555555); + checkToCookedOverflowNeg64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x5555555555555555); + checkToCookedOverflowNeg64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x5555555555555555); + + checkToRaw64(converter, 0.25, 0); + checkToRaw64(converter, -0.25, 0); + checkToRaw64(converter, 0.75, 1); + checkToRaw64(converter, -0.75, -1); + checkToRaw64(converter, 3.25, 3); + checkToRaw64(converter, -3.25, -3); + checkToRaw64(converter, 5.75, 6); + checkToRaw64(converter, -5.75, -6); + + checkToRaw64(converter, (int64_t)0x5555555555555555, 0x5555555555555555); + checkToRaw64(converter, (int64_t)0xAAAAAAAAAAAAAAAA, 0xAAAAAAAAAAAAAAAA); + checkToRaw64(converter, (uint64_t)0x5555555555555555, 0x5555555555555555); + checkToRaw64(converter, (uint64_t)0xAAAAAAAAAAAAAAAA, 0x7FFFFFFFFFFFFFFF); + checkToRaw64(converter, (int)0x55555555, 0x55555555); + checkToRaw64(converter, (int)0xAAAAAAAA, 0xFFFFFFFFAAAAAAAA); + checkToRaw64(converter, (unsigned int)0x55555555, 0x55555555); + checkToRaw64(converter, (unsigned int)0xAAAAAAAA, 0xAAAAAAAA); + checkToRaw64(converter, (short)0x5555, 0x5555); + checkToRaw64(converter, (short)0xAAAA, 0xFFFFFFFFFFFFAAAA); + checkToRaw64(converter, (unsigned short)0x5555, 0x5555); + checkToRaw64(converter, (unsigned short)0xAAAA, 0xAAAA); + + checkToRaw64(converter, (int64_t)0x5555, 0x5555); + checkToRaw64(converter, (int64_t)0xFFFFFFFFFFFFAAAA, 0xFFFFFFFFFFFFAAAA); + + // these 4 tests should pass, but they fail due to wrong implementation + // wait for new implementation and for now comment them + // checkToCooked64(converter, 0x5555555555555555, std::string("6148914691236517205")); + // checkToRaw64(converter, std::string("6148914691236517205"), 0x5555555555555555); + // checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, std::string("-6148914691236517206")); + // checkToRaw64(converter, std::string("-6148914691236517206"), 0xAAAAAAAAAAAAAAAA); + + // Boolean check + checkToCooked64(converter, 0x5555555555555555, Boolean(true)); + checkToCooked64(converter, 0x0, Boolean(false)); + + /* + // No idea how to easily replace tests for 64 bit raw as there is no type wider than 64 bits + checkToRaw(converter, (int64_t)0xFFFFFFFAAAAAAAAA, + 0x80000000); // Smallest signed representation possible + checkToRaw(converter, (uint64_t)0xFFFFFFFFF, + 0x7FFFFFFF); // max signed representation possible + checkToRaw(converter, (int64_t)0xFFFFFFFFF, 0x7FFFFFFF); + */ +} + +BOOST_AUTO_TEST_CASE(testUInt64) { + FixedPointConverter converter("Variable64unsigned", 64, 0, + false); // 64 bits, 0 fractional bits, not signed + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAAAAAAAAAA)); + checkToCooked64(converter, 0x555555555555555, HEX_TO_DOUBLE(0x555555555555555)); + checkToCookedOverflowPos64(converter, 0xAAAAAAAAAAAAAAAA); + + checkToCooked64(converter, 0x555555555555555, (int64_t)0x555555555555555); + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, (uint64_t)0xAAAAAAAAAAAAAAAA); + checkToCooked64(converter, 0x555555555555555, (uint64_t)0x555555555555555); + + checkToCookedOverflowPos64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x555555555555555); + checkToCookedOverflowPos64(converter, 0xAAAAAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x555555555555555); + + checkToCooked64(converter, 0x00000000AAAAAAAA, (int64_t)0xAAAAAAAA); + checkToCooked64(converter, 0x0000000055555555, (uint64_t)0x55555555); + + checkToRaw64(converter, 0.25, 0); + checkToRaw64(converter, -0.25, 0); + checkToRaw64(converter, 0.75, 1); + checkToRaw64(converter, -0.75, 0); + checkToRaw64(converter, 3.25, 3); + checkToRaw64(converter, -3.25, 0); + checkToRaw64(converter, 5.75, 6); + checkToRaw64(converter, -5.75, 0); + + checkToRaw64(converter, (int64_t)0x555555555555555, 0x555555555555555); + checkToRaw64(converter, (int64_t)0xAAAAAAAAAAAAAAAA, 0); + checkToRaw64(converter, (uint64_t)0x555555555555555, 0x555555555555555); + checkToRaw64(converter, (uint64_t)0xAAAAAAAAAAAAAAAA, 0xAAAAAAAAAAAAAAAA); + + checkToRaw64(converter, (int)0x55555555, 0x55555555); + checkToRaw64(converter, (int)0xAAAAAAAA, 0); + checkToRaw64(converter, (unsigned int)0x55555555, 0x55555555); + checkToRaw64(converter, (unsigned int)0xAAAAAAAA, 0xAAAAAAAA); + checkToRaw64(converter, (short)0x5555, 0x5555); + checkToRaw64(converter, (short)0xAAAA, 0); + checkToRaw64(converter, (unsigned short)0x5555, 0x5555); + checkToRaw64(converter, (unsigned short)0xAAAA, 0xAAAA); + + checkToRaw64(converter, (int64_t)0x5555, 0x5555); + + // these 4 tests should pass, but they fail due to wrong implementation + // wait for new implementation and for now comment them + // checkToCooked64(converter, 0x555555555555555, std::string("6148914691236517205")); + // checkToRaw64(converter, std::string("6148914691236517205"), 0x555555555555555); + // checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, std::string("12297829382473034410")); + // checkToRaw64(converter, std::string("12297829382473034410"), 0xAAAAAAAAAAAAAAAA); + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, Boolean(true)); + checkToCooked64(converter, 0x555555555555555, Boolean(true)); + checkToCooked64(converter, 0x0, Boolean(false)); + + /* + // No idea how to easily replace tests for 64 bit raw as there is no type wider than 64 bits + checkToRaw(converter, SIGNED_HEX32_TO_INT64(0xAAAAAAAA), + 0x0); // Lowest range of 32 bit wide unsigned register + checkToRaw(converter, (int64_t)0x100000000, 0xFFFFFFFF); + checkToRaw(converter, (uint64_t)0x100000000, + 0xFFFFFFFF); // max signed representation possible + */ +} + +BOOST_AUTO_TEST_CASE(testInt64_fractionMinus12) { + FixedPointConverter converter("Variable64minus12signed", 64, + -12); // 64 bits, -12 fractional bits, signed + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, SIGNED_HEX64_TO_DOUBLE(0xAAAAAAAAAAAAAAAA) * pow(2, 12)); + checkToCooked64(converter, 0x555555555555555, SIGNED_HEX64_TO_DOUBLE(0x555555555555555) * pow(2, 12)); + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAAAA); + checkToCooked64(converter, 0xFFFAAAAAAAAAAAAA, (int64_t)0xAAAAAAAAAAAAA000); + checkToCooked64(converter, 0x0005555555555555, (int64_t)0x5555555555555000); + + checkToCookedOverflowNeg64(converter, 0xFFFAAAAAAAAAAAAA); + checkToCooked64(converter, 0x000AAAAAAAAAAAAA, (uint64_t)0xAAAAAAAAAAAAA000); + checkToCooked64(converter, 0x0005555555555555, (uint64_t)0x5555555555555000); + + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x0005555555555555); + + checkToRaw64(converter, 0.25, 0); + checkToRaw64(converter, -0.25, 0); + checkToRaw64(converter, 0.75, 0); + checkToRaw64(converter, -0.75, 0); + checkToRaw64(converter, 3.25, 0); + checkToRaw64(converter, -3.25, 0); + checkToRaw64(converter, 5.75, 0); + checkToRaw64(converter, -5.75, 0); + + checkToRaw64(converter, (int64_t)0x5555555555555555, 0x0005555555555555); + checkToRaw64(converter, (int64_t)0xAAAAAAAAAAAAAAAA, 0xFFFAAAAAAAAAAAAB); + checkToRaw64(converter, (uint64_t)0x5555555555555555, 0x0005555555555555); + checkToRaw64(converter, (uint64_t)0xAAAAAAAAAAAAAAAA, 0x000AAAAAAAAAAAAB); + checkToRaw64(converter, (int)0x55555555, 0x0000000000055555); + checkToRaw64(converter, (int)0xAAAAAAAA, 0xFFFFFFFFFFFAAAAB); + checkToRaw64(converter, (unsigned int)0x55555555, 0x0000000000055555); + checkToRaw64(converter, (unsigned int)0xAAAAAAAA, 0x00000000000AAAAB); + + checkToRaw64(converter, (short)0x5555, 0x0000000000000005); + checkToRaw64(converter, (short)0xAAAA, 0xFFFFFFFFFFFFFFFB); + checkToRaw64(converter, (unsigned short)0x5555, 0x0000000000000005); + checkToRaw64(converter, (unsigned short)0xAAAA, 0x000000000000000B); + + checkToRaw64(converter, (int64_t)0x55555555, 0x00055555); + checkToRaw64(converter, static_cast(static_cast(0xAAAAAAAA)), 0XFFFFFFFFFFFAAAAB); + checkToRaw64(converter, (int64_t)0x5555555555555, 0x0005555555555); + checkToRaw64(converter, (int64_t)0xFFFFA55555555555, 0xFFFFFFFA55555555); + checkToRaw64(converter, (uint64_t)0x55555, 0x00000055); + checkToRaw64(converter, (uint64_t)0x5555555555555, 0x0005555555555); +} + +BOOST_AUTO_TEST_CASE(testUInt64_fractionMinus12) { + FixedPointConverter converter("Variable64minus12unsigned", 64, -12, + false); // 64 bits, -12 fractional bits, not signed + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAAAAAAAAAA) * pow(2, 12)); + checkToCooked64(converter, 0x5555555555555555, HEX_TO_DOUBLE(0x5555555555555555) * pow(2, 12)); + + checkToCooked64(converter, 0x000AAAAAAAAAAAAA, (uint64_t)0xAAAAAAAAAAAAA000); + checkToCooked64(converter, 0x0005555555555555, (uint64_t)0x5555555555555000); + checkToCooked64(converter, 0x0005555555555555, (int64_t)0x5555555555555000); + + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + checkToCookedOverflowPos64(converter, 0x000AAAAAAAAAAAAA); + + checkToRaw64(converter, 0.25, 0); + checkToRaw64(converter, -0.25, 0); + checkToRaw64(converter, 0.75, 0); + checkToRaw64(converter, -0.75, 0); + checkToRaw64(converter, 3.25, 0); + checkToRaw64(converter, -3.25, 0); + checkToRaw64(converter, 5.75, 0); + checkToRaw64(converter, -5.75, 0); + + checkToRaw64(converter, (int64_t)0x5555555555555555, 0x0005555555555555); + checkToRaw64(converter, (int64_t)0xAAAAAAAAAAAAAAAA, 0); + checkToRaw64(converter, (uint64_t)0x5555555555555555, 0x0005555555555555); + checkToRaw64(converter, (uint64_t)0xAAAAAAAAAAAAAAAA, 0x000AAAAAAAAAAAAB); + + checkToRaw64(converter, (int)0x55555555, 0x00055555); + checkToRaw64(converter, (int)0xAAAAAAAA, 0); + checkToRaw64(converter, (unsigned int)0x55555555, 0x00055555); + checkToRaw64(converter, (unsigned int)0xAAAAAAAA, 0x000AAAAB); + checkToRaw64(converter, (short)0x5555, 0x00000005); + checkToRaw64(converter, (short)0xAAAA, 0); + checkToRaw64(converter, (unsigned short)0x5555, 0x00000005); + checkToRaw64(converter, (unsigned short)0xAAAA, 0x0000000B); + checkToRaw64(converter, (int64_t)0x55555555, 0x00055555); + checkToRaw64(converter, static_cast(static_cast(0xAAAAAAAA)), 0x0); + checkToRaw64(converter, (int64_t)0x5555555555555, 0x5555555555); + checkToRaw64(converter, (uint64_t)0x55555, 0x00000055); + checkToRaw64(converter, (uint64_t)0x5555555555555, 0x5555555555); +} + +BOOST_AUTO_TEST_CASE(testInt64_fraction7) { + FixedPointConverter converter("Variable64plus7signed", 64, + 7); // 64 bits, 7 fractional bits, signed + + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, SIGNED_HEX64_TO_DOUBLE(0xAAAAAAAAAAAAAAAA) * pow(2, -7)); + checkToCooked64(converter, 0x5555555555555555, SIGNED_HEX64_TO_DOUBLE(0x5555555555555555) * pow(2, -7)); + + std::cout << "!!!!!!!!!!!!!!! testInt64_fraction7 - TEST OF 'TO COOKED' WHICH FAILS and should not !!!!!!!!!!!!!!!" + << std::endl; + checkToCooked64(converter, 0xAAAAAAAAAAAAA5FF, (int64_t)0xFF5555555555554B); // 0xff55555555555548 + checkToCooked64(converter, 0xAAAAAAAAAAAAA600, (int64_t)0xFF5555555555554C); // 0xff55555555555550 + checkToCooked64(converter, 0xAAAAAAAAAAAAAA00, (int64_t)0xFF55555555555554); // 0xff55555555555550 + checkToCooked64(converter, 0xAAAAAAAAAAAAAA01, (int64_t)0xFF55555555555554); // 0xff55555555555558 + + checkToCooked64(converter, 0xAAAAAAAAAAAAAA20, (int64_t)0xFF55555555555554); + checkToCooked64(converter, 0xAAAAAAAAAAAAAA60, (int64_t)0xFF55555555555554); + checkToCooked64(converter, 0xAAAAAAAAAAAAAA80, (int64_t)0xFFFFFFFFFF555555); + checkToCooked64(converter, 0xAAAAAAAAAAAAAAAA, (int64_t)0xFFFFFFFFFF555555); + checkToCooked64(converter, 0xAAAAAAAAAAAAAAC0, (int64_t)0xFFFFFFFFFF555555); + checkToCooked64(converter, 0xAAAAAAAAAAAAAAD0, (int64_t)0xFFFFFFFFFF555555); + checkToCooked64(converter, 0xAAAAAAAAAAAAAB00, (int64_t)0xFFFFFFFFFF555556); + + checkToCooked64(converter, 0xAAAAAAAAAAAAAC00, (int64_t)0xFFFFFFFFFF555556); + + checkToCooked64(converter, 0x555555555555553F, (int64_t)0xAAAAAAAAAAAAAA); // 0xaaaaaaaaaaaaa8 + checkToCooked64(converter, 0x5555555555555540, (int64_t)0xAAAAAAAAAAAAAA); // 0xaaaaaaaaaaaaa8 + checkToCooked64(converter, 0x55555555555555C0, (int64_t)0xAAAAAAAAAAAAAB); // 0xaaaaaaaaaaaaa8 + + // checkToCooked(converter, 0x55555555, (unsigned int)0x00AAAAAB); + // checkToCooked(converter, 0x22220222, (unsigned int)0x00444404); + + std::cout << "!!!!!!!!!!!!!!! testInt64_fraction7 - TEST OF 'TO COOKED' WHICH FAILS - ENDS !!!!!!!!!!!!!!!" + << std::endl; + + checkToCooked64(converter, 0x2222222222220222, (int64_t)0x44444444444404); // this works - funny + + checkToRaw64(converter, 0.25, 0x20); + checkToRaw64(converter, -0.25, 0xFFFFFFFFFFFFFFE0); + checkToRaw64(converter, 0.75, 0x60); + checkToRaw64(converter, -0.75, 0xFFFFFFFFFFFFFFA0); + + checkToRaw64(converter, 3.25, 0x1A0); + checkToRaw64(converter, -3.25, 0xFFFFFFFFFFFFFE60); + checkToRaw64(converter, 5.75, 0x2E0); + checkToRaw64(converter, -5.75, 0xFFFFFFFFFFFFFD20); + + checkToRaw64(converter, (int64_t)0x5555555555555555, 0x7FFFFFFFFFFFFFFF); + checkToRaw64(converter, (int64_t)0xAAAAAAAAAAAAAAAA, 0x8000000000000000); + + checkToRaw64(converter, (int64_t)0x0005555555555555, 0x2AAAAAAAAAAAA80); + checkToRaw64(converter, (int64_t)0x000AAAAAAAAAAAAA, 0x555555555555500); + + checkToRaw64(converter, (int)0x55555555, 0x2AAAAAAA80); + checkToRaw64(converter, (int)0xAAAAAAAA, 0xFFFFFFD555555500); + checkToRaw64(converter, (int)0x00888808, 0x44440400); + checkToRaw64(converter, (int)0xFF888808, 0xFFFFFFFFC4440400); + + checkToRaw64(converter, (unsigned int)0x55555555, 0x2AAAAAAA80); + checkToRaw64(converter, (unsigned int)0xAAAAAAAA, 0x5555555500); + + checkToRaw64(converter, (unsigned int)0x00888808, 0x44440400); + checkToRaw64(converter, (unsigned int)0xFF888808, 0x7FC4440400); + + checkToRaw64(converter, (short)0x5555, 0x002AAA80); + checkToRaw64(converter, (short)0xAAAA, 0xFFFFFFFFFFD55500); + + checkToRaw64(converter, (unsigned short)0x5555, 0x002AAA80); + checkToRaw64(converter, (unsigned short)0xAAAA, 0x00555500); + + checkToCooked64(converter, 0x20, std::string("0.250000")); + checkToRaw64(converter, std::string("0.25"), 0x20); + + std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! testInt64_fraction7 - END !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + << std::endl; +} + BOOST_AUTO_TEST_CASE(testInt32) { FixedPointConverter converter("Variable32signed"); // default parameters are signed 32 bit - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA)); checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555)); checkToCooked(converter, 0xAAAAAAAA, (int)0xAAAAAAAA); checkToCooked(converter, 0x55555555, (int)0x55555555); - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_INT64(0xAAAAAAAA)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_INT64(0xAAAAAAAA)); checkToCooked(converter, 0x55555555, (uint64_t)0x55555555); checkToCookedOverflowNeg(converter, 0xAAAAAAAA); @@ -188,7 +540,7 @@ BOOST_AUTO_TEST_CASE(testInt32) { BOOST_AUTO_TEST_CASE(testUInt32) { FixedPointConverter converter("Variable32unsigned", 32, 0, - false); // 32 bits, 0 fractional bits, not signed + false); // 64 bits, 0 fractional bits, not signed checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA)); checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555)); @@ -219,7 +571,7 @@ BOOST_AUTO_TEST_CASE(testUInt32) { checkToRaw(converter, (unsigned short)0x5555, 0x5555); checkToRaw(converter, (unsigned short)0xAAAA, 0xAAAA); checkToRaw(converter, (int64_t)0x5555, 0x5555); - checkToRaw(converter, SIGNED_HEX_TO_INT64(0xAAAAAAAA), + checkToRaw(converter, SIGNED_HEX32_TO_INT64(0xAAAAAAAA), 0x0); // Lowest range of 32 bit wide unsigned register checkToRaw(converter, (int64_t)0x100000000, 0xFFFFFFFF); checkToRaw(converter, (uint64_t)0x100000000, @@ -238,7 +590,7 @@ BOOST_AUTO_TEST_CASE(testInt16) { FixedPointConverter converter("Variable16signed", 16); // 16 bits, 0 fractional bits, signed - checkToCooked(converter, 0xAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFFAAAA)); + checkToCooked(converter, 0xAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFFAAAA)); checkToCooked(converter, 0x5555, HEX_TO_DOUBLE(0x5555)); checkToCooked(converter, 0xAAAA, (int)0xFFFFAAAA); checkToCooked(converter, 0x5555, (int)0x5555); @@ -328,7 +680,7 @@ BOOST_AUTO_TEST_CASE(testInt8) { FixedPointConverter converter("Variable8signed", 8); // 8 bits, 0 fractional bits, signed - checkToCooked(converter, 0xAA, SIGNED_HEX_TO_DOUBLE(0xFFFFFFAA)); + checkToCooked(converter, 0xAA, SIGNED_HEX32_TO_DOUBLE(0xFFFFFFAA)); checkToCooked(converter, 0x55, HEX_TO_DOUBLE(0x55)); checkToCooked(converter, 0xAA, (int)0xFFFFFFAA); checkToCooked(converter, 0x55, (int)0x55); @@ -417,9 +769,9 @@ BOOST_AUTO_TEST_CASE(testInt32_fractionMinus12) { FixedPointConverter converter("Variable32minus12signed", 32, -12); // 32 bits, -12 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, 12)); // Basically a left shift 12 - // places - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, 12)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * pow(2, 12)); // Basically a left shift 12 + // places + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * pow(2, 12)); checkToCookedOverflowPos(converter, 0x000AAAAA); checkToCooked(converter, 0xFFFAAAAA, (int)0xAAAAA000); checkToCooked(converter, 0x00055555, (int)0x55555000); @@ -502,8 +854,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fractionMinus1) { FixedPointConverter converter("Variable32minus1signed", 32, -1); // 32 bits, -1 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * 2); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 2); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * 2); checkToCookedOverflowNeg(converter, 0xAAAAAAAA); checkToCookedOverflowPos(converter, 0x55555555); checkToCooked(converter, 0x22222202, (int)0x44444404); @@ -545,7 +897,7 @@ BOOST_AUTO_TEST_CASE(testUInt32_fractionMinus1) { checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAAAAAA) * 2); checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x55555555) * 2); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * 2); checkToCooked(converter, 0x22222202, (int)0x44444404); checkToCooked(converter, 0x55555555, (unsigned int)0xAAAAAAAA); checkToCooked(converter, 0x22222202, (unsigned int)0x44444404); @@ -576,8 +928,8 @@ BOOST_AUTO_TEST_CASE(testInt16_fractionMinus1) { FixedPointConverter converter( "Variable16minus1signed", 16, -1); // 16 bits, -1 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFFAAAA) * 2); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x5555) * 2); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFFAAAA) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x5555) * 2); checkToCookedOverflowNeg(converter, 0xAAAAAAAA); checkToCookedOverflowPos(converter, 0x55555555); checkToCooked(converter, 0x22222202, (int)0x4404); @@ -619,7 +971,7 @@ BOOST_AUTO_TEST_CASE(testUInt16_fractionMinus1) { checkToCooked(converter, 0xAAAAAAAA, HEX_TO_DOUBLE(0xAAAA) * 2); checkToCooked(converter, 0x55555555, HEX_TO_DOUBLE(0x5555) * 2); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x5555) * 2); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x5555) * 2); checkToCooked(converter, 0x22222202, (int)0x4404); checkToCooked(converter, 0x55555555, (unsigned int)0xAAAA); checkToCooked(converter, 0x22222202, (unsigned int)0x4404); @@ -650,8 +1002,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction1) { FixedPointConverter converter("Variable32plus1signed", 32, 1); // 32 bits, 1 fractional bit, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * 0.5); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * 0.5); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * 0.5); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * 0.5); checkToCooked(converter, 0xAAAAAAA9, (int)0xD5555554); checkToCooked(converter, 0xAAAAAAAA, (int)0xD5555555); checkToCooked(converter, 0xAAAAAAAB, (int)0xD5555555); @@ -736,8 +1088,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction7) { FixedPointConverter converter("Variable32plus7signed", 32, 7); // 32 bits, 7 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -7)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -7)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * pow(2, -7)); checkToCooked(converter, 0xAAAAAA20, (int)0xFF555554); checkToCooked(converter, 0xAAAAAA60, (int)0xFF555555); checkToCooked(converter, 0xAAAAAA80, (int)0xFF555555); @@ -821,8 +1173,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction31) { FixedPointConverter converter("Variable32plus31signed", 32, 31); // 32 bits, 31 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -31)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -31)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * pow(2, -31)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * pow(2, -31)); checkToCooked(converter, 0xAAAAAAAA, (int)-1); checkToCooked(converter, 0x55555555, (int)1); checkToCooked(converter, 0x22220222, (int)0); @@ -910,8 +1262,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction32) { FixedPointConverter converter("Variable32plus32signed", 32, 32); // 32 bits, 32 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -32)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -32)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * pow(2, -32)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * pow(2, -32)); checkToCooked(converter, 0xAAAAAAAA, (int)0); checkToCooked(converter, 0x55555555, (int)0); checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); @@ -1031,8 +1383,8 @@ BOOST_AUTO_TEST_CASE(testInt32_fraction43) { FixedPointConverter converter("Variable32plus43signed", 32, 43); // 32 bits, 43 fractional bits, signed - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xAAAAAAAA) * pow(2, -43)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x55555555) * pow(2, -43)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xAAAAAAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x55555555) * pow(2, -43)); checkToCooked(converter, 0xAAAAAAAA, (int)0); checkToCooked(converter, 0x55555555, (int)0); checkToCooked(converter, 0xAAAAAAAA, (unsigned int)0); @@ -1109,12 +1461,12 @@ BOOST_AUTO_TEST_CASE(testInt18_fractionMinus12) { FixedPointConverter converter("int18_fractionMinus12", 18, -12); // 10 bits, -12 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, 12)); // the converter should ignore bits which are not in the spec - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, 12)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, 12)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, 12)); checkToCooked(converter, 0x2AAAA, (int)0xEAAAA000); checkToCooked(converter, 0x15555, (int)0x15555000); @@ -1177,12 +1529,12 @@ BOOST_AUTO_TEST_CASE(testInt18_fraction0) { FixedPointConverter converter("Variable18minus12signed", 18); // 18 bits, 0 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555)); // the converter should ignore bits which are not in the spec - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555)); checkToCooked(converter, 0x2AAAA, (int)0xFFFEAAAA); checkToCooked(converter, 0x15555, (int)0x15555); @@ -1252,11 +1604,11 @@ BOOST_AUTO_TEST_CASE(testInt18_fraction7) { FixedPointConverter converter("Variable18plus7signed", 18, 7); // 18 bits, 7 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -7)); - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -7)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -7)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -7)); checkToCooked(converter, 0x2AAAA, (int)0xFFFFFD55); checkToCooked(converter, 0x15555, (int)0x02AB); @@ -1321,11 +1673,11 @@ BOOST_AUTO_TEST_CASE(testInt18_fraction17) { FixedPointConverter converter("Variable18plus17signed", 18, 17); // 18 bits, 17 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -17)); - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -17)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -17)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -17)); checkToRaw(converter, 0.25, 0x8000); checkToRaw(converter, -0.25, 0x38000); @@ -1365,11 +1717,11 @@ BOOST_AUTO_TEST_CASE(testInt18_fraction18) { FixedPointConverter converter("Variable18plus18signed", 18, 18); // 18 bits, 18 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -18)); - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -18)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -18)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -18)); checkToRaw(converter, 0.25, 0x10000); checkToRaw(converter, -0.25, 0x30000); @@ -1416,11 +1768,11 @@ BOOST_AUTO_TEST_CASE(testInt18_fraction43) { FixedPointConverter converter("int18_fraction43", 18, 43); // 18 bits, 43 fractional bits, signed - checkToCooked(converter, 0x2AAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); - checkToCooked(converter, 0x15555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + checkToCooked(converter, 0x2AAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); + checkToCooked(converter, 0x15555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -43)); - checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); - checkToCooked(converter, 0x55555555, SIGNED_HEX_TO_DOUBLE(0x15555) * pow(2, -43)); + checkToCooked(converter, 0xAAAAAAAA, SIGNED_HEX32_TO_DOUBLE(0xFFFEAAAA) * pow(2, -43)); + checkToCooked(converter, 0x55555555, SIGNED_HEX32_TO_DOUBLE(0x15555) * pow(2, -43)); // all out of range checkToRaw(converter, 0.25, 0x1FFFF);