Une conversion de double en int peut-elle être écrite en C portable?

J’ai besoin d’écrire une fonction comme double_to_int(double val, int *err) qui convertirait double val en entier lorsque c’est possible; sinon, signalez une erreur (NAN / INFs / OUT_OF_RANGE).

donc, une pseudo-implémentation de code ressemblerait à ceci:

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

Il y a au moins deux questions similaires sur SO: dans cette réponse, le problème est résolu de manière suffisamment nette, bien qu’il s’agisse d’une solution C ++ – en C, nous n’avons pas de chiffres portables pour int signé. Dans cette réponse, il est expliqué pourquoi nous ne pouvons pas simplement vérifier (val > INT_MAX || val < INT_MIN) .

Donc, la seule façon propre que je vois est d’utiliser un environnement à virgule flottante, mais c’est indiqué comme une fonctionnalité définie par l’implémentation.

Ma question est donc la suivante : existe-t-il un moyen d’implémenter la fonction double_to_int manière multiplate-forme (en se basant uniquement sur le standard C, même en ne considérant pas les plates-formes cibles pour prendre en charge IEEE-754)?

La réponse à ” Une conversation de double à int peut-elle être écrite en C portable” est clairement ” oui “.

Par exemple, vous pouvez sprintf la valeur flottante dans une chaîne, effectuer une inspection basée sur chaîne (c’est-à-dire par comparaison basée sur chaîne avec les valeurs max et min, vous sprintf’d également), la validation, l’arrondi, etc., puis sscanf la chaîne connue-valide pour la valeur finale.

En fait, vous vous dirigeriez vers une représentation intermédiaire qui est (a) portable et (b) pratique. Les chaînes C conviennent à la portabilité, mais ne sont pas très pratiques. Si vous pouvez utiliser des bibliothèques externes, il en existe plusieurs qui conviennent mais dont la portabilité doit être confirmée.

Par exemple (qui omet d’arrondir):

 #include  #include  #include  #include  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 ssortingng buffer with input char buf[110]; sprintf(buf, "%0105.0f", inVal); // do range check on ssortingngs 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; } 

Qui produit les conversions attendues (voir les cas de test à la fin ci-dessus):

 % 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 

[Cette réponse a été modifiée avec une approche totalement nouvelle.]

Cette approche utilise la définition des formats à virgule flottante dans la norme C, sous la forme d’un nombre de base b signé, multiplié par une puissance de b . Connaître le nombre de chiffres dans le significande (fourni par DBL_MANT_DIG ) et la limite d’exposants (fournie par DBL_MAX_EXP ) permet de préparer double valeurs double exactes en tant que points d’extrémité.

Je crois que cela fonctionnera dans toutes les mises en œuvre C conformes, sous réserve des modestes exigences supplémentaires énoncées dans le commentaire initial.

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

(Cette réponse est en litige, même si je pense toujours que j’ai raison, alors ne votez pas à la légère, s’il vous plaît.)

Vous ne pouvez pas implémenter une telle fonction en C portable.

À cet égard, c’est un peu comme malloc & c.

La morale de l’histoire est vraiment que mélanger des types n’est jamais une bonne idée en C; c’est-à-dire écrire du code de telle sorte que les conversions de types ne soient pas nécessaires

Une conversion de double en int peut-elle être écrite en C (?)

existe-t-il un moyen d’implémenter la fonction double_to_int de manière multiplateforme (en se basant uniquement sur le standard C, même en ne considérant pas les plates-formes cibles pour prendre en charge IEEE-754)?

 int double_to_int(double val, int *err) 

Détail: (int)val tronque la fraction, de sorte que la plage de val convertible utilisant (int)val est mathématiquement :
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999... ou
INT_MIN - 1 < val < INT_MAX + 1 .


Oui, de manière multiplateforme, en utilisant des mathématiques et des constantes à virgule flottante exactes, le code peut tester le succès des conversions.

2.0*(INT_MAX/2+1) est certainement converti exactement en constante FP.

val - INT_MIN > -1.0 s'apparente à val > INT_MIN - 1.0 mais ne souffre pas d'imprécision (avec les machines du complément à 2 communes) possible avec INT_MIN - 1.0 . Rappelez-vous que le type entier peut avoir une précision supérieure à double . Considérons un int 64 bits et INT_MIN - 1.0 pas exactement représentable comme un double .

