views:

568

answers:

5

Two ways to normalize a Vector3 object; by calling Vector3.Normalize() and the other by normalizing from scratch:

class Tester {
    static Vector3 NormalizeVector(Vector3 v)
    {
        float l = v.Length();
        return new Vector3(v.X / l, v.Y / l, v.Z / l);
    }

    public static void Main(string[] args)
    {
        Vector3 v = new Vector3(0.0f, 0.0f, 7.0f);
        Vector3 v2 = NormalizeVector(v);
        Debug.WriteLine(v2.ToString());
        v.Normalize();
        Debug.WriteLine(v.ToString());
    }
}

The code above produces this:

X: 0
Y: 0
Z: 1

X: 0
Y: 0
Z: 0.9999999

Why?

(Bonus points: Why Me?)

A: 

Don't care about this. There's always some error involved when using floats. If you're curious, try changing to double and see if this still happens.

schnaader
A: 

You have here an interesting discussion about String formatting of floats.

Just for reference:

Your number requires 24 bits to be represented, which means that you are using up the whole mantissa of a float (23bits + 1 implied bit).
Single.ToString () is ultimately implemented by a native function, so I cannot tell for sure what is going on, but my guess is that it uses the last digit to round the whole mantissa.
The reason behind this could be that you often get numbers that cannot be represented exactly in binary, so you would get a long mantissa; for instance, 0.01 is represented internally as 0.00999... as you can see by writing:

float f = 0.01f;
Console.WriteLine ("{0:G}", f);
Console.WriteLine ("{0:G}", (double) f);

by rounding at the seventh digit, you will get back "0.01", which is what you would have expected.

For what seen above, numbers with only 7 digits will not show this problem, as you already saw.

Just to be clear: the rounding is taking place only when you convert your number to a string: your calculations, if any, will use all the available bits.

Floats have a precision of 7 digits externally (9 internally), so if you go above that then rounding (with potential quirks) is automatic.
If you drop the float down to 7 digits (for instance, 1 to the left, 6 to the right) then it will work out and the string conversion will as well.

As for the bonus points:

Why you ? Because this code was 'eager to blow on you'.
(Vulcan... blow... ok. Lamest. Punt. Ever)

VonC
A: 

You should expect this when using floats, the basic reason being that the computer processes in binary and this doesn't map exactly to decimal.

For an intuitive example of issues between different bases consider the fraction 1/3. It cannot be represented exactly in Decimal (it's 0.333333.....) but can be in Terniary (as 0.1).

Generally these issues are a lot less obvious with doubles, at the expense of computing costs (double the number of bits to manipulate). However in view of the fact that a float level of precision was enough to get man to the moon then you really shouldn't obsess :-)

These issues are sort of computer theory 101 (as opposed to programming 101 - which you're obviously well beyond), and if your heading towards Direct X code where similar things can come up regularly I'd suggest it might be a good idea to pick up a basic computer theory book and read it quickly.

Cruachan
+2  A: 

Look how they implemented it (e.g. in asm).

Maybe they wanted to be faster and produced something like:

 l = 1 / v.length();
 return new Vector3(v.X * l, v.Y * l, v.Z * l);

to trade 2 divisions against 3 multiplications (because they thought mults were faster than divs (which is for modern fpus most often not valid)). This introduced one level more of operation, so the less precision.

This would be the often cited "premature optimization".

flolo
I think this is the right answer, although I don't agree that it's a bad optimisation. Vectors are normalised a _lot_ in most non-trivial 3D apps, and I think fmul is still a lot faster than fdiv on most CPUs.
DarthPingu
Main reason that it is a bad optimization, is that it is less inaccurate. But even if div takes longer than mult I would assume, that the 3 div version is faster, because, the 3 parallel ops get computed parallel (MMX/SSE). That makes for 3 div version time for 1 div, while other takes 1 div+1 mul.
flolo
I tried the above method and it still yields the same results.
Vulcan Eager
That hasnt to mean anything, maybe your compiler is clever and optimzes it right, and the DirectX implementation is handcoded (or maybe they use double internally). A definitv answer is only possible when we have a look at the asm of the DirectX Normalize, everything else is speculation.
flolo
A: 

If your code is broken by minute floating point rounding errors, then I'm afraid you need to fix it, as they're just a fact of life.

Will Dean