Может ли преобразование из double в int быть записано в переносном C
Мне нужно написать такую функцию, как double_to_int(double val, int *err)
которая могла бы скрывать двойной val до целого числа, когда это возможно; в противном случае сообщите об ошибке (NAN/INFs/OUT_OF_RANGE).
поэтому реализация псевдокода будет выглядеть так:
if isnan(val):
err = ERR_NAN
return 0
if val < MAX_INT:
err = ERR_MINUS_INF
return MIN_INT
if ...
return (int)val
На SO есть как минимум два похожих вопроса: в этом ответе он решался достаточно чистым способом, хотя это решение C++ - в C у нас нет переносных цифр для подписанного int. В этом ответе он объяснил, почему мы не можем просто проверить (val > INT_MAX || val < INT_MIN)
.
Поэтому единственный возможный чистый способ, который я вижу, - использовать среду с плавающей запятой, но она указана как функция, определяемая реализацией.
Поэтому мой вопрос: есть ли способ реализовать функцию double_to_int
кросс-платформенным способом (основываясь только на стандарте C, даже не рассматривая целевые платформы для поддержки IEEE-754).
Ответы
Ответ 1
[Этот ответ был отредактирован с совершенно новым подходом.]
Этот подход использует определение форматов с плавающей запятой в стандарте C - как подписанную базовую цифру b, умноженную на силу b. Знание числа цифр в значении (предоставлено DBL_MANT_DIG
) и пределе экспоненты (предоставлено DBL_MAX_EXP
) позволяет нам подготовить точные double
значения в качестве конечных точек.
Я считаю, что он будет работать во всех соответствующих реализациях C с учетом скромных дополнительных требований, изложенных в первоначальном комментарии.
/* 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));
}
Ответ 2
Ответ на "Может ли разговор с двойного на int быть написанным на портативном C" явно "да".
Например, вы можете спрятать плавающее значение в строке, провести проверку на основе строк (т.е. Путем сопоставления по строкам с максимальными и минимальными значениями, которые вы также sprintfd), проверки, округления и т.д., А затем sscanf для известной допустимой строки для конечное значение.
По сути, вы должны двигаться к промежуточному представлению, которое (а) переносится и (б) удобно. Строки C отлично переносимы, но не очень удобны. Если вы можете использовать внешние библиотеки, есть несколько удобных, но мобильность которых должна быть подтверждена.
Например (опускает округление):
#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;
}
Что дает ожидаемые преобразования (см. Тестовые примеры в конце выше):
% 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
Ответ 3
(Этот ответ спорит, хотя я все еще думаю, что я прав, поэтому, пожалуйста, не выдвигайте неразумно.)
Вы не можете реализовать такую функцию в переносном C.
В этом отношении он скорее напоминает malloc
& c.
Мораль этой истории на самом деле заключается в том, что типы смешивания никогда не являются хорошей идеей в C; т.е. писать код таким образом, чтобы преобразования типов не требовались.
Ответ 4
Может ли преобразование из double
в int
быть записано в переносном C (?)
есть ли способ реализовать функцию double_to_int кросс-платформенным способом (основываясь только на стандарте C, даже не рассматривая целевые платформы для поддержки IEEE-754).
int double_to_int(double val, int *err)
Деталь: (int)val
обрезает дробную часть, поэтому диапазон конвертируемого val
с использованием (int)val
математически:
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999...
или
INT_MIN - 1 < val < INT_MAX + 1
.
Да, кросс-платформенный способ, используя точную математику и константу с плавающей запятой, код может проверить успех конверсии.
2.0*(INT_MAX/2+1)
, безусловно, точно преобразуется в константу FP.
val - INT_MIN > -1.0
сродни val > INT_MIN - 1.0
но не претерпевает неточности (с обычными машинами с INT_MIN - 1.0
дополнениями) с INT_MIN - 1.0
. Напомним, что целочисленный тип может иметь большую точность, чем double
. Рассмотрим 64-битные int
и INT_MIN - 1.0
не представляются как double
.
Код не использует (double)INT_MAX
который также может быть неточным.
Чтобы скопировать себя:
#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 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;
}
Угловая слабость: FLT == 10
и целочисленный тип> 34 бит.
Ответ 5
Возможно, это может сработать:
#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;
}
}
Ответ 6
Основная проблема заключается в том, чтобы найти 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
.
EDITED по 2018-07-03: Пересмотренный подход.
Мы можем использовать простую функцию, чтобы найти наименьшую мощность в десять, которая по меньшей мере INT_MAX
как 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
как double, мы можем использовать цикл для уменьшения этого значения с помощью 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;
}
Ответ 7
Да. (обработка наном/крыльями опущена для краткости)
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;
}
}
Объяснение.
INT_MAX
случаи, как объясняется в одном из связанных ответов, - это когда INT_MAX
не представляется как double
точно и округляется при преобразовании в double
, а симметричный случай - с INT_MIN
. Тот случай, когда if (x > INT_MAX)
терпит неудачу. То есть сравнение возвращает false
, но мы по-прежнему не можем напрямую преобразовать x
в int
.
То, что связанный ответ не распознается, состоит в том, что существует только один двойной номер, который не (double)INT_MAX
тест, а именно (double)INT_MAX
, и мы можем легко поймать этот случай, явно проверив x == INT_MAX
.
Изменить Как отмечено в комментариях, это может завершиться неудачно, если INT_MAX
или INT_MIN
находятся за пределами диапазона double
. Хотя это маловероятно, это не исключает стандарт. В такой реализации преобразование является просто (int)x
. Должна быть проще обнаружить такую реализацию во время конфигурации, чем во время выполнения. Если последнее абсолютно необходимо, можно выполнить эту операцию один раз:
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;
}
затем
if (need_simple_conversion)
return x;
else { // as above
Для параноика между нами сделайте это с помощью INT_MIN и выполните проверку отдельно для положительных и отрицательных удвоений.
Ответ 8
Насколько я могу судить, основная проблема заключается в следующем: double-> int-> double identity для значений INT_MAX и INT_MIN. Интересно, что C имеет способ выразить это:
int isok(int val) {
double dv = val;
int iv = dv;
return val == iv;
}
Из этого можно работать очень сжатую форму вышеприведенных ответов, так как вы можете использовать это, чтобы определить, насколько INT_MAX, INT_MIN достаточно сопоставимы, таким образом:
if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) {
// do your weirdo float stuff here...
}
но, конечно же, полагаясь на строгую систему преобразования Cs, вы получаете бесплатную лицензию компилятора для переформатирования вашего диска, поэтому, возможно, вместо этого вы можете использовать printf/scanf.