为什么 C 没有无符号浮点数?

我知道,这个问题看起来很奇怪。程序员有时想得太多了。请继续阅读..。

在 C 语言中,我经常使用 signedunsigned整数。我喜欢这样一个事实: 如果我将一个有符号整数赋值给一个无符号变量,编译器会提醒我。如果我将有符号整数和无符号整数进行比较,就会得到警告。

我喜欢这些警告,它们帮助我保持代码正确。

为什么我们不能享受同样的奢侈呢?平方根绝对不会返回负数。还有一些地方负浮点值没有任何意义。完美的候选人,一个未签名的花车。

顺便说一句,我并不十分热衷于通过从浮点数中移除符号位来获得额外的精确度。我对 float非常满意,因为它们现在就是这样。我只是想有时 将浮动标记为无符号,并得到同样的警告,我得到与整数。

据我所知,没有任何编程语言支持无符号浮点数。

知道为什么它们不存在吗?


编辑:

我知道 x87FPU 没有处理无符号浮点数的指令。让我们使用已签名的 float 指令。错误使用(例如低于零)可以被认为是未定义的行为,就像有符号整数的溢出是未定义的一样。

108045 次浏览

我想这取决于 IEEE 浮点规范是否只有签名的,以及大多数编程语言是否使用它们。

关于 IEEE-754浮点数的维基百科文章

编辑: 另外,正如其他人所指出的,大多数硬件不支持非负浮动,因此普通类型的浮动更有效,因为有硬件支持。

我怀疑这是因为 C 编译器针对的底层处理器没有处理无符号浮点数的好方法。

C + + 之所以不支持无符号浮点数,是因为没有等效的机器代码操作供 CPU 执行。所以支持它是非常低效的。

如果 C + + 确实支持它,那么您有时会使用一个无符号浮点数,并且没有意识到您的性能已经被扼杀了。如果 C + + 支持它,那么每个浮点操作都需要检查它是否有符号。对于执行数百万个浮点运算的程序来说,这是不可接受的。

所以问题是为什么硬件实现者不支持它。我认为这个问题的答案是没有最初定义的无符号浮点数标准。因为语言喜欢向后兼容,即使添加了语言也不能使用它。要查看浮点规范,您应该查看 IEEE 标准754浮点数

不过,您可以通过创建一个无符号浮点类来避免没有无符号浮点类型,该类封装了一个浮点数或双精度浮点数,如果您试图传入负数,则会抛出警告。这种方法的效率较低,但是如果您没有大量使用它们,您可能不会关心这种轻微的性能损失。

我确实看到了使用无符号浮点数的有用性。但是 C/C + + 倾向于选择对每个人都最有效的效率,而不是安全。

我相信创建无符号整数是因为需要比有符号整数能够提供的更大的值保证金。

浮点数的余量要大得多,所以从来不需要无符号浮点数。正如你在问题中指出的那样,额外的1位精确度是不值得为之杀戮的。

编辑: 读完 作者: 布莱恩 · R · 邦迪后,我不得不修改我的答案: 他肯定是对的,底层 CPU 没有无符号的浮点运算。然而,我仍然相信这是一个基于上述原因的设计决定; -)

(顺便说一句,Perl 6允许您编写

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

然后你可以像使用其他类型一样使用 Nonnegative::Float。)

没有对无符号浮点运算的硬件支持,所以 C 不提供它。C 主要被设计成“便携式组装”,也就是说,尽可能地接近金属,而不被绑定到特定的平台上。

[编辑]

C 语言就像汇编语言: 你所看到的正是你所得到的。隐含的“我会检查这个浮动对您来说是否是非负的”违背了它的设计哲学。如果您真的想要它,您可以添加 assert(x >= 0)或类似的,但是您必须显式地这样做。

我觉得 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。

我认为主要原因是,与无符号整型相比,无符号浮点数的使用非常有限。我不认为这是因为硬件不支持它。旧的处理器根本没有浮点运算能力,它们都是在软件中模拟的。如果无符号浮动是有用的,那么它们将首先在软件中实现,而硬件也会跟着实现。

在 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 (b.b-4.a.c))/2。等等。事实上,它可能不适用于任何复杂的函数,因为这些函数往往被实现为多项式近似,在某些地方使用负值。

所以,没有签名的花车是非常无用的。

但这并不意味着检查浮点值的类没有用处,您可能希望将值限制在给定的范围内,例如 RGB 计算。

在 C 中,无符号整数类型的定义遵循抽象代数环的规则。例如,对于任何值 X 和 Y,将 X-Y 加到 Y 将产生 X。无符号整数类型保证在所有情况下遵守这些规则,不涉及转换为或从任何其他数值类型[或不同大小的无符号类型] ,这种保证是这种类型最重要的特征之一。在某些情况下,放弃表示负数的能力以换取只有无符号类型才能提供的额外保证是值得的。浮点类型,无论是否有符号,都不能遵守代数环的所有规则[例如,它们不能保证 X + Y-Y 将等于 X ] ,事实上 IEEE 甚至不允许它们遵守等价类的规则[通过要求某些值与它们自己相比不等]。我不认为“无符号的”浮点类型可以遵守普通浮点类型所不能遵守的任何公理,所以我不确定它会提供什么优势。

