Este es el enlace a la pregunta sobre el juez en línea UVa.
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=29&page=show_problem&problem=1078
mi codigo c es
#include <stdio.h> double avg(double * arr,int students) { int i; double average=0; for(i=0;i<students;i++){ average=average+(*(arr+i)); } average=average/students; int temp=average*100; average=temp/100.0; return average; } double mon(double * arr,int students,double average) { int i; double count=0; for(i=0;i<students;i++){ if(*(arr+i)<average){ double temp=average-*(arr+i); int a=temp*100; temp=a/100.0; count=count+temp; } } return count; } int main(void) { // your code goes here int students; scanf("%d",&students); while(students!=0){ double arr[students]; int i; for(i=0;i<students;i++){ scanf("%lf",&arr[i]); } double average=avg(arr,students); //printf("%lf\n",average); double money=mon(arr,students,average); printf("$%.2lf\n",money); scanf("%d",&students); } return 0; } Una de las entradas y salidas son
Aporte
3
0.01
0.03
0.03
0
Producción
$0.01
mi salida es
$0.00.
Sin embargo, si elimino el comentario de la línea printf("%lf",average);
La salida es la siguiente
0.02 //Este es el promedio
$0.01
Estoy ejecutando el código en ideone.com
Explique por qué sucede esto.
Creo que he encontrado al culpable y una explicación razonable.
En los procesadores x86, la FPU funciona internamente con precisión extendida , que es un formato de 80 bits. Todas las instrucciones de punto flotante operan con esta precisión. Si realmente se requiere un double , el compilador generará código para convertir el valor de precisión extendida en un valor de doble precisión. Crucialmente, el printf comentado obliga a tal conversión porque los registros FPU deben guardarse y restaurarse a través de esa llamada de función, y se guardarán como double s (tenga en cuenta que avg y mon están en línea, por lo que no se guarda ni restaura allí) .
De hecho, en lugar de printf podemos usar la línea static double dummy = average; para forzar la double conversión, lo que también hace que el error desaparezca: http://ideone.com/a1wadn
Su valor de average es cercano pero no exactamente 0.02 debido a imprecisiones de coma flotante. Cuando hago todos los cálculos explícitamente con long double e imprimo el valor de average , es lo siguiente:
long double: 0.01999999999999999999959342418532 double: 0.02000000000000000041633363423443 Ahora puedes ver el problema. Cuando agrega el printf , el average se fuerza a un double que lo empuja por encima de 0.02. Pero, sin el printf , el average será un poco menos de 0.02 en su formato nativo de precisión extendida.
Cuando haces int a=temp*100; aparece el error. Sin la conversión, esto hace a = 1 . Con la conversión, esto hace a = 2 .
Para arreglar esto, simplemente use int a=round(temp*100); - todos sus errores extraños deberían desaparecer.
Cabe destacar que este error es extremadamente sensible a los cambios en el código. Cualquier cosa que haga que los registros se guarden (como un printf prácticamente en cualquier lugar) hará que el error desaparezca. Por lo tanto, este es un ejemplo extremadamente bueno de heisenbug : un error que desaparece cuando intentas investigarlo.
@nneonneo respondió bien a la mayor parte del problema: las compilaciones ligeramente variantes dan como resultado un código de punto flotante ligeramente diferente que da como resultado casi la misma respuesta double , excepto que una respuesta está justo debajo de 2.0 y la otra en o casi 2.0.
Me gustaría agregar sobre la importancia de no usar la conversión a int para el redondeo de coma flotante.
Código como double temp; ... int a=temp*100; acentúe esta diferencia dando como resultado a con un valor de 1 o 2 ya que la conversión a int es efectiva "truncar hacia cero" - suelte la fracción.
En lugar de redondear a cerca de 0,01 con un código como:
double temp; ... int a = temp*100; // Problem is here temp = a/100.0; No use int en absoluto. Usar
double temp; ... temp = round(temp*100.0)/100.0; Esto no solo proporciona respuestas más consistentes (ya que es poco probable que temp tenga valores cercanos a medio centavo ), sino que también permite valores de temp fuera del rango int . temp = 1e13; int a = temp/100; ciertamente da como resultado un comportamiento indefinido.
No use conversión a
intpara redondear números de punto flotante: useround()
roundf() , roundl() , floor() , ceil() , etc. también pueden ser útiles. @jeff
Estás dividiendo un doble por un número entero, que es una división de números enteros. En este caso dará un valor de 0.
Si lanzas a estudiante a un doble, debería darte el resultado adecuado.
average=average/(double)students;Puede haber otras ubicaciones que sean necesarias dependiendo de su aritmética.