Le code n'utilise pas (double)INT_MAX ce qui peut également être imprécis.


Pour me copier:

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

Faiblesse des FLT == 10 : FLT == 10 et le type entier> 34 bits.

Le problème sous-jacent est de trouver min_double_to_int et max_double_to_int , le double le plus petit et le plus grand, respectivement, pouvant être convertis en int .

La fonction de conversion portable elle-même peut être écrite en C11 comme suit:

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

Avant que la fonction ci-dessus ne soit utilisée pour la première fois, nous devons affecter min_double_to_int et max_double_to_int .

MODIFIÉ le 2018-07-03: Approche réécrite.

Nous pouvons utiliser une fonction simple pour trouver la plus petite puissance sur dix d’une magnitude au moins INT_MAX à INT_MAX / INT_MIN . Si ceux-ci sont inférieurs à DBL_MAX_10_EXP , la plage de double est supérieure à la plage de int , et nous pouvons INT_MAX et INT_MIN en double .

Sinon, nous construisons une chaîne contenant la représentation décimale de INT_MAX / INT_MIN et utilisons strtod() pour les convertir en double . Si cette opération déborde, cela signifie que la plage de double est inférieure à la plage de int , et nous pouvons utiliser DBL_MAX / -DBL_MAX comme max_double_to_int et min_double_to_int , respectivement.

Quand on a INT_MAX comme double , on peut utiliser une boucle pour incrémenter cette valeur en utilisant nextafter(value, HUGE_VAL) . max_double_to_int est la valeur la plus grande qui soit finie et arrondie à l’aide de floor() .

De même, lorsque nous avons un double INT_MIN , nous pouvons utiliser une boucle pour décrémenter cette valeur en utilisant nextafter(value, -HUGE_VAL) . La plus grande valeur de magnitude encore finie et ceil() ) au même double est min_double_to_int .

Voici un exemple de programme pour illustrer ceci:

 #include  #include  #include  #include  #include  #include  #include  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_ssortingng(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; } 

Peut-être que cela pourrait fonctionner:

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

Oui. (nan / inf manipulation omise pour des raisons de brièveté)

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

Explication.

Les cas INT_MAX , comme expliqué dans l'une des réponses liées, correspondent aux cas où INT_MAX n'est pas représentable en tant que double exactement, et est arrondi lors de la conversion en double et un cas symésortingque avec INT_MIN . C'est le cas lorsque if (x > INT_MAX) échoue. Autrement dit, la comparaison retourne false , mais nous ne pouvons toujours pas convertir directement x en int .

Ce que la réponse liée ne reconnaît pas, c'est qu'il n'y a qu'un seul nombre double qui échoue au test, à savoir (double)INT_MAX , et nous pouvons facilement intercepter ce cas en vérifiant explicitement x == INT_MAX .

Comme indiqué dans les commentaires, cela peut échouer si INT_MAX ou INT_MIN est en dehors de la plage de double . Bien que cela soit extrêmement improbable, cela n’est pas exclu par la norme. Dans une telle implémentation, la conversion est simplement (int)x . Il devrait être plus facile de détecter une telle implémentation au moment de la configuration qu’au moment de l’exécution. Si ce dernier est absolument nécessaire, on peut effectuer cette opération une fois :

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

ensuite

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

Faites de même avec INT_MIN pour les paranoïaques et faites la vérification séparément pour les doubles positifs et négatifs.

Autant que je sache, le problème fondamental consiste à: est double-> int-> doubler une identité pour les valeurs de INT_MAX et INT_MIN. Fait intéressant, C a une façon d’exprimer cela:

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

À partir de cela, une forme très condensée des réponses ci-dessus peut fonctionner, car vous pouvez l’utiliser pour déterminer si INT_MAX, INT_MIN sont raisonnablement comparables, ainsi:

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

mais, bien sûr, s’appuyant sur le système de conversion de type rigoureux de C donne au compilateur une licence gratuite pour reformater votre disque. Vous pouvez donc éventuellement le copier dans printf / scanf.