This is embarrassing, but I can't do division ?!?!

Status
Not open for further replies.

laptophead

Well-known member
according to Arduino website 2 integers can be divided.

So I tried

int a =33;
int b =55;


void setup() {

Serial.begin(115200); // main mac port to teensy
}

void loop() {
double c= a/b;

Serial.println (c);
delay (1000);

}

I get 0.00

What am I missing, must be super elementary , sorry....

Thanks
 
try the following - with all right side elements being int - it gives an int as result to the left assignment:

Code:
int a =33;
[B]double b =55;[/B]

void setup() {

  Serial.begin(115200);  // main mac port to teensy
}

void loop() {
 double c= a/b;

 Serial.println (c);
 delay (1000);
}
 
Thanks a lot, I wish the Arduino website was correct,

The show:
Example Code
int a = 50;
int b = 10;
int c = 0;
c = a / b; // the variable 'c' gets a value of 5 after this statement is executed
 
That example looks right given 'a' bigger than 'b' - it does go in an integer 5 times.

The post #1 example "33/55" results in a non-integer less than 1 so that is zero when done with integer math - which is the 'C' norm unless it is forced with a cast or other element that makes the compiler change from the int's provided.
 
When you have c = a / b :
  • If both a and b are integer types, c is the result truncated (rounded towards zero) to the nearest integer, even if c is a float or a double.
  • If a or b is a float, and the other is a float or an integer type, c is the result of the division using float precision and range, converted to whatever type c has if it is not a float.
  • If a or b is a double, c is the result of the division using double precision and range, converted to whatever type c has if it is not a double.
Typically, float has about seven, and double about 15 significant digits.

The reason for this behaviour is C++ implicit type promotion rules; the same applies to all standard arithmetic operators (+, -, *, /). In a promotion, the value stays unchanged [note below], but the type changes.

If an operand is an int or an integer type smaller than int, it is promoted to int.
If one operand is a long and the other operand is (or was promoted to) int , the other operand is promoted to long.
If one operand is a long long and the other operand is (or was promoted to) int or long, the other operand is promoted to long long.
If one operand is a long double, the other operand is promoted to long double too.
If one operand is a double, and the other operand is an integer type or a float, the other operand is promoted to double.
If one operand is a float, and the other operand is an integer type, the other operand is promoted to float.
The operation is then done at the range and precision of the common type; the result (in an expression) has that same type.
If the result is assigned to a variable of an integer type, the result is truncated (rounded towards zero);
if the result is assigned to a floating-point variable with less precision than the result, the extra precision is discarded (using the current floating-point rounding method wrt. the discarded bits and the least significant unit in the result).

It is easier to remember that for integer types, the base type is int, with the next higher type being long (AKA long int), and the highest type being long long (AKA long long int). unsigned types promote to unsigned types, but if one is unsigned and the other signed, both are promoted to signed types.
For floating point types, the types are float, double,and long double.
If one is an integer type (signed or unsigned), and the other is a floating-point type, the integer type operand is promoted to the same floating-point type.

[note]: When an integer type is promoted to a floating-point type, it is possible for the floating-point value to differ, if the magnitude of the value is large enough. For example, promoting an int 2147483584 to a float (on typical systems where float is IEEE-754 Binary32), yields value 2147483584.0f, which is off by 64. On typical systems, the exactly represented integer ranges are -16777216..16777216 for floats, and -9007199254740992..9007199254740992 for doubles; the range for long double varies but is at least as large as for doubles. Values larger in magnitude will be "rounded" to the nearest representable value in the target floating-point type.

To "promote" any operand to another type, simply cast it. Explicit casts precede implicit casts, so you can easily control the expression using a single cast.
For example, c = (double)a / b causes a, and implicitly also b to be promoted to (at least) double prior the division.
c = a / (double)b has the same effect; it does not matter which operand you cast.
Casting the result c makes no sense, though.

A cast has the side effect of promoting or reducing the value to the target type precision and range. So, for example,
c = (float)(a / (double)b)
calculates the division using at least double precision and range, but then limits the result to float precision and range, before (converting to whatever type c has) and assigning to c.
Usually, you don't need this, but if you ever find yourself implementing e.g. Kahan sum algorithm or other similar numerically sensitive algorithms, this may come in very useful. (In particular, you do not need to try and disable optimizations to get such numerically sensitive code working correctly, if you use such explicit casts.)
 
