Click here to Skip to main content
14,975,098 members
Articles / Programming Languages / C++11
Tip/Trick
Posted 18 Feb 2016

Stats

20.1K views
11 bookmarked

Two's Complement for Unusual Integer Sizes

Rate me:
Please Sign up or sign in to vote.
3.80/5 (2 votes)
19 Feb 2016CPOL3 min read
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)
    {
        return (~mask & value);
    }
    // Two's complement?
    uint16_t msb = 1 << (length-1);
    if (value & msb)
    {
        return (mask | value);
    }
    else
    {
        return (~mask & value);
    }
}

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; 
// Read...
...
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)
    {
        return (mask | value);
    }
    else
    {
        return (~mask & value);
    }
}

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;
        TwosComplementUpscaler temp(value & ~mask);
        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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Orjan Westin
Software Developer (Senior)
United Kingdom United Kingdom
Orjan has worked as a professional developer - in Sweden and England - since 1993, using a wide range of languages (C++, Pascal, Delphi, C, C#, Visual Basic, PHP, Python and x86 assembler), but tends to return to C++.

Comments and Discussions

 
Suggestionminor points Pin
bling19-Feb-16 11:20
Memberbling19-Feb-16 11:20 
GeneralRe: minor points Pin
Orjan Westin21-Feb-16 22:25
professionalOrjan Westin21-Feb-16 22:25 
QuestionWould not bit field handle that correctly? Pin
Philippe Mori18-Feb-16 15:47
MemberPhilippe Mori18-Feb-16 15:47 
AnswerRe: Would not bit field handle that correctly? Pin
Orjan Westin18-Feb-16 22:58
professionalOrjan Westin18-Feb-16 22:58 
GeneralRe: Would not bit field handle that correctly? Pin
Andreas Gieriet19-Feb-16 2:02
professionalAndreas Gieriet19-Feb-16 2:02 
GeneralRe: Would not bit field handle that correctly? Pin
Orjan Westin19-Feb-16 2:37
professionalOrjan Westin19-Feb-16 2:37 
GeneralRe: Would not bit field handle that correctly? Pin
Andreas Gieriet19-Feb-16 3:23
professionalAndreas Gieriet19-Feb-16 3:23 
GeneralRe: Would not bit field handle that correctly? Pin
Philippe Mori22-Feb-16 6:12
MemberPhilippe Mori22-Feb-16 6:12 
GeneralRe: Would not bit field handle that correctly? Pin
Orjan Westin22-Feb-16 23:59
professionalOrjan Westin22-Feb-16 23:59 
QuestionSome thoughts with respect to programming close to the hardware... Pin
Andreas Gieriet18-Feb-16 8:28
professionalAndreas Gieriet18-Feb-16 8:28 
I guess you have several compiler warnings with your code...

You mention that you came across this while programming some device driver.
For programming close to hardware, I suggest to use the sized integers from #include <stdint.h> (in C) or #include <cstdint> (in C++).
Use size_t (from stdlib.h/cstdlib) for sizes like the length parameter.

Use unsigned literals in unsigned context, e.g. uint32_t mask = (~0u) << length;.

Your function should in my eyes be int32_t UpscaleTwosComplement(int32_t value, size_t length) { ... } or similarly smaller sized integers, depending on the length.

What is also missing is the under-/overflow check: the "upper" bits of value must all be either one or zero, otherwise you risk nasty numeric effects.

Seems you have plenty room for improvement Wink | ;-) .

BTW: you could also make the (template) function constexpr (with some refactoring) so you may benefit of rom-able values.

Cheers
Andi
AnswerRe: Some thoughts with respect to programming close to the hardware... Pin
Orjan Westin18-Feb-16 23:36
professionalOrjan Westin18-Feb-16 23:36 
GeneralRe: Some thoughts with respect to programming close to the hardware... Pin
Andreas Gieriet19-Feb-16 1:53
professionalAndreas Gieriet19-Feb-16 1:53 
GeneralRe: Some thoughts with respect to programming close to the hardware... Pin
Orjan Westin19-Feb-16 3:09
professionalOrjan Westin19-Feb-16 3:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.