Fractions and negative numbers in assembly

Página 1/2
| 2

For my game, I'd like to implement velocities in player movement. I know how to do that in languages that have fractions and negative numbers, but I'm having trouble figuring out how to do it in assembly.

The way velocity works is like this: the player has an x-coordinate, and an x-velocity. When the right key is pressed, the velocity is increased by a fraction, say 0.4. The velocity is then added to the x-coordinate. As long as the right key is pressed, on every game loop the velocity will be increased until it reaches its maximum value, say 1.5. When the key is released, 'friction' is applied to slow the player down. Let's also set it to 0.4. So every game loop, the velocity will be decreased by 0.4 until it reaches 0. For the left key, the velocity would be decreased to -1.5.

I'm reading up on one's and two's complements for the negative number calculations. How do the fractions work? Do I simply shift the velocity left when calculating velocity and right when applying them to the x-coordinate?

Login sesión o register para postear comentarios

You need to look into fixed point numbers. That two's complement isn't really that tricky, it just comes automatically. You do all the calculations with "too big" numbers and then shift them right to get screen coordinates when drawing. With a fractional part of 4 bits (shifting by 4, the same as multiplying by 16):

X position of 10 becomes 160
X velocity of -0.5 becomes -8

Keep adding the velocity normally without any shifting or you'll lose the precision. Only when drawing the actual pixel/sprite turn the fixed point into a coordinate:

screenx = X>>4

Yes, except that you don't do the shifting. What you are looking for is called fixed point arithmetic.
What you do is that you just decide where to put you decimal point. Then for converting your value from a fixed point value to an integer you shift down that many steps.

It is common to use an 8 bit integer part with an 8 bit fractional part. That way you only use the top byte to get the integer part and avoid shifting completely.

So keep both the position and the velocity as fixed point values, that way the movement can be done with a simple addition: "add hl, de" and you have fractional positions which will look a lot smoother.
Then when you render the graphics to the screen you get the integer part from the position which only is the topmost byte.

For fractions, it’s fastest and easiest to use fixed point.

If you have a 16-bit value, xxyy, the MSB indicates the whole number and the LSB the fraction, like so: xx.yy.

To add or subtract, you simply use regular additions and subtractions. So, if you have say the number 13, and want to add 0.5, you add 0D.00 + 00.80 = 0D.80 (note, here 0.5 is 100H * 0.5 = 80H).

For multiplications, if you want to multiply by a whole number simply multiply it: 0D.80 * 0005 = 43.80. Note that there’s no dot in the multiplier here. This is because with multiplications, the number of fraction digits add up. If you want to multiply by a fraction (say, 0.5), it looks like this: 0D.80 * 00.80 = 6.C000. Because the multiplicand and multiplier both have a 2-digit fractional part, the result will have a 4-digit fractional part (you’ll need a 24- or 32-bit multiplication).

Thanks, Marq! I understand what you mean and I think that'll work perfectly.

Right, F700D booted, Compass started, game code loaded, coffee's ready too, code, code, code. Let's see if I can finish this today, along with jumping (no collisions yet, that'll come later). Edit: even more tips from Bore and Grauw. Thanks, guys! I was thinking way too complicated. I'm sure I'll get this done today. And of course you can freely choose the number of fractional bits to suit your needs (it can even be just a 1 or 2 bit fractional part), and ignore either the fractional or integer parts of calculation results (and thus potentially simplify the calculation) depending on which one you’re actually interested in.

As for negative numbers, use two’s complement. It is preferable to one’s complement because it requires very little special handling compared to unsigned math, whereas one’s complement often has to treat negative numbers differently. One’s complement has its uses (I use it in Synthesix), but usually it’s not what you want.

The basic idea is, if an 8-bit value is signed then bit 7 is interpreted as the sign, which indicates whether the value is positive (0) or negative (1). The value -1 is represented by FFH, -2 is FEH, …, -128 is 80H. From this follows that the maximum positive value is 127. If you have the number 5, and add -2, you do: 05H+FEH = 03H. Due to the overflow of the 8-bit number, you get the correct result! Similarly for subtracting -2 from 5: 05H-FEH = 07H.

You can also mix signed and unsigned math; even if the first number is unsigned, you can still subtract from it by adding a signed “-1”. If the addend is signed, the signedness of the result depends on the signedness of the augend. For example in the case of 93H+FEH = 91H, assuming the addend is signed and thus represents -2, the augend can be either the number 147 or -109, and the result should thus be interpreted as either 145 or -111 depending on whether the augend is an unsigned (0…255) or signed (-128…127) number.

Z80 has some built-in support for two’s complement, e.g. there’s the sign flag to check bit 7 of a calculation result which indicates the sign. Also it can detect sign overflows during additions using the P/V flag (though you seldom need that so you can ignore it for now ). To negate a value there’s the neg instruction (the bitwise cpl inversion is useful for 1’s complement btw). There’s also the sra r shift instruction which preserves the sign while shifting.

The assembler also comes with built in support for two’s complement signed numbers; if you enter -1 as a value anywhere, e.g. add a,-1, it will actually generate add a,FFH.

Yay, done! Well, not the jumping, but the horizontal velocity and friction work. It took some time to find a comfortable way to handle signed fixed point numbers, but I did it. Even made two new friends in the process: RET P and RET M.

Time for some Hydlide 3, but after that, I'll start working on jumping. One thing not clearly mentioned here is that it is good to remember that -X = NOT (X-1)
Meaning ie. if you want to turn HL in to -HL you can do something like:

LD A,H
CPL
LD H,A
LD A,L
CPL
LD L,A
INC HL

Or, 0 - hl:

ex de,hl
ld hl,0
sbc hl,de

Thanks, guys. I'm getting the hang of using the two's complement. It's surprisingly easy to work with, especially since Compass converts negative numbers to two's complement numbers automatically and the Z80 has the sign flag.

I haven't had time to work on the jumping part, but I'll start building that tonight, I hope. Página 1/2
| 2