c++algorithmsortingradix-sort

Radix sort get the right key to sort signed integers


I have a trouble with finding a right value for sorting with radix sorting algorithm. I have implemented it right, but I have an issue with negatives. I tried just to reverse value with a binary. It helped me a little, but negatives are in ascending order instead of being in descending order. Funniest thing is that sorting works fine with floating points and signed integers.

Here is my function to build a key for sorting:

uint32_t SortKeyToU32(int Value)
{
    uint32_t Result = (uint32_t)Value;
    if(Result & 0x80000000)
    {
        Result = ~Result;
    }
    else
    {
        Result |= 0x80000000;
    }
    return Result;
}

And here is my sorting function:

void RadixSort(int** Array, int Size)
{
    int* Dest   = new int[Size];
    int* Source = *Array;
    memset(Dest, 0, Size*sizeof(int));

    for(int Bit = 0;
        Bit < 32;
        Bit += 8)
    {
        uint32_t ArrayOfValues[256] = {};
        for(int Index = 0;
            Index < Size;
            ++Index)
        {
            uint32_t SortKey = SortKeyToU32(Source[Index]);
            uint32_t Key = (SortKey >> Bit) & 0xFF;
            ++ArrayOfValues[Key];
        }

        int Sum = 0;
        for (int Index = 0;
            Index < ArraySize(ArrayOfValues);
            ++Index)
        {
            uint32_t Count = ArrayOfValues[Index];
            ArrayOfValues[Index] = Sum;
            Sum += Count;
        }

        for(int Index = 0;
            Index < Size;
            ++Index)
        {
            uint32_t SortKey = SortKeyToU32(Source[Index]);
            uint32_t Key = (SortKey >> Bit) & 0xFF;
            Dest[ArrayOfValues[Key]++] = Source[Index];
        }

        int* Temp = Source;
        Source = Dest;
        Dest = Temp;
    }
}

But how can I deal with sorting signed integers? Sorry if it is looks obvious. Thanks.

EDIT. Here is sample array input: 1, 6, 9, 2, 3, -4, -10, 8, -30, 4

Output: -4, -10, -30, 1, 2, 3, 4, 6, 8, 9


Solution

  • Sort key formula that you need is very simple:

    uint32_t SortKeyToU32(int32_t Value) {
        return uint32_t(Value) + (1U << 31);
    }
    

    the reason for it following. First we cast from 32-bit signed to unsigned. This casting just reinterprets bits of signed value as unsigned.

    Lets see how signed values are presented in value space of a number. For simplicity lets see on 4-bit signed number, it has 16 different values in total mapped as following:

    Signed form:

    0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1

    Unsigned form:

    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15

    In signed case first half of values are non-negative in ascending order and exactly second half are negative values also in ascending order.

    For sorting we need all negative values go before positive and also in ascending order. For signed 4-bit values above (in unsigned form) we need just to add 8, this is exactly middle of a range. Thus whole range will shift right with overflow and negative values will appear before positives (below first number is number after adding 8, second number (in parenthesis) is original (before adding)):

    0 (-8), 1 (-7), 2 (-6), 3 (-5), 4 (-4), 5 (-3), 6 (-2), 7 (-1), 8 (0), 9 (1), 10 (2), 11 (3), 12 (4), 13 (5), 14 (6), 15 (7)

    This is exactly what I did in my sort key formula

    uint32_t(Value) + (1U << 31)

    I reinterpret-casted signed to unsigned form and added middle-range value which is (1U << 31).

    With this corrected formula your code works right way, in snippet below I left your original sort key formula too under name SortKeyToU32Old() you may change names of sort key functions to see how it works before (incorrectly) and after (correctly) change on my example array.

    Try it online!

    #include <cstdint>
    #include <cstring>
    
    #define ArraySize(a) (sizeof(a) / sizeof(a[0]))
    
    uint32_t SortKeyToU32(int32_t Value) {
        return uint32_t(Value) + (1U << 31);
    }
    
    uint32_t SortKeyToU32Old(int Value)
    {
        uint32_t Result = (uint32_t)Value;
        if(Result & 0x80000000)
        {
            Result = ~Result;
        }
        else
        {
            Result |= 0x80000000;
        }
        return Result;
    }
    
    void RadixSort(int** Array, int Size)
    {
        int* Dest   = new int[Size];
        int* Source = *Array;
        memset(Dest, 0, Size*sizeof(int));
    
        for(int Bit = 0;
            Bit < 32;
            Bit += 8)
        {
            uint32_t ArrayOfValues[256] = {};
            for(int Index = 0;
                Index < Size;
                ++Index)
            {
                uint32_t SortKey = SortKeyToU32(Source[Index]);
                uint32_t Key = (SortKey >> Bit) & 0xFF;
                ++ArrayOfValues[Key];
            }
    
            int Sum = 0;
            for (int Index = 0;
                Index < ArraySize(ArrayOfValues);
                ++Index)
            {
                uint32_t Count = ArrayOfValues[Index];
                ArrayOfValues[Index] = Sum;
                Sum += Count;
            }
    
            for(int Index = 0;
                Index < Size;
                ++Index)
            {
                uint32_t SortKey = SortKeyToU32(Source[Index]);
                uint32_t Key = (SortKey >> Bit) & 0xFF;
                Dest[ArrayOfValues[Key]++] = Source[Index];
            }
    
            int* Temp = Source;
            Source = Dest;
            Dest = Temp;
        }
    }
    
    #include <iostream>
    
    int main() {
        int a[] = {1000, -200, 800, -300, 900, -100};
        int * p = &a[0];
        RadixSort(&p, ArraySize(a));
        for (auto x: a)
            std::cout << x << " ";
    }
    

    Input:

    {1000, -200, 800, -300, 900, -100}

    Output (correct, new sort key):

    -300 -200 -100 800 900 1000

    Output (incorrect, original sort key):

    -100 -200 -300 800 900 1000