IHMO 这是因为在硬件或软件中同时支持有符号和无符号的浮点类型太麻烦了

对于整数类型,我们可以利用 有符号和无符号整数操作 的 < strong > same 逻辑单元在大多数情况下使用2的补数的漂亮属性,因为 在这些情况下,结果是一样的用于加法,子,非扩展 mul 和大多数按位运算。对于区分有符号版本和无符号版本的操作,我们仍然可以使用 分享大部分的逻辑。比如说

  • 算术和逻辑移位只需要在顶部位的填充略有变化
  • 拓宽乘法可以使用相同的硬件为主要部分,然后一些单独的逻辑调整 改变符号的结果。并不是说它被用于实际的乘数,但它是可能做到的
  • 有符号比较可以转换为无符号比较,反之亦然,通过切换顶部位或 加入 INT_MIN很容易。理论上也是可能的,它可能不在硬件上使用,但是它在 只支持一种比较的系统上很有用(比如8080或8051)

使用1补数的系统也只需要对逻辑进行一点修改,因为它只是包装到最低有效位的进位。不知道符号星等系统,但似乎他们 在内部使用1的补语所以同样的事情适用

不幸的是,通过简单地释放符号位,我们将得到未签名版本。但是我们应该用那一点做什么呢?

  • 通过将其添加到指数来增加范围
  • 通过加入尾数来提高精度。这通常更有用,因为我们通常需要更多的精度比范围

但是,这两种选择都需要一个 更大的毒蛇,以适应更广泛的价值范围。这增加了逻辑的复杂性,而加法器的顶部位在大部分时间都没有使用。乘法、除法或其他复杂运算需要更多的电路

在使用软件浮点运算的系统中,每个函数需要两个版本,而这在内存昂贵的时候是意想不到的,否则你将不得不寻找一些“棘手”的方法来共享部分有符号和无符号函数

但是 在 C 语言发明之前,浮点硬件就已经存在了,所以我相信选择 C 是由于缺乏硬件支持,因为我上面提到的原因

也就是说,存在几种 专业的无符号浮点格式,主要用于图像处理,如 Khronos 组的10位和11位浮点类型

问得好。

如果,正如您所说的,它仅用于编译时警告,并且没有改变它们的行为,否则底层硬件不会受到影响,因此它只会是一个 C + +/编译器更改。

我以前也想过同样的问题,但问题是: 这没有多大帮助,编译器最多只能找到静态分配。

unsigned float uf { 0 };
uf = -1f;

或者更长

unsigned float uf { 0 };
float f { 2 };
uf -= f;

但仅此而已。 对于无符号整数类型,您还可以得到一个定义的包装,即它的行为类似于同余关系。

unsigned char uc { 0 };
uc -= 1;

在这个 uc 之后保持255的值。

现在,如果给定一个无符号的 float 类型,编译器将如何处理相同的场景? 如果在编译时不知道这些值,则需要生成代码,该代码首先执行计算,然后执行符号检查。但是,当这种计算的结果是“-5.5”时,应该将哪个值存储在声明为 unsigned 的 float 中呢? 我们可以尝试使用类似于积分类型的同余关系,但这也有自己的问题: 最大值无疑是无穷大... ..。不行,你不能有“无穷 -1”。追求它所能保持的最大的独特价值也不会真正起作用,因为在那里你会遇到它的精确性。 “ NaN”应该是个候选人。你丢失了数字原本包含的所有信息——不是真的有帮助,因为你现在需要特别检查这些信息,所以你不妨检查一下这个数字是否是正的。

最后,这不会是一个问题与不动点数,因为有模定义良好。

我不知道有任何编程语言支持无符号 浮点数,知道为什么它们不存在吗?

存在无符号浮点数。请参阅图形处理器硬件 HDR 格式 DXGI _ formAT _ BC6Hunsigned float16(11个分数位、5个指数位、0个符号位)。只是它们在大多数计算机硬件中都不常见,以至于主流编程语言忽略了它们。在这种用法中,标志被省略,因为颜色 DARKER THAN BLACK -流星の双子- 没有任何意义。

甚至更为常见的 IEEE 一半或签名 float16_t,这是相当频繁地使用在图形和机器学习领域的 HDR 图像和较低的带宽张量,没有得到荣誉被纳入 C/C + + (虽然,更多的领域特定的语言,如 CUDA/HLSL 的确有 half/float16_t,也有 C + + 建议)。因此,如果即使是 signed float16也不能在编译器特定的扩展(例如 Gcc _ _ fp16)之外创建 C + + ,那么 unsigned float16就没什么希望了: b,即使是 CUDA 或 HLSL 也没有无符号类型 在语言上,只是在纹理定义本身(见。DDS 文件或 GPU 纹理存储器)。在那之前,我们必须继续实现更多的异类,而不需要通过 帮助程序库编译器的帮助。

更新2022-10-09 C + + 23包括提案 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p1467r9.html中签署的 float16_t,尽管是 可以选择扩展。但我没看到任何未签名的花车。