Author Topic: constant sufixes  (Read 2647 times)

0 Members and 1 Guest are viewing this topic.

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
constant sufixes
« on: September 15, 2021, 08:00:31 am »
I have had two days of fun trying to work out why I could not get the correct result from a calculation in 32 bit envelope on a mega 0 series (8 bit CPU). I am using defines to work things out with various calculations that I expect to be optimized out. But it turns out that writing 5000 does not mean 5000, it's down to what the compiler chooses to interpret it as. In this case possibly an 8 bit number? so gibberish. I solved all problems relating to math errors by running around putting "u" or "ul" on the end of every defined constant.

OK so lesson learnt but u means "unsigned int" which it seems in this context is 16 bits. What if I have a 16 bit signed value what do I put? Looking around the net most explanations seem to assume a non 8 bit system that seems to treat everything as 8 bit unless told.

What do I do?

I could try using constants but then I cannot carry out calculations so easily. I can't calculate a constant that is in turn calculated, the compiler will complain.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: constant sufixes
« Reply #1 on: September 15, 2021, 08:38:34 am »
In C all numerical literals are integer or double unless otherwise specified. Even hex or octals!
You can specify further with suffixes u/U for unsigned, l/L for long, both UL, ll/LL for long long and ull for unsigned long long.
If it doesn't fit, it will go up one size automatically (adding long)

Hence why sometimes ops with 0x80... will give you a warning relating to discarding the sign.

To make a float instead of double use the f/F suffix.

Example:
5000 = integer
0x123 = integer
5000u = unsigned integer
5000l = long integer
5000ul = unsigned long
3.14 = double
3.14f = float

Whatever these types end up being is in inttypes.h
« Last Edit: September 15, 2021, 08:42:38 am by Jeroen3 »
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #2 on: September 15, 2021, 09:55:15 am »
except I have problems with 16 bit numbers, unless I put u on the end they go wrong. C standards seem to be 16 bit native, I don't know what MPLABX and it's compiler/pre-processor do. I'd rather be explicit. This also helps if I move the high level code to another architecture where int means something different.
 

Offline wraper

  • Supporter
  • ****
  • Posts: 13775
  • Country: lv
Re: constant sufixes
« Reply #3 on: September 15, 2021, 10:11:08 am »
It would be helpful if you post a snippet of your code where the problem happens. Most likely root of the problem lies not in compiler but in your code.
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #4 on: September 15, 2021, 11:16:41 am »
I'm multiplying numbers, if I don't specify a size for any number over 255 the results go screwy often just spitting out "0". Unless I cast stuff it get's it wrong even though the variable the result goes into is 32 bit and all the figures are worked out so that I do not exceed that limit.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: constant sufixes
« Reply #5 on: September 15, 2021, 01:57:10 pm »
C does some implicit casting that may be what you're getting bitten by.
If you do a multiplication with two variables the result is twice the width. Eg: 2x16bit = 32 bit, 2x32 bit = 64 bit. But if you're doing two literals, the compiler will do them for you.

https://godbolt.org/ has AVR GCC so you can try to view the assembler live.
Can't run it though.

Or you are compiling with `-mint8`?
https://gcc.gnu.org/wiki/avr-gcc

Try compiling with -Wconversion -Wsign-conversion to show where conversions are happning.

Code: [Select]
    int warning = 255u * 255u;
    // implicit cast is happening
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 2684
  • Country: us
Re: constant sufixes
« Reply #6 on: September 15, 2021, 04:09:46 pm »
I'm multiplying numbers, if I don't specify a size for any number over 255 the results go screwy often just spitting out "0". Unless I cast stuff it get's it wrong even though the variable the result goes into is 32 bit and all the figures are worked out so that I do not exceed that limit.

In C the type of the variable to the left of the assignment doesn't have any impact on the evaluation of subexpressions on the right hand side. 

When you multiply two numbers they are first implicitly promoted to the wider of the two arguments and at least to 'int'.  If the promoted type of the expression is not wide enough to store the result it will be truncated even if it is then assigned to a wider type like a long.

