Domanda

In C89, floor () restituisce un doppio. È garantito il funzionamento seguente?

double d = floor(3.0 + 0.5);
int x = (int) d;
assert(x == 3);

La mia preoccupazione è che il risultato di floor potrebbe non essere esattamente rappresentabile in IEEE 754. Quindi d ottiene qualcosa come 2.99999 e x finisce per essere 2.

Affinché la risposta a questa domanda sia sì, tutti i numeri interi nell'intervallo di un int devono essere esattamente rappresentabili come doppi e floor deve sempre restituire quel valore esattamente rappresentato.

È stato utile?

Soluzione

Tutti i numeri interi possono avere una rappresentazione in virgola mobile esatta se il tipo in virgola mobile supporta i bit di mantissa richiesti. Poiché double utilizza 53 bit per mantissa, può memorizzare esattamente tutti i int a 32 bit. Dopotutto, potresti semplicemente impostare il valore come mantissa con esponente zero.

Altri suggerimenti

Se il risultato di floor () non è esattamente rappresentabile, cosa ti aspetti dal valore di d? Sicuramente se hai ottenuto la rappresentazione di un numero in virgola mobile in una variabile, allora per definizione è esattamente rappresentabile, no? Hai ottenuto la rappresentazione in d ...

(Inoltre, la risposta di Mehrdad è corretta per ints a 32 bit. In un compilatore con un doppio a 64 bit e a 64 bit int, ovviamente hai più problemi ...)

EDIT: forse intendevi "il risultato teorico di floor (), ovvero il valore intero più grande minore o uguale all'argomento, potrebbe non essere rappresentabile come int". Questo è certamente vero. Modo semplice per mostrare questo per un sistema in cui int è 32 bit:

int max = 0x7fffffff;
double number = max;
number += 10.0;
double f = floor(number);
int oops = (int) f;

Non riesco a ricordare con disinvoltura cosa fa C quando le conversioni da virgola mobile a overflow di numeri interi ... ma succederà qui.

EDIT: ci sono anche altre situazioni interessanti da considerare. Ecco un po 'di codice C # e risultati: immagino che almeno simili potrebbero accadere in C. In C #, double è definito come 64 bit e quindi lungo .

using System;
class Test
{
    static void Main()
    {
        FloorSameInteger(long.MaxValue/2);
        FloorSameInteger(long.MaxValue-2);
    }

    static void FloorSameInteger(long original)
    {
        double convertedToDouble = original;
        double flooredToDouble = Math.Floor(convertedToDouble);
        long flooredToLong = (long) flooredToDouble;

        Console.WriteLine("Original value: {0}", original);
        Console.WriteLine("Converted to double: {0}", convertedToDouble);
        Console.WriteLine("Floored (as double): {0}", flooredToDouble);
        Console.WriteLine("Converted back to long: {0}", flooredToLong);
        Console.WriteLine();
    }
}

Risultati:

  

Valore originale: 4611686018427387903
  Convertito in doppio:   4.61168601842739E + 18
Floored (come doppio): 4.61168601842739E + 18
  Convertito di nuovo in lungo:   4611686018427387904
  
  Valore originale: 9223372036854775805
  Convertito in doppio:   9.22337203685478E + 18
Rivestito (come doppio): 9.22337203685478E + 18
  Convertito di nuovo in lungo:   -9223372036854775808

In altre parole:

(long) floor((double) original)

non è sempre uguale a original . Questo non dovrebbe sorprendere: ci sono valori più lunghi dei doppi (dati i valori di NaN) e molti doppi non sono numeri interi, quindi non possiamo aspettarci che ogni long sia esattamente rappresentabile. Tuttavia, tutti gli interi a 32 bit sono rappresentabili come doppi.

Penso che tu sia un po 'confuso su ciò che vuoi chiedere. floor (3 + 0,5) non è un ottimo esempio, perché 3, 0,5 e la loro somma sono tutti esattamente rappresentabili in qualsiasi formato in virgola mobile del mondo reale. floor (0.1 + 0.9) sarebbe un esempio migliore, e la vera domanda qui non è se il risultato di floor sia esattamente rappresentabile, ma se l'inesattezza dei numeri prima di chiamare floor comporterà un valore di ritorno diverso da quello che ti aspetteresti, se tutti i numeri fossero esatti. In questo caso, credo che la risposta sia sì, ma dipende molto dai tuoi numeri particolari.

Invito altri a criticare questo approccio se è negativo, ma una possibile soluzione alternativa potrebbe essere quella di moltiplicare il numero per (1.0 + 0x1p-52) o qualcosa di simile prima di chiamare floor (forse usare nextafter sarebbe meglio). Ciò potrebbe compensare i casi in cui un errore nell'ultima posizione binaria del numero lo fa cadere appena al di sotto piuttosto che esattamente su un valore intero, ma non terrà conto degli errori che si sono accumulati su un numero di operazioni. Se hai bisogno di quel livello di stabilità / esattezza numerica, devi fare qualche analisi approfondita o utilizzare una libreria di precisione arbitraria o matematica esatta in grado di gestire correttamente i tuoi numeri.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top