Hi. :)

I tried to come up with a way to convert a real4 to (int)floor(x), that is, I want to remove the fractional component from a floating point number (I do NOT want to round it up, in other words)

I've seen floor() done by changing the rounding mode in the FPU's control word. I'm almost a total FPU newbie, so that doesn't help me much. I haven't found any good documentation on the rounding modes and other modes, so I'm trying it this way.

I looked at an 32bit IEEE format reference, and used it to come up with this algo, which seems to work for small numbers, but past a certain point it ceases to function properly.

[size=12]floorR4 proc uses ecx edx, fpnumber:REAL4

; floorR4
; returns (int)floor(float n)

xor eax, eax
xor ecx, ecx
mov edx, REAL4 PTR [fpnumber]
shld ecx, edx, 9 ;ch = sign bit
sub cl, 126 ;cl = exponent + 1
jc @done
shl edx, 8
or edx, 80000000h ;edx = decimal part
shld eax, edx, cl ;eax = edx*2^cl
shr ecx, 8
neg ecx
add eax, ecx
xor eax, ecx ;reapply sign
@done:
ret

floorR4 endp[/size]


If anybody here might want to help me understand these things a little better, I will be highly appreciative. Thanks. ;)
Posted on 2002-10-25 22:52:09 by iblis
You want to truncate the fractional part but you don't want a round off? Try this one, maybe it'll work. Where x is a floating data type to truncate and y is equal to 0.5
fld x

fsub y
frndint
fstp x
Posted on 2002-10-26 00:00:22 by stryker
Stryker, thanks.

I tried that, but for some reason it messes up on every odd whole number. 1.0, 3.0, 5.0, 7.0, etc. It gives me x - 1 as the answer. For example floor( 1.0 ) returns 0.0, which is wrong.

:(
Posted on 2002-10-26 01:37:32 by iblis
int controlword;

int controltruncate = 0xFFF;
float x = -5.9;
_asm
{
fstcw controlword
fldcw controltruncate
fld x
frndint
fstp x
fldcw controlword
}
x now contains the truncated float, sign bit is still retained only the mantissa bits were chopped off :) - sorry for tainting this in C, I just need a quick solution.
Posted on 2002-10-26 03:09:14 by stryker
Agner Fog's optimizations help file that is included in masm32 has a chapter about rounding floating point numbers (chapter 27.5). It shows a method not using the FPU control word since the fstcw & fldcw are slow instructions.

Thomas
Posted on 2002-10-26 04:40:45 by Thomas
Thanks all.

I checked out Agner Fog's code for rounding towards 0 but it seemed rather long and clunky. fxxcw can't be that slow can it?

I really liked Stryker's idea of subtracting 0.5 before doing the rounding, but for some reason it didn't work. But, on a hunch I decided to instead sub 0.49999 and so far it's 100% spot-on when compared with C Library's floor(). The program I'm using to test it generates random floating point numbers in an endless loop, comparing C's floor() with this new one. If floor1 != floor2 then it stops, prints the number, and beeps. It's been hours and the thing hasn't beeped yet. :alright:

[size=12]floor proc fpnum:dword


finit
mov eax, dword ptr [fpnum] ;eax = float
and eax, 80000000h ;save sign bit
push 3EFFFFECh ;push 0.49999
fld dword ptr [esp] ;load it
fld dword ptr [fpnum] ;load float
fabs ;remove sign
fsub st, st(1) ;adjust
frndint ;round
fst dword ptr [esp] ;store in temp
or eax, dword ptr [esp] ;replace sign
add esp, 4
ret

floor endp[/size]


Excuse my FPU code. I barely have any idea what I'm doing when it comes to the FPU. Any suggestions would be appreciated.
Posted on 2002-10-26 08:12:06 by iblis
Sorry to burst your bubble, but if you try 403FFFFFh (2.9999998) your function will exploit :-) The well-known trick of adding 0.5 (to round) or substract 0.5 (to truncate) works flawlessly if you change the order of magnitude like this:

v100 real4 100.0
v05 real4 0.5
num real4 3.0

fld num
fmul v100
fadd v05
frndint
fdiv v100

but then you have to perform the division which isn't as fast as you would like.

I really believe that changing the CW is the best option...
Posted on 2002-10-26 13:24:27 by micmic
If the rounding mode is known the problem reduces to a couple instructions, else use Agner's algorithm.
Posted on 2002-10-26 19:17:59 by bitRAKE
Thanks everybody. You've been a lot of help.

I think changing the rounding mode is the best solution for my purposes. However, I can't in good conscience just go ahead and fool around with the FPU's control word without knowing what I'm doing. At least not until I find some info on the control word, which I haven't been able to locate. FPU info seems to be scarce these days. (yes I've searched the boards extensively, didn't find anything on the cw)

Does anybody know of some reference material that details the control word and the different modes? Thanks!

Edit: Also, regarding F(N)INIT, I read somewhere on these boards while searching for CW info that FINIT isn't necessary. Yet if I don't include it my FPU code seems to stop functioning after awhile. Can anybody clear this issue up for me? Thanks.
Posted on 2002-10-26 19:35:51 by iblis
I think that is because you have to clean up the FPU stack (e.g mark the tag bits for each register as empty). You can do this using the F/EMMS instruction but I don't think it is a good idea. Probably better to use FINIT.
Posted on 2002-10-26 19:45:57 by x86asm
However, I can't in good conscience just go ahead and fool around with the FPU's control word without knowing what I'm doing.


It's not so terrible, you are not going to overheat the processor or something like that :) The code that Stryker posted is all that you have to do. For more info read the FP chapter of Hyde's book, you won't need anything more: http://webster.cs.ucr.edu/Page_asm/ArtofAssembly/ch14/CH14-1.html
Posted on 2002-10-26 20:07:59 by micmic
iblis, is it a sin to read the Intel manuals - really no better source - Volume 1 has all you need. :)
Posted on 2002-10-26 20:40:32 by bitRAKE
What I meant was that I don't want to just use any ol' line of code unless I know what's going on behind the scenes. I know the CPU isn't going to explode or anything of that sort. I'm not that stupid. Give me some credit here. ;)

Setting the control word to 0FFFh apparently works like a charm, but I still want to know what flags I'm fiddling with. I'm not the type of person to say "Hey gimme some code to do X." I don't want that, I want to learn without just accepting things on faith.

I'm going to take Bitrake's suggestion and have a look at the intel manuals, wherever they are. I'm just trying to break out of my 486 cocoon and learn about the FPU and then eventually I'll tackle MMX.

Thanks again.
Posted on 2002-10-26 22:17:28 by iblis