1

I'm implementing a relative branching function in my simple VM.

Basically, I'm given an 8-bit relative value. I then shift this left by 1 bit to make it a 9-bit value. So, for instance, if you were to say "branch +127" this would really mean, 127 instructions, and thus would add 256 to the IP.

My current code looks like this:

uint8_t argument = 0xFF; //-1 or whatever
int16_t difference = argument << 1;
*ip += difference; //ip is a uint16_t

I don't believe difference will ever be detected as a less than 0 with this however. I'm rusty on how signed to unsigned works. Beyond that, I'm not sure the difference would be correctly be subtracted from IP in the case argument is say -1 or -2 or something.

Basically, I'm wanting something that would satisfy these "tests"

//case 1
argument = -5
difference -> -10
ip = 20 -> 10 //ip starts at 20, but becomes 10 after applying difference

//case 2
argument = 127 (must fit in a byte)
difference -> 254
ip = 20 -> 274

Hopefully that makes it a bit more clear.

Anyway, how would I do this cheaply? I saw one "solution" to a similar problem, but it involved division. I'm working with slow embedded processors (assumed to be without efficient ways to multiply and divide), so that's a pretty big thing I'd like to avoid.

Earlz
  • 62,085
  • 98
  • 303
  • 499

3 Answers3

0

To clarify: you worry that left shifting a negative 8 bit number will make it appear like a positive nine bit number? Just pad the top 9 bits with the sign bit of the initial number before left shift:

diff = 0xFF;
int16 diff16=(diff + (diff & 0x80)*0x01FE) << 1;

Now your diff16 is signed 2*diff

As was pointed out by Richard J Ross III, you can avoid the multiplication (if that's expensive on your platform) with a conditional branch:

int16 diff16 = (diff + ((diff & 0x80)?0xFF00:0))<<1;

If you are worried about things staying in range and such ("undefined behavior"), you can do

int16 diff16 = diff;
diff16 = (diff16 | ((diff16 & 0x80)?0x7F00:0))<<1;

At no point does this produce numbers that are going out of range.

The cleanest solution, though, seems to be "cast and shift":

diff16 = (signed char)diff; // recognizes and preserves the sign of diff
diff16 = (short int)((unsigned short)diff16)<<1; // left shift, preserving sign

This produces the expected result, because the compiler automatically takes care of the sign bit (so no need for the mask) in the first line; and in the second line, it does a left shift on an unsigned int (for which overflow is well defined per the standard); the final cast back to short int ensures that the number is correctly interpreted as negative. I believe that in this form the construct is never "undefined".

Floris
  • 45,857
  • 6
  • 70
  • 122
  • You're multiplying, probably a bad move given the OP's situation. – Richard J. Ross III Apr 11 '13 at 02:57
  • I was trying to avoid a conditional branch. Same thing can be achieved with a `if() then add` – Floris Apr 11 '13 at 02:59
  • Or, a ternary expression :) – Richard J. Ross III Apr 11 '13 at 03:01
  • Section 6.5p5 of the C standard categorises this answer as "undefined behavior": *If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.* – autistic Apr 11 '13 at 03:03
  • @modifiablelvalue - are you saying that when a left shift overflows, the behavior is not defined? I thought it was... you lose the bits off the top end and pad the bottom with zeros. If I am wrong, I would be really interested in a reference. – Floris Apr 11 '13 at 03:06
  • Page 506 of n1124.pdf (C99), under Undefined Behaviour `An expression having signed promoted type is left-shifted and either the value of the expression is negative or the result of shifting would be not be representable in the promoted type (6.5.7).` – nhahtdh Apr 11 '13 at 03:09
  • No. This has nothing to do with the left shift operator. Your code is undefined behaviour for the same reason `int x = INT_MAX + 1;` is undefined behaviour: The result of the expression lies out of range of the type used to calculate it (int). – autistic Apr 11 '13 at 03:19
0

All of my quotes come from the C standard, section 6.3.1.3. Unsigned to signed is well defined when the value is within range of the signed type:

1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