In avr-gcc, int is normally 16 bit.  That means if you multiply two plain integers that are both over 255, the result will overflow even if you store the result in a 32 bit integer.  You need to convert the inputs to long before multiplication if you want to get a 32 bit result.
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #7 on: September 15, 2021, 06:46:26 pm »
what if I cast the entire sum so

32_bit_var =  (uint32_t)(var1*var2*var3);
 

Offline golden_labels

  • Frequent Contributor
  • **
  • Posts: 434
  • Country: pl
Re: constant sufixes
« Reply #8 on: September 15, 2021, 09:41:35 pm »
A blind guess: the good old signed overflow while multiplying seemingly unsigned values? ;)

Final casting doesn’t affect the calculations. The types, type promotions and everything they entail is resolved at the stage where it is calculated. So in your code the types var1 and var2 determine what will be the result of var1 * var2 (and if it’s even defined), and then the output type of that and the type of var3 determine that again for the next multiplication. And then, finally, the cast to uint32 (assuming uint32_t is present) is converting the value by either taking modulo of it, wrapping around or doing nothing.

If you are worried about having to guess a suffix and that you may choose one that is too large, the good news is that “too large” is rarely an issue with modern compilers, as they are smart enough to optimize out extra calculations, which would not affect the final result after the assignment. In any case, there are (U)INTn_C macros in stdint.h, which generate code with the right suffix.

Worth watching: Calling Bullshit — protect your friends and yourself from bullshit!
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 2684
  • Country: us
Re: constant sufixes
« Reply #9 on: September 15, 2021, 10:51:46 pm »
what if I cast the entire sum so

32_bit_var =  (uint32_t)(var1*var2*var3);

That does nothing.  The uint32_t is redundant because the product is promoted to 32-bit anyway for the purposes of assignment and it happens too late to change the result of the multiplications.
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #10 on: September 16, 2021, 07:10:09 am »
A blind guess: the good old signed overflow while multiplying seemingly unsigned values? ;)

Final casting doesn’t affect the calculations. The types, type promotions and everything they entail is resolved at the stage where it is calculated. So in your code the types var1 and var2 determine what will be the result of var1 * var2 (and if it’s even defined), and then the output type of that and the type of var3 determine that again for the next multiplication. And then, finally, the cast to uint32 (assuming uint32_t is present) is converting the value by either taking modulo of it, wrapping around or doing nothing.

If you are worried about having to guess a suffix and that you may choose one that is too large, the good news is that “too large” is rarely an issue with modern compilers, as they are smart enough to optimize out extra calculations, which would not affect the final result after the assignment. In any case, there are (U)INTn_C macros in stdint.h, which generate code with the right suffix.



I see, so basically I'd have to put "u" on the end of every constant anyway to make sure I don't end up with half my range. presumably at least one constant needs to be "ul" to promote the calculation to 32 bit.
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 3830
  • Country: si
Re: constant sufixes
« Reply #11 on: September 16, 2021, 07:26:09 am »
Yeah some of the compilers for tiny 8bit MCUs really really love working with 8bits in math. Some of the 8bit MCUs are not very good at dealing with large numbers, so since its slow the compiler tries to avoid it.

I usually solve it with lots of casting or breaking up longer math calculations into steps as the intermediate variables get optimized away anyway.

But i never seen it cut down a constant into a too small data type, it was usually a matter of the calculation working with a too small output type.
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #12 on: September 16, 2021, 07:32:46 am »

But i never seen it cut down a constant into a too small data type, it was usually a matter of the calculation working with a too small output type.

i think the problem was that I have to cast every constant to make sure the type of math I expect to happen is happening.
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #13 on: September 16, 2021, 07:41:33 am »
I'm starting to think that I would be better off using a constant initialization instead of a define so that the format is very clear. Or just initialize a variable and let the compiler optimize it to a constant.
 

Online rstofer

  • Super Contributor
  • ***
  • Posts: 8644
  • Country: us
Re: constant sufixes
« Reply #14 on: September 16, 2021, 02:48:05 pm »
You could also declare the variable as 'const' and, if it is truly a constant, you probably should.  The 'const' declaration may result in the variable being placed in flash rather than RAM, depending on the architecture and the compiler.
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 2684
  • Country: us
