Программно определяющее максимальное значение целочисленного типа со знаком

Этот вопрос связан с определением максимального значения подписанного типа во время компиляции:

C вопрос: off_t (и другие подписанные целочисленные типы) минимальные и максимальные значения

Однако с тех пор я понял, что определение максимального значения подписанного типа (например, time_t или off_t) в время выполнения представляется очень сложной задачей.

Самое близкое к решению, о котором я могу думать, это:

uintmax_t x = (uintmax_t)1<<CHAR_BIT*sizeof(type)-2;
while ((type)x<=0) x>>=1;

Это позволяет избежать любого цикла, пока type не имеет битов заполнения, но если type имеет биты заполнения, то листинг вызывает поведение, определяемое реализацией, которое может быть сигналом или бессмысленным преобразованием, определенным реализацией (например, снятие знакового бита).

Я начинаю думать, что проблема неразрешима, что немного неудобно и, по-моему, будет дефектом в стандарте C. Любые идеи для доказательства того, что я не прав?

Ответ 1

Обновление: К счастью, мой предыдущий ответ ниже был неправильным, и, похоже, решение этого вопроса.

intmax_t x;
for (x=INTMAX_MAX; (T)x!=x; x/=2);

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

Работа над сигнальным явлением может быть возможной, но сложной и вычислительно неосуществимой (как при установке обработчика сигнала для каждого возможного номера сигнала), поэтому я не думаю, что этот ответ является полностью удовлетворительным. Сеантика сигнала POSIX может дать достаточно дополнительных свойств, чтобы сделать это возможным; Я не уверен.

Интересная часть, особенно если вам удобно, если вы не используете реализацию, которая будет генерировать сигнал, это происходит, когда (T)x приводит к преобразованию, определенному реализацией. Трюк вышеуказанного цикла заключается в том, что он не полагается вообще на выбор реализации значения для преобразования. Все, на что он опирается, заключается в том, что (T)x==x возможен тогда и только тогда, когда x соответствует типу T, так как в противном случае значение x выходит за пределы возможных значений любого выражения типа T.


Старая идея, неправильная, потому что она не учитывает вышеуказанное свойство (T)x==x:

Я думаю, что у меня есть эскиз доказательства того, что то, что я ищу, невозможно:

  • Пусть X - соответствующая реализация C и предполагает INT_MAX>32767.
  • Определите новую реализацию C Y, идентичную X, но где значения INT_MAX и INT_MIN разделены на 2.
  • Докажите, что Y является соответствующей реализацией C.

Существенная идея этого плана состоит в том, что из-за того, что все, что связано с внеопределенными значениями с подписанными типами, определяется реализацией или поведением undefined, произвольное количество бит с высоким значением подписанного целочисленный тип может рассматриваться как биты заполнения без фактического внесения каких-либо изменений в реализацию, за исключением предельных макросов в limits.h.

Любые мысли, если это звучит правильно или фиктивно? Если бы это было правильно, я был бы рад наградить щедрость тому, кто может сделать все возможное, чтобы сделать его более строгим.

Ответ 2

Сначала посмотрим, как C определяет "целые типы". Взято из ISO/IEC 9899, §6.2.6.2:

6.2.6.2 Целочисленные типы
1
Для беззнаковых целочисленных типов, отличных от unsigned char, биты объекта представление делится на две группы: биты значений и биты заполнения (необходимо не быть последним). Если бит N значений бит, каждый бит должен представлять собой мощность 2 между 1 и 2N-1, чтобы объекты такого типа были способны представляющие значения от 0 до 2N - 1 с использованием чистого двоичного представления; это должно быть известный как представление значения. Значения любых битов дополнений не определены .44)
2
Для подписанных целочисленных типов биты представления объекта должны быть разделены на три группы: биты значений, биты заполнения и знаковый бит. Не должно быть никаких битов заполнения; должен быть ровно один знаковый бит. Каждый бит, который является битом значения, должен иметь то же значение, что и тот же бит, в представлении объекта соответствующего неподписанного типа (если есть биты значения M в подписанном типе и N в неподписанном типе, то M ≤ N). Если бит знака равен нулю, он не должен влиять на результирующее значение. Если знаковый бит равен единице, значение должно быть изменен одним из следующих способов:

- соответствующее значение со знаком бит 0 отрицается (знак и величина);
- бит знака имеет значение - (2N) (два дополнения);
- бит знака имеет значение - (2N - 1) (одно дополнение).

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

