Sign in to follow this  

Just when you thought stupidity had reached its limits (a debugging rant)

This topic is 3298 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I just spent the best part of two hours wondering why my code breaks apparently arbitrarily. Eventually I boiled it down to this:
		int tmp = -8;
		unsigned int tmp2 = 16;
		int tmp3 = tmp2/5;
		float a = float(tmp + tmp3);
		float b = float(tmp + tmp2/5);

This sets a==-5 and b==4.2949673e+009 *beats forehead against desk* For the last few years I've thought of myself as a fairly competent programmer, yet there's always a new lesson to learn. I just wasn't expecting a lesson like this. It's got something to do with unsigned vs signed integers, I guess. I still don't know if this is a C language subtlety I've overlooked, or maybe a bug in Visual Studio (I usually use gcc). Not sure if I want to find out. Thanks for letting me vent.

Share this post


Link to post
Share on other sites
It's a signed/unsigned thing. When you do arithmetic between a signed int and an unsigned int, the result is unsigned. In this case, -8 is getting transformed into 0xFFFFFFF7, and tmp2/5 is 3, so the result is 0xFFFFFFFA, which is roughly 4.29 * 109.

Share this post


Link to post
Share on other sites
yeah that is pretty gay although I always add a .0 to numbers when I want the float... thanks for sharing the lesson.

Did you fix it like this:

float b = float(tmp + int(tmp2)/5);
or
float b = float(tmp + tmp2/5.0);

Share this post


Link to post
Share on other sites
It always amazes me that otherwise competent programmers have such a stiffy for unsigned. Is that extra bit reeeeally worth losing so much assertability and running into so many more edge cases?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
It always amazes me that otherwise competent programmers have such a stiffy for unsigned. Is that extra bit reeeeally worth losing so much assertability and running into so many more edge cases?


I find often the ability to have something signed can lead to bugs, especially when it makes no sense whatsoever, and only use signed variables when I know something could be negative. Often times I don't want my integers to be negative. Whether it be physical screen or logical client coordinates, an index, etc., the need to accomodate negative integers usually turns out to be a special case for me... I guess I just haven't really been burned by something not being signed when it should be (well I can recall one minor incident... so let's say I haven't been severely burned).

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
Quote:
Original post by Sneftel
It always amazes me that otherwise competent programmers have such a stiffy for unsigned. Is that extra bit reeeeally worth losing so much assertability and running into so many more edge cases?


I find often the ability to have something signed can lead to bugs, especially when it makes no sense whatsoever, and only use signed variables when I know something could be negative. Often times I don't want my integers to be negative. Whether it be physical screen or logical client coordinates, an index, etc., the need to accomodate negative integers usually turns out to be a special case for me... I guess I just haven't really been burned by something not being signed when it should be (well I can recall one minor incident... so let's say I haven't been severely burned).


I think the caveat here is twofold:

1) The ranges restrictions in most languages are seriously underwhelming. Signed vs. unsigned. 16 bits vs. 32 bits. In reality, we want ranges for all sorts of things. Percents can be valid between 0 and 100. Perhaps hit points is valid between 0 and 9999 (Square). What we really want is a better built-in way to define ranges.

2) Converting between types which have different ranges automatically is error prone.

Of course, in C++, you can provide functionality like this with templates, but having it built-in, ala Ada would be really nice. It's a shame that as much as modern languages have lifted off predecessors that they missed that one.

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
I find often the ability to have something signed can lead to bugs, especially when it makes no sense whatsoever, and only use signed variables when I know something could be negative.

Yeah, this is always the thinking, and it is totally wrong. Using unsigned integers won't prevent the "accidentally negative" bug. It'll just transmute it into the "accidentally really big" bug, which is far more difficult to diagnose. Don't want negative numbers? Throw in some assertions.

Share this post


