Need advice: cast(integer, float)

General FreeBASIC programming questions.
Gonzo
Posts: 722
Joined: Dec 11, 2005 22:46

Need advice: cast(integer, float)

Post by Gonzo »

In C you get the floor and fractional of a float or double (assuming it never is negative) in simple and fast ways:
int integral = (int)fl;
float fractional = fl - integral;

how do you do this in freebasic?
every time i work with numbers in freebasic i have to be extra careful to avoid rounding errors
because of that **** qbasic where they thought it was a good idea to round everyones numbers

print cast(integer, 0.9) '' 1
dim as integer t = 0.9
print t '' 1

is there any way to turn this "feature" off? and why is it even there?
no one else feeling like they are coding with one eye watching for the awesome rounding feature?
(i'm really sorry, but i just can't get used to this)
my code is usually littered with int(x) because of this... it makes me teary-eyed :)

anyways, before i go out on a venting mission again, how can you get float to int with floor, without doing extra work?
what is the actual assembly for this?

edit: here are some solutions:
http://stackoverflow.com/questions/2352 ... s-to-floor
1000101
Posts: 2556
Joined: Jun 13, 2005 23:14
Location: SK, Canada

Re: Need advice: cast(integer, float)

Post by 1000101 »

Int() should give you the floored value, CInt() should give you the rounded value.
Stonemonkey
Posts: 649
Joined: Jun 09, 2005 0:08

Re: Need advice: cast(integer, float)

Post by Stonemonkey »

I have no idea if this could break anything in FB but you could change the fpu rounding mode.

Code: Select all

function get_fpu_control_word()as integer
    asm fstcw [function]
end function

sub set_fpu_control_word(byval cw as integer)
    asm fldcw [cw]
end sub

sub set_fpu_rounding_mode(byval mode as integer)
    mode=(get_fpu_control_word() and &hf3ff)or((mode and 3)shl 10)
    asm fldcw [mode]
end sub

sub main
    dim as integer original_fpu_control_word=get_fpu_control_word()
    
    'rounding mode:
    '0=nearest
    '1=round down
    '2=round up
    '3=truncate
    set_fpu_rounding_mode(1)
    
    
    dim as single f=1.6
    dim as integer i=f
    print i
    
    
    'put the fpu back the way it was
    set_fpu_rounding_mode(original_fpu_control_word)
    
end sub

main
sleep

Last edited by Stonemonkey on Dec 05, 2012 15:32, edited 1 time in total.
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Need advice: cast(integer, float)

Post by MichaelW »

There is also fix().
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Need advice: cast(integer, float)

Post by counting_pine »

You might find Frac() useful too. (All these operations are more consise than Cast(Integer, ...)).

For maximum conciseness, if you're really desparate, you could #define i(x) int(x)..

Personally, I think this is one of those cases where it's better to be explicit about what you want to happen.

I'm having trouble imagining why you might want to use Int() so frequently that it makes your code hard to read. What sort of code are you working on?
gothon
Posts: 225
Joined: Apr 11, 2011 22:22

Re: Need advice: cast(integer, float)

Post by gothon »

A related question, is there a good fast way to perform floored integer division without using the fpu? Eg. given two integers A and B, compute C = floor(A/B). Since the inputs are integers and the output is a well defined integer, it should be possible without using floating point functions like int. However the integer division operator in freebasic '\', actually rounds towards 0 like fix, and thus does not handle the negative values correctly.

I have figured out how to do this using mod twice before dividing however this is essentially using 3 divisions to compute 1 division. Is there a faster/better way?

Code: Select all

#Define FloorDivide(N, D) (((N)-((N)Mod(D)+(D))Mod(D))\(D))
Gonzo
Posts: 722
Joined: Dec 11, 2005 22:46

Re: Need advice: cast(integer, float)

Post by Gonzo »

i'm not using int() in many places in my code, the problem is the rounding!
one of many things that make me insane: (1.99) \ 2 should return 0, but returns 1 (huge debug fest)

sometimes it matters, sometimes it matters not, but who expects their conversions to be rounded at all!
(i'm not trying to do i(x) here! shorter code isn't valuable to me)
it's only about trying to avoid having the compiler perform lots of rounding on my float to ints

Fix(x) is the equivalent of C (int)x ?
i don't think so: Note: this function is also equivalent to number - Frac(number)

so, that leaves int(x), which is equivalent to what? a function call? Bryn told me int() was slow like a turtle
that's the whole point of this thread! to avoid slow int(), since it's apparent to me that it's 2 fpu calls or 1 sse1 call to do 50% of the float to int cases

