Question

I'm having some trouble trying to get a floating point output. What I'm trying to do is take the output of an accelerometer on 3 axes. The output from each axis has a high and low byte that are combined into a single variable. The magnitude of these three variables is then computed thusly: mag = sqrt(x*x + y*y + z*z). The output from each axis is output as a 16-bit signed integer.

At least, that's how it should work, but I can't even manage to multiply two large numbers together to produce the correct value in my program, and I see no reason for it as the variables should all be plenty large to hold any results. Here's the code:

    double xAccel = 0;              // 16 bit X acceleration value
    double accelSum = 0;
    xAccel = xAccelRead();

    accelSum = 10000*10000;
    char aMag[64];
    sprintf(aMag, "Accel Mag: %.2f", accelSum);

    clearDisplay();
    writeText(aMag,0,0,WHITE,BLACK,1);
    OLED_buffer();

The output of xAccelRead() is a 16 bit int. Normally, "accelSum" would be set equal to the magnitude equation given above, but for now the static numbers aren't even working. If I set the equation to 100^2, it works. But 10000 * 10000 doesn't work. The result of that should be 100,000,000. But the output I get is:

Accel Mag: -7936.00

I can't understand why this is. I've tried setting the types to int32, int64, and now floating point. Same problem with all of them. I set the correct linker options in Atmel Studio to allow floating point sprintf support, so that's not the problem. I'm guessing there is an overflow somewhere but I can't figure out where. All variables involved have types that are more than large enough to accommodate max values in the hundreds of billions, which is far more than I need anyway.

Say I set accelSum = 1000 * 1000. That's a mere million. Plenty small for an int32 to hold. But my output is:

Accel Mag: 16960.00

Even with 200 * 200, the output is -25536.00.

This has to be some stupid simple problem. If anybody can help me out I'd really appreciate it!

Was it helpful?

Solution

You have answered most of your question in your comment.

Your problem is similar to the surprise some programmers have with float f = 5 / 3; that the division is computed as an integer division, although it is destined to be assigned to a float. The solution in their case is to use float f = 5.0 / 3.0; instead.

If the expressions being divided or, in your case, multiplied, are not constants, you can convert them to a wider type with a cast: float f = (float)int_var1 / (float)int_var2; or, in your case, unsigned long foo = (unsigned long)int_var1 * (unsigned long)int_var2;. You could also convert to double so that the computed multiplication is a double multiplication: accelSum = (double)int_var1 * (double)int_var2;.

Only one of the casts is actually necessary because of “promotion rules”, but it is just as clear to write both.


If you are interested in the details, the C standard mandates that the type of an integer constant is selected as the first type that can represent the constant in a list. In C99(*), the list is int, long, long long. In your case, a 16-bit int is enough to contain 10000 or 200, so int is picked as the type of these constants.

(*) The idea was the same but the list was different in C90.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top