avr-gcc floating point problems

Mar 18, 2010 60 Replies

Someone else suggested it as a two step castings, but it was not really the problem any way.

No, it doesn't. That's been clarified days ago in this thread.

Yes, but that only applies once you _have_ an unsigned char. The problem under consideration doesn't have one --- it's trying to build one.

You think incorrectly, there.

No, it doesn't. It instructs the compiler to generate code that does exactly what the code says: convert the float to an unsigned char. That's another thing that has been clarified days ago.

Yes, and we will scale f to fit within 255 first. We are only interested in relative magnitude of an array.

.

The compiler is smart enough to understand "u =3D f or i =3D f" as well. The casting is for human reading the code. It may be unnecessary, but not brain damaged.

It is mainly for storage. Unsigned char (8 bits) takes less space than float (16 bits) or double (32 bits). We don't really care how the compiler does it, but storage is important for a 1K SRAM chip.

It works on any integer. Unsigned char's are supposed to work modulo their range.

Now I think u = (unsigned char) f; is brain damaged.

This tells the compiler to convert the float converted to an integer, then take it modulo 256.

The OP should have had:

{ int i; i = f }

He would have discovered that his code was shorter and faster than his determined attempt to confuse the compiler. (Admitting that the compiler should never get confused.)

Groetjes Albert

--

Albert van der Horst, UTRECHT,THE NETHERLANDS Economic growth -- being exponential -- ultimately falters. albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

Last time I knew, that just cast the object into what you want, but it's not going to magically convert a FLOAT to a unsigned char.. Its only going to instruct the compiler that the contents of memory the "F" occupies is a unsigned char and simply moves the data from that from ever it is in memory to "u" simply speaking.. u = f; isn't the same as u = (unsigned char) f; where I come from..

And the last time I knew, AVR's do not have native support for floating point math. UNless I've been out of the loop here? Instead, the compiler should be loading a default RTL that contains low level code to give you that results.. This of course is compiler linked and thus, makes it look natural to you, the coder.

And if the C compiler is standardized like most other compilers..

u = (unsigned char) f; simply tells the compiler to treat the object of "F" as if it was a char instead. No conversion is taking place here. As far as how the floats are implemented for the binary format of this compiler will dictate what value of data will be returned..

I can only assume it may support the Mantissa and other elements of a floating point number, in which case, the above will not work as you may think.. But who am I, I don't know anything..

stick with the "u = f;" and the compiler should be happy with that and call one of it's floattointeger conversion TRL functions.. But If you want to know how the float if constructed to represent a floating point number, I guess you could hack it with the cast. I think only the authors of the compiler would understand it's meanings..

That's exactly what a cast does. (Though there's nothing magical about the conversion -- it's a well-defined operation.)

No, it isn't. It instructs the compiler to do the conversion.

I'm not sure where you come from. Here on Earth, when using the C language, it's exactly the same.

Here's an example program:

extern volatile unsigned char u1,u2; extern volatile float f; void foo1(void) { u1 = f; } void foo2(void) { u2 = (unsigned char)f; }

And here's the generated code using gcc for IA32. Note that the code generated for the two cases is identical:

.file "testit.c" .text .p2align 4,,15 .globl foo1 .type foo1, @function foo1: subl $8, %esp fnstcw 6(%esp) flds f movzwl 6(%esp), %eax movb $12, %ah movw %ax, 4(%esp) fldcw 4(%esp) fistps 2(%esp) fldcw 6(%esp) movzwl 2(%esp), %eax movb %al, u1 addl $8, %esp ret .size foo1, .-foo1 .p2align 4,,15 .globl foo2 .type foo2, @function foo2: subl $8, %esp fnstcw 6(%esp) flds f movzwl 6(%esp), %eax movb $12, %ah movw %ax, 4(%esp) fldcw 4(%esp) fistps 2(%esp) fldcw 6(%esp) movzwl 2(%esp), %eax movb %al, u2 addl $8, %esp ret .size foo2, .-foo2 .ident "GCC: (Gentoo 4.3.4 p1.0, pie-10.1.5) 4.3.4" .section .note.GNU-stack,"",@progbits

Doesn't matter.

No idea what your point is.

Yes, a conversion _is_ taking place here. That's what a cast _is_: it's an explicit conversion operation.

No, it won't. It will take the integer portion of the float value and store that value (modulo 256) into the unsigned char.

I wouldn't say you don't know anything, but you don't seem to know what a typecast does in a C program.

A typecast doesn't to at all what you think it does.

Grant

rote:

s

the

e from..

3.4"

ts

ains

.

ect

of

t

And here is the AVR version for foo1, foo2 is identical except for the labels. Trust me.

.global foo1 .type foo1, @function foo1: push r29 push r28 in r28,__SP_L__ in r29,__SP_H__ /* prologue: function */ /* frame size =3D 0 */ lds r24,f lds r25,(f)+1 lds r26,(f)+2 lds r27,(f)+3 movw r22,r24 movw r24,r26 call __fixunssfsi movw r26,r24 movw r24,r22 sts u1,r24 /* epilogue start */ pop r28 pop r29 ret

You are getting this entirely wrong. When you assign an expression of one type to a lvalue of another type, there is always a cast. These two assignments are always the same:

x = exp; x = (typeof(x)) exp;

The OP wrote out the cast explicitly to make his code clearer - that's good programming practice.

You are mixing this up with pointer casts. Casting a float to an int (of any size and signedness) causes a proper conversion. But casting a pointer to a float to a pointer to an int accesses the memory contents without any interpretation. To get the effect you are worried about here, you must write :

u = *((unsigned char *) &f);

