Fastest way to get the decimals of a float?

Just starting out? Need help? Post your questions and find answers here.
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Fastest way to get the decimals of a float?

Post by superadnim »

How can I get the decimals of a float variable, the fastest way? (I don't mind ASM)

Here is what I'm currently doing through an equivalent of type casting

Code: Select all

Macro decimals( _n_ ) : (_n_-Int(_n_)) : EndMacro

Define.f temp = 12.34

Debug temp
Debug decimals(temp)
I know, horrible... but that's the best I could come up with, any ideas?

:lol: should I bash the keyboard and give up?
:?
Little John
Addict
Addict
Posts: 4777
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Post by Little John »

It's pretty fine IMHO, not horrible. :-)

However, in order to work for big numbers in PureBasic versions < 4.30, I think it should look like this:

Code: Select all

Macro decimalsQ( _n_ ) : (_n_-IntQ(_n_)) : EndMacro

Define.f temp = 12.34

Debug temp
Debug decimalsQ(temp)
Regards, Little John
dioxin
User
User
Posts: 97
Joined: Thu May 11, 2006 9:53 pm

Post by dioxin »

I don't mind ASM

Code: Select all

!push &h1f7f0000       'the FP control word needed to round to zero
!fstcw [esp]           'save the current FP control word
!fldcw [esp+2]         'set the FP to round to zero
!fld MyNumber          'load the FP with the number to work on
!fld st(0)             'replicate that number on the stack
!frndint               'round number to leave integer
!fsubp st(1),st(0)     'subtract from original to leave fraction part (and pop stack)
!fstp MyNumber         'store result (and pop stack, FP stack is now clean)
!fldcw [esp]           'restore the original control word (in case other code needs it)
!add esp,4             'clean up CPU stack
Depending on circumstances, there might be quicker ways.
akj
Enthusiast
Enthusiast
Posts: 668
Joined: Mon Jun 09, 2003 10:08 pm
Location: Nottingham

Post by akj »

@dioxin:

I have a fairly poor knowledge of how to implement assembler routines in PureBasic, but nevertheless I have attempted to convert your routine to work as a PB procedure, but it always returns a result of 0.0 .
What am I doing wrong?

Code: Select all

Procedure.d Frac(MyNumber.d)
; Return fractional part of FP number
Protected result.d
!push $1f7f0000      ; FP control word needed to round to zero
!fstcw [esp]         ; Save the current FP control word
!fldcw [esp+2]       ; Set the FP to round to zero
!fld qword [p.v_MyNumber] ; Load the FP with the number to work on
!fld st0             ; Replicate/push that number on the FP stack
!frndint             ; Round number to leave integer
!fsubp st1,st0       ; Subtract from original to leave fraction part (and pop stack)
!fstp qword [p.v_result] ; Store result (and pop stack, FP stack is now clean)
!fldcw [esp]         ; Restore original FP control word
!add esp,4           ; Clean up CPU stack
ProcedureReturn result
EndProcedure

Define.d temp = 12.345
Debug temp
Debug Frac(temp)
Anthony Jordan
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

As you modify the stack register, PB can't know about it, so you have to adjust your variable access according to it:

Code: Select all

Procedure.d Frac(MyNumber.d)
; Return fractional part of FP number
Protected result.d
!push $1f7f0000      ; FP control word needed to round to zero
!fstcw [esp]         ; Save the current FP control word
!fldcw [esp+2]       ; Set the FP to round to zero
!fld qword [p.v_MyNumber+4] ; Load the FP with the number to work on
!fld st0             ; Replicate/push that number on the FP stack
!frndint             ; Round number to leave integer
!fsubp st1,st0       ; Subtract from original to leave fraction part (and pop stack)
!fstp qword [p.v_result+4] ; Store result (and pop stack, FP stack is now clean)
!fldcw [esp]         ; Restore original FP control word
!add esp,4           ; Clean up CPU stack
ProcedureReturn result
EndProcedure

Define.d temp = 12.345
Debug temp
Debug Frac(temp)
You can also avoid the temporary result, by omitting the variable to procedurereturn (then the ST1 result will be returned):

Code: Select all

Procedure.d Frac(MyNumber.d)
; Return fractional part of FP number
!push $1f7f0000      ; FP control word needed to round to zero
!fstcw [esp]         ; Save the current FP control word
!fldcw [esp+2]       ; Set the FP to round to zero
!fld qword [p.v_MyNumber+4] ; Load the FP with the number to work on
!fld st0             ; Replicate/push that number on the FP stack
!frndint             ; Round number to leave integer
!fsubp st1,st0       ; Subtract from original to leave fraction part (and pop stack)
!fldcw [esp]         ; Restore original FP control word
!add esp,4           ; Clean up CPU stack
ProcedureReturn
EndProcedure

Define.d temp = 12.345
Debug temp
Debug Frac(temp)
akj
Enthusiast
Enthusiast
Posts: 668
Joined: Mon Jun 09, 2003 10:08 pm
Location: Nottingham

Post by akj »

@Fred:

Thank you for your very helpful and clear reply.

I had a feeling that ProcedureReturn (without any argument) would in this case return the contents of st0 as I think I noticed it mentioned somewhere on the forum. However it does not appear to be officially documented anywhere when the return type is non-integer.
Anthony Jordan
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

My mistake, it's ST0 indeed. If you return type is an integer then it's eax.
akj
Enthusiast
Enthusiast
Posts: 668
Joined: Mon Jun 09, 2003 10:08 pm
Location: Nottingham

Post by akj »

What is returned by ProcedureReturn (without any argument) when the procedure is defined to return a string?
Anthony Jordan
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

Nothing, as the return value isn't used.
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Post by superadnim »

thanks :) that's about 20% faster than my macro!

