15,613,716 members
Articles / Programming Languages / C++11
Tip/Trick
Posted 18 Feb 2016

25.3K views
11 bookmarked

# Two's Complement for Unusual Integer Sizes

Rate me:
Getting negative numbers from 10-bit (and other unusual size) integers

## Introduction

Negative integers are generally represented as Two's complement in binary. For most programmers, this is nothing they have to worry about, as it's handled automatically, including the conversion when assigning to a larger type. But when you work with integers of unusual length - like 5, 7 or 10 bits - you have to handle the negative representation yourself. This tip gives you a generic function to do just that.

## Background

A signed 8-bit integer with the decimal value `-2` is stored as the binary `11111110`, or `0xfe`. When this is assigned to a 16-bit integer, the value stored is not `0x00fe`, but `0xfffe`, which preserves the two's complement representation. In contrast, when you assign `0xfe` to an unsigned 16-bit integer, it stores `0x00fe`, since unsigned types do not adjust for two's complement.

However, when working with custom hardware or writing device drivers, you occasionally come across non-standard integer lengths, since register space can be limited. I recently had to cope with both 6-bit and 10-bit signed integers. When I read those, I stored them as an 8-bit and 16-bit integer, respectively, which messed up negative numbers. A 10-bit integer storing `-1` in two's complement will contain `0x3ff`, but a 16-bit integer containing `0x03ff` is taken to represent `1023` in decimal. It should be `0xffff`, which is 16-bit two's complement for `-1`.

I needed a conversion function to make sure negative numbers were represented accurately.

ETA: Philippe Mori reminded me of bit fields, which handles this for you (provided your data fits in an `int`), so I have written a little template class (because bit fields must be members) to give a third option.

## The Function

C++
```#include <cstdint>
#include <stdexcept>

int16_t UpscaleTwosComplement(int16_t value, size_t length)
{
// Too large?
if (length > 15)
{
throw std::out_of_range("UpscaleTwosComplement length too large");
}
uint16_t mask = (~0) << length;
// Too small for complement?
if (length < 2)
{
}
// Two's complement?
uint16_t msb = 1 << (length-1);
if (value & msb)
{
}
else
{
}
}```

After basic sanity-checking, it checks the most significant bit of `value` to see if it is a negative number. If it is, all the higher bits in the result are set to `1`, which preserves the representation. If it isn't, all higher bits are filtered out.

C++
```...
// 10-bit sensor value, 2's complement
int16_t registerTemp;
...
int16_t temp = UpscaleTwosComplement(registerTemp, 10);
// registerTemp temp
//   -1 (0x3ff)    -1 (0xffffffff)
//   10 (0x00a)    10 (0x0000000a)
//  -10 (0x3f6)   -10 (0xfffffff6)```

I also wrote a templated version, for more control of the target type, and slightly fewer runtime operations:

C++
```template<typename Target, size_t Length>
Target UpscaleTwosComplement(Target value)
{
// Template type sanity checks (C++11 and later)
static_assert(sizeof(Target) * 8 > Length, "Length too large for Target");
static_assert(Length > 1, "Too short for two's complement");
Target mask = (~0) << Length;
// Two's complement?
Target msb = 1 << (Length-1);
if (value & msb)
{
}
else
{
}
}```

## Bit Fields

I was reminded of bit fields, which is a mechanism in C and C++ to let you work on non-standard length integers. I cannot use them directly in my project, because of the way the hardware library is constructed, but you might be able to.

I can, however, use bitfields to do the conversion, so I have written the code to do that below as an alternative to my homespun conversions above. There are two limitations you need to be aware of, though. The first is that bit fields must be data members, of a struct or `class`. The second is that if you assign a bit field a value outside its range, the value it holds depends on the compiler implementation. For instance, a 10-bit signed bit field has a range of `-512` (`0x200`) to `511` (`0x1ff`), but if it is assigned a 16-bit value of `512` (`0x0200`) will yield `-512` in VC2008, and probably most compilers, but it is not guaranteed every compiler does the same thing - it is perfectly legal for a compiler to discard the sign bit, so you end up with `0`, for instance. Check it works as you'd expect on your compiler before using it.

