diff --git a/src/types.cpp b/src/types.cpp index 9a51d89b99..a24ca8a369 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -670,27 +671,28 @@ namespace Exiv2 { Rational floatToRationalCast(float f) { -#if defined(_MSC_VER) && _MSC_VER < 1800 - if (!_finite(f)) { -#else - if (!std::isfinite(f)) { -#endif - return {f > 0 ? 1 : -1, 0}; + + // Convert f to double because it simplifies the "in_range" check + // below. (INT_MAX can be represented accurately as a double, but + // gets rounded when it's converted to float.) + const double d = f; + const bool in_range = INT_MIN <= d && d <= INT_MAX; + if (!in_range) { + return {d > 0 ? 1 : -1, 0}; } // Beware: primitive conversion algorithm int32_t den = 1000000; - const long f_as_long = static_cast(f); - if (Safe::abs(f_as_long) > 2147) { + const long d_as_long = static_cast(d); + if (Safe::abs(d_as_long) > 2147) { den = 10000; } - if (Safe::abs(f_as_long) > 214748) { + if (Safe::abs(d_as_long) > 214748) { den = 100; } - if (Safe::abs(f_as_long) > 21474836) { + if (Safe::abs(d_as_long) > 21474836) { den = 1; } - const float rnd = f >= 0 ? 0.5F : -0.5F; - const auto nom = static_cast(f * den + rnd); + const auto nom = static_cast(std::round(d * den)); const int32_t g = gcd(nom, den); return {nom / g, den / g}; diff --git a/test/data/issue_1838_poc.crw b/test/data/issue_1838_poc.crw new file mode 100644 index 0000000000..efd88f3b9a Binary files /dev/null and b/test/data/issue_1838_poc.crw differ diff --git a/tests/bugfixes/github/test_issue_1838.py b/tests/bugfixes/github/test_issue_1838.py new file mode 100644 index 0000000000..aa02f1dd0b --- /dev/null +++ b/tests/bugfixes/github/test_issue_1838.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from system_tests import CaseMeta, path, check_no_ASAN_UBSAN_errors + +class FloatToRationalCastOutOfRange(metaclass=CaseMeta): + """ + Test for the bug described in: + https://github.com/Exiv2/exiv2/issues/1838 + """ + url = "https://github.com/Exiv2/exiv2/issues/1838" + + filename = path("$data_path/issue_1838_poc.crw") + commands = ["$exiv2 $filename"] + stderr = [""] + retval = [0] + + compare_stdout = check_no_ASAN_UBSAN_errors