Quote:Original post by thedustbustr
Should I always be using unsigned types for types that are never negative, or am I overreacting and adding complexity where it is completely unnecessary?
I've dug into this a bit deeper today and have changed my views on unsigned/signed usage. That is, except for bit-level operations such as those demanded by number generators, etc, I think I'll stick to signed integers. Here's why:
1. Today I tried to implement a system that catches lower bounds on loops, and found that with unsigned integers this is nearly imposssible, as these cannot go negative but instead tick over to the highest value. This was mentioned in the previous posts.
2. IMHO, the best way to control the boundaries of loops, etc is to use integers that can go negative. This means sticking with signed integers.
Today, after some experimentation, I've put together some code for type safety and a generic iterator type that can be used for general purpose looping, of the form:
for(obj.isFirst(),!obj.isDone(),obj.isNext()){ // do something...}
On my system, this code will run from 15% to more than 50% faster than a straightforward for(;;) loop of the same magnitude. Additionally, I've developed a generic int_t that is based upon ideas presented at http://hissa.nist.gov/sw_develop/ir5769/node6.html for safe integers for mission critical code. This can be combined with iter_t. My benchmarks of these so far indicate that there is little or no difference for small loops and that the generic types are much faster where static implementation can be made, and are slightly faster for dynamic, runtime implementation.
The rough code follows:
#include <iostream>#include <limits>#include "../Headers/Utilities/Error.h"// int_t classtemplate<typename intType>class int_t{public: int_t() : value_(0) { } int_t(const intType arg) : value_(arg) { } int_t(const int_t<intType>& arg) : value_(arg.value_) { } ~int_t() { } int_t operator=(const int_t<intType> arg) { value_ = arg.value_; return *this; } int_t operator=(const intType arg) { value_ = arg; return *this; } int_t operator+(const intType arg) const { return (int_t<intType>(value_ + arg)); } int_t operator+(const int_t<intType> arg) const { return (int_t<intType>(value_ + arg.value_)); } int_t operator-(const intType arg) const { return (int_t<intType>(value_ - arg)); } int_t operator-(const int_t<intType> arg) const { return (int_t<intType>(value_ - arg.value_)); } int_t operator*(const intType arg) const { return (int_t<intType>(value_ * arg)); } int_t operator*(const int_t<intType> arg) const { return (int_t<intType>(value_ * arg.value_)); } int_t operator/(const intType arg) const { if (arg == 0) { STUtility::error error_cond("Divide by zero"); error_cond.die(); } return (int_t<intType>(value_ / arg)); } int_t operator/(const int_t<intType> arg) const { if (arg.value_ == 0) { STUtility::error error_cond("Divide by zero"); error_cond.die(); } return (int_t<intType>(value_ / arg.value_)); } int_t operator%(const intType arg) const { return (int_t<intType>(value_ % arg)); } int_t operator%(const int_t<intType> arg) const { return (int_t<intType>(value_ % arg.value_)); } int_t operator&(const intType arg) const { return (int_t<intType>(value_ & arg)); } int_t operator&(const int_t<intType> arg) const { return (int_t<intType>(value_ & arg.value_)); } int_t operator|(const intType arg) const { return (int_t<intType>(value_ | arg)); } int_t operator|(const int_t<intType> arg) const { return (int_t<intType>(value_ | arg.value_)); } int_t operator^(const intType arg) const { return (int_t<intType>(value_ ^ arg)); } int_t operator^(const int_t<intType> arg) const { return (int_t<intType>(value_ ^ arg.value_)); } int_t operator~() const { return (int_t<intType>(~value_)); } int_t operator<<(const intType arg) const { return (int_t<intType>(value_ << arg)); } int_t operator<<(const int_t<intType> arg) const { return (int_t<intType>(value_ << arg.value_)); } int_t operator>>(const intType arg) const { return (int_t<intType>(value_ >> arg)); } int_t operator>>(const int_t<intType> arg) const { return (int_t<intType>(value_ >> arg.value_)); } int_t operator++() { ++value_; return *this; } int_t operator--() { --value_; return *this; } intType value() { return value_; } bool operator==(const intType arg) const { return (value_ == arg); } bool operator==(const int_t<intType> arg) const { return (value_ == arg.value_); } bool operator!=(const intType arg) const { return (value_ != arg); } bool operator!=(const int_t<intType> arg) const { return (value_ != arg.value_); } bool operator>=(const intType arg) const { return (value_ >= arg); } bool operator>=(const int_t<intType> arg) const { return (value_ >= arg.value_); } bool operator>(const intType arg) const { return (value_ > arg); } bool operator>(const int_t<intType> arg) const { return (value_ > arg.value_); } bool operator<=(const intType arg) const { return (value_ <= arg); } bool operator<=(const int_t<intType> arg) const { return (value_ <= arg.value_); } bool operator<(const intType arg) const { return (value_ < arg); } bool operator<(const int_t<intType> arg) const { return (value_ < arg.value_); } static intType min() { return std::numeric_limits<intType>::min(); } static intType max() { return std::numeric_limits<intType>::max(); } private: intType value_;};// Templated ostream operator.template<typename intType>std::ostream & operator<<(std::ostream & out, int_t<intType> arg){ return ( out << arg.value() ); }// Templated istream operator.template<typename intType>template<typename intType>std::istream & operator>>(std::istream & in, int_t<intType> arg){ return ( in >> arg.value() ); }// iter_t, can be used with int_t typestemplate<typename intType>class iter_t{public: iter_t(intType lower, intType upper) : counter_(0),min_(0),max_(intType::max()),lower_(lower), upper_(upper) { minErrorCheck(lower); maxErrorCheck(upper); } iter_t(intType lower, intType upper,intType minimum,intType maximum) : counter_(0),min_(minimum),max_(maximum),lower_(lower), upper_(upper) { minErrorCheck(lower); maxErrorCheck(upper); } void isFirst() { counter_ = lower_; } void isFirst(intType lower) { minErrorCheck(lower); counter_ = lower; } void isNext() { ++counter_; } bool isDone() { return (counter_ == upper_ ); } bool isDone(intType upper) { maxErrorCheck(upper); return (counter_ == upper ); } intType min() { return min_; } intType max() { return max_; }private: intType counter_; const intType min_; const intType max_; const intType lower_; const intType upper_; void minErrorCheck(intType lower) { if ( lower < min_ ) { STUtility::error error_cond("Exceeded lower limit"); error_cond.die(); } } void maxErrorCheck(intType upper) { if ( upper > max_ ) { STUtility::error error_cond("Exceeded upper limit"); error_cond.die(); } }};
This code is specific to my system, but would be easy to clean up for general use. Usage would be something like:
// Introduce safe integer types here, notice all are signed, bit type // will depend upon your system...typedef int_t<short> int16;typedef int_t<long> int32;typedef int_t<long long> int64;// Introduce 'safe and fast' iterator types here, based upon signed types...typedef iter_t<int16> iter16;typedef iter_t<int32> iter32;typedef iter_t<int64> iter64;// Initialize iterators for loop two ways...// 1. Pass lower, upper iteration limits, but the default min=0,max=numeric_limit<intType>iter32 i(0,100000000);// 2. Pass lower, upper iteration limits and min, max limitsiter32 j(0,100000000,-200,10000000000);// Now use as follows:for ( j.isFirst();!j.isDone();j.isNext() ){ // do something...}// You can reset the iteration limits but not min/max.for ( j.isFirst(-250); !isDone(100000); j.isNext() ){ // do something...}// The above loop will result in an error...as -250 is below the minimum for j.// You cannot pick up this type of error with unsigned values, as the system // will just use negative values by resetting to another unsigned value. To me// this is a potentially very serious and almost undetectable error condition!
Anyway, from a practical perspective, I'll be using signed ints for just about everything I can now. Hope this helps, and comments on the code would be welcomed...
--random
Edit--Although the above code has illustrated deterministic application, it is possible to plug in a non-deterministic iteration limit, whilst still having a check on the maximum range.
[Edited by - random_thinker on September 16, 2007 5:42:04 PM]
--random_thinkerAs Albert Einstein said: 'Imagination is more important than knowledge'. Of course, he also said: 'If I had only known, I would have been a locksmith'.