2010-04-07 9 views
17

Estoy desarrollando una plataforma sin una biblioteca matemática, así que tengo que crear mis propias herramientas. Mi forma actual de obtener la fracción es convertir el flotador en punto fijo (multiplicar con (flotante) 0xFFFF, lanzar a int), obtener solo la parte inferior (máscara con 0xFFFF) y convertirlo nuevamente a un flotador.Obtener la parte fraccionaria de un flotante sin usar modf()

Sin embargo, la imprecisión me está matando. Estoy usando mis funciones Frac() e InvFrac() para dibujar una línea anti-alias. Utilizando modf obtengo una línea perfectamente uniforme. Con mi propio método los píxeles comienzan a saltar debido a la pérdida de precisión.

Este es mi código:

const float fp_amount = (float)(0xFFFF); 
const float fp_amount_inv = 1.f/fp_amount; 

inline float Frac(float a_X) 
{ 
    return ((int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv; 
} 

inline float Frac(float a_X) 
{ 
    return (0xFFFF - (int)(a_X * fp_amount) & 0xFFFF) * fp_amount_inv; 
} 

Gracias de antemano!

+2

¿No debería fp_amount ser 0x10000 en lugar de 0xFFFF? –

+1

Santa mierda. ¡Hazlo una respuesta para que yo pueda aceptarlo! ¡Has solucionado todo mi problema de precisión! – knight666

Respuesta

35

Si entiendo su pregunta correctamente, solo quiere la parte después del decimal ¿no? No lo necesita realmente en una fracción (numerador y denominador entero)?

Así que tenemos un número, digamos 3.14159 y queremos terminar con solo 0.14159. Suponiendo que nuestro número se almacena en float f;, podemos hacer esto:

f = f-(long)f; 

Lo cual, si insertamos nuestro número, funciona así:

0.14159 = 3.14159 - 3; 

Lo que esto hace es quitar toda la parte del número de la flotar dejando solo la porción decimal. Cuando convierte el flotador en un largo, cae la porción decimal. Luego, cuando lo restas del flotador original, te queda solo la parte decimal. Necesitamos usar un largo aquí debido al tamaño del tipo float (8 bytes en la mayoría de los sistemas). Un entero (solo 4 bytes en muchos sistemas) no es necesariamente lo suficientemente grande como para cubrir el mismo rango de números que float, pero debe ser long.

+4

An if ... then ... else ... en una función matemática tan utilizada como esta? Mi caché, ¡llora! – knight666

+7

Esto está mal cuando 'f' es negativo. (Está agregando dos números negativos). No necesita el 'if' en absoluto:' f = f - (int) f'. Si 'f' es negativo, se restará una int negativa que se redondea hacia cero. – jamesdlin

+5

Además, supones que la parte entera del flotador se ajusta en un int. – jamesdlin

4

Recomendaría echar un vistazo a cómo se implementa modf en los sistemas que usa actualmente. Mira la versión de uClibc.

http://git.uclibc.org/uClibc/tree/libm/s_modf.c

(Por razones legales, que parece ser BSD licencia, pero es obvio que querría comprobar)

Algunas de las macros se definen here.

+0

¿Por qué todo el cambio de bit es realmente una gran ganancia de velocidad? ¿O mi pequeño truco de conversión int tiene algún problema que me falta? –

+1

@Daniel Bingham: probablemente este último. Los flotantes no se pueden codificar de la manera en que piensas en la plataforma que estás usando, por lo que es posible que tus máscaras estén desactivadas. @sharth: su enlace depende de algunas macros, y tengo problemas para encontrarlas. ¿Puedes probar tu suerte y encontrar definiciones para EXTRACT_WORDS, INSERT_WORDS y GET_HIGH_WORD? – Randolpho

+0

Int flotar está bien. Flotar a int es terriblemente lento. – knight666

7

Como sospechaba, modf no utiliza ningún aritmética per se - es todos los turnos y máscaras, echar un vistazo here. ¿No puedes usar las mismas ideas en tu plataforma?

+0

El enlace está roto, ¿pueden intentar actualizarlo? –

+0

Wayback machine to the rescue: https://web.archive.org/web/20121030234640/http://www.raspberryginger.com/jbailey/minix/html/modf_8c-source.html – Kaganar

2

No estoy del todo seguro, pero creo que lo que estás haciendo está mal, ya que solo estás considerando la mantisa y olvidar por completo el exponente.

Necesita usar el exponente para cambiar el valor en la mantisa para encontrar la parte entera real.

Para una descripción del mecanismo de almacenamiento de los flotadores de 32 bits, eche un vistazo a here.

0

Su método supone que hay 16 bits en la parte fraccionaria (y, como señala Mark Ransom, eso significa que debe desplazar en 16 bits, es decir, multiplicar por por 0x1000). Eso puede no ser cierto. El exponente es lo que determina cuántos bits hay en la parte fraccionaria.

Para poner esto en una fórmula, su método funciona mediante el cálculo de (x modf 1.0) como ((x << 16) mod 1<<16) >> 16, y es que hardcoded 16 que debe depender del exponente - el reemplazo exacto depende de su formato flotante.

4

Hay un error en sus constantes. Básicamente estás tratando de hacer un desplazamiento hacia la izquierda del número en 16 bits, enmascarar todo menos los bits más bajos, luego girar a la derecha en 16 bits nuevamente. Cambiar es lo mismo que multiplicar por una potencia de 2, pero no está usando una potencia de 2; está usando 0xFFFF, que está desactivado en 1. Si lo reemplaza con 0x10000, la fórmula funcionará como se esperaba.

1

¿Por qué ir a punto flotante en absoluto para su dibujo lineal? Simplemente puede apegarse a su versión de punto fijo y usar una rutina de dibujo de líneas basadas en puntos enteros/puntos fijos en su lugar - Bresenham viene a la mente. Si bien esta versión no tiene alias, sé que hay otros que sí lo son.

Bresenham's line drawing

+0

¿Quiere decir anti-aliased? –

+0

Para líneas antialias, vea [Líneas Wu] (https://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm) – Bim

1

parece que tal vez quiere esto.

float f = something; 
float fractionalPart = f - floor(f); 
+0

Esto no funciona para números negativos. –

+0

'floor' también es más lento que un elenco. – aledalgrande

0

Una opción es usar fmod(x, 1).

Cuestiones relacionadas