Link to post
Share on other sites
So in that case what's wrong with just testing if something is too big? If I'm checking bounds, instead of asserting x >= 0 as well as x <= max, I just assert that x <= max. I just don't see the harm, but I have the benefit of knowing things like "hey whatever parameter I pass to this function needs to be a positive integer". If I'm working out some sort of math, 99 times out of a hundred I need more accuracy than a whole number any way, and I am going to be using floats. I still can't see (said the guy with maybe just 1 eye but he's not quite sure)

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
I need more accuracy than a whole number any way, and I am going to be using floats.

More accuracy than a whole number, eh?

int i = 123456789;
float f = i;
std::cout << i << std::endl << std::fixed << f << std::endl;

Share this post


Link to post
Share on other sites
Quote:
Original post by DevFred
Quote:
Original post by popsoftheyear
I need more accuracy than a whole number any way, and I am going to be using floats.

More accuracy than a whole number, eh?

int i = 123456789;
float f = i;
std::cout << i << std::endl << std::fixed << f << std::endl;


... well yeah silly.

If I'm doing math ops, I usually need more accuracy than whole numbers can provide, thus I'll use floats. If I need a better range + accuracy than floats provide, I will use doubles. The above example still does not make an int any more accurate than up to a whole number....

Cheers
-Scott

[edit] Soooo since my integers rarely need to go into the negatives, why should the user of a given function think that negative numbers are ok??? It just happens to be a special case for me... still don't see the problem...

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
So in that case what's wrong with just testing if something is too big?
Because it's usually not nearly as clear how big is "too big". In contrast, "negative number" is pretty well defined.

Quote:
Soooo since my integers rarely need to go into the negatives, why should the user of a given function think that negative numbers are ok???
Nobody with half a brain thinks that a function parameter's type indicates the exact allowable range for that parameter. Or do you expect to be able to pass QNAN into a function that wants a float, and have things work properly?

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
If I'm doing math ops, I usually need more accuracy than whole numbers can provide, thus I'll use floats.


I don't think accuracy is the word you want here. Floats are inherently inaccurate. Doubles are less so, but still cannot exactly represent any given real value. If what you need is pin-point accuracy, use a fixed-point library. If you just need relatively close approximations to real numbers, floats are fine.

Share this post


Link to post
Share on other sites
Yeah I occasionally use fixed point when I need it - usually it's more of an issue of speed with turning floats <-> ints (like scaling an image) than it is one of accuracy (don't often have a problem here, and it's not like fixed point numbers can represent things like 1/3 much better than floats can).

And yeah... accuracy is probably the wrong word... more like the-ability-to-have-numbers-between-integers. I find I rarely need fixed point...

Quote:
Original post by Sneftel
Quote:
Original post by popsoftheyear
So in that case what's wrong with just testing if something is too big?
Because it's usually not nearly as clear how big is "too big". In contrast, "negative number" is pretty well defined.

Quote:
Soooo since my integers rarely need to go into the negatives, why should the user of a given function think that negative numbers are ok???
Nobody with half a brain thinks that a function parameter's type indicates the exact allowable range for that parameter. Or do you expect to be able to pass QNAN into a function that wants a float, and have things work properly?


Most likely I am just underexperienced :)

I'm thinking of when I use integers, and it is for things like indices into a buffer of some kind (x, y position in an image... index into a std::vector which is size_t which is an unsigned int... sound buffer... array of resources or files... yeah mostly arrays of one kind or another) or a count of something (how many times have I done this). Why give the option to make this negative intentially when you can, at the least, discourage the use of the variable this way?

Then I've noticed people are more likely to abuse the ability to assign to something negative to flag some special case - but this too easily leads to other bugs because it is "Ok"... sort of...

Nor did I say that the parameter's type specified the exact allowable range? It helps to know if you should or shouldn't go into the negatives though, and keeps you from asking questions such as what happens when I go into the negatives. Anyway sorry if I offended you Sneftel... just giving what seem like logical conclusions to me.

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
And yeah... accuracy is probably the wrong word... more like the-ability-to-have-numbers-between-integers. I find I rarely need fixed point...


