3

In all my years of embedded programming, I've generally never needed to work with negative numbers. It's crazy, but they just don't come up very often in my line of work.

I'm currently dealing with a sensor reading that can be positive or negative and needs to be scaled by 0.006, preserving sign. To avoid needless floating point calculations during runtime, I have an algorithm that converts this to a numerator and denominator (3/500). Everything works as expected with positive numbers, but here's what happening with negatives:

Raw data:         -103
Multiplied by 3:  -309
Divided by 500:   36893488147419102

I figured out where that number comes from, and I have a workaround, but I'd rather trust that math is math.

Here's the same calculation in hex:

Raw data:         0xFFFFFFFFFFFFFF99
Multiplied by 3:  0xFFFFFFFFFFFFFECB
Divided by 500:   0x0083126E978D4FDE

In a calculator (SpeedCrunch):

0xFFFFFFFFFFFFFECB/500 = 0x83126E978D4FDE.9D2F1A9FBE76C8B44

That original 36893488147419102 is the integral part 0x83126E978D4FDE of the result from SpeedCrunch.

I don't want to have to save off the sign, do a positive division, then re-add the sign back in every time I do division with negative numbers. What's going on here?

Environment is CortexM3 micro, GCC4.9.3 using c++11. The calculations are done on an int64_t and the numerator/denominators are uint64_t.

Edit: Here's a code snippet in response to Michael's comment below:

int64_t data = -103;
uint64_t resolutionNumerator = 3;
uint64_t resolutionDenominator = 500;

data *= resolutionNumerator;
data /= resolutionDenominator;
Jeff Lamb
  • 5,755
  • 4
  • 37
  • 54
  • 1
    I know that you say the "calculations are done on ...", but to remove any guesswork you should really show the actual source for the expression(s) involved in the calculation along with the types for any variables involved. – Michael Burr Dec 11 '15 at 22:11
  • All of these numbers are based upon tables and raw data coming in elsewhere. It's extremely generic code with no "magic numbers" anywhere. However, I condensed it and added a snippet. – Jeff Lamb Dec 11 '15 at 22:36
  • Expressions with mixed data types are generally a very bad idea. You should strive for type agreement throughout, and where this is impractical (in this case it is not), you should use explicit casts to indicate that the type disagreement is intentional. The language defines implicit conversions and type promotions that may not do what you expect or need (as in this case). – Clifford Dec 12 '15 at 12:23
  • It seems unlikely that your sensor provides data to a resolution that warrants 64 bit and by dividing by 0.0006 you are discarding more than 10 bits of information in any case. Are you sure either than you need 64 bit or that this is the right place to perform scaling? You would normally only scale the data for presentation in "real-world" units. Internal calculations are best performed at the full resolution of the sensor data. – Clifford Dec 12 '15 at 12:25
  • Yes, you're right. This sensor is only 16 bits. However, this is extremely shared code and a different sensor could supply data up to 64 bits. There's a configuration for each sensor reading that allows you higher resolution by multiplying by 10 or 100, etc, for the sole purpose of avoiding floating point math and translation into "real-world" units that the individual application cares about. – Jeff Lamb Dec 16 '15 at 23:34

2 Answers2

3

An operation with signed and unsigned integers of the same width, such asuint64_t and int64_t, results in the signed operand being converted to the type of the unsigned operand.

Therefore these two expressions are equivalent:

  • (int64_t) -103 * (uint64_t) 3 / (uint64_t) 500
  • (uint64_t) -103 * (uint64_t) 3 / (uint64_t) 500

If the signed type int64_t were used for the numerator and denominator, the result would retain the sign.

D Krueger
  • 2,446
  • 15
  • 12
1

What's going on here?

When you cast it from an unsigned type to a signed type, the leading 1-bits remained for the negative numbers from the 2's compliment representation.

You might be able to recover the expected result by by casting the final result back to a signed value. As a signed value, the leading 1-bits will mean its a negative value.


We can only say might because we have not seen the routines. Michael pointed out the same in a comment.

int64_t data = -103;
uint64_t resolutionNumerator = 3;
uint64_t resolutionDenominator = 500;

The integral promotion rules of C/C++ means the signed type is promoted to an unsigned type. Its why -1 > 1:

int i = -1;
unsigned int j = i;

if ( i > j )
    printf("-1 is greater than 1\n");

You did not perform the cast mentioned in the response above; but the compiler did perform the integral promotion. For what you are observing, its the same difference.

jww
  • 97,681
  • 90
  • 411
  • 885
  • 0x0083126E978D4FDE (the result) has no leading `1` bit. Casting won't help. No custom math routines here. Just multiply and divide. – Jeff Lamb Dec 11 '15 at 22:43
  • Yes, the floating point thing was pretty much unrelated to the question. This is not about floating point. As an aside, float math (software implementation) is 24x more costly on my target than integral math. With this code being run 250 times a second, I have motivation to avoid floating point. – Jeff Lamb Dec 11 '15 at 22:43
  • Too bad? First, I implemented the 'hack' that fixed this before posting this. The question was answering why simple division doesn't work. Second, why should anyone have to write custom routines to deal with dividing negatives? Math is math. I'd like to trust it. – Jeff Lamb Dec 11 '15 at 22:44
  • Yeah, sorry for the tone. I tried to edit the comment, but there's a 5 minute limit. I didn't mean it to come across that way. That `-1 > 1` thing is confusing for sure. Thanks for your help. – Jeff Lamb Dec 11 '15 at 22:52
  • @Jeff - *"That -1 > 1 thing is confusing for sure..."* - oh yeah... Its a *very* unexpected result. I use it in my secure coding slide deck. – jww Dec 11 '15 at 22:54
  • 1
    Aaaand now this whole comment thread is in reference to content you edited out. Oh well, your answer is more to the point now. – Jeff Lamb Dec 11 '15 at 22:59
  • @Jeff - yeah, there's no reason to retain it, especially if it will cause future visitors confusion :) Sorry about that. – jww Dec 11 '15 at 23:05