Следовательно, мы можем заключить следующее:

  • ~(int)0 может быть ловушечным представлением, т.е. установка всех битов в плохую идею
  • В int могут быть биты заполнения, которые не влияют на его значение
  • Порядок бит, фактически представляющий степень двух, равен undefined; так же как и положение знакового бита, если оно существует.

Хорошей новостью является то, что:

  • существует только один знаковый бит
  • существует только один бит, который представляет значение 1


Имея это в виду, есть простой способ найти максимальное значение int. Найдите бит знака, затем установите его в 0 и установите все остальные биты в 1.

Как найти бит знака? Рассмотрим int n = 1;, который строго положителен и гарантированно имеет только однобитовые и, возможно, некоторые биты дополнений, установленные в 1. Тогда для всех остальных битов i, если i==0 имеет значение true, установите его равным 1 и посмотрите, результирующее значение отрицательно. Если это не так, верните его обратно на 0. В противном случае мы нашли бит знака.

Теперь, когда мы знаем положение знакового бита, мы берем наш int n, устанавливаем бит знака в ноль и все остальные биты в 1 и tadaa, у нас есть максимально возможное значение int.

Определение минимума int немного сложнее и остается в качестве упражнения для читателя.




Обратите внимание, что в стандарте C юмористически не требуется два разных int, чтобы вести себя одинаково. Если я не ошибаюсь, могут быть два различных объекта int, которые имеют, например, их соответствующие знаковые биты в разных положениях.




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

Ответ 3