The word you're looking for is 'precision'. Many people get these two mixed up because these words tend to be used interchangeably by a lot of people.

Precision is the amount of deviation between values, whereas accuracy is the correctness of the value. Ex: I can hit a target on the wall within 0.01 inches, but if I'm aiming at the floor, the high precision doesn't help me hit the accurate target.

Back to the topic at hand, unsigned values are handy when manipulating image formats, for example - not too many good ways to get around that.

Jeremiah

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
It always amazes me that otherwise competent programmers have such a stiffy for unsigned. Is that extra bit reeeeally worth losing so much assertability and running into so many more edge cases?


Given a compiler that will warn whenever I mix them (even in harmless cases), I like using unsigned.

It's quite possible that (new int[5])[-1] will pass at runtime without visible complaint. (new int[5])[unsigned(-1)] is almost sure to fail loud and hard.

I use it like a kind of poor man's dimensional analysis. If I use unsigned for my sizes and indicies (like the standard library does), then any attempt to use them as a proper variable -- where I just use int, because of the conversion and math scariness in unsigned -- will get the compiler mad at me.

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
I'm thinking of when I use integers, and it is for things like indices into a buffer of some kind (x, y position in an image... index into a std::vector which is size_t which is an unsigned int... sound buffer... array of resources or files... yeah mostly arrays of one kind or another) or a count of something (how many times have I done this). Why give the option to make this negative intentially when you can, at the least, discourage the use of the variable this way?
Because you can't discourage it that way. Negative integers passed into such a function will be invisibly converted into very large numbers, at most with a compiler warning (one which is often disabled, because of the large false-positive rate). The proper place for someone to learn the domain of a function is the function's documentation. Types are a weak substitute.

Quote:
Then I've noticed people are more likely to abuse the ability to assign to something negative to flag some special case - but this too easily leads to other bugs because it is "Ok"... sort of...
I'm not sure how this relates to the issue at hand. The use of special extreme flags is just as possible with unsigned numbers as it is with signed numbers.

Quote:
Anyway sorry if I offended you Sneftel... just giving what seem like logical conclusions to me.
Sorry, if I seemed offended it's probably because I came off as too strident, and for that I should apologize.

Share this post


Link to post
Share on other sites
Quote:
Original post by me22
It's quite possible that (new int[5])[-1] will pass at runtime without visible complaint. (new int[5])[unsigned(-1)] is almost sure to fail loud and hard.
Nope! [grin] Signed and unsigned addition are identical, and you're passing in identical bit patterns.
Quote:
I use it like a kind of poor man's dimensional analysis.
Why act like a poor man? You've got <cassert>! You're rich!

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
Quote:
Original post by me22
It's quite possible that (new int[5])[-1] will pass at runtime without visible complaint. (new int[5])[unsigned(-1)] is almost sure to fail loud and hard.
Nope! [grin] Signed and unsigned addition are identical, and you're passing in identical bit patterns.

Not on my 64-bit box. size_t(-1) != size_t(unsigned(-1)) when sizeof(size_t) > sizeof(unsigned).

