преобразование uint8_t в sint8_t - PullRequest
4 голосов
/ 08 октября 2010

Какой лучший способ преобразовать "uint8_t" в "sint8_t" в переносном C.

Это код, который я придумал ....

#include <stdint.h>

sint8_t DESER_SINT8(uint8_t x)
(
  return
     (sint8_t)((x >= (1u << 8u))
               ? -(UINT8_MAX - x)
               : x);
)

Есть лилучший / более простой способ сделать это?Может быть, путь без использования условного?

Редактировать: Спасибо, ребята.Итак, чтобы подвести итог, то, что я уже изучил ...

  • sint8_t действительно называется int8_t
  • 128 выражается 1 << 7, а не 1 << 8
  • 2s дополнение «отрицание одним»

:)

Итак, вот обновленная версия моего исходного кода:

#include <stdint.h>

int8_t DESER_INT8(uint8_t x)
(
  return ((x >= (1 << 7))
          ? -(UINT8_MAX - x + 1)
          : x);
)

Ответы [ 7 ]

12 голосов
/ 08 октября 2010

1u << 8u равно 0x100u, что больше, чем каждое значение uint8_t, поэтому условие никогда не выполняется.Ваша процедура «преобразования» на самом деле просто:

return x;

, что на самом деле имеет некоторый смысл.

Вам нужно более четко определить, что вы хотите для преобразования.C99 определяет преобразование целых типов без знака в целые числа следующим образом ( §6.3.1.3 «Целые числа со знаком и без знака» )

Когда значение с целочисленным типом преобразуется в другой целочисленный типкроме _Bool, если значение может быть представлено новым типом, оно не изменяется.

В противном случае новый тип подписывается и значение не может быть представлено в нем;либо результат определяется реализацией, либо генерируется определяемый реализацией сигнал.

Таким образом, значения uint8_t между 0 и 127 сохраняются, а поведение для значений больше 127 не определено. Многие (но не все) реализации будут просто интерпретировать беззнаковые значения как представление с двойным дополнением целого числа со знаком.Возможно, вы действительно спрашиваете, как гарантировать такое поведение на разных платформах?

Если это так, вы можете использовать:

return x < 128 ? x : x - 256;

Значение x - 256 равно int, гарантированоиметь значение x, интерпретируемое как 8-битное целое число с дополнением до двух.Затем неявное преобразование в int8_t сохраняет это значение.

Все это предполагает, что sint8_t означает int8_t, так как sint8_t не является стандартным типом.Если это не так, то все ставки выключены, потому что правильность преобразования, которое я предложил, зависит от гарантии того, что int8_t имеет представление, дополняющее два ( §7.18.1.1 "Целочисленные типы точной ширины" ).

Если sint8_t вместо этого является каким-то дурацким платформо-зависимым типом, он может использовать другое представление, например ones-дополнение, которое имеет другой набор представимых значений, что делает преобразование, описанное выше, реализацией.-определено (следовательно, непереносимо) для определенных входных данных.


EDIT

Альф утверждал, что это "глупо", и что это никогда не будетнеобходимо на любой производственной системе.Я не согласен, но по общему признанию это угловой случай углового случая.Его аргумент не совсем беспочвенен.

Его утверждение, что это "неэффективно" и поэтому его следует избегать, тем не менее, является необоснованным.Разумный оптимизирующий компилятор оптимизирует это на платформах, где это не нужно.Использование GCC на x86_64, например:

#include <stdint.h>

int8_t alf(uint8_t x) {
    return x;
}

int8_t steve(uint8_t x) {
    return x < 128 ? x : x - 256;
}

int8_t david(uint8_t x) {
    return (x ^ 0x80) - 0x80;
}

, скомпилированный с -Os -fomit-frame-pointer, дает следующее:

_alf:
0000000000000000    movsbl  %dil,%eax
0000000000000004    ret
_steve:
0000000000000005    movsbl  %dil,%eax
0000000000000009    ret
_david:
000000000000000a    movsbl  %dil,%eax
000000000000000e    ret

Обратите внимание, что все три реализации идентичны после оптимизации.Clang / LLVM дает точно такой же результат.Точно так же, если мы собираем для ARM вместо x86:

_alf:
00000000        b240    sxtb    r0, r0
00000002        4770    bx  lr
_steve:
00000004        b240    sxtb    r0, r0
00000006        4770    bx  lr
_david:
00000008        b240    sxtb    r0, r0
0000000a        4770    bx  lr

Защита вашей реализации от угловых случаев, когда она не требует затрат для «обычного» случая, никогда не бывает «глупой».

Дляаргумент, что это добавляет ненужную сложность, я говорю: что сложнее - написать комментарий, чтобы объяснить преобразование и почему оно там, или стажер вашего преемника пытается отладить проблему через 10 лет, когда новый компилятор ломает счастливую случайностьчто ты молча зависел от всего этого времени?Неужели так трудно поддерживать следующее?

// The C99 standard does not guarantee the behavior of conversion
// from uint8_t to int8_t when the value to be converted is larger
// than 127.  This function implements a conversion that is
// guaranteed to wrap as though the unsigned value were simply
// reinterpreted as a twos-complement value.  With most compilers
// on most systems, it will be optimized away entirely.
int8_t safeConvert(uint8_t x) {
    return x < 128 ? x : x - 256;
}

