可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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/INFs/OUT_OF_RANGE).
so pseudo code implementation would look like:
if isnan(val):
err = ERR_NAN
return 0
if val < MAX_INT:
err = ERR_MINUS_INF
return MIN_INT
if ...
return (int)val
There are at least two similar questions on SO:
in this answer it's solved in enough clean way, though it's C++ solution - in C we do not have portable digits for signed int.
In this answer, it's explained why we cannot just check (val > INT_MAX || val < INT_MIN)
.
So the only possible clean way i see is to use floating point environment, but it's stated as implementation-defined feature.
So my question: 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).?
回答1:
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
回答2:
[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));
}
回答3:
(This answer is in dispute, although I still think I'm correct, please therefore don't upvote unwisely.)
You cannot implement such a function in portable C.
In this respect, it's rather like malloc
&c.
The moral of the story really is that mixing types is never a good idea in C; i.e. write code in such a way that type conversions are not necessary.
回答4:
Can a conversion from double
to int
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.
回答5:
The underlying problem is to find min_double_to_int
and max_double_to_int
, the smallest and largest double
, respectively, that can be converted to an int
.
The portable conversion function itself can be written in C11 as
int double_to_int(const double value, int *err)
{
if (!isfinite(value)) {
if (isnan(value)) {
if (err) *err = ERR_NAN;
return 0;
} else
if (signbit(value)) {
if (err) *err = ERR_NEG_INF;
return INT_MIN;
} else {
if (err) *err = ERR_POS_INF;
return INT_MAX;
}
}
if (value < min_double_to_int) {
if (err) *err = ERR_TOOSMALL;
return INT_MIN;
} else
if (value > max_double_to_int) {
if (err) *err = ERR_TOOLARGE;
return INT_MAX;
}
if (err) *err = 0;
return (int)value;
}
Before the above function is first used, we need to assign min_double_to_int
and max_double_to_int
.
EDITED on 2018-07-03: Rewritten approach.
We can use a simple function to find the smallest power of ten that is at least as large as INT_MAX
/INT_MIN
in magnitude. If those are smaller than DBL_MAX_10_EXP
, the range of double
is greater than the range of int
, and we can cast INT_MAX
and INT_MIN
to double
.
Otherwise, we construct a string containing the decimal representation of INT_MAX
/INT_MIN
, and use strtod()
to convert them to double
. If this operation overflows, it means the range of double
is smaller than the range of int
, and we can use DBL_MAX
/-DBL_MAX
as max_double_to_int
and min_double_to_int
, respectively.
When we have INT_MAX
as a double
, we can use a loop to increment that value using nextafter(value, HUGE_VAL)
. The largest value that is finite, and rounded down using floor()
still yields the same double
value, is max_double_to_int
.
Similarly, when we have INT_MIN
as a double, we can use a loop to decrement that value using nextafter(value, -HUGE_VAL)
. The largest value in magnitude that is still finite, and rounds up (ceil()
) to the same double
, is min_double_to_int
.
Here is an example program to illustrate this:
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <float.h>
#include <stdio.h>
#include <errno.h>
#include <math.h>
static double max_double_to_int = -1.0;
static double min_double_to_int = +1.0;
#define ERR_OK 0
#define ERR_NEG_INF -1
#define ERR_POS_INF -2
#define ERR_NAN -3
#define ERR_NEG_OVER 1
#define ERR_POS_OVER 2
int double_to_int(const double value, int *err)
{
if (!isfinite(value)) {
if (isnan(value)) {
if (err) *err = ERR_NAN;
return 0;
} else
if (signbit(value)) {
if (err) *err = ERR_NEG_INF;
return INT_MIN;
} else {
if (err) *err = ERR_POS_INF;
return INT_MAX;
}
}
if (value < min_double_to_int) {
if (err) *err = ERR_NEG_OVER;
return INT_MIN;
} else
if (value > max_double_to_int) {
if (err) *err = ERR_POS_OVER;
return INT_MAX;
}
if (err) *err = ERR_OK;
return (int)value;
}
static inline double find_double_max(const double target)
{
double next = target;
double curr;
do {
curr = next;
next = nextafter(next, HUGE_VAL);
} while (isfinite(next) && floor(next) == target);
return curr;
}
static inline double find_double_min(const double target)
{
double next = target;
double curr;
do {
curr = next;
next = nextafter(next, -HUGE_VAL);
} while (isfinite(next) && ceil(next) == target);
return curr;
}
static inline int ceil_log10_abs(int value)
{
int result = 1;
while (value < -9 || value > 9) {
result++;
value /= 10;
}
return result;
}
static char *int_string(const int value)
{
char *buf;
size_t max = ceil_log10_abs(value) + 4;
int len;
while (1) {
buf = malloc(max);
if (!buf)
return NULL;
len = snprintf(buf, max, "%d", value);
if (len < 1) {
free(buf);
return NULL;
}
if ((size_t)len < max)
return buf;
free(buf);
max = (size_t)len + 2;
}
}
static int int_to_double(double *to, const int ivalue)
{
char *ival, *iend;
double dval;
ival = int_string(ivalue);
if (!ival)
return -1;
iend = ival;
errno = 0;
dval = strtod(ival, &iend);
if (errno == ERANGE) {
if (*iend != '\0' || dval != 0.0) {
/* Overflow */
free(ival);
return +1;
}
} else
if (errno != 0) {
/* Unknown error, not overflow */
free(ival);
return -1;
} else
if (*iend != '\0') {
/* Overflow */
free(ival);
return +1;
}
free(ival);
/* Paranoid overflow check. */
if (!isfinite(dval))
return +1;
if (to)
*to = dval;
return 0;
}
int init_double_to_int(void)
{
double target;
if (DBL_MAX_10_EXP > ceil_log10_abs(INT_MAX))
target = INT_MAX;
else {
switch (int_to_double(&target, INT_MAX)) {
case 0: break;
case 1: target = DBL_MAX; break;
default: return -1;
}
}
max_double_to_int = find_double_max(target);
if (DBL_MAX_10_EXP > ceil_log10_abs(INT_MIN))
target = INT_MIN;
else {
switch (int_to_double(&target, INT_MIN)) {
case 0: break;
case 1: target = -DBL_MAX; break;
default: return -1;
}
}
min_double_to_int = find_double_min(target);
return 0;
}
int main(void)
{
int i, val, err;
double temp;
if (init_double_to_int()) {
fprintf(stderr, "init_double_to_int() failed.\n");
return EXIT_FAILURE;
}
printf("(int)max_double_to_int = %d\n", (int)max_double_to_int);
printf("(int)min_double_to_int = %d\n", (int)min_double_to_int);
printf("max_double_to_int = %.16f = %a\n", max_double_to_int, max_double_to_int);
printf("min_double_to_int = %.16f = %a\n", min_double_to_int, min_double_to_int);
temp = nextafter(max_double_to_int, 0.0);
for (i = -1; i <= 1; i++) {
val = double_to_int(temp, &err);
printf("(int)(max_double_to_int %+d ULP)", i);
switch (err) {
case ERR_OK: printf(" -> %d\n", val); break;
case ERR_POS_OVER: printf(" -> overflow\n"); break;
case ERR_POS_INF: printf(" -> infinity\n"); break;
default: printf(" -> BUG\n");
}
temp = nextafter(temp, HUGE_VAL);
}
temp = nextafter(min_double_to_int, 0.0);
for (i = 1; i >= -1; i--) {
val = double_to_int(temp, &err);
printf("(int)(min_double_to_int %+d ULP)", i);
switch (err) {
case ERR_OK: printf(" -> %d\n", val); break;
case ERR_NEG_OVER: printf(" -> overflow\n"); break;
case ERR_NEG_INF: printf(" -> infinity\n"); break;
default: printf(" -> BUG\n");
}
temp = nextafter(temp, -HUGE_VAL);
}
return EXIT_SUCCESS;
}
回答6:
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;
}
}
回答7:
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.
回答8:
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.