[C] Type punning acceptable on float?

Got questions? Got answers? Go here for both.

Moderator: MaxCoderz Staff

King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

[C] Type punning acceptable on float?

Post by King Harold »

I saw this:

Code: Select all

bool is_negative(float x) {
    unsigned int *ui = (unsigned int *)&x;
    return (*ui >> 31);
}
and it scared me a bit. Assuming a float is indeed a IEEE 32bits float, then it should return the right result (I think?), but is writing code like this acceptable and advisable?

Is it faster than "return (x < 0.0f);" anyway?
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

Well, if I analyzed this correctly. Casting on changes how data is accessed. Meaning that the 'int' that you are using better be a 32-bit integer.

If it is a 16-bit integer, then you are only going to be getting half of that float.

The code above is perfectly fine.
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

if it would be 16bit you'd just shift 15 instead wouldn't you..

so is this faster than comparing the float?
CoBB
MCF Legend
Posts: 1601
Joined: Mon 20 Dec, 2004 8:45 am
Location: Budapest, Absurdistan
Contact:

Post by CoBB »

I guess it depends on the compiler. I wouldn’t be very surprised if a modern compiler used similar shortcuts instead of turning to full-fledged fp arithmetic when the circumstances allow.

However, I’m not exactly sure whether this particular solution is sensitive to endianness. In any case, I would only use it with caution.
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

King Harold: Correct.

Yeah, I am not sure about endianness either.

If you are really bothered over it though King Harold, then you could always just pop open that generated assembly.
King Harold
Calc King
Posts: 1513
Joined: Sat 05 Aug, 2006 7:22 am

Post by King Harold »

The endianness of the float has to be the same as that of the unsigned int of course (although if they're little endian the "just shift less" thing wouldn't work) otherwise you'd need to bitwise & it with some number and shift and even weirder number, right?
so, lots of complications..
I'll disassemble it and check the code
Goplat
New Member
Posts: 12
Joined: Mon 16 Jul, 2007 2:46 pm

Post by Goplat »

If that's a standalone function, and the float is being passed on the stack rather than in a floating point register (under standard x86 calling conventions, everything is passed on the stack), then yes, the trick will be faster than a normal comparison.

However, it's not realistic to have an "is_negative" function - if performance is desired this is the kind of thing you want to inline rather than have as a real function. But when you're in the body of a function that's been using x, it could be the case that x is already in an FPU register, in which case the normal x < 0 would be faster as the bit trick would require it to be stored into memory.
User avatar
Dwedit
Maxcoderz Staff
Posts: 579
Joined: Wed 15 Dec, 2004 6:06 am
Location: Chicago!
Contact:

Post by Dwedit »

You might use an "is_negative" function if something requires a function pointer.
You know your hexadecimal output routine is broken when it displays the character 'G'.
coelurus
Calc Wizard
Posts: 585
Joined: Sun 19 Dec, 2004 9:02 pm
Location: Sweden
Contact:

Post by coelurus »

Modern compilers inline tiny functions like that.

As is always said around here, try both, they're so ridiculously easy to try anyway. If there's hardly any difference, go for the safe approach (the fp op).
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

I think you should look at the assembly, because modern compilers most definitely don't inline functions like that. You should either explicity suggest, or never suspect that it is inlined.
coelurus
Calc Wizard
Posts: 585
Joined: Sun 19 Dec, 2004 9:02 pm
Location: Sweden
Contact:

Post by coelurus »

C code:

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) {
    printf("%d\n", is_negative(strtod(argv[1], NULL)));
    return 0;
}
Compiled to assembler with gcc -O3 -S:

Code: Select all

    .file   "main.c"
    .text
    .p2align 4,,15
.globl is_negative
    .type   is_negative, @function
is_negative:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    popl    %ebp
    shrl    $31, %eax
    ret
    .size   is_negative, .-is_negative
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $36, %esp
    movl    4(%ecx), %eax
    movl    $0, 4(%esp)
    movl    4(%eax), %eax
    movl    %eax, (%esp)
    call    strtod
    pushl   %eax
    movl    -8(%ebp), %eax
    fildl   (%esp)
    addl    $4, %esp
    fstps   -8(%ebp)
    movl    $.LC0, (%esp)
    shrl    $31, %eax
    movl    %eax, 4(%esp)
    call    printf
    addl    $36, %esp
    xorl    %eax, %eax
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret
    .size   main, .-main
    .ident  "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
    .section    .note.GNU-stack,"",@progbits
Inlined in yer face!
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

Haha, yes with the switch -O3 ;)

C code:

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) { 
    printf("%d\n", is_negative(strtod(argv[1], NULL))); 
    return 0; 
} 
Assembler compiled with 'gcc' (3.4.2)

Code: Select all

	.text
.globl _is_negative
	.def	_is_negative;	.scl	2;	.type	32;	.endef
_is_negative:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$4, %esp
	leal	8(%ebp), %eax
	movl	%eax, -4(%ebp)
	movl	-4(%ebp), %eax
	movl	(%eax), %eax
	shrl	$31, %eax
	leave
	ret
	.def	___main;	.scl	2;	.type	32;	.endef
	.section .rdata,"dr"
LC0:
	.ascii "%d\12\0"
	.text
.globl _main
	.def	_main;	.scl	2;	.type	32;	.endef
_main:
	pushl	%ebp
	movl	%esp, %ebp
	subl	$24, %esp
	andl	$-16, %esp
	movl	$0, %eax
	addl	$15, %eax
	addl	$15, %eax
	shrl	$4, %eax
	sall	$4, %eax
	movl	%eax, -4(%ebp)
	movl	-4(%ebp), %eax
	call	__alloca
	call	___main
	movl	$0, 4(%esp)
	movl	12(%ebp), %eax
	addl	$4, %eax
	movl	(%eax), %eax
	movl	%eax, (%esp)
	call	_strtod
	pushl	%eax
	fildl	(%esp)
	leal	4(%esp), %esp
	fstps	(%esp)
	call	_is_negative
	movl	%eax, 4(%esp)
	movl	$LC0, (%esp)
	call	_printf
	movl	$0, %eax
	leave
	ret
	.def	_strtod;	.scl	3;	.type	32;	.endef
	.def	_printf;	.scl	3;	.type	32;	.endef
Not inlined in your face! Why don't you try specifying what you mean by modern compilers, because obviously your definition of a modern compiler is 'gcc -O3'.
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:Assembler compiled with 'gcc' (3.4.2)
coelurus wrote:

Code: Select all

    .ident  "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)" 
At any rate thats a bad example. If your using floats, you need to have some real float math going on. Goplat is probably right, even if it is inlined if its in the FPU its probably faster to do the comparison. You'd just be guessing where that number is stored at the moment.
Image
coelurus
Calc Wizard
Posts: 585
Joined: Sun 19 Dec, 2004 9:02 pm
Location: Sweden
Contact:

Post by coelurus »

You know what the only correct thing to do is? Profiling.
User avatar
Halifax
Sir Posts-A-Lot
Posts: 225
Joined: Mon 01 Jan, 2007 10:39 am
Location: Pennsylvania, US

Post by Halifax »

Jim e wrote:
Halifax wrote:Assembler compiled with 'gcc' (3.4.2)
coelurus wrote:

Code: Select all

    .ident  "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)" 
At any rate thats a bad example. If your using floats, you need to have some real float math going on. Goplat is probably right, even if it is inlined if its in the FPU its probably faster to do the comparison. You'd just be guessing where that number is stored at the moment.
Jim e: Yes that may be right.

But also just so you know, I can compile it with -O3 and it inlines it.
Post Reply