Почему в C нет беззнаковых чисел с плавающей запятой?

StackOverflow https://stackoverflow.com/questions/512022

Вопрос

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

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

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

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

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

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

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


РЕДАКТИРОВАТЬ:

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

value >> shift

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

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

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;

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

Также рассмотрим стандартные математические функции:sin, cos и tan будут работать только для половины своих входных значений, вы не сможете найти журнал значений < 1, вы не сможете решить квадратные уравнения:x = (-b +/- корень (b.b - 4.a.c)) / 2.a и так далее.Фактически, это, вероятно, не будет работать для какой-либо сложной функции, поскольку они, как правило, реализуются как полиномиальные аппроксимации, которые где-то будут использовать отрицательные значения.

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

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

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

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

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

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

[редактировать]

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

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

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

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

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

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

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

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

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

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

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.

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

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

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

ИХМО, это потому, что поддержка как знаковых, так и беззнаковых типов с плавающей запятой как в аппаратном, так и в программном обеспечении была бы слишком затруднительной.

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

  • Арифметический и логический сдвиг требует лишь небольшого изменения заполнителя для старших битов.
  • Расширение умножения может использовать одно и то же оборудование для основной части, а затем некоторую отдельную логику для настройки. результат изменения знака.Не то чтобы это использовалось в реальных множителях, но это возможно сделать
  • Знаковое сравнение можно легко преобразовать в беззнаковое сравнение и наоборот, переключив верхний бит или добавление INT_MIN.Также теоретически возможно, оно, вероятно, не используется на оборудовании, но полезно на системы, поддерживающие только один тип сравнения (например, 8080 или 8051)

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

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

  • Увеличьте диапазон, добавив его к показателю степени
  • Увеличьте точность, добавив ее к мантиссе.Часто это более полезно, поскольку обычно нам требуется больше точности, чем диапазона.

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

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

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

Тем не менее, существует несколько специализированный беззнаковые форматы с плавающей запятой, в основном для целей обработки изображений, например 10- и 11-битный тип с плавающей запятой группы Khronos

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top