Математически, если у вас есть конечное множество (X, размера n (na положительное целое число) и оператор сравнения (x, y, z в X; x <= y и y <= z влечет x <= z), это очень простая задача, чтобы найти максимальное значение. (Кроме того, он существует.)

Самый простой способ решить эту проблему, но наиболее вычислительно дорогостоящий, - создать массив со всеми возможными значениями, а затем найти max.

Часть 1. Для любого типа с конечным набором элементов существует конечное число бит (m), которое может использоваться для однозначного представления любого данного элемента этого типа. Мы просто создаем массив, который содержит все возможные битовые шаблоны, где любой заданный битовый шаблон представлен заданным значением в конкретном типе.

Часть 2. Затем нам нужно преобразовать каждое двоичное число в заданный тип. Эта задача заключается в том, что моя неопытность программирования не позволяет мне говорить о том, как это может быть достигнуто. Я читал некоторые о кастинге, может быть, это будет трюк? Или какой-либо другой метод преобразования?

Часть 3. Предполагая, что предыдущий шаг был закончен, теперь мы имеем конечный набор значений в желаемом типе и оператор сравнения на этом множестве. Найдите максимальный размер.

Но что, если...

... мы не знаем точное число членов данного типа? Чем мы переоцениваем. Если мы не сможем получить разумную переоценку, то должны быть физические ограничения на число. Как только у нас есть переоценка, мы проверяем все эти возможные бит-паттеры, чтобы подтвердить, какие бит-паттеры представляют члены типа. После отбрасывания тех, которые не используются, теперь мы имеем набор всех возможных битовых шаблонов, которые представляют собой некоторый член данного типа. Этот последний созданный набор - это то, что мы будем использовать сейчас в части 1.

... у нас нет оператора сравнения в этом типе? Чем конкретная проблема не только невозможна, но и логически неактуальна. То есть, если наша программа не имеет доступа, чтобы дать осмысленный результат, если мы сопоставим два значения из нашего данного типа, то наш заданный тип не имеет упорядочения в контексте нашей программы. Без заказа нет такой вещи, как максимальное значение.

... мы не можем преобразовать заданное двоичное число в заданный тип? Затем метод ломается. Но, как и предыдущее исключение, если вы не можете преобразовать типы, наш набор инструментов кажется логически очень ограниченным.

Технически вам может не понадобиться преобразовывать двоичные представления и заданный тип. Вся точка преобразования заключается в том, чтобы обеспечить, чтобы сгенерированный список был исчерпывающим.

... мы хотим оптимизировать проблему? Нам нужна некоторая информация о том, как данный тип отображает двоичные числа. Например, unsigned int, подписанный int (2 комплимента) и подписанный int (1 комплимент) каждая карта из бит в числа очень документированным и простым способом. Таким образом, если бы мы хотели наивысшего возможного значения для unsigned int, и мы знали, что работаем с m битами, то можем просто заполнить каждый бит 1, преобразовать битовый шаблон в десятичный, затем вывести номер.

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

Удачи.

Ответ 4

Я мог бы просто писать глупые вещи здесь, так как я относительно новичок в C, но не будет ли это работать для получения max signed?

unsigned x = ~0;
signed y=x/2;

Это может быть глупым способом сделать это, но насколько я видел unsigned max значения signed max * 2 + 1. Не будет ли он работать в обратном направлении?

Извините за потраченное время, если это окажется абсолютно неадекватным и неправильным.

Ответ 5

Не следует ли делать что-то вроде следующего псевдокода?

signed_type_of_max_size test_values =
    [(1<<7)-1, (1<<15)-1, (1<<31)-1, (1<<63)-1];

for test_value in test_values:
    signed_foo_t a = test_value;
    signed_foo_t b = a + 1;
    if (b < a):
        print "Max positive value of signed_foo_t is ", a

Или гораздо проще, почему не следует работать?

signed_foo_t signed_foo_max = (1<<(sizeof(signed_foo_t)*8-1))-1;

Для моего собственного кода я бы определенно пошел на проверку времени сборки, определяя макрос препроцессора.

Ответ 6

Предполагая, что модификация битов заполнения не будет создавать ловушки, вы можете использовать unsigned char * для перебора и переброски отдельных битов, пока не нажмете знаковый бит. Если ваше начальное значение было ~(type)0, это должно получить максимум:

type value = ~(type)0;
assert(value < 0);

unsigned char *bytes = (void *)&value;
size_t i = 0;
for(; i < sizeof value * CHAR_BIT; ++i)
{
    bytes[i / CHAR_BIT] ^= 1 << (i % CHAR_BIT);
    if(value > 0) break;
    bytes[i / CHAR_BIT] ^= 1 << (i % CHAR_BIT);
}

assert(value != ~(type)0);
// value == TYPE_MAX

Ответ 7

Так как вы позволяете этому быть во время выполнения, вы можете написать функцию, которая де-факто выполняет итеративный сдвиг влево (type)3. Если вы остановитесь, как только значение упадет ниже 0, это никогда не даст вам ловушку. И количество итераций - 1 укажет вам позицию знакового бита.

Остается проблема сдвига влево. Поскольку просто использование оператора << приведет к переполнению, это будет поведение undefined, поэтому мы не сможем использовать оператор напрямую.

Простейшим решением для этого является не использование сдвинутого 3, как указано выше, но для итерации по позициям бит и добавление всегда младшего значащего бита.

type x;
unsigned char*B = &x;
size_t signbit = 7;
for(;;++signbit) {
  size_t bpos = signbit / CHAR_BIT;
  size_t apos = signbit % CHAR_BIT;
  x = 1;
  B[bpos] |= (1 << apos);
  if (x < 0) break;
}

(Начальное значение 7 - это минимальная ширина, которую должен иметь подписанный тип).

Ответ 8

Зачем это проблема? Размер типа фиксируется во время компиляции, поэтому проблема определения размера времени выполнения этого типа сводится к задаче определения размера времени компиляции типа. Для любой конкретной целевой платформы объявление, такое как off_t offset, будет скомпилировано для использования определенного фиксированного размера, и этот размер будет всегда использоваться при запуске результирующего исполняемого файла на целевой платформе.

ETA:. Вы можете получить размер типа type через sizeof(type). Затем вы можете сравнить с общими размерами целого и использовать соответствующий препроцессор MAX/MIN define. Возможно, вам будет проще использовать:

uintmax_t bitWidth = sizeof(type) * CHAR_BIT;
intmax_t big2 = 2;  /* so we do math using this integer size */
intmax_t sizeMax = big2^bitWidth - 1;
intmax_t sizeMin = -(big2^bitWidth - 1);

Просто потому, что значение является представимым базовым "физическим" типом, не означает, что это значение действительно для значения типа "логический". Я предполагаю, что причины max и min константы не предоставляются, так это то, что они являются "полупрозрачными" типами, использование которых ограничено конкретными доменами. Если требуется меньше непрозрачности, вы часто найдете способы получения необходимой информации, такие как константы, которые вы можете использовать, чтобы выяснить, насколько велика off_t, упомянутая SUSv2 в его описание <unistd.h>.

Ответ 9

Прежде чем задавать вопрос "Как?" , сначала вам нужно задать вопрос "Почему?" . Почему вам действительно нужно знать, что во время работы? Связано ли это с реальной проблемой или это просто академический вопрос?

Я действительно не вижу необходимости определять это во время компиляции. Если программист пишет программу для удовлетворения конкретных потребностей, он определенно имеет некоторые сведения о среде, в которой он будет работать.