even with the doubles :? I wonder, if I wanted single precision, would changing qword to dword and the return type to float (as well as the parameter type) fix it?

now that I think about it, is 20% really negligible?. right now I doubt any bottleneck would form around the current macro and quite frankly using asm will only lead to maintenance issues in the future :?

but im all for speed :lol:

:lol: should I bash the keyboard and give up?
:?
Fred
Administrator
Administrator
Posts: 18162
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Post by Fred »

Depending of what you do, but yes, 20% isn't much for an asm replacement.
dioxin
User
User
Posts: 97
Joined: Thu May 11, 2006 9:53 pm

Post by dioxin »

Superadnim,
I'd have expected better than 20% improvement, it runs in about 30clks on my PC. Have you tried using a macro for the ASM function instead of calling it in a procedure? For very short code like this the overhead of calling the procedure becomes significant.
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Post by superadnim »

But isn't Int() a procedure? or is it expanded inlined by the compiler? (not sure, that's why I'm asking)

if it's being inlined then I'll try turning the current asm solution into a macro to make the test more fair

:lol: should I bash the keyboard and give up?
:?
superadnim
Enthusiast
Enthusiast
Posts: 480
Joined: Thu Jul 27, 2006 4:06 am

Post by superadnim »

Code: Select all

Macro decimals2( _n_, _result_ )
	
	!push $1f7f0000      ; FP control word needed to round to zero 
	!fstcw [esp]         ; Save the current FP control word 
	!fldcw [esp+2]       ; Set the FP to round to zero 
	!fld dword[v_#_n_] ; Load the FP with the number to work on 
	!fld st0             ; Replicate/push that number on the FP stack 
	!frndint             ; Round number to leave integer 
	!fsubp st1,st0       ; Subtract from original to leave fraction part (and pop stack) 
	!fldcw [esp]         ; Restore original FP control word 
	!fstp dword[v_#_result_]
	!add esp,4           ; Clean up CPU stack 
	
EndMacro
I don't know if I should store the value after the add or not but it works anyway I think.

with the macro I got 29% compared to the previous 20% against the int macro :?

10000000 iterations with the process priority set to realtime and with a delay between each test, the result is an average of 6 tests but in this case they all clocked the same 20% and 29% :P

:lol: should I bash the keyboard and give up?
:?
dioxin
User
User
Posts: 97
Joined: Thu May 11, 2006 9:53 pm

Post by dioxin »

superadnim,
It doesn't matter if you store the value before or after that final ADD.

Something doesn't add up. Looking at the ASM code produced by the compiler for your original macro (_n_-Int(_n_)) there's no way that the ASM I posted above should be only 29% faster.
How long does your 10,000,000 iterations take in seconds? Here it only takes 0.25s which works out at around 40 cpu clks per iteration, about what I'd expect.
Have you turned off the debugger before timing it?
Post Reply