Square root comparison

BlitzMax Forums/BlitzMax Programming/Square root comparison

Czar Flavius(Posted 2010) [#1]
Not sure why anybody would care about this. In my particular game I need to take square roots that calculate exactly the same on every computer, and I don't know if Floats are safe for this. I tried using Mike's Arbitrary Precision Math Library but it's very slow, so I discovered an integer-based square root approximation which is much faster. (The results do not need to be 100% accurate, just identical on all machines) Any other suggestions welcome.

Strict
Import bah.mapm

Function integer_square_root:Int(a)
	'based on http://www.lbebooks.com/downloads/exportal/Verilog_NEXYS_Example24.pdf 
	Local square = 1, delta = 3
	While square <= a
		square = square + delta
		delta = delta + 2
	Wend
	Return delta/2 - 1
End Function

Const tests = 1000
Local time

time = MilliSecs()
For Local i = 0 Until tests
	Sqr(i)
Next
Print "Sqr took " + String(MilliSecs() - time) + " milliseconds."

time = MilliSecs()
For Local i = 0 Until tests
	integer_square_root(i)
Next
Print "integer_square_root took " + String(MilliSecs() - time) + " milliseconds."

time = MilliSecs()
For Local i = 0 Until tests
	New TMAPM.Create(i).Sqrt(8)
Next
Print "TMAPM took " + String(MilliSecs() - time) + " milliseconds."


Sqr took 0 milliseconds.

integer_square_root took 4 milliseconds.

TMAPM took 61 milliseconds.


Edit: That was in debug mode. In release mode:

Sqr took 0 milliseconds.

integer_square_root took 0 milliseconds.

TMAPM took 33 milliseconds.


I also realised it wasn't entirely fair as TMAPM was operating at 8 decimal places compared to integer at 0! However changing this only shaved off a few milliseconds - it's still very slow by comparison.


TaskMaster(Posted 2010) [#2]
Depending on what numbers you are taking the square roots for, you may be able to also use look up tables. It would be even faster if you are taking square roots of a limited set of numbers, like just 1 to 1000 whole numbers. Calculate them ahead of time to 3 decimal places or something and save them in an array.


Czar Flavius(Posted 2010) [#3]
I have a look up table for arctan that will blow your mind. (Coming soon)


Floyd(Posted 2010) [#4]
Just plain Int( Sqr(n) ) should be fine.

This is double precision so there is far more accuracy than you really need.


TaskMaster(Posted 2010) [#5]
Int(Sqr(1))=1
Int(Sqr(2))=1
Int(Sqr(3))=1

I don't think Int(Sqr(n)) will do it?!?!


Floyd(Posted 2010) [#6]
It's exactly what he's doing now, just a lot faster.


Czar Flavius(Posted 2010) [#7]
what if a number is 4.99999 on one computer and 5.00001 on another?


Warpy(Posted 2010) [#8]
Both would have integer root 2. If one was 3.999999 and the other was 4.000001, *then* you'd have a problem.


TomToad(Posted 2010) [#9]
You could also round the result. Decide what precision you need and round to it. Need 3 decimal places? Then something like this should work
SquareRoot:Float = (Int(Sqr(Number)*1000+.5)/1000.0



Warpy(Posted 2010) [#10]
Still doesn't quite get round the boundary problem Czar Flavius is talking about.


Floyd(Posted 2010) [#11]
The proposed work around integer_square_root:Int(a) is working with integers, both input and output.

Sqr( integer ) can't be that close to an integer unless it is exactly an integer. It won't be 4.99999 or 5.00001 for example. Sqr(25) is exactly 5.0 while Sqr(24) and Sqr(26) are nowhere near 5.


Czar Flavius(Posted 2010) [#12]
Ok i'm not worried about the square root so much as for trigonometry. Its a multiplayer game so calculations need to be exact on all computers. I have made look up tables for sin cos and tan but atan2 is proving to be a nightmare. How can i calculate it so that it's the exact same on every computer? One decimal place is sufficient percision. It doesn't matter if the results aren't 100% accurate so long as they are identical on all computers.


ziggy(Posted 2010) [#13]
Czar: Remember than for speed reasons you can always compare the absolute values. To say, to compare 2 distances for instances, you can avoid the SQR, just compare the quadratic value and use SQR only when you need the exact magnitude


Czar Flavius(Posted 2010) [#14]
Ok here is the full problem. A turret needs to find a target in range (distance and sqrt), calculate the angle it needs to face taking into account movement of the unit too (atan2). I have this working with both floats and mapm. I'm concerned about floats being the same in every computer (am i worrying about nothing?) and mapm is SLOOOOW if you have more than a few dozen things going on.


Warpy(Posted 2010) [#15]
if distance < max, then distance*distance < max*max. You don't need to do the square root.
For atan2, you could pick an arbitrarily big radius and compute an atan2 lookup table for all integer co-ordinates on that circle.


TaskMaster(Posted 2010) [#16]
You are trying to solve some multi-player networking issues...

The solution to this sort of problem is to not let the turret make that decision on all of the computers. Pick one machine that will actually be the brains of the turret, either a master server, or on the machine of the player who created it. Let that machine be the only one that controls the brains of the turret. Then when the turret picks a target and fires, just tell all of the other players what the turret has decided to do.

Never let multiple machines try to predict the same output. It will always fail. This goes for everything, firing, whether a bullet hits, collisions, etc. Only let one machine check to see if a certain bullet hit, and if that machine decides there is a hit, then tell the other machines.


Czar Flavius(Posted 2010) [#17]
taskmaster, that's a good idea but it means the game won't run smooth laggy connections. User input is scheduled for a few game frames ahead of the current time, so if there is a sudden lag spike it might not even be perceivable if it's below a few frames' time! letting a computer be the turret's brains would negate this trick and make game performance too dependant on connectivity.

I appreciate the advice and i've experienced first hand how annoying sync issues can be but i actually have it working - just very slow with mapm. my game's network design is based on the implementation of age of empires.

Warpy could you give more info about how to make that lookup table?


Kurator(Posted 2010) [#18]
I don't think that your benchmark has realistic results:

Basically you are comparing 1000 function calls against 1000 object creation + 1000 method calls.

For Local i = 0 Until tests
	New TMAPM.Create(i).Sqrt(8)
Next


to get a reliable result you should only compare the direct calls :)

Local instance:T... = new T...
For Local i = 0 Until tests
	instance.Sqrt(8)
Next


don't know the exact type names, therefor the T...


Czar Flavius(Posted 2010) [#19]
Good point! That saves about a third of the time. Still not enough though :(


Kurator(Posted 2010) [#20]
Should be faster: Herons Method or Babylonian Method

Function integer_square_root:Int(a:Int)
	
	If a=0 Return 0

	Local _a:Int = a
	Local _n:Int = a
	
	Local treshold:Int = 2
	
	_n = (_a + a/_a) / 2
	
	While _n <> _a And treshold > 0
		_a = _n
		_n = (_a + a/_a) / 2
		If Abs(_n - _a) = 1 Then treshold:-1
		'Print "n="+ _n +" a=" + _a
	Wend
	If _n < _a Then _a = _n
	Return _a	
	
EndFunction

http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method

Speed for 1000000 tests (all three functions deliver the same results)

Sqr took 156 milliseconds.
integer_square_root took 31815 milliseconds.
integer_square_root2 took 9461 milliseconds.
integer_square_root3 took 872 milliseconds.

sqr = bmax sqr
integer_square_root = from post 1
integer_square_root2 = enhanced from post 1 (doubling the values instead of incrementing until it is higher than a, then counting down
integer_square_root3 = herons method from this post


Czar Flavius(Posted 2010) [#21]
Thanks!!