Как преобразовать uint в int в C с минимальной потерей диапазона результатов - PullRequest
5 голосов
/ 06 ноября 2019

Мне нужна разница между двумя неограниченными целыми числами, каждое из которых представлено значением uint32_t, которое является неограниченным целым числом, взятым по модулю 2 ^ 32. Как, например, в порядковых номерах TCP. Обратите внимание, что представление по модулю 2 ^ 32 может переноситься вокруг 0, в отличие от более ограниченных вопросов, которые не позволяют переносить вокруг 0 ​​.

. Предположим, что разница между базовыминеограниченные целые числа находятся в диапазоне нормальных int. Я хочу это значение разницы со знаком. Другими словами, вернуть значение в пределах нормального int диапазона, которое эквивалентно разности двух входов uint32_t по модулю 2 ^ 32.

Например, 0 - 0xffffffff = 1, поскольку мы предполагаем, что лежащие в основе неограниченные целые числанаходятся в диапазоне int. Доказательство: если A mod 2 ^ 32 = 0 и B mod 2 ^ 32 = 0xffffffff, то (A = 0, B = -1) (mod 2 ^ 32) и, следовательно, (AB = 1) (mod 2 ^ 32) ив диапазоне int этот класс по модулю имеет единственного представителя 1.

. Я использовал следующий код:

static inline int sub_tcp_sn(uint32_t a, uint32_t b)
{
    uint32_t delta = a - b;

    // this would work on most systems
    return delta;

    // what is the language-safe way to do this?
}

. Это работает в большинстве систем, поскольку они используют модуль-2. ^ 32 представления как для uint и int, так и для нормального вычитания по модулю-2 ^ 32 - единственный разумный код сборки, который можно сгенерировать здесь.

Однако я считаю, что стандарт C определяет только результатвышеуказанного кода, если delta>=0. Например, на этот вопрос один ответ гласит:

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

Как преобразование по модулю 2 ^ 32 из uint в int должно выполняться в соответствии с Cстандарт?

Примечание. Я бы предпочел, чтобы код ответа не включал условные выражения, если вы не докажете, что это требуется. (анализ случая в объяснении кода в порядке).

1 Ответ

0 голосов
/ 06 ноября 2019

Должна быть стандартная функция, которая делает это ... но в то же время:

#include <stdint.h>  // uint32_t
#include <limits.h>  // INT_MAX
#include <assert.h>  // assert

static inline int sub_tcp_sn(uint32_t a, uint32_t b)
{
    uint32_t delta = a - b;
    return delta <= INT_MAX ? delta : -(int)~delta - 1;
}

Обратите внимание, что это UB в случае, когда результат не представим, но вопрос сказал, что былоOK.

Если система имеет 64-битный тип long long, диапазон можно легко настроить и проверить:

typedef long long sint64_t;

static inline sint64_t sub_tcp_sn_custom_range(uint32_t a, uint32_t b,
                             sint64_t out_min, sint64_t out_max)
{
    assert(sizeof(sint64_t) == 8);
    uint32_t delta = a - b;
    sint64_t result = delta <= out_max ? delta : -(sint64_t)-delta;
    assert(result >= out_min && result <= out_max);
    return result;
}

Например, sub_tcp_sn_custom_range(0x10000000, 0, -0xf0000000LL, 0x0fffffffLL) == -0xf00000000.

Благодаря настройке диапазона это решение минимизирует потерю диапазона во всех ситуациях, предполагая, что временные метки ведут себя линейно (например, нет особого значения для переноса вокруг 0), и доступен закаленный 64-битный тип.

...