C language typecast question - Page 1
Forum section: electronics / beginners

author: Veketti
author_rank: Regular Contributor
timestamp: January 28, 2024, 02:47:39 pm
content: Dear All,It is said that in C language implicit typecast no need to manually typecast as lower data be converted automatically. But why is it that following doesn't work without manual (float) typecast?Code:float b;uint16_t adc_val = 3086;b = adc_val / 4096 * 3.300; // Result 0.000000There is higher datatype 3.300 present. Only if also 4096 is shown with decimal it works eg.Code:b = adc_val / 4096.00 * 3.300; // works if both values are with decimalorCode:b = (float) adc_val / 4096 * 3.300; // or by manual typecastor if only two values are presentCode:b = adc_val / 4096.0; // result is float

author: radiolistener
author_rank: Super Contributor
timestamp: January 28, 2024, 03:14:00 pm
content: Quote from: Veketti on January 28, 2024, 02:47:39 pmdivide and multiply operators have the same priority, so the compiler doing it from left to the right: first it doing divide adc_val / 4096 and then multiply by 3.3.Since both arguments for divide is integer (adc_val and 4096), the compiler using integer divide. And since adc_val is always below 4096, it always result to integer 0.Then integer 0 is multiplied with float 3.3, first argument is integer and second is float so it uses float multiply. But since first argument is always 0, the result also will be 0.0.Quote from: Veketti on January 28, 2024, 02:47:39 pmif you change 4096 to 4096.0 the compiler will use float divide, because one argument is float, so all operations will be float and you will get expected result.

author: radiolistener
author_rank: Super Contributor
timestamp: January 28, 2024, 03:36:47 pm
content: you can do in it the following way:Code:auto b = adc_val * 3.3 / 4096;If you replace multiplier 3.3 with integer (for example 3), this way allows you to do integer calculations and don't use floating point at all. But be careful, max adc_val value should fit into integer resolution, otherwise you will get overflow error.for example:Code:uint16_t adc_val = 3086;auto b = adc_val * 3 / 4096;in this case the b variable will have int type. The expression adc_val * 3 also has int type.If you use it on 16-bit platform, you're needs to make sure that adc_val * 3 will be always smaller than 2^16/2-1=32767.For 32-bit platform you're needs to make sure that adc_val * 3 will be always smaller than 2^32/2-1=2147483647.

author: Veketti
author_rank: Regular Contributor
timestamp: January 28, 2024, 03:53:33 pm
content: Makes sense, thanks. Oh, how good would have it been if the compiler would check it up to the semicolon and do the typecast automatically. In my opinion it would be better to have no automatic typecasting at all, if it can't handle the whole calculation.

author: magic
author_rank: Super Contributor
timestamp: January 28, 2024, 03:59:04 pm
content: Wouldn't always be good because on MCUs floating point math is a lot slower than integer, so you want to do as much as possible on ints. There is no way to please everybody.

author: radiolistener
author_rank: Super Contributor
timestamp: January 28, 2024, 04:23:10 pm
content: Quote from: Veketti on January 28, 2024, 03:53:33 pmThe compiler already check it up and do typecast automatically, but it don't know what you're want to do. It just doing what you requested. Since you wrote to do integer divide first and then float multiply, the compiler doing all what you requested. It cannot change integer divide to float, just because you wrote to do it as integer, so it needs to do it as integer. And there is no magic ball to forecast what you had in mind when you wrote some expression. The compiler just doing what is requested by your code.

author: Nominal Animal
author_rank: Super Contributor
timestamp: January 28, 2024, 04:49:12 pm
content: You actually want to dofloat b = adc_val / 4096.0f * 3.3f;orfloat b = adc_val * 3.3f / 4096;because constants with decimals are by default double precision, not single precision (like float is).The latter expression, because the divisor is a power of two, should optimize tofloat b = adc_val * 0.0008056640625f;where the numerical constant is exactly 3.3/4096, but evaluates to 13841203/234= 3.2999999523162841796875/4096 because of the limited precision in single-precision floating point, 3.2999999523162841796875 is the closest representable value to 3.3. So, in reality, it becomesfloat b = adc_val * 0.0008056640508584678173065185546875f;with the result rounded to nearest representable single-precision value.Many compilers allow "folding" double to the float type, but relying on that makes your code fragile when moving to a different architecture.It is much better to be explicit on what you tell the compiler to do, and not rely on implicit stuff or compiler options.Personally, I'd calculatebin integer millivolts, which yields the expressionuint16_t b = ((uint32_t)adc_val + 512) * 825 / 1024; // 3300/4096 = 825/1024noting that on an 8-bit microcontroller, 825 = 512 + 256 + 64 - 8 + 1, i.e.x*825/1024 = ((x << 9) + (x << 8) + (x << 6) - (x << 3) + x)) >> 10The addition causes rounding (halfway up).The other easy option is in integer tenths of millivolts, or hundreds of microvolts,uint16_t b = ((uint32_t)adc_val + 256) * 4125 / 512; // 33000/4096 = 4125/512noting that on an 8-bit microcontroller, 4125 = 4096 + 29 = 4096 + 32 - 3, i.e.x*4125/512 = ((x << 12) + (x << 5) - (x << 1) - x)) >> 9The tenths of millivolts will not have the correct least significat digit, because there are only 4096 possible values mapping to 33000. As ADC 0 maps to 0.0000, ADC 1 maps to 0.0008 (or 0.001), while the correct value is 3.3/4096 = 0.0008056640625 V.