1

Do any of you know how this will be calculated in C?

uint8_t samplerate = 200;
uint8_t Result;
Result = 0.5 * samplerate;

Now, the problem is that 0.5 is a float and samplerate an integer. Result could then be either 0, because 0.5 is converted in an integer and therefore rounded to 0 (Result = 0 * 200 = 0). Or Result could be 100, because the compiler sees 0.5 first and converts samplerate into float (Result = 0.5 * 200 = 100).

Is there a standarized way how the compiler will handle these calculations? I mean will the compiler look at the variable on the very left (in this case 0.5) first and convert the other to this, or will it look at the variable on the very right (samplerate) and convert the other variables to this?

I know how I could solve this problem but I look for an general answer, if this is C standarized and how will it calculate such equations?

Lundin
  • 195,001
  • 40
  • 254
  • 396
oxidafo
  • 13
  • 3
  • 2
    Please read [this implicit conversion reference](https://en.cppreference.com/w/c/language/conversion). It will tell you that `0.5` will *not* be converted to an integer, but rather the opposite, `samplerate` will be converted to a `double`. – Some programmer dude Feb 01 '19 at 14:42
  • 1
    [Implicit type promotion rules](https://stackoverflow.com/questions/46073295/implicit-type-promotion-rules) – Lundin Feb 01 '19 at 14:58
  • @Someprogrammerdude, I'm afraid you are using as reference a c++ reference with it's (different) conversion rules. Due to possible one parameter constructor existence, automatic implicit conversion rules in c++ are far more complex (they are dynamic, run time) than they are in C and the sample you posted is not valid. – Luis Colorado Feb 02 '19 at 09:30
  • 1
    @LuisColorado That site may be *called* "cppreference", but the site contains references for both [C](https://en.cppreference.com/w/c) and [C++](https://en.cppreference.com/w/cpp). The link I provided goes to the C conversion reference – Some programmer dude Feb 02 '19 at 11:17
  • @Someprogrammerdude, my apologies for my mistake. You were right. – Luis Colorado Feb 04 '19 at 14:38

3 Answers3

4

Yes, of course this is controlled by the standard, there is no uncertainty here.

Basically the integer will be promoted to double (since the type of 0.5 is double, it's not float) and the computation will happen there, then the result will be truncated back down to uint8_t. The compiler will shout at you for the loss of precision, typically. If it does not, add more warning options as required.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • Note also that the behaviour on converting a `double` that's less than or equal to -1 to an unsigned type is undefined. – Bathsheba Feb 01 '19 at 15:43
  • but the behaviour here is not to convert a `double` that is less than -1, it is indeed `100.0`, and allows even exact representation in case of IEEE-752 internal representation of `double`s. What's the intended reason of your comment? the `double` result here is `100.0` so it can be converted to `unsigned` and fits in it's representation (it is less than `255.0`) – Luis Colorado Feb 02 '19 at 09:16
4

When numeric values of various types are combined in a expression, they are subject to the usual arithmetic conversions, which is a set of rules which dictate which operand should be converted and to what type.

These conversions are spelled out in section 6.3.1.8 of the C standard:

Many operators that expect operands of arithmetic type cause conversions and yield result types in a similar way. The purpose is to determine a common real type for the operands and result. For the specified operands, each operand is converted, without change of type domain, to a type whose corresponding real type is the common real type. Unless explicitly stated otherwise, the common real type is also the corresponding real type of the result, whose type domain is the type domain of the operands if they are the same, and complex otherwise. This pattern is called the usual arithmetic conversions :

  • First, if the corresponding real type of either operand is long double , the other operand is converted, without change of type domain, to a type whose corresponding real type is long double .
  • Otherwise, if the corresponding real type of either operand is double , the other operand is converted, without change of type domain, to a type whose corresponding real type is double .
  • Otherwise, if the corresponding real type of either operand is float , the other operand is converted, without change of type domain, to a type whose corresponding real type is float .
  • Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
    • If both operands have the same type, then no further conversion is needed.
    • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
    • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
    • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
    • Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.

Note in particular the paragraph in bold, which is what applies in your case.

The floating point constant 0.5 has type double, so the value of other operand is converted to type double, and the result of the multiplication operator * has type double. This result is then assigned back to a variable of type uint8_t, so the double value is converted to this type for assignment.

So in this case Result will have the value 100.

dbush
  • 205,898
  • 23
  • 218
  • 273
0

Yes, there is a standard. In this case, the numbers in the expression are automatically converted to the wider type (one that occupies more bytes), so your expression will be evaluated as follows:

(0.5: double) * (0: uint8_t) => (0.5: double) * (0.0: double) == (0.0: double)
uint8_t Result = (0.0: double) => (0: uint8_t) // this is a forced cast, because Result is of type uint8_t

double is wider than uint8_t, so (0: uint8_t) is widened to (0.0: double). This cast doesn't lose information since double occupies enough space to fit all the data stored in uint8_t.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
  • 1
    To be picky, `long long` likely occupies more bytes than `float`, yet it will be converted to `float` if the other operand is of that type. – Lundin Feb 01 '19 at 15:05
  • Characterizing this as an issue of widening is both misleading and incomplete. In this case, the `uint8_t` is converted to `double` for the computation because the other operand's corresponding real type is `double`, no more, no less. If the floating operand were `0.5f`, a `float`, and the other were a `long long`, then the latter would be converted to `float` for the operation, even though `long long` is likely wider than `float` (and regardless of whether it actually is wider). – John Bollinger Feb 01 '19 at 15:05
  • @Lundin, to be picky picky, `long long` likely occupies **at least** as many bytes than `long`. That doesn't imply that `long long` is necessarily greater than `float`. Can be same size, or even shorter. I'm afraid you are not being picky, but just mistaken. – Luis Colorado Feb 02 '19 at 09:22
  • 1
    @LuisColorado On pretty much all real world implementations known, long long is 8 bytes and float 4 bytes. Yet long long will be "promoted" to float. – Lundin Feb 02 '19 at 17:46
  • @Lundin, promotions in C from integer values (any size, even `char`) go to `double` values. I'm afraid you have already been told about this. Anyway, promotions from `long long` always lose some information, as `long long` have 64 significant bits, while `double` has only 52. What's the problem? I think you insist in looking for a wider floating point number... and that's not the thing. Standard says integers mixed with floating point promote to the floating point, even if that means losing some precision (and you almost always do) – Luis Colorado Feb 04 '19 at 14:26
  • `float`s by the way have only 24 bits for the significand... even a 32 bit `int` loses precision when promoted to `float`. You have to check your idea. Normally you don't try a wider precision number... rules are fixed. The other operand type is what mandates the kind of conversion, as John Bollinger told you in another comment. – Luis Colorado Feb 04 '19 at 14:30
  • Implementations don't fix the standard... it is just the opposite: the standard mandates what implementations do. The only requirement for `long long` is to be at least wider or equal than `long`, as `long` must be at least wider than `int` so they can be all equal. So nothing impedes `long long` to be 16 bits in a compliant implementation. Live with that in mind. – Luis Colorado Feb 04 '19 at 14:34
  • Well, I think I have extralimited myself. Some standard makes `long` to be at least 32 bit.... so `long long` must be at least that size. My apologies for that. – Luis Colorado Feb 04 '19 at 14:36
  • @LuisColorado "promotions in C from integer values (any size, even char) go to double values" No. Please study "the usual arithmetic conversions", C17 6.3.1.8. `Otherwise, if the corresponding real type of either operand is float, the other operand is converted, without change of type domain, to a type whose corresponding real type is float.`. You are also incorrect about the size of types, `long long` must be at least 63 bits to be compliant. C17 5.2.4.2.1/1 Sizes of integer types "minimum value for an object of type long long int -9223372036854775807 // −(2^63 − 1)" – Lundin Feb 04 '19 at 14:46
  • @Lundin, please consider the _go to double values_ as a typo. I know the usual arithmetic conversions. What is wrong is to consider (as you do in your comment) that the thing is to get to a wider precision type, that is wrong. In case of other operand being narrower, there's a loss in precision. For the actual sizes of integers I have been in the C world since Unix system III, and i'm afraid I've seen far too much integer sizes in my life to remember them all. I normally don't assume a minimum or maximum size for any generic integer, but consider as the standar did for a long time that... – Luis Colorado Feb 04 '19 at 15:10
  • ... `long` is at least larger (or equal, never forget) than `int` and `long long` to be at least larger (or equal) than `long`. I've lived systems with `int` being 16 bits, `long` being 32 and `long long` even didn't exist at all. Don't complaint about this to me, as I remember as many integer types right now that it is difficult to enumerate which standard version you are talking about. – Luis Colorado Feb 04 '19 at 15:12
  • @LuisColorado `int` always had to be at least 15 bits+sign and `long` always had to be at least 31 bits+sign. Since the dawn of C and Unix - it has never been changed. `long long` was introduced 20 years ago with the C99 standard. As for precision, I haven't mentioned it in any of my comments. – Lundin Feb 04 '19 at 15:21
  • @Lundin, then I misunderstood you. My apologies for that. My observation was only that you said _to be picky `long long` likely occupies more bytes than `float`_ and that's not necessarily true. – Luis Colorado Feb 04 '19 at 15:22