Kann eine Konvertierung von double nach int in portablem C geschrieben werden

Lesezeit: 21 Minuten

Benutzer-Avatar
Wolodymyr Boiko

Ich muss Funktion wie schreiben double_to_int(double val, int *err) was Double Val in Integer umwandeln würde, wenn es möglich ist; andernfalls einen Fehler melden (NAN/INFs/OUT_OF_RANGE).

Die Pseudocode-Implementierung würde also folgendermaßen aussehen:

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

Es gibt mindestens zwei ähnliche Fragen zu SO: In dieser Antwort wird es ausreichend sauber gelöst, obwohl es sich um eine C ++ – Lösung handelt – in C haben wir keine tragbaren Ziffern für signed int. In dieser Antwort wird erklärt, warum wir nicht einfach nachsehen können (val > INT_MAX || val < INT_MIN).

Der einzig mögliche saubere Weg, den ich sehe, ist die Verwendung einer Gleitkommaumgebung, aber es wird als implementierungsdefiniertes Feature angegeben.

Also meine Frage: gibt es eine Möglichkeit zur Umsetzung double_to_int Funktion plattformübergreifend (basierend nur auf dem C-Standard, auch ohne Berücksichtigung von Zielplattformen zur Unterstützung von IEEE-754).?

  • “Bitte lesen, bevor Sie es als Duplikat markieren.” sollte in den Kommentarbereich gehen

    – Sternengatter

    29. Juni 2018 um 15:26 Uhr

  • ich frage mich, ob frexp ist jede Hilfe.

    – Steve Summit

    29. Juni 2018 um 15:30 Uhr

  • Ich habe wirklich das Gefühl, dass Ihre Frage durch die Antwort beantwortet wird, die Sie in Ihrer Frage verlinkt haben. Machen Sie Ihre Frage also zu einem Duplikat.

    – Sternengatter

    29. Juni 2018 um 15:33 Uhr

  • Sie sollten wirklich erklären, warum die von Stargateur gezeigte Antwort Ihre Frage nicht beantwortet.

    – Serge Ballesta

    29. Juni 2018 um 15:35 Uhr

  • Ich habe das Gefühl, dass alle “engen Duplikate” irgendwann in ihren Antworten auf eine bestimmte Implementierung übergehen. Ich glaube fest daran, dass dies nicht möglich ist; Meine Antwort ist kaum mehr als eine Einladung zur Peer-Review.

    – Bathseba

    29. Juni 2018 um 15:35 Uhr


Benutzer-Avatar
Bob Jacobson

Die Antwort auf „dürfen ein Gespräch von double nach int in portable C” geschrieben werden ist klar “Jawohl“.

Zum Beispiel könnten Sie den Fließkommawert in einen String sprinten, eine stringbasierte Inspektion durchführen (dh durch einen stringbasierten Vergleich mit Max- und Min-Werten, die Sie auch sprinten), Validierung, Rundung usw. und dann den bekanntermaßen gültigen String scannen für den Endwert.

Tatsächlich würden Sie sich in Richtung einer Zwischendarstellung bewegen, die (a) portabel und (b) bequem ist. C-Saiten sind gut tragbar, aber nicht so bequem. Wenn Sie externe Bibliotheken verwenden können, gibt es mehrere, die praktisch sind, deren Portabilität jedoch bestätigt werden sollte.

Zum Beispiel (ohne Rundung):

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

Was zu den erwarteten Konvertierungen führt (siehe Testfälle am Ende oben):

% 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

Benutzer-Avatar
Eric Postpischil

[This answer has been edited with a completely new approach.]

Dieser Ansatz verwendet die Definition von Fließkommaformaten im C-Standard – als vorzeichenbehaftete Basis-b Zahl multipliziert mit einer Potenz von b. Die Kenntnis der Anzahl der Stellen im Signifikanten (bereitgestellt von DBL_MANT_DIG) und die Exponentengrenze (bereitgestellt von DBL_MAX_EXP) ermöglicht uns eine exakte Vorbereitung double Werte als Endpunkte.

