sen, cos, tan y error de redondeo

3 minutos de lectura

sen cos tan y error de redondeo
jose kelley

Estoy haciendo algunos cálculos de trigonometría en C/C++ y tengo problemas con errores de redondeo. Por ejemplo, en mi sistema Linux:

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    printf("%e\n", sin(M_PI));
    return 0;
}

Este programa da la siguiente salida:

1.224647e-16

cuando la respuesta correcta es, por supuesto, 0.

¿Cuánto error de redondeo puedo esperar al usar funciones trigonométricas? ¿Cómo puedo manejar mejor ese error? Estoy familiarizado con la técnica de Unidades en último lugar para comparar números de punto flotante, de Bruce Dawson Comparación de números de punto flotantepero eso no parece funcionar aquí, ya que 0 y 1.22e-16 están bastante separados por ULP.

1647701951 491 sen cos tan y error de redondeo
Martín Beckett

La respuesta es solo 0 para sin (pi). ¿Incluyó todos los dígitos de Pi?

-¿Alguien más ha notado una clara falta de ironía/sentido del humor por aquí?

  • Estoy usando M_PI palabra por palabra de math.h. Si el compilador no incluye todos sus dígitos, entonces algo anda muy mal. 🙂

    –Josh Kelley

    6 oct 2009 a las 19:30

  • Pi tiene una cantidad infinita de dígitos, por lo que dudo mucho que los incluya a todos. Lo que estás viendo está muy cerca de 0 (0.000000000000000001), especialmente para un doble.

    – Ciego

    6 de octubre de 2009 a las 19:34

  • ¿Quizás su math.h no incluye todos los dígitos de pi? ¿Tienes un número infinito de discos duros?

    – Martín Beckett

    6 oct 2009 a las 19:35

  • En realidad, si PI solo se define hasta el límite de precisión de la flotación, entonces cualquier operación que devuelva una flotación solo puede tener una precisión más pobre: ​​fue en esa aburrida conferencia sobre entropía y teoría de la computabilidad.

    – Martín Beckett

    6 de octubre de 2009 a las 19:48

  • @Josh Kelley: “Todavía necesito una forma de manejar esa imprecisión”. Utilice la biblioteca GMP BigNum u otra biblioteca matemática de precisión arbitraria.

    – Chris Lutz

    6 de octubre de 2009 a las 22:18

Un doble IEEE almacena 52 bits de mantisa, con el “principal implícito” formando un número de 53 bits. Por lo tanto, un error en el bit inferior de un resultado constituye aproximadamente 1/2 ^ 53 de la escala de los números. Su salida es del mismo orden que 1.0, por lo que resulta ser exactamente una parte en 10^16 (porque 53*log(2)/log(10) == 15.9).

Entonces sí. Se trata del límite de la precisión que puede esperar. No estoy seguro de cuál es la técnica ULP que está utilizando, pero sospecho que la está aplicando mal.

  • La técnica ULP como se describe en el enlace de la publicación original tendrá este problema para respuestas cercanas a cero. Sospecho que sin (M_PI/2.0) saldrá a algún número c. 10^-16 lejos de 1, pero en ese caso, la técnica ULP debería funcionar.

    –Chris Johnson

    6 de octubre de 2009 a las 19:39

  • Entonces, debido a que la salida de sen, cos y tan está en el rango [0, 1], puedo esperar que los resultados se desvíen en no más de ~ 10e-16 (que, como dijiste, es una ULP de 1). ¿Es eso correcto? (__DBL_EPSILON__ en C y std::numeric_limits<double>::epsilon() en C++ proporcione este número ~10e-16).

    –Josh Kelley

    6 oct 2009 a las 20:19

  • Para ángulos de orden 1, puede esperar una precisión de unos pocos * 10e-16 de seno y coseno (cuya salida está en el rango [-1,1]). Con tan=sin/cos, el rango de salida es ilimitado y es probable que la mejor precisión dependa de cómo se haya escrito la función tan. Sin embargo, los errores absolutos y fraccionarios serán al menos 10e-16.

    –Chris Johnson

    6 oct 2009 a las 21:33

  • Tenga en cuenta que no está calculando sin(pi), ¡sino sin(M_PI)! La constante M_PI no es exactamente Pi, sino la aproximación más cercana representable como un doble: M_PI = (Pi + delta), donde delta es ~10e-16. Combine esto con la expansión de sin(Pi+x) = -x para |x| << 1, y no sorprende en absoluto que sin(M_PI) arroje un resultado tan pequeño.

    – Stephen C. acero

    6 de octubre de 2009 a las 22:21

1647701952 373 sen cos tan y error de redondeo
chux – Reincorporar a Monica

El seno de π es 0.0.
Seno de M_PI es aproximadamente 1.224647e-16.

M_PI no es π.

programa da … 1.224647e-16 cuando la respuesta correcta es, por supuesto, 0.

