Question

I'm trying to write code that will let me print out the value of the data field of CAN messages as a series of shorts in hex form. CAN message data fields contain 4 shorts, so for instance, I might print out one message as "FFFF EEEE ABAB 2013".

I can encode 2 shorts of a CAN message in an integer value, and everything works fine so long as the 32nd bit of the integer isn't set. Because Java uses signed integers, setting the 32nd bit causes it to print out a series of "f"s. What I want is the integer to act as an unsigned value, so that printing it out doesn't cause problems.

Here is an example of it going wrong:

(int value): 0x80000000
(string rep short 1): 0xffff8000
<crash>

And here is how it works in every other case:

(int value): 0x40000000
(string rep short 1): 0x4000
(string rep short 2): 0x0

The code where I try to represent the CAN message data and where the crash occurs:

public String getFormattedData() {
    String dataString = "";

    for(String aShort : Utility.splitStringByWhitespace(getData())) {
        try{
            dataString += ("0000".substring(0, 4 - aShort.length()) + aShort) + " ";
        } catch(Exception e) {
            System.out.println(getData());
            System.exit(1);
        }
    }

    return dataString.toUpperCase();
}

Specifically, the crash occurs because the "f"s bump the length of the short's string representation to 8 characters, so "4 - aShort.length()" will yield a negative value, which is invalid.

Edit: For those asking, in this particular case this is how I'm using integers to encode the data before I convert it to a string representation:

public String convertToCANMessageData(PWMChannel channel) {
    int channelVal = channel.getValue();
    int entryDataHI = 0, entryDataLO = 0;

    entryDataHI += channelVal << 24;
    entryDataHI += smallData << 8;
    entryDataHI += (largeData >> 8) & 0xFF;
    entryDataLO += (largeData & 0xFF) << 24;

    if(ramping) {
        entryDataHI = entryDataHI | (1 << 16);
    } else {
        entryDataHI = entryDataHI & ~(1 << 16);
    }

    if(jumping) {
        entryDataHI = entryDataHI | (1 << 17);
    } else {
        entryDataHI = entryDataHI & ~(1 << 17);
    }

    if(frequencySetting) {
        entryDataHI = entryDataHI | (1 << 18);
    } else {
        entryDataHI = entryDataHI & ~(1 << 18);
    }

    return String.format("%x %x %x %x", entryDataHI >> 16, entryDataHI & 0xFFFF,
                            entryDataLO >> 16, entryDataLO & 0xFFFF);
}
Was it helpful?

Solution

To answer the question in the title, to set the 32nd bit of an int you can use the bitwise OR operator:

myInteger |= (1 << 31);

If you don't want this to "mess up" the string representation, that's an issue of how you convert the integer into string. For example the Integer.toHexString method treats ints as unsigned:

print(Integer.toHexString(-3)); // output: fffffffd

UPDATE: Use the unsigned left shift >>> when formatting numbers: The regular left shift >> extends the sign bit, so that a left shift of a negative number stays negative and doesn't "suddenly" become a huge positive number.

return String.format("%x %x %x %x", entryDataHI >>> 16, entryDataHI & 0xFFFF,
                        entryDataLO >>> 16, entryDataLO & 0xFFFF);

Alternatively you can use masking which you already seem to be familiar with:

return String.format("%x %x %x %x", (entryDataHI >> 16) & 0xFFFF, entryDataHI & 0xFFFF,
                        (entryDataLO >> 16) & 0xFFFF, entryDataLO & 0xFFFF);

OTHER TIPS

Just use unsigned bitshift to get top half of the integer.

int i = 0x80000000;
short s = (short)(i >>> 16);
s is 0x8000

I've resolved the problem for now by adding a utility function:

public static int parseShort(String aShort) {
    if(aShort.length() > 4) aShort = aShort.substring(aShort.length()-4);

    return Integer.parseInt(aShort, 16);
}

If anyone's got a better solution, I'd be happy to mark their answer as accepted and try that out instead.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top