punto online iee flotante float c floating-point standards

online - ¿Se puede escribir una conversión de doble a int en C portátil?



ieee 32 bits online (8)

Necesito escribir una función como double_to_int(double val, int *err) que convertiría double val en entero cuando sea posible; de lo contrario, informe un error (NAN / INFs / OUT_OF_RANGE).

por lo que la implementación de pseudo código se vería así:

if isnan(val): err = ERR_NAN return 0 if val < MAX_INT: err = ERR_MINUS_INF return MIN_INT if ... return (int)val

Hay al menos dos preguntas similares en SO: en this respuesta se resuelve de manera bastante limpia, aunque es una solución de C ++; en C no tenemos dígitos portátiles para int firmado. En this respuesta, se explica por qué no podemos simplemente verificar (val > INT_MAX || val < INT_MIN) .

Por lo tanto, la única forma limpia posible que veo es usar un entorno de punto flotante, pero se establece como una característica definida por la implementación.

Entonces, mi pregunta: ¿hay alguna manera de implementar la función double_to_int de manera multiplataforma (basándose solo en el estándar C, incluso sin considerar las plataformas de destino para admitir IEEE-754)?


¿Se puede escribir una conversión de double a int en portable C (?)

¿Hay alguna forma de implementar la función double_to_int de forma multiplataforma (basándose solo en el estándar C, incluso sin considerar las plataformas de destino compatibles con IEEE-754)?

int double_to_int(double val, int *err)

Detalle: (int)val trunca la parte fraccionaria, por lo que el rango de val convertible que usa (int)val es matemáticamente :
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999... o
INT_MIN - 1 < val < INT_MAX + 1 .

Sí, de forma multiplataforma, al usar matrices y constantes de punto flotante exactas, el código puede probar el éxito de la conversión.

2.0*(INT_MAX/2+1) ciertamente se convierte exactamente a una constante FP.

val - INT_MIN > -1.0 es similar a val > INT_MIN - 1.0 pero no sufre imprecisión (con las máquinas de complemento a 2 comunes) posible con INT_MIN - 1.0 . Recuerde que el tipo entero puede tener mayor precisión que el double . Considere un int 64 bits y INT_MIN - 1.0 no exactamente representable como un double .

El código no usa (double)INT_MAX que también puede ser impreciso.

Para 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; }

Debilidad de esquina: FLT == 10 y el tipo entero> 34 bits.


(Esta respuesta está en disputa, aunque aún creo que estoy en lo correcto, por lo tanto, por favor, no promueva de forma imprudente).

No se puede implementar tal función en C. portátil

En este sentido, es más bien como malloc & c.

La moraleja de la historia es que mezclar tipos nunca es una buena idea en C; es decir, escribir código de tal manera que las conversiones de tipo no sean necesarias.


El problema subyacente es encontrar min_double_to_int y max_double_to_int , el double más pequeño y más grande, respectivamente, que se puede convertir en un int .

La función de conversión portátil se puede escribir en C11 como

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; }

Antes de utilizar la función anterior por primera vez, debemos asignar min_double_to_int y max_double_to_int .

EDITADO el 2018-07-03: enfoque reescrito.

Podemos usar una función simple para encontrar la potencia más pequeña de diez que sea al menos tan grande como INT_MAX / INT_MIN en magnitud. Si esos son más pequeños que DBL_MAX_10_EXP , el rango de double es mayor que el rango de int , y podemos convertir INT_MAX e INT_MIN al double .

De lo contrario, construimos una cadena que contiene la representación decimal de INT_MAX / INT_MIN , y usamos strtod() para convertirlos al double . Si esta operación se desborda, significa que el rango de double es más pequeño que el rango de int , y podemos usar DBL_MAX / -DBL_MAX como max_double_to_int y min_double_to_int , respectivamente.

Cuando tenemos INT_MAX como double , podemos usar un bucle para incrementar ese valor usando nextafter(value, HUGE_VAL) . El valor más grande que es finito, y redondeado hacia abajo usando floor() aún produce el mismo valor double , es max_double_to_int .

