I need to write function like double_to_int(double val, int *err)
which
would covert double val to integer when it\'s possible; otherwise report an error (NAN/I
Perhaps this might work:
#define BYTES_TO_BITS(x) (x*8)
void numToIntnt(double num, int *output) {
const int upperLimit = ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1))-1;
const int lowerLimit = (-1)*ldexp(1.0, (BYTES_TO_BITS(sizeof(int))-1));
/*
* or a faster approach if the rounding is acceptable:
* const int upperLimit = ~(1<<(BYTES_TO_BITS(sizeof(int))-1));
* const int lowerLimit = (1<<(BYTES_TO_BITS(sizeof(int))-1));
*/
if(num > upperLimit) {
/* report invalid conversion */
} else if (num < lowerLimit) {
/* report invalid conversion */
} else {
*output = (int)num;
}
}
The answer to "Can a conversation from double to int be written in portable C" is clearly "yes".
For example, you could sprintf the floating value to a string, do string-based inspection (i.e. by string-based comparison to max and min values you also sprintf’d), validation, rounding, etc and then sscanf the known-valid string for the final value.
In effect, you’d be moving toward an intermediate representation that’s (a) portable and (b) convenient. C strings are fine at portability, but not so convenient. If you can use external libraries, there are several that are convenient, but whose portability should be confirmed.
For example (which omits rounding):
#include <stdio.h>
#include <math.h>
#include <limits.h>
#include <string.h>
int convert(double inVal) {
// basic range check - does anybody have an integer format with more than 300 bits?
if (fabs(inVal) > 1.0E100) {
printf("well out of range");
return 1;
}
// load string buffer with input
char buf[110];
sprintf(buf, "%0105.0f", inVal);
// do range check on strings
if (inVal < 0) {
char minVal[110];
sprintf(minVal, "%0105d", INT_MIN);
if (strcmp(buf, minVal) > 0) {
printf("too small input: %f\n", inVal);
return -1; // needs better error signify
}
} else {
char maxVal[110];
sprintf(maxVal, "%0105d", INT_MAX);
if (strcmp(maxVal, buf) < 0) {
printf("too large input: %f\n", inVal);
return -1; // needs better error signify
}
}
// do final conversion
int result;
sscanf(buf, "%d", &result);
printf("input: %f result: %d\n", inVal, result); // diagnostic
return result;
}
int main()
{
// test values
convert( 0.);
convert( -123.5);
convert( 123.5);
convert( ((double)INT_MIN)-1);
convert( ((double)INT_MIN));
convert( ((double)INT_MIN)+1);
convert( 2.0*((double)INT_MIN));
convert( ((double)INT_MIN)/2);
convert( ((double)INT_MAX)-1);
convert( ((double)INT_MAX));
convert( ((double)INT_MAX)+1);
convert( 2.0*((double)INT_MAX));
convert( ((double)INT_MAX)/2);
return 0;
}
Which produces the expected conversions (see test cases at end above):
% gcc test.c ; ./a.out
input: 0.000000 result: 0
input: -123.500000 result: -124
input: 123.500000 result: 124
too small input: -2147483649.000000
input: -2147483648.000000 result: -2147483648
input: -2147483647.000000 result: -2147483647
too small input: -4294967296.000000
input: -1073741824.000000 result: -1073741824
input: 2147483646.000000 result: 2147483646
input: 2147483647.000000 result: 2147483647
too large input: 2147483648.000000
too large input: 4294967294.000000
input: 1073741823.500000 result: 1073741824
Can a conversion from
double
toint
be written in portable C (?)is there any way to implement double_to_int function in cross-platform way (basing only on C standard, even not considering target platforms to support IEEE-754).?
int double_to_int(double val, int *err)
Detail: (int)val
truncates the fractional portion, so the range of convertible val
using (int)val
is mathematically:
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999...
or
INT_MIN - 1 < val < INT_MAX + 1
.
Yes a cross-platform way, by using exact floating-point math and constants, code can test for conversion success.
2.0*(INT_MAX/2+1)
is certainly exactly converted to a FP constant.
val - INT_MIN > -1.0
is akin to val > INT_MIN - 1.0
but does not suffer imprecision (with the common 2's complement machines) possible with INT_MIN - 1.0
. Recall that the integer type may have greater precision than double
. Consider a 64-bit int
and INT_MIN - 1.0
not exactly representable as a double
.
Code does not use (double)INT_MAX
which also may be imprecise.
To copy myself:
#include <limits.h>
#define DBL_INT_MAXP1 (2.0*(INT_MAX/2+1))
#define DBL_INT_MINM1 (2.0*(INT_MIN/2-1))
int double_to_int(double val, int *err) {
if (val < DBL_INT_MAXP1) {
#if -INT_MAX == INT_MIN
// rare non-2's complement machine
if (val > DBL_INT_MINM1) {
*err = OK;
return (int) val;
}
#else
if (val - INT_MIN > -1.0) {
*err = OK;
return (int) val;
}
#endif
// Underflow
*err = ERR_MINUS_INF;
return INT_MIN;
}
if (x > 0) {
// Overflow
*err = ERR_PLUS_INF;
return INT_MAX;
}
// NaN;
*err = ERR_NAN;
return 0;
}
Corner weakness: FLT == 10
and the integer type > 34 bits.
Yes. (nan/inf handling omitted for brevity)
int convert(double x) {
if (x == INT_MAX) {
return INT_MAX;
} else if (x > INT_MAX) {
err = ERR_OUT_OF_RANGE;
return INT_MAX;
} else if (x == INT_MIN) {
return INT_MIN;
} else if (x < INT_MIN)
err = ERR_OUT_OF_RANGE;
return INT_MIN;
} else {
return x;
}
}
Explanation.
The edge cases, as explained in one of the linked answers, are when INT_MAX
is not representable as double
exactly, and is rounded up when converted to double
, and a symmetric case one with INT_MIN
. That's the case when if (x > INT_MAX)
fails. That is, the comparison returns false
, but we still cannot convert x
to int
directly.
What the linked answer fails to recognise is that there's only one double number that fails the test, namely (double)INT_MAX
, and we can easily catch this case by checking for x == INT_MAX
explicitly.
Edit As noted in the comments, this may fail if INT_MAX
or INT_MIN
is outside of the range of double
. While extremely unlikely, this is not precluded by the standard. In such an implementation, the conversion is just (int)x
. It should be easier to detect such an implementation at configuration time than at run time. If the latter is absolutely needed, one can perform this operation once:
static int need_simple_conversion = 0;
char* str = malloc(sizeof(int)*CHAR_BIT+1);
sprintf (str, "%d", INT_MAX);
errno = 0;
if (strtod(s, NULL) == HUGE_VAL && errno == ERANGE) {
// INT_MAX overflows double => double can never overflow int
need_simple_conversion = 1;
}
Then
if (need_simple_conversion)
return x;
else { // as above
For the paranoid among us, do this with INT_MIN too and perform the check separately for positive and negative doubles.
[This answer has been edited with a completely new approach.]
This approach uses the definition of floating-point formats in the C standard—as a signed base-b numeral multiplied by a power of b. Knowing the number of digits in the significand (provided by DBL_MANT_DIG
) and the exponent limit (provided by DBL_MAX_EXP
) allows us to prepare exact double
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.
/* This code demonstrates safe conversion of double to int in which the
input double is converted to int if and only if it is in the supported
domain for such conversions (the open interval (INT_MIN-1, INT_MAX+1)).
If the input is not in range, an error is indicated (by way of an
auxiliary argument) and no conversion is performed, so 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:
Conversion of an int that is representable in double produces the
exact value.
The following operations are exact in floating-point:
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.
FLT_RADIX is representable in int.
DBL_MIN_EXP is not greater than -DBL_MANT_DIG. (The code can be
modified to eliminate this requirement.)
Deviations from the requested routine include:
This code names the routine DoubleToInt instead of double_to_int.
The only error indicated is ERANGE. Code to distinguish the error more
finely, such as providing separate values for NaNs, infinities, and
out-of-range finite values, could easily be added.
*/
#include <float.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
/* These values will be initialized to the greatest double value not greater
than INT_MAX+1 and the least double value not less than INT_MIN-1.
*/
static double UpperBound, LowerBound;
/* Return the double of the same sign of x that has the greatest magnitude
less than x+s, where s is -1 or +1 according to whether x is negative or
positive.
*/
static double BiggestDouble(int x)
{
/* All references to "digits" in this routine refer to digits in base
FLT_RADIX. For example, in base 3, 77 would have four digits (2212).
In this routine, "bigger" and "smaller" refer to magnitude. (3 is
greater than -4, but -4 is bigger than 3.)
*/
// Determine the sign.
int s = 0 < x ? +1 : -1;
// Count how many digits x has.
int digits = 0;
for (int t = x; t; ++digits)
t /= FLT_RADIX;
/* If the double type cannot represent finite numbers this big, return the
biggest finite number it can hold, with the desired sign.
*/
if (DBL_MAX_EXP < digits)
return s*DBL_MAX;
// Determine whether x is exactly representable in double.
if (DBL_MANT_DIG < 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 double less than it, which is also the biggest
double less than x.
*/
/* Figure out how many digits we have to remove to leave at most
DBL_MANT_DIG digits.
*/
digits = digits - DBL_MANT_DIG;
// Calculate FLT_RADIX to the power of digits.
int t = 1;
while (digits--) t *= FLT_RADIX;
return x / t * t;
}
else
{
/* x is representable. To return the biggest double smaller than
x+s, we will fill the remaining digits with FLT_RADIX-1.
*/
// Figure out how many additional digits double can hold.
digits = DBL_MANT_DIG - digits;
/* Put a 1 in the lowest available digit, then subtract from 1 to set
each digit to FLT_RADIX-1. (For example, 1 - .001 = .999.)
*/
double t = 1;
while (digits--) t /= FLT_RADIX;
t = 1-t;
// Return the biggest double smaller than x+s.
return x + s*t;
}
}
/* Set up supporting data for DoubleToInt. This should be called once prior
to any call to DoubleToInt.
*/
static void InitializeDoubleToInt(void)
{
UpperBound = BiggestDouble(INT_MAX);
LowerBound = BiggestDouble(INT_MIN);
}
/* Perform the conversion. If the conversion is possible, return the
converted value and set *error to zero. Otherwise, return zero and set
*error to ERANGE.
*/
static int DoubleToInt(double x, int *error)
{
if (LowerBound <= x && x <= UpperBound)
{
*error = 0;
return x;
}
else
{
*error = ERANGE;
return 0;
}
}
#include <string.h>
static void Test(double x)
{
int error, y;
y = DoubleToInt(x, &error);
printf("%.99g -> %d, %s.\n", x, y, error ? strerror(error) : "No error");
}
#include <math.h>
int main(void)
{
InitializeDoubleToInt();
printf("UpperBound = %.99g\n", UpperBound);
printf("LowerBound = %.99g\n", LowerBound);
Test(0);
Test(0x1p31);
Test(nexttoward(0x1p31, 0));
Test(-0x1p31-1);
Test(nexttoward(-0x1p31-1, 0));
}
As far as I can tell, the basic problem distills to: is double->int->double an identity for the values of INT_MAX and INT_MIN. Interestingly, C has a way of expressing this:
int isok(int val) {
double dv = val;
int iv = dv;
return val == iv;
}
From this, a much condensed form of the above answers can work, as you can use this to determine whether INT_MAX,INT_MIN are reasonably comparable, thus:
if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) {
// do your weirdo float stuff here...
}
but, of course, relying upon C’s rigorous type conversion system gives the compiler free license to reformat your disk, so maybe stuff it through printf/scanf instead.