Ich glaube, dass es in allen konformen C-Implementierungen funktionieren wird, vorbehaltlich der bescheidenen zusätzlichen Anforderungen, die im ersten Kommentar angegeben sind.

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

  • @NominalAnimal: Eine interessante Idee, werde mir überlegen ob ich umrüste unsigned int gibt uns etwas Spielraum. Allerdings bedingt die Umstellung durch einen Guss aus double zu unsigned int ist nicht notwendigerweise modulo. Gemäß C 2011 (N1570) 6.3.1.4 Anmerkung 61: „Die verbleibende Operation, die ausgeführt wird, wenn ein Wert vom Integer-Typ in einen vorzeichenlosen Typ konvertiert wird, muss nicht ausgeführt werden, wenn ein Wert vom realen Floating-Typ in einen vorzeichenlosen Typ konvertiert wird. Somit ist der Bereich der tragbaren reellen Floating-Werte (−1, UTyp_MAX+1).“

    – Eric Postpischil

    3. Juli 2018 um 1:01 Uhr


  • limits.h definiert INT_MAX und INT_MIN.

    – Bob Jarvis – Слава Україні

    3. Juli 2018 um 1:01 Uhr

  • @BobJarvis: Was ist Ihr Punkt? Wir haben kein Problem mit der Beschaffung INT_MAX oder INT_MIN in int. Das Problem ist, dass wir nicht wissen, dass sie konvertiert werden können double ohne Fehler, aber wir müssen das Größte finden double das ist weniger als INT_MAX+1. Wir müssen also einen Weg finden, Rundungsfehler zu umgehen oder zu korrigieren, die während der Konvertierung auftreten können.

    – Eric Postpischil

    3. Juli 2018 um 1:03 Uhr

  • @EricPostpischil: Ich habe meine Antwort zur Verwendung umgeschrieben floor(max_double_to_int) == (double)INT_MAX und ceil(min_double_to_int) == (double)INT_MIN in dem nextafter() Schleifen, plus Handhabung für den ungeraden Fall, wenn DBL_MAX <= INT_MAX oder -DBL_MAX >= INT_MIN über strtod().

    – Nominelles Tier

    3. Juli 2018 um 6:40 Uhr


  • Die Anfrage bezog sich auf “portable C”, was die Frage aufwirft: “Welches?” SO VIEL ICH WEISS, nexttoward(..) erschien zuerst in C99; es ist nicht in ANSI C. Can nexttoward(..) in ANSI C geschrieben werden?

    – Bob Jacobsen

    4. Juli 2018 um 17:45 Uhr


Benutzer-Avatar
Chux – Wiedereinsetzung von Monica

Kann eine Konvertierung von double zu int in portablem C geschrieben werden (?)

Gibt es eine Möglichkeit, die double_to_int-Funktion plattformübergreifend zu implementieren (basierend nur auf dem C-Standard, auch ohne Berücksichtigung von Zielplattformen zur Unterstützung von IEEE-754).?

int double_to_int(double val, int *err)

Detail: (int)val schneidet den Nachkommaanteil ab, also den Bereich von Convertible val verwenden (int)val ist mathematisch:
INT_MIN - 0.9999... ≤ val ≤ INT_MAX + 0.9999... oder
INT_MIN - 1 < val < INT_MAX + 1.


Ja, ein plattformübergreifender Weg, durch die Verwendung von exakter Gleitkomma-Mathematik und Konstanten, kann der Code den Konvertierungserfolg testen.

2.0*(INT_MAX/2+1) wird sicherlich genau in eine FP-Konstante umgewandelt.

val - INT_MIN > -1.0 ist verwandt val > INT_MIN - 1.0 leidet aber nicht unter Ungenauigkeit (bei den üblichen 2er-Komplement-Maschinen) möglich mit INT_MIN - 1.0. Denken Sie daran, dass der Integer-Typ eine höhere Genauigkeit als haben kann double. Betrachten Sie eine 64-Bit-Version int und INT_MIN - 1.0 nicht genau darstellbar als double.

Code wird nicht verwendet (double)INT_MAX was auch ungenau sein kann.


Um mich selbst zu kopieren:

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

Eckenschwäche: FLT == 10 und der ganzzahlige Typ > 34 Bit.

Benutzer-Avatar
Nominelles Tier

Das zugrunde liegende Problem ist zu finden min_double_to_int und max_double_to_intdie kleinste und die größte doubledie in eine umgewandelt werden können int.

Die portable Konvertierungsfunktion selbst kann in C11 als geschrieben werden

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

Bevor die obige Funktion zum ersten Mal verwendet wird, müssen wir zuweisen min_double_to_int und max_double_to_int.

