knight666
knight666

Reputation: 1619

Getting the fractional part of a float without using modf()

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!

Upvotes: 35

Views: 84471

Answers (10)

Emil Laine
Emil Laine

Reputation: 42828

If you can use the standard library, fmod(x, 1) does it:

#include <math.h>

fmod(1.42, 1); // => 0.42
fmod(-1.42, 1); // => -0.42

Upvotes: -2

TheWhitde
TheWhitde

Reputation: 41

double frac(double val)
{
    return val - trunc(val);
}

// frac(1.0) = 1.0 - 1.0 = 0.0 correct
// frac(-1.0) = -1.0 - -1.0 = 0.0 correct
// frac(1.4) = 1.4 - 1.0 = 0.4 correct
// frac(-1.4) = -1.4 - -1.0 = -0.4 correct

Simple and works for -ve and +ve

Upvotes: 0

AVB
AVB

Reputation: 4024

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?

Upvotes: 10

Daniel Bingham
Daniel Bingham

Reputation: 12914

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)?

So we have some number, say 3.14159 and we want to end up with just 0.14159. Assuming our number is stored in float f;, we can do this:

f = f-(long)f;

Which, if we insert our number, works like this:

0.14159 = 3.14159 - 3;

What this does is remove the whole number portion of the float leaving only the decimal portion. When you convert the float to a long, it drops the decimal portion. Then when you subtract that from your original float, you're left with only the decimal portion. We need to use a long here because of the size of the float type (8 bytes on most systems). An integer (only 4 bytes on many systems) isn't necessarily large enough to cover the same range of numbers as a float, but a long should be.

Upvotes: 62

Victor Engel
Victor Engel

Reputation: 2113

Seems like maybe you want this.

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

Upvotes: 1

Michael Dorgan
Michael Dorgan

Reputation: 12515

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

Upvotes: 1

Mark Ransom
Mark Ransom

Reputation: 308130

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.

Upvotes: 5

MSalters
MSalters

Reputation: 179789

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.

Upvotes: 0

Bill Lynch
Bill Lynch

Reputation: 81926

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.

Upvotes: 5

Bruno Brant
Bruno Brant

Reputation: 8564

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.

Upvotes: 3

Related Questions