¿Por qué dividir dos int no produce el valor correcto cuando se asigna a double?

110

¿Cómo es que en el siguiente fragmento

int a = 7;
int b = 3;
double c = 0;
c = a / b;

ctermina teniendo el valor 2, en lugar de 2,3333, como era de esperar. Si ay bson dobles, la respuesta pasa a 2.333. ¿Pero seguramente porque c ya es un doble debería haber trabajado con enteros?

Entonces, ¿cómo int/int=doublees que no funciona?

Jahoe
fuente
El posible duplicado del resultado
phuclv

Respuestas:

161

Esto se debe a que está utilizando la versión de división entera de operator/, que tarda 2 intsy devuelve un int. Para utilizar la doubleversión, que devuelve a double, al menos uno de los ints debe convertirse explícitamente en a double.

c = a/(double)b;
Chad La Guardia
fuente
9
Yo preferiría convertir explícitamente tanto ay bque doublesimplemente por claridad, pero realmente no importa.
John Dibling
31
Dado que la pregunta está etiquetada como C ++, preferiría ver static_cast <> en lugar de un elenco de C.
Martin York
16
Personalmente, creo que los moldes de estilo C son más claros (el lanzamiento en la mayoría de los otros lenguajes comunes se realiza al estilo de C). static_cast<>siempre me pareció largo aliento. En el caso de primitivas, en realidad no hay ningún peligro de conseguir static_cast<>y reinterpret_cast<>hasta mixto.
Chad La Guardia
6
@ Tux-D: ¿Para conversiones aritméticas? Preferiría evitarlo static_casten este caso y usar yeso estilo C. No hay ningún beneficio en el uso de versiones de estilo C ++ aquí y desordenan el código mucho más que las versiones de estilo C. El reparto aritmético es exactamente el contexto en el que los modelos de estilo C son perfectamente apropiados y, en realidad, más apropiados que otros modelos.
2011
19
A veces puedes burlar a la gente que "no usa el estilo C" escribiendo double(b). No siempre se dan cuenta de que es una conversión, ya que se ve igual que una llamada explícita a un constructor.
Steve Jessop
12

Aquí está:

a) La división de dos ints siempre realiza la división de enteros. Entonces, el resultado de a/ben su caso solo puede ser un int.

Si desea mantener ay bcomo ints, pero dividirlos completamente, debe lanzar al menos uno de ellos para duplicar: (double)a/bo a/(double)bo (double)a/(double)b.

b) ces un double, por lo que puede aceptar un intvalor en la asignación: el intse convierte automáticamente doubley se asigna a c.

c) Recuerde que en la asignación, la expresión a la derecha de =se calcula primero (de acuerdo con la regla (a) anterior, y sin tener en cuenta la variable a la izquierda de =) y luego se asigna a la variable a la izquierda de =(según ( b) arriba). Creo que esto completa el cuadro.

nplatis
fuente
11

Con muy pocas excepciones (solo puedo pensar en una), C ++ determina el significado completo de una expresión (o subexpresión) a partir de la expresión misma. No importa lo que hagas con los resultados de la expresión. En tu caso, en la expresión a / b, no hay un doublea la vista; todo es int. Entonces el compilador usa la división de enteros. Solo una vez que tiene el resultado, considera qué hacer con él y lo convierte a double.

James Kanze
fuente
3
La única excepción en la que puedo pensar es elegir una sobrecarga de función al tomar un puntero; el valor de &funcnamedepende del tipo al que lo eches.
Steve Jessop
2
@Steve Jessop Esa es la única excepción en la que también puedo pensar. (Pero dado el tamaño y la complejidad del estándar, no me gustaría jurar que no me he perdido ninguno.)
James Kanze
6

ces una doublevariable, pero el valor que se le asigna es un intvalor porque resulta de la división de dos ints, lo que le da una "división entera" (descartando el resto). Entonces, lo que pasa en la fila c=a/bes

  1. a/b se evalúa, creando un temporal de tipo int
  2. el valor del temporal se asigna cdespués de la conversión a tipo double.

El valor de a/bse determina sin referencia a su contexto (asignación a double).

Fred Foo
fuente
6

Cuando divide dos enteros, el resultado será un entero, independientemente del hecho de que lo almacene en un doble.

Alok Save
fuente
5

