我知道,这个问题似乎很奇怪。程序员有时想得太多。请继续阅读...
在 CI 中大量使用 signed
和 unsigned
整数。我喜欢这样一个事实,即如果我执行诸如将有符号整数分配给无符号变量之类的操作,编译器会警告我。如果我比较有符号整数和无符号整数等等,我会收到警告。
我喜欢这些警告。他们帮助我保持我的代码正确。
为什么我们对花车没有同样的奢侈?平方根绝对不会返回负数。还有其他地方负浮点值没有意义。无符号浮点数的完美候选者。
顺便说一句 - 我并不热衷于通过从浮点数中删除符号位可以获得的单个额外精度。我对 float
现在的状态非常满意。我有时只是想将浮点数标记为无符号,并得到与整数相同的警告。
我不知道任何支持无符号浮点数的编程语言。
知道为什么它们不存在吗?
编辑:
我知道 x87 FPU 没有处理无符号浮点数的指令。让我们只使用签名的浮点指令。滥用(例如低于零)可以被视为未定义的行为,就像未定义有符号整数的溢出一样。
为什么 C++ 不支持无符号浮点数是因为没有等效的机器代码操作供 CPU 执行。所以支持它是非常低效的。
如果 C++ 确实支持它,那么您有时会使用无符号浮点数并且没有意识到您的性能刚刚被杀死。如果 C++ 支持它,则需要检查每个浮点运算以查看它是否已签名。而对于进行数百万次浮点运算的程序,这是不可接受的。
所以问题是为什么硬件实现者不支持它。我认为答案是最初没有定义无符号浮点标准。由于语言喜欢向后兼容,即使添加了语言也无法使用它。要查看浮点规范,您应该查看 IEEE standard 754 Floating-Point。
您可以通过创建一个封装浮点或双精度的无符号浮点类来解决没有无符号浮点类型的问题,如果您尝试传入负数,则会引发警告。这效率较低,但如果您不频繁使用它们,您可能不会关心轻微的性能损失。
我绝对看到了无符号浮点数的用处。但是 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;
由于 2 的补码系统的性质,这对于整数来说不是问题。
还要考虑标准数学函数:sin、cos 和 tan 仅适用于其输入值的一半,您找不到值 < 1 的对数,您无法求解二次方程:x = (-b +/- root ( bb - 4.ac)) / 2.a,依此类推。事实上,它可能不适用于任何复杂的函数,因为这些函数往往被实现为多项式近似,它会在某处使用负值。
所以,无符号浮点数是非常没用的。
但这并不意味着范围检查浮点值的类没有用,您可能希望将值限制在给定范围内,例如 RGB 计算。
2's complement
一样有效的浮点数,那么使用无符号浮点数就没有问题吗?
value >> shift for signed values leave the top bit unchanged (sign extend)
你确定吗?我认为这是实现定义的行为,至少对于负符号值。
0.0
,或者可能是 Inf 或 NaN。或者只是未定义的行为,就像在问题编辑中建议的 OP 一样。回复:三角函数:所以不要定义 sin
等的无符号输入版本,并确保将它们的返回值视为有符号。问题不是提出用无符号浮点数替换浮点数,只是将 unsigned float
添加为新类型。
(顺便说一句,Perl 6 允许您编写
subset Nonnegative::Float of Float where { $_ >= 0 };
然后您可以像使用任何其他类型一样使用 Nonnegative::Float
。)
无符号浮点运算没有硬件支持,因此 C 不提供它。 C 主要被设计为“便携式组件”,即尽可能靠近金属而不被束缚在特定平台上。
[编辑]
就像汇编:所见即所得。一个隐含的“我会检查这个浮点数对你来说是非负的”违背了它的设计理念。如果你真的想要它,你可以添加 assert(x >= 0)
或类似的,但你必须明确地这样做。
of ...
不解析。
我相信创建 unsigned int 是因为需要比已签名 int 所能提供的更大的值余量。
浮点数的边距要大得多,因此对无符号浮点数从来没有“物理”需求。正如您在问题中指出的那样,额外的 1 位精度没什么可杀的。
编辑: 阅读answer by Brian R. Bondy后,我必须修改我的答案:他绝对正确,底层 CPU 没有无符号浮点操作。但是,我坚持认为这是基于我上述原因的设计决定;-)
我认为 Treb 走在正确的轨道上。对于具有无符号对应类型的整数来说更重要。这些是用于位移和位图中的那些。一个符号位刚刚进入。例如,右移一个负值,结果值是 C++ 中定义的实现。使用无符号整数或溢出这样的整数具有完美定义的语义,因为没有这样的位。
所以至少对于整数来说,对单独的无符号类型的需求比仅仅给出警告要强。浮动不需要考虑以上所有点。所以,我认为,对它们的硬件支持并不真正需要,而且 C 那时已经不支持它们了。
平方根绝对不会返回负数。还有其他地方负浮点值没有意义。无符号浮点数的完美候选者。
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。
sqrt(-0.0)
经常产生 -0.0
。当然-0.0 不是负值。
我想这取决于仅对 IEEE 浮点规范进行签名,并且大多数编程语言都使用它们。
Wikipedia article on IEEE-754 floating-point numbers
编辑:另外,正如其他人所指出的,大多数硬件不支持非负浮点数,因此由于有硬件支持,因此普通类型的浮点数更有效。
中的无符号整数类型以遵循抽象代数环规则的方式定义。例如,对于任何值 X 和 Y,将 XY 添加到 Y 将产生 X。无符号整数类型保证在所有不涉及与任何其他数字类型 [或不同大小的无符号类型] 之间转换的情况下遵守这些规则,而这种保证是此类类型最重要的特征之一。在某些情况下,放弃表示负数的能力以换取只有无符号类型才能提供的额外保证是值得的。浮点类型,无论是否有符号,都不能遵守代数环的所有规则[例如,它们不能保证 X+YY 等于 X],实际上 IEEE 甚至不允许它们遵守代数环的规则等价类[通过要求某些值与它们自己比较不相等]。我不认为“无符号”浮点类型可以遵守普通浮点类型不能遵守的任何公理,所以我不确定它会提供什么优势。
我认为主要原因是与无符号整数相比,无符号浮点数的用途非常有限。我不相信这是因为硬件不支持它的论点。较旧的处理器根本没有浮点功能,它们都是在软件中模拟的。如果无符号浮点数有用,它们将首先在软件中实现,然后硬件也会效仿。
IHMO 这是因为在硬件或软件中同时支持有符号和无符号浮点类型太麻烦了
对于整数类型,我们可以在大多数情况下使用 2 的补码的 nice 属性来使用 the same logic unit for both signed and unsigned integer operations,因为 the result is identical in those cases 用于加法、减法、非扩展 mul 和大多数按位运算。对于区分签名和未签名版本的操作,我们仍然可以共享大部分逻辑。例如
算术和逻辑移位只需要对最高位的填充进行轻微更改
加宽乘法可以对主要部分使用相同的硬件,然后使用一些单独的逻辑来调整结果以改变符号。并不是说它用于实际乘数,但可以这样做
有符号比较可以通过切换最高位或添加 INT_MIN 轻松转换为无符号比较,反之亦然。理论上也是可能的,它可能不在硬件上使用,但在仅支持一种比较类型(如 8080 或 8051)的系统上很有用
使用 1 的补码的系统也只需要对逻辑进行一些修改,因为它只是将进位位包裹到最低有效位。不确定符号幅度系统,但似乎它们use 1's complement internally,所以同样适用
不幸的是,我们对浮点类型并不那么奢侈。通过简单地释放符号位,我们将获得无符号版本。但是那我们应该用那个位做什么呢?
通过将范围添加到指数来增加范围
通过将其添加到尾数来提高精度。这通常更有用,因为我们通常需要比范围更高的精度
但是这两种选择都需要一个更大的加法器来适应更广泛的价值范围。这增加了逻辑的复杂性,而加法器的最高位大部分时间都没有使用。乘法、除法或其他复杂运算将需要更多电路
在使用软件浮点的系统上,每个函数都需要 2 个版本,这在内存非常昂贵的时候是意料之中的,或者你必须找到一些“棘手”的方法来共享部分有符号和无符号函数
但是floating-point hardware existed long before C was invented,所以我认为C中的选择是由于我上面提到的原因缺乏硬件支持
也就是说,存在几种专用无符号浮点格式,主要用于图像处理目的,例如 Khronos group's 10 and 11-bit floating-point type
好问题。
如果,如您所说,它仅用于编译时警告并且它们的行为没有改变,否则底层硬件不会受到影响,因此它只会是 C++/编译器的更改。
我以前也有同样的想法,但问题是:这没有多大帮助。编译器充其量只能找到静态分配。
unsigned float uf { 0 };
uf = -1f;
或极简主义更长
unsigned float uf { 0 };
float f { 2 };
uf -= f;
但仅此而已。使用无符号整数类型,您还可以获得定义的环绕,即它的行为类似于模算术。
unsigned char uc { 0 };
uc -= 1;
在这个“uc”之后,它的值是 255。
现在,在给定无符号浮点类型的情况下,编译器会如何处理相同的场景?如果在编译时不知道这些值,则需要生成首先执行计算然后进行符号检查的代码。但是,当这种计算的结果是“-5.5”时——应该将哪个值存储在声明为无符号的浮点数中?可以尝试像整数类型那样的模算术,但这有其自身的问题:最大值无疑是无穷大......那不起作用,你不能有“无穷大 - 1”。追求它可以保持的最大不同值也不会真正起作用,因为你会遇到它的精确度。 “NaN”将是一个候选人。您丢失了该数字最初包含的所有信息 - 并没有真正的帮助,因为您现在需要专门检查这一点,因此您不妨检查一下您自己的数字是否为正。
最后,这不会是定点数的问题,因为模数是明确定义的。
我不知道任何支持无符号浮点数的编程语言。知道为什么它们不存在吗?
存在无符号浮点数。请参阅 GPU 硬件的 unsigned float16
(11 个小数位、5 个指数位、0 个符号位)HDR format DXGI_FORMAT_BC6H。只是它们在大多数计算硬件中并不常见,以至于主流编程语言都忽略了它们。在这种用法中,符号被省略,因为比黑色深的颜色无论如何都没有意义。
即使是更常见的 IEEE half 或带符号的 float16_t
,它在图形和机器学习领域中非常频繁地用于 HDR 图像和较低带宽张量,也没有获得被纳入 C/C++ 的荣誉(尽管,更多特定领域的语言(如 CUDA/HLSL)确实有 half
/float16_t
,也有 C++ proposals)。因此,即使 signed float16
不能在编译器特定扩展(例如 gcc __fp16)之外变成 C++,那么 unsigned float16
就没有希望了 :b,甚至 CUDA 或 HLSL 在语言,就在纹理定义本身中(在 .DDS 文件或 GPU 纹理内存中)。在那之前,我们将不得不通过 helper libraries 继续实现更多奇特的类型,而无需编译器帮助。
我怀疑这是因为 C 编译器所针对的底层处理器没有处理无符号浮点数的好方法。
int
一样,所有与符号相关的类型检查都可能在编译时发生。 OP 建议将unsigned float
实现为具有编译时检查的常规float
,以确保永远不会执行某些无意义的操作。无论您的浮点数是否已签名,生成的机器代码和性能都可能相同。