(Yes, I know I've just opened myself up for criticism about containers with 2**32 or more elements. I don't have any. Files, sure, but that's why std::fstream::pos_type exists and is bigger than size_t on many platforms.)

Quote:
Original post by Sneftel
Quote:
I use it like a kind of poor man's dimensional analysis.
Why act like a poor man? You've got <cassert>! You're rich!

Compiler warnings test every line. I'm not going to cassert every line, and if I did, I'd probably forget at least once. When was the last time you wrote a program that checked every single printf, or checked for fail on std::cout?

Share this post


Link to post
Share on other sites
Quote:
Original post by Sneftel
The proper place for someone to learn the domain of a function is the function's documentation. Types are a weak substitute.
Fundamental types are a weak substitute.

Types are a better solution than documentation, since they're runtime-verifiable documentation, and in C++, need not impose a runtime cost in release.

In fact, given pervasive use, impose only slight cost at runtime, too, since assignment between instances of the type need not re-verify invariants. They could even lead to less cost at runtime, by moving the precondition check into the construction of instances of the type, which is only done once, rather than every time a function takes the value as an argument.

Imagine something to be the equivalent of GLclampf. sqrt? Stays in range, no checks on either end. Cubic spline sampling? It's in range, no checks. Etc.

Share this post


Link to post
Share on other sites
Quote:

Because you can't discourage it that way. Negative integers passed into such a function will be invisibly converted into very large numbers, at most with a compiler warning (one which is often disabled, because of the large false-positive rate). The proper place for someone to learn the domain of a function is the function's documentation. Types are a weak substitute.


I still don't get what's bad about doing one test instead of two. And if the "very large unsigned" turns out to be in range, wouldn't it be out of range for signed type too (untestable)?

I was also under the impression that using types that enforce rules (OK perhaps unsigned doesn't do that) is a lot better that putting the rules in the documentation which the compiler doesn't read. Isn't code supposed to be self-documenting?

Share this post


Link to post
Share on other sites
Quote:
I still don't get what's bad about doing one test instead of two.
As I said, there's often no reasonable maximum to test against.
Quote:
And if the "very large unsigned" turns out to be in range, wouldn't it be out of range for signed type too (untestable)?
I have no idea what you're asking here. Sorry.

Quote:
I was also under the impression that using types that enforce rules (OK perhaps unsigned doesn't do that) is a lot better that putting the rules in the documentation which the compiler doesn't read. Isn't code supposed to be self-documenting?
As you said, the compiler doesn't enforce that rule. In contrast, assertions are one of the best ways to make code self-documenting.

Share this post


Link to post
Share on other sites
Quote:
Original post by me22
Compiler warnings test every line. I'm not going to cassert every line, and if I did, I'd probably forget at least once. When was the last time you wrote a program that checked every single printf, or checked for fail on std::cout?

I agree to some extent, but it's a matter of priorities. It's rare for me to write functions without putting assertions on the arguments as my first few lines, because that's an important and useful method of limiting the scope of bugs.
Quote:
Types are a better solution than documentation, since they're runtime-verifiable documentation, and in C++, need not impose a runtime cost in release.
It sounds like you're suggesting range-parameterized types. It's a great idea, and I've seen it done a few times, but it never seems to be done elegantly, or in a way that gives the impression that the solution is better than the problem. C++'s morass of casting tools force one to decide between ugly, distracting boilerplate syntax and clever but bug-prone syntax. Also, the usefulness there is limited to when the domain of a given parameter is independent of all other variables. If you want to specify that the first parameter is less than the second, for instance, you'll need to stuff them into a type together, which... this is what I mean about whether the solution is better than the problem.

Share this post


Link to post
Share on other sites
Wow, I never expected such a flurry of responses!

I'm a bit of a pedant when it comes to signed/unsigned etc. I would never, for example, use ints to store RGB values. It's a similar philosophy as underlies Hungarian notation (REAL Hungarian notation, that is): variables have meaningful names and types because types have meaning! It is important to use unsigned integers for indices (or size_t, really, which I only avoid because I don't like to #include things liberally).

This is not the first time I've been burned by automatic casting, but in the past it's been because I was using variables contrary to their proper semantics, and it was always fixed by actually following the conventions I set for myself more strictly. The const modifier springs to mind -- I make everything const unless there's a good reason not to.

Here's what pissed me off this time round: if you implicitly cast an int to a short int, the compiler complains. If you implicitly cast an int to a float, the compiler complains. Again because of a loss of precision. If you compare signed and unsigned numbers with a < or >, the compiler complains. Somehow, implicit signed/unsigned casts go silent.

Dumb.

Share this post


Link to post
Share on other sites

This topic is 3298 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this