En lenguaje C ++, el resultado de la subexpresión nunca se ve afectado por el contexto circundante (con algunas raras excepciones). Este es uno de los principios que sigue cuidadosamente el lenguaje. La expresión c = a / bcontiene una subexpresión independiente a / b, que se interpreta independientemente de cualquier cosa fuera de esa subexpresión. Al idioma no le importa que luego le asignes el resultado a un double. a / bes una división entera. Cualquier otra cosa no importa. Verá que este principio se sigue en muchos aspectos de la especificación del lenguaje. Así es como funciona C ++ (y C).

Un ejemplo de una excepción que mencioné anteriormente es la asignación / inicialización del puntero de función en situaciones con sobrecarga de funciones

void foo(int);
void foo(double);

void (*p)(double) = &foo; // automatically selects `foo(fouble)`

Este es un contexto donde el lado izquierdo de una asignación / inicialización afecta el comportamiento del lado derecho. (Además, la inicialización de referencia a matriz evita la caída del tipo de matriz, que es otro ejemplo de comportamiento similar). En todos los demás casos, el lado derecho ignora por completo el lado izquierdo.

Hormiga
fuente
4

El /operador se puede utilizar para la división de enteros o la división de punto flotante. Le está dando dos operandos enteros, por lo que está haciendo una división de enteros y luego el resultado se almacena en un doble.

Vicky
fuente
2

Técnicamente, esto depende del idioma, pero casi todos los idiomas tratan este tema de la misma manera. Cuando hay una falta de coincidencia de tipos entre dos tipos de datos en una expresión, la mayoría de los lenguajes intentarán convertir los datos en un lado del =para hacer coincidir los datos en el otro lado de acuerdo con un conjunto de reglas predefinidas.

Al dividir dos números del mismo tipo (enteros, dobles, etc.) el resultado siempre será del mismo tipo (por lo que 'int / int' siempre resultará en int).

En este caso, tiene lo double var = integer result que convierte el resultado entero en un doble después del cálculo, en cuyo caso los datos fraccionarios ya se han perdido. (la mayoría de los lenguajes harán esta conversión para evitar imprecisiones de tipo sin generar una excepción o error).

Si desea mantener el resultado como el doble, querrá crear una situación en la que tenga double var = double result

La forma más fácil de hacerlo es forzar la expresión en el lado derecho de una ecuación para que se duplique:

c = a/(double)b

La división entre un entero y un doble resultará en convertir el entero al doble (tenga en cuenta que al hacer matemáticas, el compilador a menudo "convertirá" al tipo de datos más específico, esto es para evitar la pérdida de datos).

Después del upcast, aterminará como un doble y ahora tienes una división entre dos dobles. Esto creará la división y asignación deseadas.

OTRA VEZ, tenga en cuenta que esto es específico del idioma (e incluso puede ser específico del compilador), sin embargo, casi todos los idiomas (ciertamente todos los que se me ocurren) tratan este ejemplo de manera idéntica.

matthewdunnam
fuente
Esta pregunta está etiquetada como [C ++], y el estándar C ++ dicta exactamente cómo funciona. No estoy seguro de lo que quiere decir con "específico del lenguaje" y ciertamente no es específico del compilador, asumiendo que no hay extensiones de compilador activadas.
John Dibling
También es incorrecto decir que "double var = integer result que arroja la doble var a int". El doble no se lanza a un int. El resultado int se convierte en un doble.
John Dibling
Estaba permitiendo la posibilidad de extensiones del compilador (de hecho, tuve este problema una vez en el que mi entorno "arrojaba mal" los resultados y no podía entender por qué). Y el resultado es un idioma específico, ya que en algunos idiomas no siguen las mismas reglas de casting. No consideré que fuera una etiqueta específica de C ++. Tienes razón sobre el comentario "double var = integer result". Editado para reflejar eso. ¡Gracias!
matthewdunnam
0

Lo importante es que uno de los elementos de cálculo sea del tipo float-double. Luego, para obtener un resultado doble, debe lanzar este elemento como se muestra a continuación:

c = static_cast<double>(a) / b;

o c = a / static_cast (b);

O puede crearlo directamente:

c = 7.0 / 3;

Tenga en cuenta que uno de los elementos de cálculo debe tener '.0' para indicar una división de un tipo flotante-doble por un entero. De lo contrario, a pesar de que la variable c sea doble, el resultado también será cero.

ElArquitecto
fuente
¿Qué aporta su respuesta que ninguna de las otras 9 respuestas aún no esté presente?
bolov