views:

355

answers:

7

Hi,

I'm developing for a platform without a math library, so I need to build my own tools. My current way of getting the fraction is to convert the float to fixed point (multiply with (float)0xFFFF, cast to int), get only the lower part (mask with 0xFFFF) and convert it back to a float again.

However, the imprecision is killing me. I'm using my Frac() and InvFrac() functions to draw an anti-aliased line. Using modf I get a perfectly smooth line. With my own method pixels start jumping around due to precision loss.

This is my code:

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;
}

Thanks in advance!

+2  A: 

If I understand your question correctly, you just want the part after the decimal right? You don't need it actually in a fraction (integer numerator and denominator)?

For float f;:

f = f-(long)f;
Daniel Bingham
An if... then... else... in a math function as often used as this? My cache, it weeps!
knight666
This is wrong when `f` is negative. (You're adding two negative numbers.) You don't need the `if` at all: `f = f - (int) f`. If `f` is negative it'll be subtracting a negative int that's rounded toward zero.
jamesdlin
Also, you're assuming that the integer portion of the float fits in an int.
jamesdlin
@jamesdlin Thanks, psyched myself out with the negative part. And right you are about the float/integer thing.
Daniel Bingham
Oh well. This answer is both the easiest to implement and the fastest (about 20% faster than my method). Accepted!
knight666
+2  A: 

I would recommend taking a look at how modf is implemented on the systems you use today. Check out uClibc's version.

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

(For legal reasons, it appears to be BSD licensed, but you'd obviously want to double check)

Some of the macros are defined here.

sharth
Why all the bit shifting, is really that much of a speed gain? Or does my little int conversion trick have some issue I'm missing?
Daniel Bingham
@Daniel Bingham: likely the latter. Floats may not be encoded in the way you think on the platform you're using, so your masks may be off. @sharth: your link relies on some macros, and I'm having trouble finding them. Can you try your luck and find definitions for EXTRACT_WORDS, INSERT_WORDS, and GET_HIGH_WORD?
Randolpho
Int to float is fine. Float to int is horribly slow.
knight666
@Randolpho: http://git.uclibc.org/uClibc/tree/libm/math_private.h
sharth
+2  A: 

As I suspected, modf does not use any arithmetic per se -- it's all shifts and masks, take a look here. Can't you use the same ideas on your platform?

AVB
+2  A: 

I'm not completly sure, but I think that what you are doing is wrong, since you are only considering the mantissa and forgetting the exponent completely.

You need to use the exponent to shift the value in the mantissa to find the actual integer part.

For a description of the storage mechanism of 32bit floats, take a look here.

Bruno Brant
A: 

Your method is assuming that there are 16 bits in the fractional part (and as Mark Ransom notes, that means you should shift by 16 bits, i.e. multiply by by 0x1000). That might not be true. The exponent is what determines how many bit there are in the fractional part.

To put this in a formula, your method works by calculating (x modf 1.0) as ((x << 16) mod 1<<16) >> 16, and it's that hardcoded 16 which should depend on the exponent - the exact replacement depends on your float format.

MSalters
A: 

There's a bug in your constants. You're basically trying to do a left shift of the number by 16 bits, mask off everything but the lower bits, then right shift by 16 bits again. Shifting is the same as multiplying by a power of 2, but you're not using a power of 2 - you're using 0xFFFF, which is off by 1. Replacing this with 0x10000 will make the formula work as intended.

Mark Ransom
A: 

Why go to floating point at all for your line drawing? You could just stick to your fixed point version and use an integer/fixed point based line drawing routine instead - Bresenham's comes to mind. While this version isn't aliased, I know there are others that are.

Bresenham's line drawing

Michael Dorgan