Почему у C нет беззнаковых поплавков? - PullRequest
113 голосов
/ 04 февраля 2009

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

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

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

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

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

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

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


EDIT:

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

Ответы [ 11 ]

102 голосов
/ 04 февраля 2009

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

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

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

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

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

13 голосов
/ 05 февраля 2009

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

value >> shift

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

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

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

Какое значение имеет c? -8. Но что бы это значило в системе без отрицательных чисел. FLOAT_MAX - 8 возможно? На самом деле, это не работает, так как FLOAT_MAX - это 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 будут работать только для половины их входных значений, вы не можете найти журнал значений <1, вы не можете решить квадратные уравнения: x = (-b + / - root (bb - 4.ac)) / 2.a и т. д. Фактически, это, вероятно, не будет работать для любой сложной функции, поскольку они, как правило, реализуются в виде полиномиальных приближений, которые где-то будут использовать отрицательные значения. </p>

Таким образом, беззнаковые поплавки довольно бесполезны.

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

9 голосов
/ 04 февраля 2009

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

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

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

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

[править]

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

7 голосов
/ 04 февраля 2009

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

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

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

6 голосов
/ 04 февраля 2009

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

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

5 голосов
/ 04 февраля 2009

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

статья в Википедии о числах с плавающей запятой ieee

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

4 голосов
/ 04 февраля 2009

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

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


Комментаторы выдвинули на первый план небольшой глянец, поскольку я имел в виду макрос 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.

3 голосов
/ 04 февраля 2009

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

2 голосов
/ 02 июня 2014

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

1 голос
/ 04 февраля 2009

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

...