Почему у C нет неподписанных поплавков?


Я знаю, вопрос кажется странным. Программисты иногда слишком много думают. Пожалуйста, читайте дальше...

В C я использую знаковые и беззнаковые целые числа много. Мне нравится тот факт, что компилятор предупреждает меня, если я делаю такие вещи, как назначение целого числа со знаком для беззнаковой переменной. Я получаю предупреждения, если сравниваю подписанные с беззнаковыми целыми числами и многое другое.

Мне нравятся эти предупреждения. Они помогают мне держать мой код правильный.

почему у нас нет такой же роскоши для поплавков? Квадратный корень определенно никогда не вернет отрицательное число. Есть и другие места, где отрицательное значение float не имеет никакого значения. Идеальный кандидат для неподписанного поплавка.

кстати-я не очень заинтересован в одном дополнительном бите точности, который я мог бы получить, удалив знак-флаг с поплавков. Я очень доволен floats, как они сейчас. Я просто хотел бы отметить поплавок как неподписанный иногда и получить те же предупреждения, что и я целые.

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

есть идеи, почему они не существуют?


EDIT: я знаю, что у x87 FPU нет инструкций для работы с неподписанными поплавками. Давайте просто использовать подписанные инструкции float. Неправильное использование (например, ниже нуля) может рассматриваться как неопределенное поведение так же, как переполнение целых чисел со знаком не определено.

10 106

10 ответов:

Почему C++ не поддерживает беззнаковые поплавки, потому что нет эквивалентных операций машинного кода для выполнения ЦП. Поэтому было бы очень неэффективно поддерживать его.

Если бы C++ поддерживал его, то вы иногда использовали бы беззнаковый float и не понимали, что ваша производительность только что была убита. Если C++ поддерживает его, то каждая операция с плавающей запятой должна быть проверена, чтобы увидеть, подписана она или нет. И для программ, которые делают миллионы операции с плавающей запятой, это не приемлемо.

поэтому возникает вопрос, почему аппаратные разработчики не поддерживают его. И я думаю, что ответ на это заключается в том, что не было никакого неподписанного стандарта float, определенного изначально. Поскольку языки любят быть обратно совместимыми, даже если бы они были добавлены, языки не могли бы использовать его. Чтобы увидеть спецификацию с плавающей точкой, вы должны посмотреть на стандарт IEEE 754 с плавающей запятой.

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

Я определенно вижу целесообразность без поплавка. Но C / C++ имеет тенденцию выбирать эффективность, которая лучше всего работает для всех, а не для безопасности.

существует значительная разница между целыми числами со знаком и без знака в C / C++:

value >> shift

значения со знаком оставляют верхний бит без изменений (знак расширения), значения без знака очищают верхний бит.

причина отсутствия unsigned float заключается в том, что вы быстро сталкиваетесь со всевозможными проблемами, если нет отрицательных значений. Рассмотрим это:

float a = 2.0f, b = 10.0f, c;
c = a - b;

какое значение имеет c? -8. Но что бы это значило в системе без отрицательных чисел. FLOAT_MAX-8 может быть? На самом деле, это не работает, поскольку FLOAT_MAX - 8-это FLOAT_MAX из-за эффектов точности, поэтому все еще более странно. Что, если это было частью более сложного выражения:

float a = 2.0f, b = 10.0f, c = 20.0f, d = 3.14159f, e;
e = (a - b) / d + c;

Это не проблема для целых чисел из-за природы системы дополнения 2.

также рассмотрим стандартные математические функции: sin, cos и tan будут работать только для половины их входных значений, вы не можете найти журнал значений

Итак, неподписанные поплавки довольно бесполезны.

но это не значит, что класс, который проверяет значения float, не полезен, вы можете захотеть закрепить значения в заданном диапазоне, например, вычисления RGB.

(в стороне, Perl 6 позволяет писать

subset Nonnegative::Float of Float where { $_ >= 0 };

и тогда вы можете использовать Nonnegative::Float как и любой другой тип.)

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

[edit]

C похоже на сборку: то, что вы видите, - это именно то, что вы получаете. Один неявное "я проверю, что этот поплавок неотрицателен для вас" идет вразрез с его философией дизайна. Если вы действительно этого хотите, вы можете добавить assert(x >= 0) или похожие, но вы должны сделать это явно.

Я считаю, что unsigned int был создан из-за необходимости большего значения маржи, чем может предложить подписанный int.

float имеет гораздо больший запас прочности, поэтому никогда не было "физической" необходимости без поплавка. И как вы указываете сами в своем вопросе, дополнительная 1-битная точность-это ничто, чтобы убить.

Edit: После прочтения ответ Брайана Р. Бонди, Я должен изменить свой ответ: Он, безусловно, правы в том, что базовые процессоры не имели операций без знака float. Однако я продолжаю верить, что это было проектное решение, основанное на причинах, которые я указал выше ; -)

Я думаю, что Treb находится на правильном пути. Для целых чисел более важно, чтобы у вас был соответствующий тип без знака. Это те, которые используются в бит-смещение и в bit-maps. Знак немного просто встает на пути. Например, смещение вправо отрицательного значения, полученное значение является реализацией, определенной в C++. Выполнение этого с целым числом без знака или переполнение такого имеет идеально определенную семантику, потому что нет такого бита на пути.

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

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

статья Википедии на числа с плавающей запятой стандарта IEEE точки

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

квадратный корень определенно никогда не вернет отрицательное число. Есть и другие места, где отрицательное значение float не имеет никакого значения. Идеальный кандидат для неподписанного поплавка.

C99 поддерживает комплексные числа и тип универсальной формы sqrt, поэтому sqrt( 1.0 * I) будет отрицательным.


комментаторы выделили небольшой блеск выше, в том, что я имел в виду тип-generic sqrt макрос, а не функция, и она вернется скалярное значение с плавающей запятой путем усечения комплекса до его реальной составляющей:

#include <complex.h>
#include <tgmath.h>

int main () 
{
    complex double a = 1.0 + 1.0 * I;

    double f = sqrt(a);

    return 0;
}

Он также содержит мозговой пердеж, так как реальная часть sqrt любого комплексного числа положительна или равна нулю, а sqrt(1.0*I) - sqrt(0.5) + sqrt(0.5)*I не -1.0.

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

целочисленные типы без знака в C определяются таким образом, чтобы подчиняться правилам абстрактного алгебраического кольца. Например, для любого значения X и Y добавление X-Y к Y приведет к X. целочисленные типы без знака гарантированно подчиняются этим правилам во всех случаях, которые не связаны с преобразованием в или из любого другого числового типа [или беззнаковых типов разных размеров], и эта гарантия является одной из наиболее важных особенностей таких типов. В некоторых случаях стоит отказаться от возможности представлять отрицательные числа в обмен на дополнительные гарантии могут предоставить только беззнаковые типы. Типы с плавающей запятой, подписанные или нет, не могут соблюдать все правила алгебраического кольца [например, они не могут гарантировать, что X+Y-Y будет равно X], и действительно IEEE даже не позволяет им соблюдать правила класса эквивалентности [требуя, чтобы некоторые значения сравнивались неравными для себя]. Я не думаю, что" неподписанный " тип с плавающей запятой может соблюдать любые аксиомы, которые обычные тип с плавающей запятой не мог, поэтому я не уверен, какие преимущества он предложит.

Я подозреваю, что это связано с тем, что базовые процессоры, предназначенные для компиляторов C, не имеют хорошего способа работы с беззнаковыми числами с плавающей запятой.