2010-02-22 28 views
6

¿Cómo es que ceil() redondea un flotante uniforme sin partes fraccionarias?
Cuando trato de hacer esto:math.h ceil no funciona como se esperaba en C

double x = 2.22; 
x *= 100; //which becomes 222.00... 
printf("%lf", ceil(x)); //prints 223.00... (?) 

Pero cuando cambio el valor de 2,22 a 2,21

x *= 100; //which becomes 221.00... 
printf("%lf", ceil(x)); //prints 221.00... as expected 

Traté de hacerlo de otra manera como esto utilizando modf() y me encontré con otra extraña cosa:

double x = 2.22 * 100; 
double num, fraction; 
fraction = modf(x, &num); 
if(fraction > 0) 
    num += 1; //goes inside here even when fraction is 0.00... 

Entonces, ¿qué sucede es 0.000 ... es mayor que 0?
¿Alguien puede explicar por qué están pasando estas dos situaciones? También estoy compilando usando la versión 4.1.2 de cc en RedHat.

+0

Duplicados: http://stackoverflow.com/questions/177506/why-do-i-see-a-double-variable-initialized-to-some-value-like-21-4-as-21-39999961 , http://stackoverflow.com/questions/1089018/why-cant-decimal-numbers-be-represented-excaly-in-binary –

Respuesta

12

Esto es normal ya que los números se almacenan usando binarios. Mientras que sus números se pueden escribir en números finitos de dígitos usando decimal, esto no se aplica a binarios.

Debe leer el informe de Goldberg llamado What Every Computer Scientist Should Know About Floating-Point Arithmetic.

+2

Todo el mundo siempre apunta a ese artículo, pero no es genial. Es mejor que lea wikipedia –

12

La respuesta básica es que el número de coma flotante que se obtiene con:

double x = 2.22; 

en realidad es un poquito más grande que el valor 2.22, y el valor que se obtiene con

double x = 2.21; 

es una un poco más pequeño que 2.21.

2

Esto se debe a 2,22 no es exactamente 2,22 y por lo tanto 2,22 * 100 no es 222, pero 222.000000000x

double x = 2.22; 
printf("%.20lf\n", x); //prints 2.22000000000000019540 
x *= 100; 
printf("%.20lf\n", x); //prints 222.00000000000002842171 

Si necesita precisión de número entero (por ejemplo, para calcular cosas relacionadas con dinero) usar la aritmética integral (es decir centavos de cómputo en lugar de dólares).

+3

Es un error común sugerir aritmética de enteros o puntos fijos para aplicaciones financieras, en la práctica se usa el punto decimal flotante (en lugar del punto flotante binario). Para cálculos porcentuales (intereses o impuestos, por ejemplo), ¡calcular en centavos conduce a un escenario de Superman III/Espacio de oficina! ;-) – Clifford

+0

@Clifford: ¿tiene un puntero sobre cómo el uso del punto flotante decimal corrige los escenarios de Superman III/Espacio de oficina? (No estoy diciendo que no, sinceramente soy curioso). –

+0

@Clifford - AFAIK GnuCash usa aritmética de punto fijo, por lo que dudo que usar el punto fijo correctamente sea imposible. – diciu

7

No todos los valores de punto flotante pueden ser representados correctamente por float y double tipos C - más probable es que lo que usted piensa es en realidad algo así como 222222.00000000000000001.

No es un estándar, pero es muy poco conocido para obtener alrededor de ella - C99 utilizar nextafter() función:

printf("%lf", ceil(nextafter(x, 0))); 

Ver man nextafter para más detalles.

+3

¡Interesante! Ver también 'nearbyint' y 'rint'. –

+0

@Jonathan 'nearbyint()' redondea al entero, 'nextafter()' le permite especificar la dirección - 'INFINITY',' 0', '-INFINITY' o cualquier número arbitrario. – qrdl

+1

Sí, lo reconozco. Pero 'nextafter()' produce el valor del punto flotante con el bit menos significativo de la mantisa ajustado en uno: 'se agrega o resta un valor correspondiente al valor del bit menos significativo en la mantisa, dependiendo de la dirección'. La función 'nextafter()' funcionará siempre que el error no sea más de 1 en el bit menos significativo, que probablemente sea el caso aquí. No estaba sugiriendo que 'nearint' o 'rint' reemplazaran a 'nextafter' (o 'nexttoward'); solo que también eran interesantes, y posiblemente relevantes para el PO. –

0

Es posible que desee considerar el uso de decimal floating point types para obtener los resultados que cabría esperar de la aritmética decimal. El hardware de los procesadores de PC de escritorio típicos solo admite el punto flotante binario, por lo que no se puede esperar que produzca los mismos resultados que un cálculo decimal. Por supuesto, si no se admite en hardware, el punto flotante decimal es más lento.

Nota que decimal de coma flotante no es necesariamente más precisa (por ejemplo 1/3 no se puede representar con precisión, así como hay valores de FP binario que no pueden ser representados), se acaba de producir el resultado esperado, como si realizó el cálculo a mano larga en decimales.

1

Si se va a escribir:

const int x = 1.2; 

en un programa en C, ¿qué pasaría? El literal 1.2 no se puede representar como un valor entero, por lo que el compilador se convertiría de acuerdo con las reglas habituales en un entero, y x se le asignaría el valor 1.

Lo mismo está sucediendo aquí.

Usted escribió:

double x = 2.22; 

2.22 no es representable como un número de doble precisión, por lo que el compilador convierte de acuerdo a las reglas de la norma C. Se obtiene el número más cercano representable de doble precisión, que es exactamente:

2.220000000000000195399252334027551114559173583984375 

Cuando este valor se multiplica por 100 en double, el resultado es:

222.000000000000028421709430404007434844970703125 

y cuando se llama ceil() con ese valor como argumento, la biblioteca matemática devuelve correctamente 223.0.

Cuestiones relacionadas