如何正确比较整数和浮点值? [英] How to properly compare an integer and a floating-point value?
问题描述
如何比较整数和浮点值正确的方法™?
How do I compare an integer and a floating-point value the right way™?
在某些极端情况下,内置的比较运算符会给出错误的结果,例如:
The builtin comparsion operators give incorrect results in some edge cases, for example:
#include <iomanip>
#include <iostream>
int main()
{
long long a = 999999984306749439;
float b = 999999984306749440.f; // This number can be represented exactly by a `float`.
std::cout << std::setprecision(1000);
std::cout << a << " < " << b << " = " << (a < b) << '\n';
// Prints `999999984306749439 < 999999984306749440 = 0`, but it should be `1`.
}
显然,比较运算符在实际比较它们之前将两个操作数都转换为相同类型.在这里,lhs被转换为float
,这会导致精度损失并导致错误的结果.
Apparently, the comparsion operators convert both operands to a same type before actually comparing them. Here lhs gets converted to float
, which causes a loss of precision, and leads to an incorrect result.
即使我了解发生了什么,也不确定如何解决此问题.
Even though I understand what's going on, I'm not sure how to work around this issue.
免责声明:该示例使用了float
和long long
,但是我正在寻找一种通用解决方案,该解决方案适用于整数类型和浮点类型的每种组合.
Disclaimer: The example uses a float
and a long long
, but I'm looking for a generic solution that works for every combination of an integral type and a floating-point type.
推荐答案
这就是我最终得到的结果.
Here's what I ended up with.
该算法的积分转到@chux;他的方法似乎胜过其他建议.您可以在编辑历史记录中找到一些替代实现.
Credit for the algorithm goes to @chux; his approach appears to outperform the other suggestions. You can find some alternative implementations in the edit history.
如果您认为有任何改进,欢迎提出建议.
If you can think of any improvements, suggestions are welcome.
#include <cmath>
#include <limits>
#include <type_traits>
enum partial_ordering {less, equal, greater, unordered};
template <typename I, typename F>
partial_ordering compare_int_float(I i, F f)
{
if constexpr (std::is_integral_v<F> && std::is_floating_point_v<I>)
{
return compare_int_float(f, i);
}
else
{
static_assert(std::is_integral_v<I> && std::is_floating_point_v<F>);
static_assert(std::numeric_limits<F>::radix == 2);
// This should be exactly representable as F due to being a power of two.
constexpr F I_min_as_F = std::numeric_limits<I>::min();
// The `numeric_limits<I>::max()` itself might not be representable as F, so we use this instead.
constexpr F I_max_as_F_plus_1 = F(std::numeric_limits<I>::max()/2+1) * 2;
// Check if the constants above overflowed to infinity. Normally this shouldn't happen.
constexpr bool limits_overflow = I_min_as_F * 2 == I_min_as_F || I_max_as_F_plus_1 * 2 == I_max_as_F_plus_1;
if constexpr (limits_overflow)
{
// Manually check for special floating-point values.
if (std::isinf(f))
return f > 0 ? less : greater;
if (std::isnan(f))
return unordered;
}
if (limits_overflow || f >= I_min_as_F)
{
// `f <= I_max_as_F_plus_1 - 1` would be problematic due to rounding, so we use this instead.
if (limits_overflow || f - I_max_as_F_plus_1 <= -1)
{
I f_trunc = f;
if (f_trunc < i)
return greater;
if (f_trunc > i)
return less;
F f_frac = f - f_trunc;
if (f_frac < 0)
return greater;
if (f_frac > 0)
return less;
return equal;
}
return less;
}
if (f < 0)
return greater;
return unordered;
}
}
如果要尝试使用它,这里有一些测试用例:
If you want to experiment with it, here are a few test cases:
#include <cmath>
#include <iomanip>
#include <iostream>
void compare_print(long long a, float b, int n = 0)
{
if (n == 0)
{
auto result = compare_int_float(a,b);
std::cout << a << ' ' << "<=>?"[int(result)] << ' ' << b << '\n';
}
else
{
for (int i = 0; i < n; i++)
b = std::nextafter(b, -INFINITY);
for (int i = 0; i <= n*2; i++)
{
compare_print(a, b);
b = std::nextafter(b, INFINITY);
}
std::cout << '\n';
}
}
int main()
{
std::cout << std::setprecision(1000);
compare_print(999999984306749440,
999999984306749440.f, 2);
compare_print(999999984306749439,
999999984306749440.f, 2);
compare_print(100,
100.f, 2);
compare_print(-100,
-100.f, 2);
compare_print(0,
0.f, 2);
compare_print((long long)0x8000'0000'0000'0000,
(long long)0x8000'0000'0000'0000, 2);
compare_print(42, INFINITY);
compare_print(42, -INFINITY);
compare_print(42, NAN);
std::cout << '\n';
compare_print(1388608,
1388608.f, 2);
compare_print(12388608,
12388608.f, 2);
}
这篇关于如何正确比较整数和浮点值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!