diff --git a/include/internal/csv_row.hpp b/include/internal/csv_row.hpp index 01182633..c88fab23 100644 --- a/include/internal/csv_row.hpp +++ b/include/internal/csv_row.hpp @@ -92,7 +92,13 @@ namespace csv { // Allow fallthrough from previous if branch IF_CONSTEXPR(!std::is_floating_point::value) { - if (internals::type_num() < this->_type) { + IF_CONSTEXPR(std::is_unsigned::value) { + // Quick hack to perform correct unsigned integer boundary checks + if (this->value > internals::get_uint_max()) { + throw std::runtime_error(internals::ERROR_OVERFLOW); + } + } + else if (internals::type_num() < this->_type) { throw std::runtime_error(internals::ERROR_OVERFLOW); } } diff --git a/include/internal/data_type.h b/include/internal/data_type.h index 680b0a8c..4ec4b2ab 100644 --- a/include/internal/data_type.h +++ b/include/internal/data_type.h @@ -95,6 +95,9 @@ namespace csv { /** Given a byte size, return the largest number than can be stored in * an integer of that size + * + * Note: Provides a platform-agnostic way of mapping names like "long int" to + * byte sizes */ template CONSTEXPR long double get_int_max() { @@ -124,7 +127,38 @@ namespace csv { HEDLEY_UNREACHABLE(); } - /** Largest number that can be stored in a 1-bit integer */ + /** Given a byte size, return the largest number than can be stored in + * an unsigned integer of that size + */ + template + CONSTEXPR long double get_uint_max() { + static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8, + "Bytes must be a power of 2 below 8."); + + IF_CONSTEXPR(sizeof(unsigned char) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned short) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + HEDLEY_UNREACHABLE(); + } + + /** Largest number that can be stored in a 8-bit integer */ CONSTEXPR_VALUE long double CSV_INT8_MAX = get_int_max<1>(); /** Largest number that can be stored in a 16-bit integer */ @@ -136,6 +170,18 @@ namespace csv { /** Largest number that can be stored in a 64-bit integer */ CONSTEXPR_VALUE long double CSV_INT64_MAX = get_int_max<8>(); + /** Largest number that can be stored in a 8-bit ungisned integer */ + CONSTEXPR_VALUE long double CSV_UINT8_MAX = get_uint_max<1>(); + + /** Largest number that can be stored in a 16-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT16_MAX = get_uint_max<2>(); + + /** Largest number that can be stored in a 32-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT32_MAX = get_uint_max<4>(); + + /** Largest number that can be stored in a 64-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT64_MAX = get_uint_max<8>(); + /** Given a pointer to the start of what is start of * the exponential part of a number written (possibly) in scientific notation * parse the exponent @@ -164,13 +210,13 @@ namespace csv { // We can assume number is always non-negative assert(number >= 0); - if (number < internals::CSV_INT8_MAX) + if (number <= internals::CSV_INT8_MAX) return CSV_INT8; - else if (number < internals::CSV_INT16_MAX) + else if (number <= internals::CSV_INT16_MAX) return CSV_INT16; - else if (number < internals::CSV_INT32_MAX) + else if (number <= internals::CSV_INT32_MAX) return CSV_INT32; - else if (number < internals::CSV_INT64_MAX) + else if (number <= internals::CSV_INT64_MAX) return CSV_INT64; else // Conversion to long long will cause an overflow return CSV_DOUBLE; diff --git a/single_include/csv.hpp b/single_include/csv.hpp index 516b1270..f69f6f24 100644 --- a/single_include/csv.hpp +++ b/single_include/csv.hpp @@ -3332,6 +3332,9 @@ namespace csv { /** Given a byte size, return the largest number than can be stored in * an integer of that size + * + * Note: Provides a platform-agnostic way of mapping names like "long int" to + * byte sizes */ template CONSTEXPR long double get_int_max() { @@ -3361,7 +3364,38 @@ namespace csv { HEDLEY_UNREACHABLE(); } - /** Largest number that can be stored in a 1-bit integer */ + /** Given a byte size, return the largest number than can be stored in + * an unsigned integer of that size + */ + template + CONSTEXPR long double get_uint_max() { + static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8, + "Bytes must be a power of 2 below 8."); + + IF_CONSTEXPR(sizeof(unsigned char) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned short) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + HEDLEY_UNREACHABLE(); + } + + /** Largest number that can be stored in a 8-bit integer */ CONSTEXPR_VALUE long double CSV_INT8_MAX = get_int_max<1>(); /** Largest number that can be stored in a 16-bit integer */ @@ -3373,6 +3407,18 @@ namespace csv { /** Largest number that can be stored in a 64-bit integer */ CONSTEXPR_VALUE long double CSV_INT64_MAX = get_int_max<8>(); + /** Largest number that can be stored in a 8-bit ungisned integer */ + CONSTEXPR_VALUE long double CSV_UINT8_MAX = get_uint_max<1>(); + + /** Largest number that can be stored in a 16-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT16_MAX = get_uint_max<2>(); + + /** Largest number that can be stored in a 32-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT32_MAX = get_uint_max<4>(); + + /** Largest number that can be stored in a 64-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT64_MAX = get_uint_max<8>(); + /** Given a pointer to the start of what is start of * the exponential part of a number written (possibly) in scientific notation * parse the exponent @@ -3401,13 +3447,13 @@ namespace csv { // We can assume number is always non-negative assert(number >= 0); - if (number < internals::CSV_INT8_MAX) + if (number <= internals::CSV_INT8_MAX) return CSV_INT8; - else if (number < internals::CSV_INT16_MAX) + else if (number <= internals::CSV_INT16_MAX) return CSV_INT16; - else if (number < internals::CSV_INT32_MAX) + else if (number <= internals::CSV_INT32_MAX) return CSV_INT32; - else if (number < internals::CSV_INT64_MAX) + else if (number <= internals::CSV_INT64_MAX) return CSV_INT64; else // Conversion to long long will cause an overflow return CSV_DOUBLE; @@ -3818,7 +3864,13 @@ namespace csv { // Allow fallthrough from previous if branch IF_CONSTEXPR(!std::is_floating_point::value) { - if (internals::type_num() < this->_type) { + IF_CONSTEXPR(std::is_unsigned::value) { + // Quick hack to perform correct unsigned integer boundary checks + if (this->value > internals::get_uint_max()) { + throw std::runtime_error(internals::ERROR_OVERFLOW); + } + } + else if (internals::type_num() < this->_type) { throw std::runtime_error(internals::ERROR_OVERFLOW); } } diff --git a/single_include_test/csv.hpp b/single_include_test/csv.hpp index 516b1270..f69f6f24 100644 --- a/single_include_test/csv.hpp +++ b/single_include_test/csv.hpp @@ -3332,6 +3332,9 @@ namespace csv { /** Given a byte size, return the largest number than can be stored in * an integer of that size + * + * Note: Provides a platform-agnostic way of mapping names like "long int" to + * byte sizes */ template CONSTEXPR long double get_int_max() { @@ -3361,7 +3364,38 @@ namespace csv { HEDLEY_UNREACHABLE(); } - /** Largest number that can be stored in a 1-bit integer */ + /** Given a byte size, return the largest number than can be stored in + * an unsigned integer of that size + */ + template + CONSTEXPR long double get_uint_max() { + static_assert(Bytes == 1 || Bytes == 2 || Bytes == 4 || Bytes == 8, + "Bytes must be a power of 2 below 8."); + + IF_CONSTEXPR(sizeof(unsigned char) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned short) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + IF_CONSTEXPR(sizeof(unsigned long long int) == Bytes) { + return (long double)std::numeric_limits::max(); + } + + HEDLEY_UNREACHABLE(); + } + + /** Largest number that can be stored in a 8-bit integer */ CONSTEXPR_VALUE long double CSV_INT8_MAX = get_int_max<1>(); /** Largest number that can be stored in a 16-bit integer */ @@ -3373,6 +3407,18 @@ namespace csv { /** Largest number that can be stored in a 64-bit integer */ CONSTEXPR_VALUE long double CSV_INT64_MAX = get_int_max<8>(); + /** Largest number that can be stored in a 8-bit ungisned integer */ + CONSTEXPR_VALUE long double CSV_UINT8_MAX = get_uint_max<1>(); + + /** Largest number that can be stored in a 16-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT16_MAX = get_uint_max<2>(); + + /** Largest number that can be stored in a 32-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT32_MAX = get_uint_max<4>(); + + /** Largest number that can be stored in a 64-bit unsigned integer */ + CONSTEXPR_VALUE long double CSV_UINT64_MAX = get_uint_max<8>(); + /** Given a pointer to the start of what is start of * the exponential part of a number written (possibly) in scientific notation * parse the exponent @@ -3401,13 +3447,13 @@ namespace csv { // We can assume number is always non-negative assert(number >= 0); - if (number < internals::CSV_INT8_MAX) + if (number <= internals::CSV_INT8_MAX) return CSV_INT8; - else if (number < internals::CSV_INT16_MAX) + else if (number <= internals::CSV_INT16_MAX) return CSV_INT16; - else if (number < internals::CSV_INT32_MAX) + else if (number <= internals::CSV_INT32_MAX) return CSV_INT32; - else if (number < internals::CSV_INT64_MAX) + else if (number <= internals::CSV_INT64_MAX) return CSV_INT64; else // Conversion to long long will cause an overflow return CSV_DOUBLE; @@ -3818,7 +3864,13 @@ namespace csv { // Allow fallthrough from previous if branch IF_CONSTEXPR(!std::is_floating_point::value) { - if (internals::type_num() < this->_type) { + IF_CONSTEXPR(std::is_unsigned::value) { + // Quick hack to perform correct unsigned integer boundary checks + if (this->value > internals::get_uint_max()) { + throw std::runtime_error(internals::ERROR_OVERFLOW); + } + } + else if (internals::type_num() < this->_type) { throw std::runtime_error(internals::ERROR_OVERFLOW); } } diff --git a/tests/test_csv_field.cpp b/tests/test_csv_field.cpp index 6d995aed..f6d9b7e8 100644 --- a/tests/test_csv_field.cpp +++ b/tests/test_csv_field.cpp @@ -59,8 +59,20 @@ TEST_CASE("CSVField get<>() - Integral Value", "[test_csv_field_get_int]") { REQUIRE(ex_caught); } +TEST_CASE("CSVField get<>() - Integer Boundary Value", "[test_csv_field_get_boundary]") { + // Note: Tests may fail if compiler defines typenames differently than + // Microsoft/GCC/clang + REQUIRE(CSVField("127").get() == 127); + REQUIRE(CSVField("32767").get() == 32767); + REQUIRE(CSVField("2147483647").get() == 2147483647); + + REQUIRE(CSVField("255").get() == 255); + REQUIRE(CSVField("65535").get() == 65535); + REQUIRE(CSVField("4294967295").get() == 4294967295); +} + // Test converting a small integer to unsigned and signed integer types -TEMPLATE_TEST_CASE("CSVField get<>() - Integral Value to Int", "[test_csv_field_get_int]", +TEMPLATE_TEST_CASE("CSVField get<>() - Integral Value to Int", "[test_csv_field_convert_int]", unsigned char, unsigned short, unsigned int, unsigned long long, char, short, int, long long int) { CSVField savage("21"); diff --git a/tests/test_data_type.cpp b/tests/test_data_type.cpp index 20c08f10..b6c56c8e 100644 --- a/tests/test_data_type.cpp +++ b/tests/test_data_type.cpp @@ -49,22 +49,40 @@ TEST_CASE( "Recognize Floats Properly", "[dtype_float]" ) { REQUIRE(is_equal(out, 2.71828L)); } -TEST_CASE("Integer Overflow", "[int_overflow]") { +TEST_CASE("Integer Size Recognition", "[int_sizes]") { std::string s; long double out; - s = std::to_string((long long)csv::internals::CSV_INT16_MAX + 1); - REQUIRE(data_type(s, &out) == CSV_INT32); - REQUIRE(out == (long long)CSV_INT16_MAX + 1); + SECTION("Boundary Values") { + s = std::to_string((long long)csv::internals::CSV_INT8_MAX); + REQUIRE(data_type(s, &out) == CSV_INT8); + REQUIRE(out == (long long)CSV_INT8_MAX); - s = std::to_string((long long)csv::internals::CSV_INT32_MAX + 1); - REQUIRE(data_type(s, &out) == CSV_INT64); - REQUIRE(out == (long long)CSV_INT32_MAX + 1); + s = std::to_string((long long)csv::internals::CSV_INT16_MAX); + REQUIRE(data_type(s, &out) == CSV_INT16); + REQUIRE(out == (long long)CSV_INT16_MAX); - // Case: Integer too large to fit in int64 --> store in long double - s = std::to_string((long long)csv::internals::CSV_INT64_MAX); - s.append("1"); - REQUIRE(data_type(s, &out) == CSV_DOUBLE); + s = std::to_string((long long)csv::internals::CSV_INT32_MAX); + REQUIRE(data_type(s, &out) == CSV_INT32); + REQUIRE(out == (long long)CSV_INT32_MAX); + + // Note: data_type() doesn't have enough precision for CSV_INT64 + } + + SECTION("Integer Overflow") { + s = std::to_string((long long)csv::internals::CSV_INT16_MAX + 1); + REQUIRE(data_type(s, &out) == CSV_INT32); + REQUIRE(out == (long long)CSV_INT16_MAX + 1); + + s = std::to_string((long long)csv::internals::CSV_INT32_MAX + 1); + REQUIRE(data_type(s, &out) == CSV_INT64); + REQUIRE(out == (long long)CSV_INT32_MAX + 1); + + // Case: Integer too large to fit in int64 --> store in long double + s = std::to_string((long long)csv::internals::CSV_INT64_MAX); + s.append("1"); + REQUIRE(data_type(s, &out) == CSV_DOUBLE); + } } TEST_CASE( "Recognize Sub-Unit Double Values", "[regression_double]" ) {