Floating point accuracy across computers?

This topic is 1321 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I read the 1500 Archers paper on AoE, which specifies a networking model for RTS games known as "lockstepping", where only the actions and mouse positions each player issues are sent over the network. Contrary to the server-client networking model, this requires all clients to be able to simulate the game the exact same way.

How viable is it to use floating point numbers for things like unit positions?

Do all computers (including different operating systems) compute floats the same way, so that every client gets the same floating point error after every operation? What are potential pitfalls to look out for if trying to synchronise floating point arithmetic over multiple computers?

Would it be wise to go down the floating point route, or do you think it would be safer to keep all critical variables as integers?

Share on other sites

Thanks for the great information thus far!

@ApochPiQ - If I ever knock on your PM front door with a wall of text on how gay floats are, you will know I have failed and can be safe to assume I was weeping in a fetal position in the dustiest corner of my room as I typed it.

@Frob - That "Real" class you talked about, is that publicly available?

From what I gather, the safest approach would be to use a fixed point implementation for all critical data. I would have done this with two integers representing a "value" and a "factor". I suppose converting to and from floating point values is determent so long as the floating point error is insignificant enough. Is something like the following a good approach?

#include <iostream>

template <class I, class F>
class Real
{
public:

inline Real(const I factor) : m_Factor(factor)
{
}
inline Real(const I factor, const F value) : m_Factor(factor), m_Value(static_cast<I>(factor*value))
{
}
inline Real(const I factor, const I value) : m_Factor(factor), m_Value(factor*value)
{
}

// assignment
inline Real& operator=(const Real& other)
{
m_Value = other.m_Value;
m_Factor = other.m_Factor;
return *this;
}
inline Real& operator=(const F f)
{
m_Value = static_cast<I>(m_Factor * f);
return *this;
}
inline Real& operator=(const I i)
{
m_Value = m_Factor*i;
return *this;
}
inline F operator=(const Real& r) const
{
return static_cast<F>(r.m_Value) / r.m_Factor;
}

// binary arithmetic
inline Real& operator+=(const Real& rhs)
{
m_Value += rhs.m_Value * m_Factor / rhs.m_Factor;
return *this;
}
inline Real& operator+=(const F f)
{
m_Value += static_cast<I>(f * m_Factor);
return *this;
}
inline Real& operator+=(const I i)
{
m_Value += m_Factor * i;
}
inline friend Real operator+(Real lhs, const Real& rhs)
{
lhs += rhs;
return lhs;
}
inline friend Real operator+(Real lhs, const F rhs)
{
lhs += rhs;
return lhs;
}
inline friend Real operator+(Real lhs, const I rhs)
{
lhs += rhs;
return lhs;
}

// ... got lazy

friend std::ostream& operator<<(std::ostream& os, const Real& r)
{
std::cout << "Real(" << r.operator=(r) << ")" << std::endl;
}
private:

I m_Value;
const I m_Factor;
};


Example usage:

#include <fpp.hxx>

#define REAL Real<unsigned long, float>

int main()
{
REAL test1(1024, 6.0f); // two numbers with different fixed-point factors
REAL test2(2048, 6.5f);

test1 += test2;  // test1 = 12.5
test1 += 7.7f;    // test1 = 20.2

std::cout << test1 << std::endl;
return 0;
}



Lots of overloads for the operators, though...