Основная проблема заключается в том, чтобы найти min_double_to_int
и max_double_to_int
, самый маленький и самый большой double
, соответственно, которые можно преобразовать в int
.
Сама портативная функция преобразования может быть записана в C11 как
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;
}
Перед первым использованием вышеуказанной функции нам нужно присвоить min_double_to_int
и max_double_to_int
.
РЕДАКТИРОВАНИЕ на 2018-07-03: переписанный подход.
Мы можем использовать простую функцию, чтобы найти наименьшую степень десяти, которая по крайней мере равна INT_MAX
/ INT_MIN
по величине. Если они меньше, чем DBL_MAX_10_EXP
, диапазон double
больше, чем диапазон int
, и мы можем разыграть INT_MAX
и INT_MIN
до double
.
В противном случае мы создаем строку, содержащую десятичное представление INT_MAX
/ INT_MIN
, и используем strtod()
, чтобы преобразовать их в double
. Если эта операция переполнена, это означает, что диапазон double
меньше диапазона int
, и мы можем использовать DBL_MAX
/ -DBL_MAX
в качестве max_double_to_int
и min_double_to_int
соответственно.
Когда у нас есть INT_MAX
как double
, мы можем использовать цикл для увеличения этого значения, используя nextafter(value, HUGE_VAL)
. Наибольшее значение, которое является конечным и округляется в меньшую сторону с использованием floor()
, все еще дает то же значение double
, равное max_double_to_int
.
Точно так же, когда у нас есть INT_MIN
как двойное число, мы можем использовать цикл для уменьшения этого значения, используя nextafter(value, -HUGE_VAL)
. Наибольшее значение по величине, которое все еще является конечным и округляется (ceil()
) до того же double
, составляет min_double_to_int
.
Вот пример программы, иллюстрирующей это:
#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;
}