为啥 C 没有无符号浮点数?

Posted

技术标签:

【中文标题】为啥 C 没有无符号浮点数?【英文标题】:Why doesn't C have unsigned floats?为什么 C 没有无符号浮点数? 【发布时间】:2010-10-05 10:48:40 【问题描述】:

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

在 C 语言中,我经常使用 signedunsigned 整数。我喜欢这样一个事实,即如果我执行诸如将有符号整数分配给无符号变量之类的操作,编译器会警告我。如果我比较有符号整数和无符号整数等等,我会收到警告。

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

为什么我们对花车没有同样的奢侈?平方根绝对不会返回负数。还有其他地方负浮点值没有意义。无符号浮点数的完美候选者。

顺便说一句 - 我并不热衷于通过从浮点数中删除符号位来获得额外的精度。我对floats 非常满意,就像他们现在一样。我只是想有时将浮点数标记为无符号,并得到与整数相同的警告。

我不知道任何支持无符号浮点数的编程语言。

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


编辑:

我知道 x87 FPU 没有处理无符号浮点数的指令。让我们只使用签名的浮点指令。滥用(例如低于零)可能被视为未定义行为,就像未定义有符号整数溢出一样。

【问题讨论】:

有趣,你能发布一个签名类型检查有用的例子吗? litb,你的评论是针对我的吗?如果是这样,我不明白 Iraimbilanja 是的 :) 晶圆厂不能返回负数,因为它返回其参数的绝对值 对。我没有问假设的 unsignedfloat 如何帮助正确性。我问的是:在什么情况下 pipenbrinck 发现 Int 签名类型检查有帮助(导致他为浮点数寻求相同的机制)。我的原因问是我发现无符号在类型安全方面完全没用 范围内检查有一个无符号微优化:((unsigned)(p-min)) 【参考方案1】:

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

如果 C++ 确实支持它,那么您有时会使用无符号浮点数而没有意识到您的性能刚刚被杀死。如果 C++ 支持它,则需要检查每个浮点运算以查看它是否已签名。而对于进行数百万次浮点运算的程序,这是不可接受的。

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

您可以通过创建一个封装浮点或双精度的无符号浮点类来解决没有无符号浮点类型的问题,如果您尝试传入负数,则会引发警告。这效率较低,但如果您不频繁使用它们,您可能不会在意这种轻微的性能损失。

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

【讨论】:

C/C++ 不需要特定的机器代码操作来实现该语言。早期的 C/C++ 编译器可以为 386 生成浮点代码 - 一个没有 FPU 的 CPU!编译器会生成库调用来模拟 FPU 指令。因此,可以在没有 CPU 支持的情况下完成 ufloat Skizz,虽然这是正确的,但 Brian 已经解决了这个问题 - 因为没有等效的机器代码,相比之下性能会很糟糕。 @Brian R. Bondy:我把你弄丢了:“因为没有等效的机器代码操作供 CPU 执行......”。你能用更简单的方式解释一下吗? OP想要支持无符号浮点数的原因是为了警告消息,所以它实际上与编译器的代码生成阶段无关——只与它如何预先进行类型检查有关——所以在机器代码中对它们的支持是无关紧要的,并且(已添加到问题的底部)普通浮点指令可用于实际执行。 我不知道为什么这会影响性能。就像int 一样,所有与符号相关的类型检查都可能在编译时发生。 OP 建议将unsigned float 实现为常规的float,并进行编译时检查,以确保永远不会执行某些无意义的操作。无论您的浮点数是否已签名,生成的机器代码和性能都可能相同。【参考方案2】:

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 仅适用于其输入值的一半,您找不到值

所以,无符号浮点数是毫无用处的。

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

【讨论】:

@Skizz:如果表示是一个问题,你的意思是如果有人可以设计一种存储浮点数的方法,它与2's complement 一样有效,那么使用无符号浮点数就不会有问题? value >> shift for signed values leave the top bit unchanged (sign extend) 你确定吗?我认为这是实现定义的行为,至少对于负符号值而言。 @Dan:刚刚查看了最近的标准,它确实声明它是实现定义的 - 我想这只是以防万一 CPU 没有使用符号扩展指令右移。 Signed right shift: which compiler use logical shift 浮点传统上是饱和的(到 -/+Inf)而不是环绕。您可能期望无符号减法溢出饱和到0.0,或者可能是 Inf 或 NaN。或者只是未定义的行为,就像在问题编辑中建议的 OP 一样。回复:三角函数:所以不要定义sin等的无符号输入版本,并确保将它们的返回值视为有符号。问题不是提出用无符号浮点数替换浮点数,只是将unsigned float 添加为新类型。【参考方案3】:

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

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

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

没有对无符号浮点运算的硬件支持,因此 C 不提供它。 C 主要被设计为“便携式组件”,即尽可能接近金属而不被束缚在特定平台上。

[编辑]

C 就像汇编:所见即所得。一个隐含的“我会检查这个浮点数对你来说是非负的”违背了它的设计理念。如果你真的想要它,你可以添加 assert(x >= 0) 或类似的,但你必须明确地这样做。

【讨论】:

svn.perl.org/parrot/trunk/languages/perl6/docs/STATUS 说是,但 of ... 没有解析。【参考方案4】:

我相信创建 unsigned int 是因为需要比已签名 int 所能提供的更大的值边距。

浮点数的余量要大得多,因此对于无符号浮点数从来没有“物理”需求。正如您在问题中指出的那样,额外的 1 位精度没什么可杀的。