Когда все сказано и сделано, я согласен, что это неясно, но я также думаю, что мы должны попытаться ответить на вопрос за чистую монету.Конечно, лучшим решением было бы для стандарта C определить поведение преобразований из беззнакового в подписанное, когда подписанный тип представляет собой целое число из двух дополнений без дополнения (таким образом, определяя поведение для всех типов intN_t).

5 голосов
/ 08 октября 2010

Преобразование uint8_t в int8_t, по сути, обращает порядок двух полу-диапазонов.«Высокие» цифры становятся «низкими».Это может быть выполнено с помощью XOR.

x ^ 0x80

Однако все числа остаются положительными.Это не хорошо.Нам нужно ввести правильный знак и восстановить правильную величину.

return ( x ^ 0x80 ) - 0x80;

Вот, пожалуйста!

2 голосов
/ 09 октября 2010

Я не знаю, имеет ли это какое-либо практическое значение, но вот другой подход, который пришел в голову:

uint8_t input;
int8_t output;
*(uint8_t *)&output = input;

Обратите внимание, что:

  • int8_t требуется, чтобы быть дополнением до двух.
  • Соответствующие типы со знаком и без знака должны иметь одинаковое представление для перекрывающейся части их диапазонов, чтобы к значению, находящемуся в диапазоне как со знаком, так и без знака, можно было обращаться через любой тип указателя.
  • Это оставляет только один бит, который должен быть двоичным битом знака дополнения.

Единственный способ увидеть, что это рассуждение может не может быть действительным, это если CHAR_BIT>8 и 8-битные целочисленные типы являются расширенными целочисленными типами с битами-ловушками, которые каким-то образом указывают, подписано ли значение или без знака. Однако следующий аналогичный код, явно использующий типы char, никогда не сможет завершиться ошибкой:

unsigned char input;
signed char output;
*(unsigned char *)output = input;

, поскольку char типы не могут иметь биты заполнения / прерывания.

Потенциальный вариант будет:

return ((union { uint8_t u; int8_t s; }){ input }).s;

или для char типов:

return ((union { unsigned char u; signed char s; }){ input }).s;

Редактировать: Как Стив Джессоп указал в другом ответе, int8_t и uint8_t должны не иметь битов заполнения, если они существуют, поэтому их существование подразумевает CHAR_BIT==8. Поэтому я уверен, что этот подход является действительным. С учетом вышесказанного я бы все равно никогда не использовал бы uint8_t и всегда явно использовал бы unsigned char, в случае, если реализация реализует uint8_t как расширенный целочисленный тип равного размера, потому что типы char имеют специальные привилегии в отношении правил наложения имен и введите punning, которые делают их более желательными.

0 голосов
/ 09 октября 2010

Если вы хотите избежать ветки, вы всегда можете сделать что-то безумное, как это:

int selector= 127 - x; // 0 or positive if x <=127, negative otherwise
int selector>>= 8; // arithmetic rotate to get -1 or 0
int wrapped_value= x - 256;

return (x&~selector)|(wrapped_value&selector); // if selector is 0, use x, otherwise, use the wrapped value.
0 голосов
/ 08 октября 2010

Предполагая, что вы sint8_t действительно int8_t из <stdint.h>, тогда гарантируется форма дополнения до двух и гарантируется, что битов заполнения нет.

Предположим, что вы хотите, чтобы обратное (неявное) преобразование работало и давало исходное значение.

Затем, учитывая значение v типа uint8_t, все, что вам нужно сделать, это ...

    int8_t( v )

Вот и все.

Стандарт C делает AFAIK не гарантирует это преобразование, только обратное преобразование. Однако нет ни одной известной системы или компилятора, где бы он не работал (учитывая, что эти типы доступны).

Забудьте все ручные игры. Или, чтобы проверить, правильно ли вы это делаете, выполните обратное преобразование, просто присвоив значение uint8_t и проверьте, получите ли вы исходное значение для всех случаев. В частности, формула, которую вы использовали, дает - ((2 ^ n-1) -x) = 1 + x-2 ^ n, а правильное преобразование для сохранения значения - x-2 ^ n.

Приветствия & hth.,

- Альф

0 голосов
/ 08 октября 2010

Хм, ... Я думаю, что вы пытались вернуть x, если x может быть представлен в sint8, или abs (SINT8_MAX - x), если нет, верно?

В этом случае, вот чтоработает (у вас была маленькая ошибка, я думаю):

#define HIGHBIT(X) ((X) & (1 << (sizeof(X) * 8 - 1)))

char utos8(unsigned char ux)
{
    return HIGHBIT(ux) ? -ux : ux;
}

Обратите внимание, что используя этот код, вы можете преобразовать любой тип без знака в тип со знаком , заключив макрос HIGHTBIT в функцию.

Надеюсь, это поможет.

0 голосов
/ 08 октября 2010

при условии, что типы sint8_t и uint8_t совместимы с присвоением, это работает

sint8_t DESER_SINT8(uint8_t x) { return x; }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...