But of course that's not what the OP wanted, so it's not what he wrote.

It doesn't make any difference whether floating point is implemented in a hardware FPU or with software functions. The compiler (and library) give you the same functionality for floats and doubles regardless of the underlying implementation.

Clearly the implementation makes a difference for speed. It /may/ make a difference if your code relies on the underlying format and your toolset uses a non-IEEE format. But that would be a weird piece of source code, and an unusual compiler - most compilers, including avr-gcc, use IEEE format for their software floating point. It /may/ also make a difference if you are looking for IEEE corner-case functionality - NaNs, signed zeros, rounding modes, etc. But again, that's not the case in most software.

avr-gcc is as standard as it gets - but what you are describing is /not/ standard C behaviour.

I'm not going to generalise, but you are certainly getting this one wrong.

I stand corrected.

iso 9899-1990 c-standard

6.2.1.3 When a value of floating type is converted to an integral type the fractional part is discarded. If the value of the integral part cannot be represented by the integral type, the behaviour is undefined.

So undefined would be invoked outside 0.00000-255.99999999999999999

Groetjes Albert

--

Albert van der Horst, UTRECHT,THE NETHERLANDS Economic growth -- being exponential -- ultimately falters. albert@spe&ar&c.xs4all.nl &=n http://home.hccnet.nl/a.w.m.van.der.horst

too

one.

integer,

does

I have seen block scaled integer FFT that had almost no artifacts. I=20 wish i could remember where i saw it. I think it must be related to=20 some VAX or PDP-11 integer FFT code that i have seen. Must have been=20

25 to 30 years ago.

Really? I've never run across a 16 bit float. Every compiler I've worked with uses 32 bits for float (sign, 8 bit exp, 23 bit mantissa w/ implied MSB) and 64 bits for double. It's been a long time since I read IEEE-754 and it wouldn't surprise me that it allowed for other formats, I've just never seen them.

Bob

Yes, my mistake. As you can see in the example, it transferred 4 bytes.

lds r24,f lds r25,(f)+1 lds r26,(f)+2 lds r27,(f)+3

No, I don't think so.

The last time I used avr-gcc a float was 32 bits. I seriously doubt that's changed in the past year or two.

Gcc on some platforms has a option to make doubles 32 bits also.

IIRC, it only specifies the two formats (32 bits and 64 bits).

Grant Edwards grant.b.edwards Yow! Your CHEEKS sit like at twin NECTARINES above gmail.com a MOUTH that knows no BOUNDS --

SHARC DSP from Analog Devices supports 16-bit float in the hardware; it is supported by tools as well.

Me uses self made 24-bit float class (16 bit mantessa + 8 bit exponent) routinely; it is very handy when you work with 8-bit or 16-bit integer machine.

Vladimir Vassilevsky DSP and Mixed Signal Design Consultant

formatting link

Well, that only prove it's imperative that you get away from last time you knew, into the real time we're in, and find better things to know.

You remember pretty much the opposite of the truth.

No. There's a statement involving a cast that would do that, but it's _not_ the statement we've been discussing here.

Then you need to come from a different place.

Wrong.

No, it won't. The platform's floating point format is almost always irrelevant to the resulting value of this expression. Typically the _only_ things relevant are the range of unsigned char, and the decision made by the compiler implementor (or platform ABI) about what kind of undefined behaviour to exhibit if the float's value is outside that range.

Not exactly. The SHARC fpu operates on 32 or 40 bit data. A 16-bit packed storage format is supported by the ISA using FPACK/FUNPACK instructions which are special shifter operations.

The shifter can't access memory - only the register file - so you need to load before unpacking and pack before storing. In the worst case - an isolated operation on packed operands producing a packed result - the (un)packing can add 80% overhead to the FPU operation. However, because the SHARC is multiple issue and the shifter works in parallel with the FPU, the overhead of (un)packing multiple values can be amortized over longer FPU sequences.

FWIW: IEEE 754-2008 now defines a standard 16-bit binary float format. (see

formatting link

I recall some software FP libraries offering 16-bit floating point operations back in the days when FP coprocessors were expensive luxuries.

George

Yep.

I've used the gcc-avr compiler, and it will handle that just fine.

Grant

Well I may have had a brain scrub on that one, I'll admit to that how ever. This will produced what I was talking about.

u2 = *(unsigned int *) &f;

I would be very surprised if the gcc-AVR compiler could handle that..

Yeah, that's the sort of construct that has the effect you were thinking about. It's incredibly non-portable, ill-defined, and "How lucky do you feel today?"

If you do it the other way (e.g.)

f2 = *(unsigned float *) &u2; thing could be even worse, due to misalignment issues. Segfaults are entirely possible on many architectures.

Dave Platt AE6EO Friends of Jade Warrior home page: http://www.radagast.org/jade-warrior I do _not_ wish to receive unsolicited commercial email, and I will boycott any company which has the gall to send me such ads!

I am not sure if such statements are well-defined in terms of C standards, but they are well-defined in practice. You are reading the underlying memory pattern for the float. It's going to be dependent on the compiler/library combination for the format (though almost all compilers, including avr-gcc, use IEEE format), the endianness, and possibly alignment issues of the particular cpu.

But it is certainly valid and consistent to use pointer casts like this. If you've ever used memcpy to copy a struct containing floats, you've effectively done this already. And you'll see code like that in the library implementation of software floating point routines - that's how the floating point data is broken up and interpreted to carry out the calculations in integer operations.

I haven't seen an "unsigned float" before - guess that's a typo.

But yes, you always have to look out for alignment issues when mixing pointers to objects of different sizes. Of course, on an 8-bit avr, everything is aligned!

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required