BEARBEITET am 03.07.2018: Neu geschriebener Ansatz.

Wir können eine einfache Funktion verwenden, um die kleinste Zehnerpotenz zu finden, die mindestens so groß ist wie INT_MAX/INT_MIN in der Größenordnung. Wenn diese kleiner sind als DBL_MAX_10_EXPdie Reichweite von double ist größer als der Bereich von intund wir können werfen INT_MAX und INT_MIN zu double.

Andernfalls konstruieren wir eine Zeichenfolge, die die Dezimaldarstellung von enthält INT_MAX/INT_MINund verwenden strtod() sie umzuwandeln double. Wenn diese Operation überläuft, bedeutet dies den Bereich von double ist kleiner als der Bereich von intund wir können verwenden DBL_MAX/-DBL_MAX wie max_double_to_int und min_double_to_intbeziehungsweise.

Wenn wir haben INT_MAX Als ein doublekönnen wir eine Schleife verwenden, um diesen Wert mit zu erhöhen nextafter(value, HUGE_VAL). Der größte Wert, der endlich ist und mit abgerundet wird floor() bringt immer noch das gleiche double Wert ist max_double_to_int.

Ebenso, wenn wir haben INT_MIN als Double können wir diesen Wert mit einer Schleife dekrementieren nextafter(value, -HUGE_VAL). Der betragsmäßig größte Wert, der noch endlich ist, und aufgerundet (ceil()) zum Selben doubleist min_double_to_int.

Hier ein Beispielprogramm zur Veranschaulichung:

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

Benutzer-Avatar
José

Vielleicht könnte das funktionieren:

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

  • Wie würde das einen Wert von Null zurückgeben?

    – Andreas Henle

    2. Juli 2018 um 10:41 Uhr

  • @AndrewHenle IMO ist das Ziel, anstelle eines Fehlerprotokolls, das ich versucht habe, es zu vereinfachen, eine ordnungsgemäße Konvertierung zu erreichen (eigentlich melden beide Fehler denselben Wert in einer Variablen, in der ein beliebiger Wert erwartet wird). Wie auch immer, ich habe den Code bearbeitet, um es klarer zu machen.

    – José

    2. Juli 2018 um 11:02 Uhr


  • Die Berechnung von upperLimit versucht, 2^Breite-1 zu berechnen, wobei Breite die Anzahl der Bits in einem ist int. Selbst wenn einige dieser Bits Füllbits sind, also nicht zu den verfügbaren Werten beitragen, ist das Subtrahieren von 1 ein Problem. C gibt nicht an, was passiert, wenn das Ergebnis nicht exakt in Fließkomma darstellbar ist. Es kann auf- oder abgerundet werden. Dann wissen Sie nicht, ob Sie verwenden sollten val < upperLimit oder val <= upperLimit.

    – Eric Postpischil

    3. Juli 2018 um 1:10 Uhr


  • Testen val > upperLimit wird für einen NaN falsch melden, ebenso wie der andere Vergleich, sodass dieser Code zum durchfällt *err = (int) val Fall, den wir nicht wollen. (Warum heißt es „err“? Das deutet auf einen Fehler hin, aber das dient dazu, den richtigen Wert zurückzugeben, nicht wahr?) Diese Tests sollten so strukturiert sein wenn der Wert liegt im Bereich, dann es wird umgewandelt, anders ein Fehler wird gemeldet. Dann fließen NaNs zum Fehlerpfad. Oder NaNs könnten separat getestet werden.

    – Eric Postpischil

    3. Juli 2018 um 1:12 Uhr

  • Dieser Code geht davon aus, dass der minimale ganzzahlige Wert das Negative einer Zweierpotenz ist, aber der C-Standard erfordert dies nicht.

    – Eric Postpischil

    3. Juli 2018 um 1:13 Uhr

Ja. (nan/inf-Handhabung der Kürze halber weggelassen)

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

Erläuterung.

Die Grenzfälle, wie in einer der verlinkten Antworten erläutert, sind wann INT_MAX ist nicht darstellbar als double genau und wird bei der Umrechnung aufgerundet doubleund ein symmetrischer Fall eins mit INT_MIN. Das ist der Fall, wenn if (x > INT_MAX) scheitert. Das heißt, der Vergleich kehrt zurück falseaber wir können immer noch nicht konvertieren x zu int direkt.

