¿Por qué es el resultado de esta conversión explícita diferente a la implícita?

StackOverflow https://stackoverflow.com/questions/752738

  •  09-09-2019
  •  | 
  •  

Pregunta

¿Por qué es el resultado de esta conversión explícita diferente a la implícita?

#include <stdio.h>

double  a;
double  b;
double  c;

long    d;

double    e;

int main() {
    a = 1.0;
    b = 2.0;
    c = .1;

    d = (b - a + c) / c;
    printf("%li\n", d);        //    10

    e = (b - a + c) / c;
    d = (long) e;
    printf("%li\n", d);        //    11
    }

Si hago d = (long) ((b - a + c) / c); También consigo 10. ¿Por qué la asignación a una doble marca la diferencia?

¿Fue útil?

Solución

Sospecho que la diferencia es una conversión de un valor de coma flotante de 80 bits a una larga vs una conversión de un valor de coma flotante de 80 bits a un 64-bit uno y entonces una conversión a un largo.

(La razón de 80 bits que viene en absoluto es que eso es una precisión típica usada para la aritmética real, y la anchura de registros de coma flotante.)

Supongamos que el resultado de 80 bits es algo así como 10,999999999999999 - la conversión de que a la larga los rendimientos 10. Sin embargo, el valor de punto flotante de 64 bits más cercana al valor de 80 bits es en realidad 11.0, por lo que la conversión de dos etapas termina cediendo 11.

EDIT: Para dar a esto un poco más de peso ...

Aquí hay un programa Java que usa la aritmética de precisión arbitraria para hacer el mismo cálculo. Tenga en cuenta que convierte el doble valor más cercano a 0,1 en un BigDecimal - que el valor es 0,1000000000000000055511151231257827021181583404541015625. (En otras palabras, el resultado exacto del cálculo es no 11 de todos modos.)

import java.math.*;

public class Test
{
    public static void main(String[] args)
    {
        BigDecimal c = new BigDecimal(0.1d);        
        BigDecimal a = new BigDecimal(1d);
        BigDecimal b = new BigDecimal(2d);

        BigDecimal result = b.subtract(a)
                             .add(c)
                             .divide(c, 40, RoundingMode.FLOOR);
        System.out.println(result);
    }
}

Aquí está el resultado:

10.9999999999999994448884876874217606030632

En otras palabras, eso es correcto a aproximadamente 40 dígitos decimales (manera más que cualquiera de los 64 o 80 bits de coma flotante puede manejar).

Ahora, vamos a considerar lo que este número se ve como en binario. No tengo ninguna herramienta para hacer fácil la conversión, pero de nuevo, puedo utilizar Java para ayudar. Suponiendo un número normalizado, la parte de "10" termina usando tres bits (uno menos que para las once = 1,011). Eso deja 60 bits de mantisa de precisión extendida (80 bits) y 48 bits para doble precisión (64 bits).

Así que, ¿cuál es el número más cercano a 11 en cada una de precisión? Una vez más, vamos a usar Java:

import java.math.*;

public class Test
{
    public static void main(String[] args)
    {
        BigDecimal half = new BigDecimal("0.5");        
        BigDecimal eleven = new BigDecimal(11);

        System.out.println(eleven.subtract(half.pow(60)));
        System.out.println(eleven.subtract(half.pow(48)));        
    }
}

Resultados:

10.999999999999999999132638262011596452794037759304046630859375
10.999999999999996447286321199499070644378662109375

Por lo tanto, los tres números que tenemos son:

Correct value: 10.999999999999999444888487687421760603063...
11-2^(-60): 10.999999999999999999132638262011596452794037759304046630859375
11-2^(-48): 10.999999999999996447286321199499070644378662109375

Ahora calcular el valor más cercano a la correcta para cada precisión -. Por la precisión extendida, que es menos de 11. Ronda de cada uno de esos valores a un tiempo, y se termina con 10 y 11 respectivamente

Esperamos que esto es evidencia suficiente para convencer a los que dudan;)

Otros consejos

consigo 10 y 11 en mi 32-bit x86 gcc sistema Linux corriendo 4.3.2, también.