De manera similar, cuando tenemos INT_MIN como doble, podemos usar un bucle para disminuir ese valor usando nextafter(value, -HUGE_VAL) . El mayor valor en magnitud que aún es finito y se redondea ( ceil() ) al mismo double , es min_double_to_int .

Aquí hay un programa de ejemplo para ilustrar esto:

#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; }


La respuesta a " ¿Se puede escribir una conversación de doble a int en C portátil" es claramente " ".

Por ejemplo, podría aplicar el valor flotante a una cadena, realizar una inspección basada en la cadena (es decir, mediante una comparación basada en la cadena a los valores máximos y mínimos que usted también incluye), validación, redondeo, etc. y luego sscanf la cadena válida conocida para el valor final.

En efecto, se movería hacia una representación intermedia que es (a) portátil y (b) conveniente. Las cuerdas C son buenas para la portabilidad, pero no tan convenientes. Si puede usar bibliotecas externas, hay varias que son convenientes, pero cuya portabilidad debe ser confirmada.

Por ejemplo (que omite el redondeo):

#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; }

Que produce las conversiones esperadas (ver casos de prueba al final de arriba):

% 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


Por lo que puedo decir, el problema básico se centra en: es double-> int-> double una identidad para los valores de INT_MAX e INT_MIN. Curiosamente, C tiene una forma de expresar esto:

int isok(int val) { double dv = val; int iv = dv; return val == iv; }

A partir de esto, una forma muy condensada de las respuestas anteriores puede funcionar, ya que puede usar esto para determinar si INT_MAX, INT_MIN son razonablemente comparables, por lo tanto:

if (isok(INT_MAX) && isok(INT_MIN) && f >= INT_MIN && f < INT_MAX) { // do your weirdo float stuff here... }

pero, por supuesto, confiar en el riguroso sistema de conversión de tipos de C le da al compilador una licencia libre para reformatear su disco, por lo que tal vez rellene con printf / scanf en su lugar.


Quizás esto podría funcionar:

#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; } }


Sí. (El manejo de nan / inf se omite por brevedad)

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; } }

Explicación.

Los casos de borde, como se explica en una de las respuestas vinculadas, son cuando INT_MAX no se puede representar exactamente como double , y se redondea cuando se convierte al double , y uno simétrico con INT_MIN . Ese es el caso cuando if (x > INT_MAX) falla. Es decir, la comparación devuelve false , pero aún no podemos convertir x a int directamente.

Lo que la respuesta vinculada no reconoce es que solo hay un número doble que falla la prueba, es decir, (double)INT_MAX , y podemos detectar fácilmente este caso al verificar explícitamente x == INT_MAX .

Editar Como se señaló en los comentarios, esto puede fallar si INT_MAX o INT_MIN están fuera del rango de double . Si bien es extremadamente improbable, esto no está excluido por la norma. En tal implementación, la conversión es solo (int)x . Debería ser más fácil detectar dicha implementación en el momento de la configuración que en el tiempo de ejecución. Si esto último es absolutamente necesario, uno puede realizar esta operación una vez :

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; }

Entonces

if (need_simple_conversion) return x; else { // as above

Para los paranoicos entre nosotros, haga esto también con INT_MIN y realice la comprobación por separado para los dobles positivos y negativos.


[Esta respuesta ha sido editada con un enfoque completamente nuevo.]

Este enfoque utiliza la definición de formatos de punto flotante en el estándar C, como un número base b firmado por un poder de b . Conocer el número de dígitos en el significado (proporcionado por DBL_MANT_DIG ) y el límite de exponente (proporcionado por DBL_MAX_EXP ) nos permite preparar valores double exactos como puntos finales.

Creo que funcionará en todas las implementaciones de C conforme a los modestos requisitos adicionales establecidos en el comentario inicial.

/* 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)); }