[C] Type punning acceptable on float?

Got questions? Got answers? Go here for both.

Moderator: MaxCoderz Staff

User avatar
Jim e
Calc King
Posts: 2457
Joined: Sun 26 Dec, 2004 5:27 am
Location: SXIOPO = Infinite lives for both players
Contact:

Post by Jim e »

Halifax wrote:Jim e: Yes that may be right.

But also just so you know, I can compile it with -O3 and it inlines it.
He clearly has a more modern compiler than you. The point is when gcc will decide to inline and when not to. It's obviously moot, -O3 doesn't mean squat here theres nothing to optimize.

Good compilers, generating code for speed, will try to inline functions like that. Trick is how are you going to use that number. Both of you printed as if it were a 1 or a 0. But its bool, it just needs to be true or false if all you want is to branch you code at that point. At which case theres a pretty good chance that gcc will realize that bit 31 is the sign bit.

Here's my example:

Code: Select all

#include <stdio.h>


int is_negative(float x) {
    unsigned int *ui = (unsigned int *)&x;
    return (*ui >> 31);
}

int main(int argc, char **argv) {
	float i;
	for(i = -10.4f; i<10;i+=1.2f) {
		if (is_negative(i)) {
		    puts("Negative");
		} else {
			puts("Positive");
		}
	}
	system("Pause");
	return 0;
}

Code: Select all

	.file	"main.c"
	.text
	.p2align 4,,15
.globl _is_negative
	.def	_is_negative;	.scl	2;	.type	32;	.endef
_is_negative:
	pushl	%ebp
	movl	%esp, %ebp
	movl	8(%ebp), %eax
	popl	%ebp
	shrl	$31, %eax
	ret
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC2:
	.ascii "Negative\0"
LC3:
	.ascii "Positive\0"
LC5:
	.ascii "Pause\0"
	.align 4
LC1:
	.long	1092616192
	.align 4
LC4:
	.long	1067030938
	.text
	.p2align 4,,15
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	$16, %eax
	movl	%esp, %ebp
	pushl	%ebx
	subl	$20, %esp
	andl	$-16, %esp
	call	__alloca
	call	___main
	movl	$0xc1266666, %ebx
	jmp	L10
	.p2align 4,,7
L18:
	movl	$LC2, (%esp)
L15:
	call	_puts
	movl	%ebx, -8(%ebp)
	flds	-8(%ebp)
	fadds	LC4
	flds	LC1
	fucomp	%st(1)
	fnstsw	%ax
	fstps	-8(%ebp)
	sahf
	movl	-8(%ebp), %ebx
	jbe	L17
L10:
	testl	%ebx, %ebx
	js	L18
	movl	$LC3, (%esp)
	jmp	L15
	.p2align 4,,7
L17:
	movl	$LC5, (%esp)
	call	_system
	movl	-4(%ebp), %ebx
	xorl	%eax, %eax
	leave
	ret
	.def	_system;	.scl	3;	.type	32;	.endef
	.def	_puts;	.scl	3;	.type	32;	.endef
While it does create the function is_negative, it never used it. Rather it bit tested ebx for its sign bit.


Like I said before, You shouldn't try to out think the compiler, chances are your just gonna F' things up and make your code slower.

But I'm still guessing, treating it as a float is faster. I'm not certain how slow it is to constantly reload the value for FPU use.
Image
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

Yes you shouldn't try to out think a compiler, you should read the source code to the compiler.
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

I haven't timed this in C yet, but I was wondering what would happen in C#.
Seeing as you cannot implicitly (not even explicitly) cast an int to bool I had to return something else.

Code: Select all

        static unsafe uint IsNegative(float f)
        {
            uint* ui = (uint*)&f;
            return (*ui >> 31);
        }
is turned into

Code: Select all

    .method private hidebysig static uint32 IsNegative(float32 f) cil managed
    {
        .maxstack 2
        .locals init (
            [0] uint32* ui)
        L_0000: ldarga.s f
        L_0002: conv.u 
        L_0003: stloc.0 
        L_0004: ldloc.0 
        L_0005: ldind.u4 
        L_0006: ldc.i4.s 0x1f
        L_0008: shr.un 
        L_0009: ret 
    }
I see some pretty weird things going on there,
but it's only a tiny little bit slower than the safe way:

Code: Select all

        static int IsNegativeSafe(float f)
        {
            return f < 0 ? 1 : 0;
        }
which is turned into:

Code: Select all

    .method private hidebysig static int32 IsNegativeSafe(float32 f) cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: ldc.r4 0
        L_0006: blt.s L_000a
        L_0008: ldc.i4.0 
        L_0009: ret 
        L_000a: ldc.i4.1 
        L_000b: ret 
    }
but how can you check what code is actually executed? some trick with ngen? (how does it work?)
User avatar
benryves
Maxcoderz Staff
Posts: 3087
Joined: Thu 16 Dec, 2004 10:06 pm
Location: Croydon, England
Contact:

Post by benryves »

King Harold wrote:Seeing as you cannot implicitly (not even explicitly) cast an int to bool I had to return something else.
Returning integer values for boolean results is pretty silly. However, if you really must (for future reference) you can convert bools and ints like this:

Code: Select all

int True = (int)Convert.ChangeType(true, typeof(int));
int False = (int)Convert.ChangeType(false, typeof(int));
This thread is obviously concerned about micro-optimisations, however, and the above technique relies on automatic boxing and unboxing between value and reference types.
User avatar
Jim e
Calc King
Posts: 2457
Joined: Sun 26 Dec, 2004 5:27 am
Location: SXIOPO = Infinite lives for both players
Contact:

Post by Jim e »

King Harold wrote:Seeing as you cannot implicitly (not even explicitly) cast an int to bool I had to return something else.
Why do you want the value returned as an int at all? Bool is far more useful. In C# you'll just have to test if the returned int is a 1 or 0.

However, judging from that code, the vm handles the sign on floats. Theres no question that the potential advantage to the trick is lost here. In fact making it function and return an int is probably slowing things down quite a bit.
Image
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

Probably, so the whole thing is useless in C#, which doesn't matter at all because it was a C trick
Post Reply