El C / asm es relevante aquí:

26:foo.c         ****     d = (b - a + c) / c;                                               
  42                            .loc 1 26 0
  43 0031 DD050000              fldl    b
  43      0000
  44 0037 DD050000              fldl    a
  44      0000
  45 003d DEE9                  fsubrp  %st, %st(1)
  46 003f DD050000              fldl    c
  46      0000
  47 0045 DEC1                  faddp   %st, %st(1)
  48 0047 DD050000              fldl    c
  48      0000
  49 004d DEF9                  fdivrp  %st, %st(1)
  50 004f D97DFA                fnstcw  -6(%ebp)
  51 0052 0FB745FA              movzwl  -6(%ebp), %eax
  52 0056 B40C                  movb    $12, %ah
  53 0058 668945F8              movw    %ax, -8(%ebp)
  54 005c D96DF8                fldcw   -8(%ebp)
  55 005f DB5DF4                fistpl  -12(%ebp)
  56 0062 D96DFA                fldcw   -6(%ebp)
  57 0065 8B45F4                movl    -12(%ebp), %eax
  58 0068 A3000000              movl    %eax, d
  58      00
  27:foo.c         ****
  28:foo.c         ****     printf("%li\n", d);                                                
  59                            .loc 1 28 0
  60 006d A1000000              movl    d, %eax
  60      00
  61 0072 89442404              movl    %eax, 4(%esp)
  62 0076 C7042400              movl    $.LC3, (%esp)
  62      000000
  63 007d E8FCFFFF              call    printf
  63      FF
  29:foo.c         ****     //    10                                                           
  30:foo.c         ****
  31:foo.c         ****     e = (b - a + c) / c;                                               
  64                            .loc 1 31 0
  65 0082 DD050000              fldl    b
  65      0000
  66 0088 DD050000              fldl    a
  66      0000
  67 008e DEE9                  fsubrp  %st, %st(1)
  68 0090 DD050000              fldl    c
  68      0000
  69 0096 DEC1                  faddp   %st, %st(1)
  70 0098 DD050000              fldl    c
  70      0000
  71 009e DEF9                  fdivrp  %st, %st(1)
  72 00a0 DD1D0000              fstpl   e
  72      0000
  32:foo.c         ****
  33:foo.c         ****     d = (long) e;                                                      
  73                            .loc 1 33 0
  74 00a6 DD050000              fldl    e
  74      0000
  75 00ac D97DFA                fnstcw  -6(%ebp)
  76 00af 0FB745FA              movzwl  -6(%ebp), %eax
  77 00b3 B40C                  movb    $12, %ah
  78 00b5 668945F8              movw    %ax, -8(%ebp)
  79 00b9 D96DF8                fldcw   -8(%ebp)
  80 00bc DB5DF4                fistpl  -12(%ebp)
  81 00bf D96DFA                fldcw   -6(%ebp)
  82 00c2 8B45F4                movl    -12(%ebp), %eax
  83 00c5 A3000000              movl    %eax, d
  83      00

La respuesta se deja como ejercicio para el lector interesado.

codepad.org (gcc 4.1.2) invierte los resultados de su ejemplo, mientras que en mi sistema local (gcc 4.3.2) consigo 11 en ambos casos. Esto me hace pensar que se trata de una cuestión de punto flotante. Alternativamente, se podría teóricamente truncar (b - a + c) que, en un contexto número entero evaluaría a (2 - 1 + 0) / 0,1, lo que sería 10, mientras que en un contexto de flotador (2,0 - 1.0 + 0.1 ) / 0,1 = 1,1 / 0,1 = 11. Eso sería extraño sin embargo.

Recta copiar / pegar y compilar en Linux me da 11 para ambos. Añadiendo d = (long) ((b - a + c) / c); también da 11. Lo mismo sucede en OpenBSD.

Aquí hay un montón de detalles sobre cuestiones de punto flotante y un muy buen artículo . Pero, básicamente, no todos los valores de punto flotante pueden ser representados por un cierto número de bits (32 bits o 64 bits) o lo que sea. Este es un tema muy profundo, pero me gusta porque me recuerda a Prof. Kahan . :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top