Last edited:
Great explanation. There is a small error in it which is worth correcting:

unsigned types promote to unsigned types, but if one is unsigned and the other signed, both are promoted to signed types.
It is the other way round. Signed will be promoted to unsigned which can be quite confusing.

Code:
int a = 7; 
int b = -10; 
float c = a+b;

Serial.printf("%.0f\n",c);

This will print -3 as expected.

Code:
unsigned a = 7; 
int b = -10; 
float c = a+b;

Serial.printf("%.0f\n",c);

This prints 4294967296 since the -10 will be interpreted as unsigned.

Another thing which sometime confuses users: For a Teensy int and long are both 32bit
Code:
 Serial.printf("sizeof(int): %d\n",sizeof(int));
 Serial.printf("sizeof(long): %d\n", sizeof(long));
 Serial.printf("sizeof(long long): %d\n", sizeof(long long));

prints:
Code:
sizeof(int): 4
sizeof(long): 4
sizeof(long long): 8
 
It is the other way round. Signed will be promoted to unsigned which can be quite confusing.
Quite true! Thanks for pointing out the error, luni. (Edit time window closed, can't fix :().


Some further detail, to atone for my error, for anyone working with non-integer numeric data:

In practice, I like to use explicit-width signed and unsigned integer types, int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t which promote in that order. For Teensy LC/3.x/4.0, the minimum promotion is to int32_t, but on AVRs to int16_t (since int is just 16 bits there, deviating from current C/C++ standards).

For maximum speed, I sometimes use fixed-point arithmetic. For example, if my values are always between -1 and +1, I might use Q1.30 (int32_t, where the low 30 bits denote the fraction, and one bit the integer part; the one leftover bit is used for the sign -- except that int32_t uses two's complement format). Essentially, the integer variable int32_t v can have actual values -2147483648 (= INT32_MIN) to 2147483647 (=INT32_MAX), which correspond to logical value v/1073741824.0 , for a logical range of -2.000000000 to 1.999999999.

Addition and subtraction work normally, without any tricks. To multiply two such values a and b , we need temporarily twice the range. Using explicit casts (to make it more obvious to us humans what is happening, the expression is (((int64_t)a * (int64_t)b) >> 30) . Basically, we cast the multiplicands to a type with sufficient range, then do the multiplication. We get a value in essentially Q2.60 format. Since we only need 30 fractional bits, we shift the result down by 30 bits.

This works in GCC, which is the compiler used in the Arduino environment, but technically signed bit shift down is implementation defined. To avoid this, in portable C or C++ code, one might use (((int64_t)a * b) / (int64_t)1073741824LL) . Most compilers do generate the exact same code for the two ways of writing it, however.

For division, a / b, we need much the same, except we shift the dividend a before the shift, and in the other direction: (((int64_t)a << 30) / b) .

Note that technically the result after multiplication is Q2.30, so it can overflow. (For very small divisors, the result after division can be up to Q1.60, so division can overflow too.) If you store the result to a int64_t variable, you can test for overflow by checking if the result is within INT32_MIN and INT32_MAX, inclusive. That is, if int64_t c is the result, overflow happened if and only if (c < INT32_MIN || c > INT32_MAX) , or conversely, the result did not overflow iff (c >= INT32_MIN && c <= INT32_MAX).

Fixed-point types are easier/faster to convert to and from strings, but their downside is that none of the standard functions or libraries in Arduino recognize them; to them, they are just int32_t's. Teensy 3.5 and 3.6 do have hardware floating-point support for floats, and Teensy 4.0 for floats and doubles, so on those I usually use native floating-point types instead. However, the above fixed-point types work even on AVRs, like Teensy 2.0 and 2.0++, and are much faster than the software floating-point emulation. The Q1.30 type above is particularly useful on AVRs for vector algebra and unit quaternions (3D rotations).
 
In practice, I like to use explicit-width signed and unsigned integer types, int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t which promote in that order. For Teensy LC/3.x/4.0, the minimum promotion is to int32_t, but on AVRs to int16_t (since int is just 16 bits there, deviating from current C/C++ standards).

Ummm, that is incorrect. Since the original 1989 ANSI/1990 ISO standard of the C, the int type has always been allowed to be 16 bits.

In fact the original machines that Dennis Ritchie used to create the C language (originally the PDP-7 and later PDP-11, both from Digital Equipment Corporation) were 16-bit machines. The AVR microprocessors are actually 8-bit machines, but the GCC compiler masks that, giving 16-bit int types.

From the last draft of the C18 standard:
  • minimum value for an object of type int INT_MIN: -32767
  • maximum value for an object of type int INT_MAX: +32767
  • maximum value for an object of type unsigned int UINT_MAX: +65535

And the eagle eyed among you may notice that on most systems with 16-bit integers, the INT_MIN is -32767 when on the hardware it actually goes to -32768. This is because some older systems did not use the binary twos complement arithmetic that is used almost everywhere today. These other representations include:
  • Sign magnitude where the sign bit was independent of the rest of the integer part (i.e. like how IEEE floating point is encoded). So on a sign magnitude system, the limit for 16 bit integers would -32767 to +32767, and it would also have the encoding for -0. The Burroughs computers from the 1960s/1970s used sign magnitude and they were represented on the original ANSI X3J11 committee that produced the ANSI and later ISO C standards.
  • One's complement where negation is done by complementing each bit. On a one's complement system, the limit for 16 bit integers would be -32767 to + 32768.
    While several of the earlier mainframes from the 1960s and 1970s used one's complement, the only one that was represented on the original ANSI X3J11 committee was Unisys (previously Sperry Rand, and Sperry + Remington Rand before that).

And I should mention, I was on the original X3J11 comittee from its inception through the publication of the 1989/1990 standards, representing two different employers (Data General and later Open Source Foundation). While both Unisys and Burroughs were original members, when Burroughs bought Unisys, they had to drop down to one representative who had to protect the interests of both legacy systems.

Where the AVR systems (like the Arduino UNO) depart from the standard is their double uses the IEEE single precision representation that gives you effectively 6 decimal digits, and the ANSI/ISO standards require 10 decimal digits.
 
Yep, but there is no way you can run a full POSIX system on an AVR (or a Teensy for that matter).

I only meant that I misremembered the limit.h differences between POSIX and C. (C++, which Arduino environment uses, delegates the definition to C standards.)
I also mostly ignore C11 and rely on C99 instead, because of the standards-committee-stuffing Microsoft did for C11.
 
I wish the Arduino website was correct,

You should be careful what you wish for. When you use the word "correct" about semantics of programming languages..... ;)

Any chance I can talk you into posting the link to that specific Arduino documentation page? I really don't believe this sort of highly detailed info C++ type promotion rules belongs there, but maybe the Arduino devs could the talked into adding a brief mention of using float types to preserve fractional results. Maybe that could help others in the future?
 
Returning to the issue of division, then, how should it be done?. In my designs I always use floating and to present in serial port or in the text editor of the screens that I regularly use, I use sprintf.

The key is these lines lines:

Code:
float a=33, b=55, c=0;
sprintf(floatTXT,"c = %g / %g = %g",a, b, c);

vs1wwedet5shmzezg.jpg


T4@NHD-FT813-3.5"
 
Returning to the issue of division, then, how should it be done?
If your variables are floats or doubles, normal operations (+ - * /) work as expected, remembering that floating-point values are not always exact. (For example, 0.1 cannot usually be represented exactly, but is actually slightly under or above.)

Let's say you have int a, b; instead.
Then, you need to calculate c using e.g.
Code:
    float c = (float)a / b;
or say print the result directly (to Serial) using
Code:
    Serial.print("c = ");
    Serial.print(a);
    Serial.print("/");
    Serial.print(b);
    Serial.print(" = ");
    Serial.println((float)a / b, 2);  // Two decimals

In my posts, there were just two little details wrong:
  • luni noted that signed types are promoted to unsigned types, and not vice versa as I wrote,
    and
  • MichaelMeissner noted that the C standard does allow 16-bit ints. (It is POSIX.1 that requires 32-bit ints, not the C standard; and POSIX.1 is not relevant in the Arduino environment.)
Otherwise my posts stand.
 
Status
Not open for further replies.
Back
Top