C++
```template <typename Target, size_t Length>
class TwosComplementUpscaler
{
Target bitfield : Length;
// Private constructors
TwosComplementUpscaler(Target value)
: bitfield(value)
{}
TwosComplementUpscaler();
public:

static Target Convert(Target value)
{
// Template type sanity checks (C++11 and later)
static_assert(sizeof(Target) * 8 > Length, "Length too large for Target");
static_assert(Length > 1, "Too short for two's complement");

Target mask = (~0) << Length;
return static_cast<Target>(temp.bitfield);
}
}; ```

You use it as a stand-alone template function:

C++
```int16_t tt = TwosComplementUpscaler<int16_t, 10>::Convert(0x3ff); // = -1
tt = TwosComplementUpscaler<int16_t, 10>::Convert(0x1ff); // = 511
tt = TwosComplementUpscaler<int16_t, 10>::Convert(0x200); // = -512```

## History

• 18th Feb, 2016 - First posted
• 19th Feb, 2016 - Updates after comments. Added section on bit fields and code for that, changed `UpscaleTwosComplement` to use size-specified types, and check length

Written By
Software Developer (Senior)
Sweden
Orjan has worked as a professional developer - in Sweden and England - since 1993, using a range of languages (C++, Pascal, Delphi, C, C#, Visual Basic, Python and assemblers), but tends to return to C++.

 First Prev Next
 minor points bling19-Feb-16 11:20 bling 19-Feb-16 11:20
 Re: minor points Orjan Westin21-Feb-16 22:25 Orjan Westin 21-Feb-16 22:25
 Would not bit field handle that correctly? Philippe Mori18-Feb-16 15:47 Philippe Mori 18-Feb-16 15:47
 Re: Would not bit field handle that correctly? Orjan Westin18-Feb-16 22:58 Orjan Westin 18-Feb-16 22:58
 Re: Would not bit field handle that correctly? Andreas Gieriet19-Feb-16 2:02 Andreas Gieriet 19-Feb-16 2:02
 Re: Would not bit field handle that correctly? Orjan Westin19-Feb-16 2:37 Orjan Westin 19-Feb-16 2:37
 Re: Would not bit field handle that correctly? Andreas Gieriet19-Feb-16 3:23 Andreas Gieriet 19-Feb-16 3:23
 Re: Would not bit field handle that correctly? Philippe Mori22-Feb-16 6:12 Philippe Mori 22-Feb-16 6:12
 Re: Would not bit field handle that correctly? Orjan Westin22-Feb-16 23:59 Orjan Westin 22-Feb-16 23:59
 Some thoughts with respect to programming close to the hardware... Andreas Gieriet18-Feb-16 8:28 Andreas Gieriet 18-Feb-16 8:28
 Re: Some thoughts with respect to programming close to the hardware... Orjan Westin18-Feb-16 23:36 Orjan Westin 18-Feb-16 23:36
 Thanks for your comments. I have only compiled this in VC2008, which compiles both functions (with the static_assert commented out, because it's not supported) without any warnings at all, even on warning level four. I guess your compiler is more strict, or that you have stylistic warnings (would GCC's Scott Meyers flag complain about anything here?) of some sort enabled. I will try it on some other compilers in the weekend. Any suggestions? In the production code, I use the `ViInt16` and `ViUInt16` types from the VISA library. I replaced them with int and unsigned when I put this quick tip together, which was sloppy of me. I'll amend that. Good point about `size_t`, I'll amend that too. Mind you, an unsigned char would be enough, since I don't expect this to work on any type longer than 64 bits, let alone 128. But `size_t` improves clarity. Since I used short ints (noted in the text, if not the code), I couldn't use unsigned literals. `u` is for `unsigned int`, which is `uint_32t` on my platform. There is no string literal for `uint16_t`. And, of course, it in the template version it makes no sense either, since there's no way of knowing whether to use `u`, `ul` or `ull`. I'm not entirely clear what you mean by over/underflow here. The mask sets all "upper" bits in the result to one or zero as appropriate, and the only operations done on `value` are bitwise `and` and `or`, which carry no risk of nasty numeric effects that I can see. I do, however, need to check that `length` isn't greater than the bit size of the type. All in all, I admit to having some room for improvement, but not plenty.
 Re: Some thoughts with respect to programming close to the hardware... Andreas Gieriet19-Feb-16 1:53 Andreas Gieriet 19-Feb-16 1:53
 Re: Some thoughts with respect to programming close to the hardware... Orjan Westin19-Feb-16 3:09 Orjan Westin 19-Feb-16 3:09
 Last Visit: 31-Dec-99 18:00     Last Update: 28-Mar-23 18:05 Refresh 1