Re: constant sufixes
« Reply #15 on: September 16, 2021, 03:06:54 pm »
Yeah some of the compilers for tiny 8bit MCUs really really love working with 8bits in math. Some of the 8bit MCUs are not very good at dealing with large numbers, so since its slow the compiler tries to avoid it.

It has nothing to do with that. The C semantics are very clear on integral promotion once the type mapping ABI is chosen.  The compiler will faithfully execute what you write regardless of it's opinion as to the performance of the architecture.

@Simon: yes, static constant int is often a better choice than #define constants.  The compiler will generally optimize them away completely as long as you never use the address-of & operator on them.  In C you can't use them everywhere because variables, even const variables can't be used as array length specifiers but for arithmetic you should be fine.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7717
  • Country: fr
Re: constant sufixes
« Reply #16 on: September 16, 2021, 04:40:53 pm »
In any case, there are (U)INTn_C macros in stdint.h, which generate code with the right suffix.

Sounds like a good idea for portability reasons. Now of course this is extra typing.
But mixing stdint types with legacy suffixes such as U or UL is not a good idea.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: constant sufixes
« Reply #17 on: September 16, 2021, 07:19:18 pm »

But i never seen it cut down a constant into a too small data type, it was usually a matter of the calculation working with a too small output type.

i think the problem was that I have to cast every constant to make sure the type of math I expect to happen is happening.
If your doing this, and you want an unsigned subtract to happen then you must make it 5u, yes.
Code: [Select]
unsigned int var;
int ans = var - 5;
Typically what I do it I don't put constants in expressions.
Code: [Select]
uint16_t a;
uint16_t b;
uint32_t c = a * b;
Doesn't leave much room for imagination.
So you going around telling everything to be ul, may not be your intended result due to what choice of (slow) multiply it picks.

TI has this short note describes it, for your archiving: https://www.ti.com/lit/an/spra683/spra683.pdf
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 2684
  • Country: us
Re: constant sufixes
« Reply #18 on: September 17, 2021, 02:49:57 am »

Typically what I do it I don't put constants in expressions.
Code: [Select]
uint16_t a;
uint16_t b;
uint32_t c = a * b;
Doesn't leave much room for imagination.


You say that but the whole point of this conversation is that this code is defective and will fail on 8 and 16 bit platforms.
 

Offline golden_labels

  • Frequent Contributor
  • **
  • Posts: 434
  • Country: pl
Re: constant sufixes
« Reply #19 on: September 17, 2021, 11:28:41 am »
Note: this has nothing to do with optimization. Optimization doesn’t change semantics of the code.(1) At most it exposes errors in already invalid code, that might’ve been missed in other circumstances.

I see, so basically I'd have to put "u" on the end of every constant anyway to make sure I don't end up with half my range. presumably at least one constant needs to be "ul" to promote the calculation to 32 bit.
That depends on the intention. A multiplication of two unsigned int values will be done entirely within unsigned int. For most of the inputs it will overflow. An unsigned overflow is well defined in both C and C++, but it may not be what you want.

Typically what I do it I don't put constants in expressions.
Code: [Select]
uint16_t a;
uint16_t b;
uint32_t c = a * b;
Doesn't leave much room for imagination.
So you going around telling everything to be ul, may not be your intended result due to what choice of (slow) multiply it picks.
Except that this code shows exactly what has been explained to be wrong: both in this  thread and the document you have yourself linked. uint32_t in the third line bears no significance for the multiplication. That multiplication is determined by the types of a and b, which are uint16_t in this case. Depending on the platform that may be signed multiplication and, again depending on the platform, that may overflow.

If you ever used this code and it was giving the results you expected, it’s because of the lucky circumstances. Typicall this is a combination of working with 2’s complement representation of integers and compilers optimizing undefined cases. Since a compiler is allowed to do so, it substitutes that with code acting as if the operation was valid (which is more optimal). By magic of 2’s complement the intermediate result, despite being actually invalid, has binary representation matching what it would be if the whole operation was unsigned. And since the code downstream re-interprets it as unsigned, it happens to be what you expected.