编辑: 看完answer by Brian R. Bondy后,我不得不修改我的答案: 他绝对正确,底层 CPU 没有无符号浮点操作。但是,我仍然认为这是基于我上面所说的原因的设计决定 ;-)

【讨论】:

此外,整数的加减法是相同的有符号或无符号的——浮点数,不是那么多。鉴于此类功能的边际效用相对较低,谁会做额外的工作来支持有符号和无符号浮点数?【参考方案5】:

我认为 Treb 走在正确的轨道上。对于具有无符号对应类型的整数来说更重要。这些是在 bit-shifting 中使用并在 bit-maps 中使用的那些。一个符号位刚刚进入。例如,右移一个负值,结果值是 C++ 中定义的实现。使用无符号整数或溢出这样的整数具有完美定义的语义,因为方式中没有这样的位。

因此,至少对于整数而言,对单独的无符号类型的需求比仅仅给出警告要强。浮动不需要考虑以上所有点。所以,我认为,对它们的硬件支持并不真正需要,而且 C 那时已经不支持它们了。

【讨论】:

【参考方案6】:

平方根绝对不会返回负数。还有其他地方负浮点值没有意义。无符号浮点数的完美候选者。

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(i) 的结果是一个复数。而且由于复数没有排序,所以不能说复数是负数(即 quinmars,确定不是 csqrt 吗?还是您谈论数学而不是C?无论如何,我同意这是一个好观点:) 确实,我说的是数学。我从未处理过 c 中的复数。 " 平方根绝对不会返回负数。" --> sqrt(-0.0) 经常产生-0.0。当然 -0.0 不是负【参考方案7】:

我想这取决于仅对 IEEE 浮点规范进行签名,并且大多数编程语言都使用它们。

Wikipedia article on IEEE-754 floating-point numbers

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

【讨论】:

C 早在 IEEE-754 标准出现之前就引入了 @phuclv 两者都不是普通的浮点硬件。 “几年”后,它被采用到标准 C 中。互联网上可能有一些关于它的文档。 (此外,***文章提到了 C99)。 我不明白你的意思。您的答案中没有“硬件”,并且 IEEE-754 是在 C 之后诞生的,因此 C 中的浮点类型不能依赖于 IEEE-754 标准,除非这些类型在很久以后才引入 C @phuclv C 也被称为便携式组件,因此它可以非常接近硬件。多年来,语言获得了一些特性,即使(在我之前)float 是用 C 实现的,它也可能是一个基于软件的操作并且相当昂贵。在回答这个问题时,我显然比现在更好地理解了我试图解释的内容。如果您查看接受的答案,您可能会理解我为什么提到 IEE754 标准。我不明白的是,你挑剔了一个 10 年前的答案,而这不是被接受的答案?【参考方案8】:

我认为主要原因是与无符号整数相比,无符号浮点数的用途非常有限。我不相信这是因为硬件不支持它的论点。较旧的处理器根本没有浮点功能,它们都是在软件中模拟的。如果无符号浮点数有用的话,它们会首先在软件中实现,然后硬件也会效仿。

【讨论】:

C 的第一个平台 PDP-7 有一个可选的硬件浮点单元。 C 的下一个平台 PDP-11 在硬件中具有 32 位浮点数。 80x86 出现在一代人之后,其中一些技术落后了一代人。【参考方案9】:

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

【讨论】:

【参考方案10】:

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

对于整数类型,我们可以在大多数情况下使用 the same logic unit for both signed and unsigned integer operations 使用 2 的补码的好属性,因为 the result is identical in those cases 用于加法、减法、非扩展 mul 和大多数按位运算。对于区分签名和未签名版本的操作,我们仍然可以共享大部分逻辑。例如

算术和逻辑移位只需要对高位的填充进行轻微更改 加宽乘法可以对主要部分使用相同的硬件,然后使用一些单独的逻辑来调整the result to change the signness。并不是说它用于实数乘法器,但可以这样做 有符号比较可以通过切换最高位或adding INT_MIN 轻松转换为无符号比较,反之亦然。理论上也是可能的,它可能不会用在硬件上,但它在systems that support only one type of comparison 上很有用(比如 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

【讨论】:

【参考方案11】:

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

存在无符号浮点数。有关 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 继续在没有编译器帮助的情况下实现更多奇特的类型。

【讨论】:

【参考方案12】:

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

【讨论】:

底层处理器是否有处理带符号浮点数的好方法?当浮点辅助处理器具有特殊性且几乎没有通用性时,C 语言变得流行起来。 我不知道所有的历史时间表,但有新兴的硬件支持签名浮点数,尽管正如你所指出的那样罕见。语言设计者可以加入对它的支持,而编译器后端根据目标架构提供不同级别的支持。【参考方案13】:

好问题。

如果,如您所说,它仅用于编译时警告并且它们的行为没有变化,否则底层硬件不会受到影响,因此它只会是 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”将是一个候选人。您丢失了该数字最初包含的所有信息 - 并没有真正的帮助,因为您现在需要专门检查这一点,因此您不妨检查一下该数字是否为您自己。

最后,这对于定点数来说不是问题,因为模数是明确定义的。

【讨论】:

以上是关于为啥 C 没有无符号浮点数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 x86(32 位)程序集中将无符号整数转换为浮点数?

如何从 Java 中的无符号字节中获取浮点数?

将浮点数转换为无符号短时的舍入问题

在汇编中将无符号字符转换为浮点数(为浮点向量计算做准备)

opencv c++:将图像矩阵从无符号字符转换为浮点数

字符串格式符