Signed to unsigned is well defined:

2 Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

Unsigned to signed, when the value lies out of range isn't too well defined:

3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

Unfortunately, your question lies in the realm of point 3. C doesn't guarantee any implicit mechanism to convert out-of-range values, so you'll need to explicitly provide one. The first step is to decide which representation you intend to use: Ones' complement, two's complement or sign and magnitude

The representation you use will affect the translation algorithm you use. In the example below, I'll use two's complement: If the sign bit is 1 and the value bits are all 0, this corresponds to your lowest value. Your lowest value is another choice you must make: In the case of two's complement, it'd make sense to use either of INT16_MIN (-32768) or INT8_MIN (-128). In the case of the other two, it'd make sense to use INT16_MIN - 1 or INT8_MIN - 1 due to the presense of negative zeros, which should probably be translated to be indistinguishable from regular zeros. In this example, I'll use INT8_MIN, since it makes sense that (uint8_t) -1 should translate to -1 as an int16_t.

Separate the sign bit from the value bits. The value should be the absolute value, except in the case of a two's complement minimum value when sign will be 1 and the value will be 0. Of course, the sign bit can be where-ever you like it to be, though it's conventional for it to rest at the far left hand side. Hence, shifting right 7 places obtains the conventional "sign" bit:

uint8_t sign =  input >> 7;
uint8_t value = input & (UINT8_MAX >> 1);
int16_t result;

If the sign bit is 1, we'll call this a negative number and add to INT8_MIN to construct the sign so we don't end up in the same conundrum we started with, or worse: undefined behaviour (which is the fate of one of the other answers).

if (sign == 1) {
    result = INT8_MIN + value;
}
else {
    result = value;
}

This can be shortened to:

int16_t result = (input >> 7) ? INT8_MIN + (input & (UINT8_MAX >> 1)) : input;

... or, better yet:

int16_t result = input <= INT8_MAX ? input
                                   : INT8_MIN + (int8_t)(input % (uint8_t) INT8_MIN);

The sign test now involves checking if it's in the positive range. If it is, the value remains unchanged. Otherwise, we use addition and modulo to produce the correct negative value. This is fairly consistent with the C standard's language above. It works well for two's complement, because int16_t and int8_t are guaranteed to use a two's complement representation internally. However, types like int aren't required to use a two's complement representation internally. When converting unsigned int to int for example, there needs to be another check, so that we're treating values less than or equal to INT_MAX as positive, and values greater than or equal to (unsigned int) INT_MIN as negative. Any other values need to be handled as errors; In this case I treat them as zeros.

/* Generate some random input */
srand(time(NULL));
unsigned int input = rand();
for (unsigned int x = UINT_MAX / ((unsigned int) RAND_MAX + 1); x > 1; x--) {
    input *= (unsigned int) RAND_MAX + 1;
    input += rand();
}


int result = /* Handle positives: */ input <= INT_MAX ? input
           : /* Handle negatives: */ input >= (unsigned int) INT_MIN ? INT_MIN + (int)(input % (unsigned int) INT_MIN)
           : /* Handle errors: */ 0;
autistic
  • 1
  • 3
  • 35
  • 80
  • Hmm... So, are there even any C compilers/processors which use 1s complement? A quick search didn't bring up anything, but I can see why the standard was left "open" for that reason – Earlz Apr 11 '13 at 05:05
  • @Earlz Irrelevant. The question isn't about the representation that compilers/processors use, but using a representation to transform an unsigned value into a signed value. – autistic Apr 11 '13 at 05:12
0

If the offset is in the 2's complement representation, then

convert this

uint8_t argument = 0xFF; //-1
int16_t difference = argument << 1;
*ip += difference;

into this:

uint8_t argument = 0xFF; //-1
int8_t signed_argument;

signed_argument = argument; // this relies on implementation-defined
                            // conversion of unsigned to signed, usually it's
                            // just a bit-wise copy on 2's complement systems
// OR
// memcpy(&signed_argument, &argument, sizeof argument);

*ip += signed_argument + signed_argument;
Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180