But this is an accident. This is not what this expression indicates in terms of language’s semantics.
____
(1) The exception being bugs in the compiler itself, but those are very rare compared to mistakes of whoever uses the compiler.
Worth watching: Calling Bullshit — protect your friends and yourself from bullshit!
 

Offline Simon

  • Global Moderator
  • *****
  • Posts: 16172
  • Country: gb
  • Did that just blow up? No? might work after all !!
    • Simon's Electronics
Re: constant sufixes
« Reply #20 on: September 17, 2021, 12:37:38 pm »
so I either create the variables to be the same size as that which they will be multiplied into or I have to cast each one at a time in any multiplication.
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: constant sufixes
« Reply #21 on: September 17, 2021, 12:44:19 pm »

Typically what I do it I don't put constants in expressions.
Code: [Select]
uint16_t a;
uint16_t b;
uint32_t c = a * b;
Doesn't leave much room for imagination.


You say that but the whole point of this conversation is that this code is defective and will fail on 8 and 16 bit platforms.
Fascinating, I've learned from year 1 C that a multiply always ends up with a result twice input width.
How have I not been bitten by that in all those years?

Seems it doesn't work right only on targets that don't have a 16 bit multiply. It will call you a library.

msp430: int16 __mspabi_mpyi (int16 x, int16 y)
avr: __mulhi3 ;;; (C1:C0) = (A1:A0) * (B1:B0)
Can't check tasking 8051 now, since that's the only 8 bitters I've worked with.

While on ARM, PowerPC or x86 it promotes them first to 32 bit, while it could very well have used the 16 bit multiply instructions....
 

Offline Jeroen3

  • Super Contributor
  • ***
  • Posts: 3698
  • Country: nl
  • Embedded Engineer
    • jeroen3.nl
Re: constant sufixes
« Reply #22 on: September 17, 2021, 12:47:19 pm »
so I either create the variables to be the same size as that which they will be multiplied into or I have to cast each one at a time in any multiplication.
From what I did just now it looks like at least one of the multiplication terms needs to be the size you want the result the be in. Be it via a cast, type suffix, or being the actual type.
 

Online ejeffrey

  • Super Contributor
  • ***
  • Posts: 2684
  • Country: us
Re: constant sufixes
« Reply #23 on: September 17, 2021, 02:34:03 pm »

Typically what I do it I don't put constants in expressions.
Code: [Select]
uint16_t a;
uint16_t b;
uint32_t c = a * b;
Doesn't leave much room for imagination.


You say that but the whole point of this conversation is that this code is defective and will fail on 8 and 16 bit platforms.
Fascinating, I've learned from year 1 C that a multiply always ends up with a result twice input width.
How have I not been bitten by that in all those years?

Seems it doesn't work right only on targets that don't have a 16 bit multiply. It will call you a library.

All that matters is whether the platform is natively 16 or 32 bit: i.e., what is the size of an unqualified int.  The C standard promotes integers in arithmetic operations to 'int' because that is assumed to be the most efficient size for arithmetic operations.  So your code will work fine on 32 bit platforms due to that implicit promotion.  However it won't work on 8 or 16 bit platforms and the version that does 32*32 -> 64 bit won't work on any standard system.

Note that this is a required part of the standard and is done regardless of whether it is actually more or less efficient on any given hardware.  It is just the integer promotion rules that the C standard specifies.
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 7717
  • Country: fr
Re: constant sufixes
« Reply #24 on: September 17, 2021, 05:44:05 pm »
Fascinating, I've learned from year 1 C that a multiply always ends up with a result twice input width.

It's unfortunately not that fascinating, as C arithmetics is very poorly taught, and very poorly talked about in most books. The rules are "clear" in the standard, but not that many developers really master them.

Certainly, the result of a multiply of N-bit x N-bit requires 2N bits to be represented in the general case, but C doesn't care about that. Thing is, C always favors efficiency, so it uses simple rules that do mostly favor efficient compiled code, rather than ensure "implicit correctness" in the math sense.

While that allows efficient code in most situations, I admit that C arithmetics is one of the most awful things ever. ;D

How have I not been bitten by that in all those years?

Maybe a mix of luck and the use of some compilers that would not strictly follow the standard, to your benefit. Who knows. :)
 
The following users thanked this post: newbrain


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf