Agosto 11, 2015, 03:59:41 AM Ultima modificación: Agosto 11, 2015, 06:25:32 PM por Clamud
Hola, hice un programa para encontrar la circunferencia que pasa por 3 puntos (usando el método de sistema de ecuaciones), pero hay pérdidas debidas al redondeo y en ocasiones, al comparar el radio con las distancias de los puntos al centro de la circunferencia,  no se consideran iguales (usando el operador ==).
A veces las diferencias son insignificantes y los números no se consideran iguales y otras veces las diferencias son de la misma magnitud ¡y si se consideran iguales!.

Si sólo quisiera dibujar el círculo no importaría, el problema es que pienso usar el algoritmo para generar una triangulación de Deloné (o Delaunay) y si no hay seguridad en las comparaciones el sistema se va a complicar mucho.

En GMS el problema se puede solucionar con la función math_set_delta, sin embargo, en GM8 no existe esa función. He pensado en usar string_format para redondear hasta un número fijo de cifras decimales, pero esa función es demasiado lenta y sería necesario usarla en cientos o miles de iteraciones, además hay que hacer comparaciones de intervalos (<, >), entonces se debería usar la función real que también es lenta. Otra solución sería comparar los números a nivel de bits, pero no estoy seguro de cómo hacerlo. ¿Alguien tiene otra idea?

El proyecto está adjunto. Los controles son:
Click izquierdo - agregar un punto
Click derecho - reiniciar
Enter - agregar puntos con el teclado
Espacio - agregar puntos aleatorios

Las siguientes combinaciones de puntos producen resultados diferentes:

una causa error
( 365, 302 )
( 383, 184 )
( 548, 199 )

y la otra no
( 394, 470 )
( 369, 184 )
( 191, 251 )

#1 Agosto 11, 2015, 04:48:58 AM Ultima modificación: Agosto 11, 2015, 05:19:26 AM por fasst007
Tengo dos alternativas, la más rápida sería la segunda pero de todas formas las listo a las dos:

PRIMERA ALTERNATIVA

¿la función abs es lenta? porque podrías comparar de la siguiente manera:

en lugar de: if (a == b) podrías hacer:

Primero definir una tolerancia o epsilon, como por ejemplo:0.0001;
if( abs(a-b) < 0.0001)

esto sería como decir: la diferencia entre esos dos valores numéricos es menor a 0.0001, si lo es entra en el if considerándolos iguales.

SEGUNDA ALTERNATIVA

sino tambien sin usar abs:

diferencia = a-b;
if (diferencia < 0) diferencia *= -1;

if (diferencia < 0.0001)
{

}

o incluso mejor, ahorrando un cálculo de multiplicación y una escritura de menos en la variable diferencia:

if ((a-b) < 0) 
    diferencia = b-a;
else
    diferencia = a-b;

if (diferencia < 0.0001)
{

}


lo que no llamaría a ninguna función. Esto sería muy rápido.

Y como última alternativa quedaría que luego de diferencia = a-b; quitarle el bit de signo a la variable diferencia mediante alguna operación de bit (primero hay que saber la estructura interna de las variables numéricas de game maker) antes de hacer la comparacion . Pero creo que sería innecesario complicarse, el último código dado sería muy rápido.

#2 Agosto 11, 2015, 06:59:51 AM Ultima modificación: Agosto 11, 2015, 07:16:45 AM por fasst007
Te adjunté un archivo fuente que puedes descargar. Con la idea del comentario anterior hice un pequeño script llamado  "igual" que verifica si dos valores pasados por parámetros son iguales y devuelve "true" si lo son y "false" si no lo son. Aquí su código:

epsilon = 0.0001;

if((argument0-argument1) < 0)
   return((argument1-argument0) < epsilon);
else
   return((argument0-argument1) < epsilon);

   
y en lugar de:

if( r!=r1 or r!=r2 or r!=r3 ) show_message("radio incorrecto"); 

(en el script scCirc) lo reemplazo por su equivalente pero con la función nuestra:

    if !(igual(r,r1) and igual(r,r2) and igual(r,r3)) show_message("radio incorrecto");

y parece que funciona.



Gracias fasst007, es un buen método. Entre todas las alternativas prefiero usar la que lleva abs, pues he notado que es mejor usar poco código; en GM8 la velocidad de interpretación del código es lenta en comparación con la velocidad de las funciones matemáticas.

Acabo de recordar que hay otro método para resolver el problema (con mediatrices), si en ese método se necesitan menos operaciones es probable que el resultado sea más preciso.

Y en mi mensaje anterior me equivoqué en el nombre de una función, en realidad se llama math_set_epsilon.

#4 Agosto 11, 2015, 02:50:04 PM Ultima modificación: Agosto 11, 2015, 03:46:36 PM por fasst007
Buen dato! entonces mucho mejor, porque el uso de abs() te simplifica mucho las cosas y te ahorrarías de hacer código casero. Claro yo estoy acostumbrado a un lenguaje compilado donde en ese caso sería mucho más rápido dos o tres if que un llamado a función y en game maker solo tengo un mes de experiencia XD y olvidaba que es interpretado.