Code dio una respuesta correcta a 7 lugares significativos.


Lo siguiente no imprime el seno de π. Imprime el seno de un número cercano a π. Vea la foto de abajo.

π                            // 3.141592653589793 2384626433832795...
printf("%.21\n", M_PI);      // 3.141592653589793 115998
printf("%.21f\n", sin(M_PI));// 0.000000000000000 122465

Nota: Con la función matemática seno(x)la pendiente de la curva es -1.0 en x = π. La diferencia de π y M_PI es sobre el sin(M_PI)como se esperaba.


estoy teniendo problemas con errores de redondeo

El problema de redondeo ocurre cuando se usa M_PI para representar π. M_PI es el double más cercano a π, sin embargo, dado que π es irracional y todo finito double son racionales, deben diferir, incluso en una pequeña cantidad. Así que no es un problema de redondeo directo con sin(), cos(), tan(). sin(M_PI) simple expuesto el problema comenzó con el uso M_PI – un π inexacto.


Este problema, con diferentes resultados distintos de cero de sin(M_PI)ocurre si el código usa un tipo de FP diferente como float, long double o double con algo más que 53 bits binarios de precisión. Este no es tanto un problema de precisión como uno irracional/racional.

Seno(x) cerca de π

@Josh Kelley: está bien, respuesta seria.
En general, nunca debe comparar los resultados de ninguna operación que involucre flotadores o dobles entre sí.

Las únicas excepciones es la asignación.
flotante a=10.0;
flotante b=10.0;
entonces a==b

De lo contrario, siempre debe escribir alguna función como bool IsClose (float a, float b, float error) para permitirle verificar si dos números están dentro del ‘error’ uno del otro.
Recuerde verificar también los letreros/usar fabs; podría tener -1.224647e-16

Hay dos fuentes de error. La función sin() y el valor aproximado de M_PI. Incluso si la función sin() fuera ‘perfecta’, no devolvería cero a menos que el valor de M_PI también fuera perfecto, que no lo es.

  • En realidad, el requisito de sin(M_PI) devolver cero no sería que M_PI fuera perfecto, sino que la reducción de modulación se realizaría como si M_PI fuera perfecto. Excepto en casos excepcionales en los que se intenta simular una función cuyo período es un múltiplo de pi representable con precisión, los argumentos de las funciones trigonométricas a menudo se escalarán mediante M_PI o un equivalente; se puede lograr una precisión óptima si se realiza la reducción de modulación usando ese mismo valor.

    – Super gato

    7 junio 2014 a las 18:20

1647701953 441 sen cos tan y error de redondeo
david thornley

Prefiero pensar que dependerá del sistema. No creo que el Estándar tenga nada que decir sobre qué tan precisas serán las funciones trascendentales. Desafortunadamente, no recuerdo haber visto ninguna discusión sobre la precisión de la función, por lo que probablemente tendrá que resolverlo usted mismo.

  • En realidad, el requisito de sin(M_PI) devolver cero no sería que M_PI fuera perfecto, sino que la reducción de modulación se realizaría como si M_PI fuera perfecto. Excepto en casos excepcionales en los que se intenta simular una función cuyo período es un múltiplo de pi representable con precisión, los argumentos de las funciones trigonométricas a menudo se escalarán mediante M_PI o un equivalente; se puede lograr una precisión óptima si se realiza la reducción de modulación usando ese mismo valor.

    – Super gato

    7 junio 2014 a las 18:20

A menos que su programa requiera dígitos significativos hasta el lugar decimal 16 o más, probablemente pueda hacer el redondeo manualmente. Desde mi experiencia programando juegos, siempre redondeamos nuestros decimales a un dígito significativo tolerable. Por ejemplo:

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define HALF 0.5
#define GREATER_EQUAL_HALF(X) (X) >= HALF

double const M_PI = 2 * acos(0.0);

double round(double val, unsigned  places = 1) 
{
    val = val * pow(10.0f, (float)places);
    long longval = (long)val;
    if ( GREATER_EQUAL_HALF(val - longval) ) {
       return ceil(val) / pow(10.0f, (float)places);
    } else {
      return floor(val) / pow(10.0f, (float)places);
    }
}

int main() 
{
    printf("\nValue %lf", round(sin(M_PI), 10));
    return 0;
}

  • Tenga en cuenta que double round(double); es una función en la biblioteca estándar C99, y ese código de producción debería usar otro nombre para ella. Además, esta es una pregunta de C, no de C++, así que no <cmath> y amigos.

    – Chris Lutz

    6 de octubre de 2009 a las 22:21

  • Además de lo anterior, parece que ISO ya no incluye M_PI como una constante en la biblioteca estándar C99. El uso de M_PI no funcionará en todos los compiladores, como Visual Studio.

    –Matt Pascoe

    7 de octubre de 2009 a las 13:26

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad