Guys,
I am having a problem with float division using PK51. I was wondering if anyone could explain this or has seen it before.
I have a float, and want to divide it by 36000. If I do this directly, I get strange results, even -ve numbers although all the variables are +ve.
accrued_seconds =(total_accrued_seconds/36000);
Both variables are floats, however, if I do the following
accrued_seconds = (unsigned long)(total_accrued_seconds/10); accrued_hours = (float)(accrued_seconds/3600);
I get the correct result. For some reason that I don't understand using too big a divisor in the first code causes an error but not a two step as in the second. I have also tried the two step without the cast to an unsigned long and I get the errored result.
I tired the same thing on GCC on a PC and of course it works fine.
Any clues anyone?
Cheers,
Dirk
What happens if you write the constant as 36000u? Remember that a 16-bit signed integer only stores positive values up to 32767.
Another alternative - what happens if you write 36000.0?
Thanks Per,
Both solutions work. I hadn't appreciated that the constant value would work that way and be created as a 16 bit signed value.
Thanks for spotting that.
Remember that the 'C' programming language basically assumes that everything is an int unless explicitly specified otherwise.
Also, int defaults to signed.
Do you have all warnings enabled for the compiler?
It is bad form if the Keil compiler (with all warnings enabled) do not generate a warning about out-of-range integers either generating an incorrect/unexpected result or being promoted to long.
It isn't a bug not to warn, but I would consider contacting Keil support and report it as a bug anyway.
Yes, I have all warnings on and there is absolutely nothing flagged by the compiler.
I will send this in to Keil and see what they say.
I don't think it should be issuing a warning in this case. Surely the constant 36000 should be interpreted correctly and silently as signed long, converted to float prior to the division and the answer should be correct? Unless I'm in 'missing the blindingly obvious' mode today this seems wrong.
To the OP: can you tell us what value you actually do get when you run the original code?
"Unless I'm in 'missing the blindingly obvious' mode today this seems wrong."
Indeed. A decimal integer constant's type is the first from the list of 'int', 'long int', 'long long int' (C99) in which its value can be represented.
Exactly. In C90 (aka ANSI C), the list would be 'int', 'long int', 'unsigned long int'. Apparently, Keil's C51 compiler doesn't follow the C90 standard in this case. I cannot think of a good explanation why.
The standard doesn't say anything about a requirement to issue warnings, but most compilers do generate warnings for integer constants larger than what fits in an int.
I tested a version of gcc with a value larger than signed in. In this case 3000000000 on a 32-bit machine. The warning: test.c:5: warning: this decimal constant is unsigned only in ISO C90
Do you linke a compiler that does not upgrade to unsigned or long but silently made it a negative value without a warning, even when all warnings are turned on? Isn't the goal with warnings to inform the developer that what he expects and what he gets will not match?
The suffixes u, l, ul etc are there to specify a required size of an integer. For a compiler for an embedded target, the compiler should really consider giving a warning about potential cost of upgrading a constant to long. And using a negative integer value instead of switching to an unsigned constant should merrit a warning.
Maybe it depends on whether Integer Promotion is enabled or not...
"And using a negative integer value instead of switching to an unsigned constant should merrit a warning."
In the case of C51 with 16-bit int and 32-bit long, if it does in fact use the integer value -29536 for for the constant 36000, then it is not abiding by the standard, which says it should have treated 36000 as 'long int'. "Switching" to an unsigned type should not be an option in this case.
Correct. Since long is larger than int, it should first have stepped up to a signed long.
then it is not abiding by the standard, which says it should have treated 36000 as 'long int'
Which makes it all the more important for the OP to answer the question raised upthread: is this happening with Keil C51, and if so, was its option "standard integer promotions" turned on? Because if it is, and it wasn't (in that order ;-), it's not really surprising that this happened. C51 doesn't promise ANSI compatibility in that mode, so code shouldn't expect it.
I would prefer it to do what I expect, then no warning would be required.
For a compiler for an embedded target, the compiler should really consider giving a warning about potential cost of upgrading a constant to long.
I'm not so sure. One of the purposes of standardisation is to provide you with a manual which, if followed to the letter, allows you to write code which should produce the result you expect. If the compiler translates that code in any way that does not conform to the standard it should issue a warning.
While it may seem a good idea to warn about things other than this to be helpful before you know it you can't see the wood for the trees.
And using a negative integer value instead of switching to an unsigned constant should merrit a warning.
Absolutely. It's a deviation from the standard.
Your implicit trust of humans beings and their flawed abilities (evident in the forum, from time to time) is the most scary things about you, Jack.