问题
Is there a safe way to reliably determine if an integral type T
can store a floating-point integer value f
(so f == floor(f)
) without any overflow?
Keep in mind that there is no guarantee that the floating point type F
is IEC 559 (IEEE 754) compatible, and that signed integer overflow is undefined behavior in C++. I'm interested in a solution which is correct according to the current C++ (C++17 at the writing) standard and avoids undefined behavior.
The following naive approach is not reliable, since there is no guarantee that type F
can represent std::numeric_limits<I>::max()
due to floating-point rounding.
#include <cmath>
#include <limits>
#include <type_traits>
template <typename I, typename F>
bool is_safe_conversion(F x)
{
static_assert(std::is_floating_point_v<F>);
static_assert(std::is_integral_v<I>);
// 'fmax' may have a different value than expected
static constexpr F fmax = static_cast<F>(std::numeric_limits<I>::max());
return std::abs(x) <= fmax; // this test may gives incorrect results
}
Any idea?
回答1:
Is there a safe way to reliably determine if an integral type T can store a floating-point integer value f?
Yes. The key is to test if f
is in the range T::MIN - 0.999...
to T::MAX + 0.999...
using floating point math - with no rounding issues. Bonus: rounding mode does not apply.
There are 3 failure paths: too big, too small, not-a-number.
The below assumes
int/double
. I'll leave the C++ template forming for OP.
Forming exact T::MAX + 1
exactly using floating point math is easy as INT_MAX
is a Mersenne Number. (We are not talking about Mersenne Prime here.)
Code takes advantage of:
A Mersenne Number divided by 2 with integer math is also a Mersenne Number.
The conversion of a integer type power-of-2 constant to a floating point type can be certain to be exact.
#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1))
// Below needed when -INT_MAX == INT_MIN
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1))
Forming exact T::MIN - 1
is hard as its absolute value is usually a power-of-2 + 1 and the relative precision of the integer type and the FP type are not certain. Instead code can subtract the exact power of 2 and compare to -1.
int double_to_int(double x) {
if (x < DBL_INT_MAXP1) {
#if -INT_MAX == INT_MIN
// rare non-2's complement machine
if (x > DBL_INT_MINM1) {
return (int) x;
}
#else
if (x - INT_MIN > -1.0) {
return (int) x;
}
#endif
Handle_Underflow();
} else if (x > 0) {
Handle_Overflow();
} else {
Handle_NaN();
}
}
Regarding floating-point types with non-binary radix (FLT_RADIX != 2
)
With FLT_RADIX = 4, 8, 16 ...
, the conversion would be exact too. With FLT_RADIX == 10
, code is at least exact up to a 34-bit int
as a double
must encode +/-10^10 exactly. So a problem with say a FLT_RADIX == 10
, 64-bit int
machine - a low risk. Based on memory, the last FLT_RADIX == 10
in production was over a decade ago.
The integer type is always encoded as 2's complement (most common), 1s' complement, or sign magnitude. INT_MAX
is always a power-2-minus-1. INT_MIN
is always a - power-2 or 1 more. Effectively, always base 2.
回答2:
Any idea?
template <typename I, typename F>
constexpr F maxConvertible()
{
I i = std::numeric_limits<I>::max();
F f = F(i);
while(F(i) == f)
{
--i;
}
return F(i);
}
Due to rounding, we might have got a too large maximum, now downcounting until we get the next representable double being smaller, which should fit into the integral...
Problem left open: This works fine, if conversion to double involves up-rounding; however, even IEEE 754 allows different rounding modes (if rounding to nearest is applied, which should be the most common rounding mode across current hardware, up-rounding will always occur...).
I have not spotted a solution to safely detect down-rounding yet (might add later; at least detecting "rounding to nearest" has already a solution here), if this occurs, we get some negative falses near the maxima and minima of the integral values, you might consider this "acceptable" for those few exotic architectures actually doing down-rounding.
Independent from up- or down-rounding, there is a special case for signed integrals anyway: Provided the integral number is represented in two's complement and has more bits than the mantissa of the floating point value, then the types minimum value will be representable as floating point value whereas some greater values will not. Catching this case requires special treatment.
回答3:
This approach uses the definition of floating-point formats in the C (not C++, see first comment) standard. Knowing the number of digits in the significand (provided by numeric_limits::digits
) and the exponent limit (provided by numeric_limits::max_exponent
) allows us to prepare exact values as end points.
I believe it will work in all conforming C++ implementations subject to the modest additional requirements stated in the initial comment. It supports floating-point formats with or without infinities, with ranges wider or narrower than the destination integer format, and with any rounding rules (because it uses only floating-point arithmetic with exactly representable results, so rounding should never be needed).
/* This code demonstrates safe conversion of floating-point to integer in
which the input floating-point value is converted to integer if and only if
it is in the supported domain for such conversions (the open interval
(Min-1, Max+1), where Min and Max are the mininum and maximum values
representable in the integer type). If the input is not in range, an error
throw and no conversion is performed. This throw can be replaced by any
desired error-indication mechanism so that all behavior is defined.
There are a few requirements not fully covered by the C++ standard. They
should be uncontroversial and supported by all reasonable C++
implementations:
The floating-point format is as described in C 2011 5.2.4.2.2 (modeled
by the product of a sign, a number of digits in some base b, and base b
raised to an exponent). I do not see this explicitly specified in the
C++ standard, but it is implied by the characteristics specified in
std::numeric_limits. (For example, C++ requires numeric_limits to
provide the number of base-b digits in the floating-point
representation, where b is the radix used, which means the
representation must have base-b digits.)
The following operations are exact in floating-point. (All of them
are elementary operations and have mathematical results that are
exactly representable, so there is no need for rounding, and hence
exact results are expected in any sane implementation.)
Dividing by the radix of the floating-point format, within its
range.
Multiplying by +1 or -1.
Adding or subtracting two values whose sum or difference is
representable.
std::numeric_limits<FPType>::min_exponent is not greater than
-std::numeric_limits<FPType>::digits. (The code can be modified to
eliminate this requirement.)
*/
#include <iostream> // Not needed except for demonstration.
#include <limits>
/* Define a class to support safe floating-point to integer conversions.
This sample code throws an exception when a source floating-point value is
not in the domain for which a correct integer result can be produced, but
the throw can be replaced with any desired code, such as returning an error
indication in an auxiliary object. (For example, one could return a pair
consisting of a success/error status and the destination value, if
successful.)
FPType is the source floating-point type.
IType is the destination integer type.
*/
template<typename FPType, typename IType> class FPToInteger
{
private:
/* Wrap the bounds we need in a static object so it can be easily
initialized just once for the entire program.
*/
static class StaticData
{
private:
/* This function helps us find the FPType values just inside the
interval (Min-1, Max+1), where Min and Max are the mininum and
maximum values representable in the integer type).
It returns the FPType of the same sign of x+s that has the greatest
magnitude less than x+s, where s is -1 or +1 according to whether x
is non-positive or positive.
*/
static FPType BiggestFPType(IType x)
{
/* All references to "digits" in this routine refer to digits in
base std::numeric_limits<FPType>::radix. For example, in base
3, 77 would have four digits (2212). Zero is considered to
have zero digits.
In this routine, "bigger" and "smaller" refer to magnitude. (3
is greater than -4, but -4 is bigger than 3.) */
// Abbreviate std::numeric_limits<FPType>::radix.
const int Radix = std::numeric_limits<FPType>::radix;
// Determine the sign.
int s = 0 < x ? +1 : -1;
// Count how many digits x has.
IType digits = 0;
for (IType t = x; t; ++digits)
t /= Radix;
/* If the FPType type cannot represent finite numbers this big,
return the biggest finite number it can hold, with the desired
sign.
*/
if (std::numeric_limits<FPType>::max_exponent < digits)
return s * std::numeric_limits<FPType>::max();
// Determine whether x is exactly representable in FPType.
if (std::numeric_limits<FPType>::digits < digits)
{
/* x is not representable, so we will return the next lower
representable value by removing just as many low digits as
necessary. Note that x+s might be representable, but we
want to return the biggest FPType less than it, which, in
this case, is also the biggest FPType less than x.
*/
/* Figure out how many digits we have to remove to leave at
most std::numeric_limits<FPType>::digits digits.
*/
digits = digits - std::numeric_limits<FPType>::digits;
// Calculate Radix to the power of digits.
IType t = 1;
while (digits--) t *= Radix;
return x / t * t;
}
else
{
/* x is representable. To return the biggest FPType smaller
than x+s, we will fill the remaining digits with Radix-1.
*/
// Figure out how many additional digits FPType can hold.
digits = std::numeric_limits<FPType>::digits - digits;
/* Put a 1 in the lowest available digit, then subtract from 1
to set each digit to Radix-1. (For example, 1 - .001 =
.999.)
*/
FPType t = 1;
while (digits--) t /= Radix;
t = 1-t;
// Return the biggest FPType smaller than x+s.
return x + s*t;
}
}
public:
/* These values will be initialized to the greatest FPType value less
than std::numeric_limits<IType>::max()+1 and the least FPType value
greater than std::numeric_limits<IType>::min()-1.
*/
const FPType UpperBound, LowerBound;
// Constructor to initialize supporting data for FPTypeToInteger.
StaticData()
: UpperBound(BiggestFPType(std::numeric_limits<IType>::max())),
LowerBound(BiggestFPType(std::numeric_limits<IType>::min()))
{
// Show values, just for illustration.
std::cout.precision(99);
std::cout << "UpperBound = " << UpperBound << ".\n";
std::cout << "LowerBound = " << LowerBound << ".\n";
}
} Data;
public:
FPType value;
// Constructor. Just remember the source value.
FPToInteger(FPType x) : value(x) {}
/* Perform the conversion. If the conversion is defined, return the
converted value. Otherwise, throw an exception.
*/
operator IType()
{
if (Data.LowerBound <= value && value <= Data.UpperBound)
return value;
else
throw "Error, source floating-point value is out of range.";
}
};
template<typename FPType, typename IType>
typename FPToInteger<FPType, IType>::StaticData
FPToInteger<FPType, IType>::Data;
typedef double FPType;
typedef int IType;
// Show what the class does with a requested value.
static void Test(FPType x)
{
try
{
IType y = FPToInteger<FPType, IType>(x);
std::cout << x << " -> " << y << ".\n";
}
catch (...)
{
std::cout << x << " is not in the domain.\n";
}
}
#include <cmath>
int main(void)
{
std::cout.precision(99);
// Simple demonstration (not robust testing).
Test(0);
Test(0x1p31);
Test(std::nexttoward(0x1p31, 0));
Test(-0x1p31-1);
Test(std::nexttoward(-0x1p31-1, 0));
}
回答4:
Can you not just do
static_cast<F>(static_cast<I>(x)) == floor(x)
?
来源:https://stackoverflow.com/questions/51304323/reliable-overflow-detection-of-floating-point-integer-type-conversion