According to stonemonkey (since i don't know any ASM), FB doesn't use any extra asm to keep this rounding-circus going?
it's too bad, that it rounds by default.. i wish it didn't
TJF
Posts: 3809
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Re: Need advice: cast(integer, float)

Post by TJF »

Gonzo wrote:... the problem is the rounding!
I second that! Rounding should get banished to #LANG "qb" or completetly removed.
Stonemonkey
Posts: 649
Joined: Jun 09, 2005 0:08

Re: Need advice: cast(integer, float)

Post by Stonemonkey »

According to stonemonkey (since i don't know any ASM), FB doesn't use any extra asm to keep this rounding-circus going?
it's too bad, that it rounds by default.. i wish it didn't
All that's happening is that FB uses the FIST (or FISTP) instruction 'Floating point Integer STore (and Pop)'

With that instruction, the FPU has to round the number first before it's stored and that rounding depends on what rounding mode the FPU control word is set to, not anything to do with the code FB generates other than what it's already set the FPU control word to.

You can change the rounding mode like I showed though I'm not sure if that could break anything else in FB, I would hope that anything that's sensitive to the FPU rounding mode will deal with that itself but I can't say for sure.
dafhi
Posts: 1641
Joined: Jun 04, 2005 9:51

Re: Need advice: cast(integer, float)

Post by dafhi »

Thank you stonemonkey for the rounding mode sample!
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: Need advice: cast(integer, float)

Post by MichaelW »

I can’t see any efficient way to do the truncation with integer code, but it’s apparently possible to do better than the FreeBASIC INT function.

Edit:

Changed the code and results to include the CRT floor function.

Code: Select all

''===================================================================================
#include "counter.bas"
#include "crt.bi"
''===================================================================================
''
'' The newer cycle count macros are available here:
''
''    http://www.freebasic.net/forum/viewtopic.php?f=7&t=20003
''
''===================================================================================

dim as double d = 12345.6789
dim as integer i

''===================================================================================

asm nop
i = int(d)
asm nop

/'
    nop
    fld qword ptr [ebp-12]
    sub esp, 4
    fnstcw [esp]
    mov eax, [esp]
    and eax, 0b1111001111111111
    or eax, 0b0000010000000000
    push eax
    fldcw [esp]
    add esp, 4
    frndint
    fldcw [esp]
    add esp, 4
    fistp dword ptr [ebp-16]
    nop
'/

''===================================================================================

''--------------------------------------------------------------------------
'' This procedure is very nearly a direct copy of Agner Fog’s MASM code for
'' truncating a double towards zero without changing the FPU control word.
'' See optimizing_assembly.pdf available here:
''
''    http://www.agner.org/optimize/
''
''--------------------------------------------------------------------------

function truncate naked( byval x as double ) as integer
    asm
        fld qword ptr [esp+4]   '' x
        sub esp, 12             '' space for local variables
        fist dword ptr [esp]    '' rounded value
        fst dword ptr [esp+4]   '' float value
        fisub dword ptr [esp]   '' subtract rounded value
        fstp dword ptr [esp+8]  '' difference
        pop eax                 '' rounded value
        pop ecx                 '' float value
        pop edx                 '' difference (float)
        test ecx, ecx           '' test sign of x
        js short NEGATIVE
        add edx, 0x7FFFFFFF     '' produce carry if difference < -0
        sbb eax, 0              '' subtract 1 if x-round(x) < -0
        ret 8
      NEGATIVE:
        xor ecx, ecx
        test edx, edx
        setg cl                 '' 1 if difference > 0
        add eax, ecx            '' add 1 if x-round(x) > 0
        ret 8
    end asm
end function

''===================================================================================

i = int(d)
print i
i = floor(d)
print i
i = truncate(d)
print i
print

SetProcessAffinityMask( GetCurrentProcess(), 1)

sleep 5000

for j as integer = 1 to 4

    counter_begin( 10000000, REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL )
    counter_end()
    print counter_cycles;" cycles, empty"

    counter_begin( 10000000, REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL )
        i = int(d)
    counter_end()
    print counter_cycles;" cycles, int()"

    counter_begin( 10000000, REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL )
        i = floor(d)
    counter_end()
    print counter_cycles;" cycles, floor()"

    counter_begin( 10000000, REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL )
        i = truncate(d)
    counter_end()
    print counter_cycles;" cycles, truncate()"
    print

next

sleep
Running on a P3:

Code: Select all

 12345
 12345
 12345

 0 cycles, empty
 65 cycles, int()
 156 cycles, floor()
 33 cycles, truncate()

 0 cycles, empty
 65 cycles, int()
 156 cycles, floor()
 33 cycles, truncate()

 0 cycles, empty
 65 cycles, int()
 156 cycles, floor()
 33 cycles, truncate()

 0 cycles, empty
 65 cycles, int()
 156 cycles, floor()
 33 cycles, truncate()
Running on a P4 (Northwood):

Code: Select all

 12345
 12345
 12345

 0 cycles, empty
 78 cycles, int()
 223 cycles, floor()
 124 cycles, truncate()

 0 cycles, empty
 78 cycles, int()
 226 cycles, floor()
 124 cycles, truncate()

 0 cycles, empty
 78 cycles, int()
 224 cycles, floor()
 124 cycles, truncate()

 0 cycles, empty
 77 cycles, int()
 224 cycles, floor()
 124 cycles, truncate()
Hopefully, the more recent processors will improve on the P3 results.
Last edited by MichaelW on Dec 06, 2012 22:21, edited 1 time in total.
Gonzo
Posts: 722
Joined: Dec 11, 2005 22:46

Re: Need advice: cast(integer, float)

Post by Gonzo »

xmm (sse2):

Code: Select all

cvttss2si  0x4(%esp), %eax    ; truncation
ret

modern (x86 sse3):
sub     $0x4,%esp
flds    0x8(%esp)           ; can be removed if already in register
fisttpl (%esp)
mov     (%esp),%eax
add     $0x4,%esp
ret
classic (x86 pre-2004):

Code: Select all

sub      $0x8, %esp       ; allocate stack space
fnstcw   0x6(%esp)        ; save floating-point control word
flds     $0xc(%esp)       ; push floating-point param onto fp stack
movzwl   0x6(%esp), %eax  ; move prev fp control word into %eax
mov      $0xc, %ah        ; set rounding mode of control word to "truncate"
mov      %ax, 0x4(%esp)   ; save it *back* to the stack
fldcw    0x4(%esp)        ; set the floating-point control word to truncate
fistp    0x2(%esp)        ; store integer from the fp stack to the stack
fldcw    0x6(%esp)        ; set the fp control word back to what it was
movzwl   0x2(%esp), %eax  ; read the value into eax (the return value)
add      $0x8, %esp       ; give the stack space back
ret
Stonemonkey
Posts: 649
Joined: Jun 09, 2005 0:08

Re: Need advice: cast(integer, float)

Post by Stonemonkey »

Is there any reason not to just switch the fpu rounding mode? `
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Need advice: cast(integer, float)

Post by counting_pine »

gothon wrote:A related question, is there a good fast way to perform floored integer division without using the fpu? Eg. given two integers A and B, compute C = floor(A/B). Since the inputs are integers and the output is a well defined integer, it should be possible without using floating point functions like int. However the integer division operator in freebasic '\', actually rounds towards 0 like fix, and thus does not handle the negative values correctly.

I have figured out how to do this using mod twice before dividing however this is essentially using 3 divisions to compute 1 division. Is there a faster/better way?

Code: Select all

#Define FloorDivide(N, D) (((N)-((N)Mod(D)+(D))Mod(D))\(D))
Once you have the quotient, the remainder can be found using a multiply and subtract. A small amount of extra logic is needed with signed division to make sure the remainder has the right sign: it has to be non-negative for positive divisors and non-positive for negative divisors. And of course the quotient has to be adjusted to ensure a = b * quotient + remainder
Here's some code that does it. It is hopefully easy enough to understand, and can probably be optimised.

Code: Select all

function floordivmod(byval a as integer, byval b as integer, byref r as integer = 0) as integer
    
    if b = 0 then return 0
    
    dim as integer q = a \ b
    r = a - q*b
    
    if (r < 0) xor (b < 0) then
        if r <> 0 then
            r += b
            q -= 1
        end if
    end if
    
    assert( q = int(a / b) )
    assert( a = q * b + r )
    
    return q
    
end function

dim as integer a, b, q, r
for b = -2 to 2: if b = 0 then continue for
    for a = -2 to 2
        q = floordivmod(a, b, r)
        print a; " / "; b; " = "; q; ", r" & r
    next
next
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Re: Need advice: cast(integer, float)

Post by counting_pine »

By the way, how does C tend perform (int)f in x86? Does it have to adjust the rounding mode each time?
Post Reply