Was die verlinkte Antwort nicht erkennt, ist, dass es nur eine doppelte Zahl gibt, die den Test nicht besteht, nämlich (double)INT_MAXund wir können diesen Fall leicht erkennen, indem wir nach suchen x == INT_MAX ausdrücklich.

Bearbeiten Wie in den Kommentaren erwähnt, kann dies fehlschlagen, wenn INT_MAX oder INT_MIN liegt außerhalb des Bereichs von double. Obwohl dies äußerst unwahrscheinlich ist, wird dies durch die Norm nicht ausgeschlossen. Bei einer solchen Implementierung ist die Konvertierung gerecht (int)x. Es sollte einfacher sein, eine solche Implementierung zur Konfigurationszeit als zur Laufzeit zu erkennen. Wenn letzteres unbedingt benötigt wird, kann man diese Operation durchführen einmal:

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

Dann

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

Für die Paranoiker unter uns, tun Sie dies auch mit INT_MIN und führen Sie die Prüfung getrennt auf positive und negative Doppel durch.

  • Wie würde das einen Wert von Null zurückgeben?

    – Andreas Henle

    2. Juli 2018 um 10:41 Uhr

  • @AndrewHenle IMO ist das Ziel, anstelle eines Fehlerprotokolls, das ich versucht habe, es zu vereinfachen, eine ordnungsgemäße Konvertierung zu erreichen (eigentlich melden beide Fehler denselben Wert in einer Variablen, in der ein beliebiger Wert erwartet wird). Wie auch immer, ich habe den Code bearbeitet, um es klarer zu machen.

    – José

    2. Juli 2018 um 11:02 Uhr


  • Die Berechnung von upperLimit versucht, 2^Breite-1 zu berechnen, wobei Breite die Anzahl der Bits in einem ist int. Selbst wenn einige dieser Bits Füllbits sind, also nicht zu den verfügbaren Werten beitragen, ist das Subtrahieren von 1 ein Problem. C gibt nicht an, was passiert, wenn das Ergebnis nicht exakt in Fließkomma darstellbar ist. Es kann auf- oder abgerundet werden. Dann wissen Sie nicht, ob Sie verwenden sollten val < upperLimit oder val <= upperLimit.

    – Eric Postpischil

    3. Juli 2018 um 1:10 Uhr


  • Testen val > upperLimit wird für einen NaN falsch melden, ebenso wie der andere Vergleich, sodass dieser Code zum durchfällt *err = (int) val Fall, den wir nicht wollen. (Warum heißt es „err“? Das deutet auf einen Fehler hin, aber das dient dazu, den richtigen Wert zurückzugeben, nicht wahr?) Diese Tests sollten so strukturiert sein wenn der Wert liegt im Bereich, dann es wird umgewandelt, anders ein Fehler wird gemeldet. Dann fließen NaNs zum Fehlerpfad. Oder NaNs könnten separat getestet werden.

    – Eric Postpischil

    3. Juli 2018 um 1:12 Uhr

  • Dieser Code geht davon aus, dass der minimale ganzzahlige Wert das Negative einer Zweierpotenz ist, aber der C-Standard erfordert dies nicht.

    – Eric Postpischil

    3. Juli 2018 um 1:13 Uhr

Benutzer-Avatar
trifft

Soweit ich das beurteilen kann, destilliert das grundlegende Problem auf: ist double->int->double eine Identität für die Werte von INT_MAX und INT_MIN. Interessanterweise hat C eine Möglichkeit, dies auszudrücken:

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

Daraus kann eine stark komprimierte Form der obigen Antworten funktionieren, da Sie damit feststellen können, ob INT_MAX, INT_MIN einigermaßen vergleichbar sind, also:

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

Aber wenn Sie sich auf das strenge Typkonvertierungssystem von C verlassen, erhalten Sie natürlich eine kostenlose Lizenz zum Neuformatieren Ihrer Festplatte, also stopfen Sie sie vielleicht stattdessen durch printf/scanf.

  • In dem Fall wo double dv = val; rundet auf eine Zahl größerer Größenordnung, int iv = dv; ist undefiniertes Verhalten. Ein Beispiel wäre INT_MAX für 64-bit 2er-Komplement int und IEEE 754 mit doppelter Genauigkeit double.

    – Pascal Cuoq

    4. Juli 2018 um 6:38 Uhr


1228550cookie-checkKann eine Konvertierung von double nach int in portablem C geschrieben werden

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy