Sidestepping undefined behavior

B

Brian

Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

Best

Brian
 
I

Ian Collins

Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){

Why not a simple equality test?
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;

if you are bit twiddling, use an unsigned type.
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

It should be, excluding mythical trap representations. But then again,
the "clever bit-twiddling" may well be slower than an FPU.
 
K

Keith Thompson

Ian Collins said:
Why not a simple equality test?


if you are bit twiddling, use an unsigned type.


It should be, excluding mythical trap representations. But then again,
the "clever bit-twiddling" may well be slower than an FPU.

int and float could have the same size but different alignment
requirements.

The "clever bit-twiddling" will inevitably be non-portable; it has
to be customized for the compiler's representation of float.

It seems highly unlikely that whatever bit-twiddling operations you
write will be faster than what the FPU has implemented *in hardware*.

It could be an interesting exercise if you're trying to understand
how floating-point is implemented. It might even give you a speed
improvement on a system that emulates floating-point in software
(though again, it's not likely that *your* bit-twiddling will be
faster than what the compiler provides). But you're unlikely to
be able to improve on what the compiler already does for you.
 
B

Ben Pfaff

Brian said:
float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

The /* use slow ordinary FP operations */ block above guarantees
maximum portability. Adding an alternative implementation cannot
improve portability.
 
J

Jorgen Grahn

Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

No. The way to maximize portability is to use floating-point
operations to implement floating-point stuff. (Unless you have some
very specific restrictions you're not telling us about.)

/Jorgen
 
J

Joe Pfeiffer

Brian said:
Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

No, it isn't. It is sufficiently unlikely that your clever
bit-twiddling will end up outperforming the "slow" ordinary FP
operations that you're barking up the wrong tree right from the outset.
 
P

Philip Lantz

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

I'm confused--if you don't know a priori what size a float is,
how can you write code to implement some operation on it?

I'm also very curious why you used xor.
 
B

Brian

I'm confused--if you don't know a priori what size a float is, how can
you write code to implement some operation on it?

Hi Phillip,

There is no issue there, floats are 32 bits by IEEE standard. The
question is whether sizeof(int) is something different (e.g. 64 on some
64 bit platforms, or 16 bits on old Windows/DOS etc)
I'm also very curious why you used xor.

This is a neat optimization trick I read about on the web. Most
processors have a single instruction for xor. It also saves typing for
the programmer (^ is one less character than !=).

Thanks for all the replies. Don't worry, I think my bit-twiddling will be
pretty fast :)

Cheers

Brian
 
M

Morris Keesan

Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

Leaving aside the advisability of your intended bit-twiddling,
you could eliminate the undefined behavior of
*(int *)&f
(which does not become defined simply because i and f are the same size)
by using memcpy to copy the bits from f into i.
 
J

James Kuyper

Hi Phillip,

There is no issue there, floats are 32 bits by IEEE standard.

You were asking about portability in your original message. You cannot
portably assume IEEE compliance. The C standard explicitly allows for
the possibility of non-compliance with IEEE - that's what
__STDC_IEC_559__ is for (IEC 559 is an older term for what's now known
as IEC 60599, both of which are essentially equivalent to IEEE 754).
This is a neat optimization trick I read about on the web. Most
processors have a single instruction for xor. It also saves typing for
the programmer (^ is one less character than !=).

Keep in mind that any decent compiler will know the same trick (and it's
limits of applicability). If you can't trust your compiler to perform
such optimizations itself, you shouldn't be using it. It's clearer to
write != when you mean "not equal".
 
B

Ben Bacarisse

Brian said:
This is a neat optimization trick I read about on the web. Most
processors have a single instruction for xor.

If this is an optimisation (neat or not) you need a better compiler!
The value of the expression can be determined at translation time so
there is no reason not to be as clear as you can be.

<snip>
 
P

Philip Lantz

This is a neat optimization trick I read about on the web. Most
processors have a single instruction for xor. It also saves typing for
the programmer (^ is one less character than !=).

This is a really bad idea for several reasons:
A. I haven't encountered a processor that has a single instruction for xor
and doesn't also have one for equality. (I went back and checked the PDP-8
instruction set, just to make sure I wasn't overlooking one. Of course, it
doesn't have either one.) I'd love to know if you really know of one.

B. If you did have such a processor, surely the compiler would be aware of
it's vagaries and use the best instructions.

C. It is really confusing for the reader, since it doesn't say what you
mean.

Whatever web site gave you that advice is probably one to avoid.
 
T

Tim Rentsch

Brian said:
Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

Strictly speaking, no, because it violates effective type
rules. Assuming sizeof (int) == sizeof (float), this can
be done compliantly using memcpy() or a union. Practically
speaking memcpy() is probably safest, but a union is also
required to work, eg,

int i = (union { float f; int i; }){ f }.i;
 
T

Tim Rentsch

Ian Collins said:
Hello.

Consider this code.

float
baz(float f)
{
if(sizeof(float) ^ sizeof(int)){
/* use slow ordinary FP operations */
}else{
int i = *(int *)&f;
/* do clever bit-twiddling to implement FP operation */
}
}

It attempts to detect whether the type punning will cause undefined
behavior on the implementation at hand, and if so it avoids that branch.

Obviously it's not 100% guaranteed probably, but do you agree this is a
useful way to maximize portability?

It should be, excluding mythical trap representations. [snip]

Probably it will work, but strictly speaking it's undefined
behavior. Using memcpy() or an intermediate union would
give defined behavior.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top