为什么C没有未签名的浮点数?


131

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

在CI中使用signedunsigned整数很多。我喜欢这样的事实,即编译器会在执行诸如将带符号的整数分配给无符号的变量之类的操作时警告我。如果将带符号整数与无符号整数进行比较,则会收到警告。

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

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

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

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

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


编辑:

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


4
有趣的是,您可以举一个例子说明签名类型检查很有帮助吗?

小伙子,你的评论是针对我的吗?如果是这样,我不明白

Iraimbilanja yeah :)晶圆厂不能返回负数,因为它返回其参数的绝对值
Johannes Schaub-litb

没错,我没有问一个假设的无符号浮点数如何帮助增强共心性。关于类型安全性

1
有一个用于范围内检查的无符号微优化:((unsigned)(p-min))<(max-min),它只有一个分支,但是像往常一样,最好进行概要分析以查看是否它确实有帮助(我主要在386内核上使用过它,所以我不知道现代的CPU如何应付)。
Skizz,2009年

Answers:


114

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

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

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

通过创建一个封装浮点数或双精度数并在尝试传递负数时引发警告的无符号浮点类,您可以解决没有无符号浮点类型的问题。这效率较低,但是如果您不大量使用它们,则可能会担心性能会有所下降。

我绝对看到使用未签名的浮点数的有用性。但是C / C ++倾向于选择效率最高的产品,而不是安全产品。


17
C / C ++不需要特定的机器代码操作即可实现该语言。早期的C / C ++编译器可以为386生成浮点代码-没有FPU的CPU!编译器将生成库调用以模拟FPU指令。因此,可以在没有CPU支持的情况下完成任务
Skizz 2009年

10
Skizz,虽然这是正确的,但Brian已经解决了这一问题-因为没有等效的机器代码,相比之下,性能将很糟糕。
安东尼

2
@Brian R. Bondy:我在这里迷路了:“因为没有等效的机器代码操作可让CPU执行...”。您能用简单的方式解释一下吗?
Lazer

2
OP希望支持无符号浮点数的原因是用于警告消息,因此,实际上它与编译器的代码生成阶段无关-仅与它事先进行类型检查的方式有关-因此在机器代码中对它们的支持无关紧要并且(已添加到问题的底部)正常的浮点指令可用于实际执行。
Joe F

2
我不确定我为什么会影响性能。就像使用一样int,所有与符号相关的类型检查都可能在编译时发生。OP建议unsigned float将其作为常规float进行编译时检查,以确保永远不会执行某些无意义的操作。无论浮点数是否已签名,最终的机器代码和性能都可能相同。
xanderflood

14

在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 +/-根( bb-4.ac))/ 2.a,依此类推。实际上,对于任何复杂的函数来说,它可能都不起作用,因为这些函数通常被实现为多项式逼近,在某处使用负值。

因此,未签名的浮点数几乎没有用。

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


@Skizz:如果表示存在问题,则意味着如果有人可以设计一种与一样高效的存储浮点数的方法2's complement,那么使用未签名的浮点数不会有问题吗?
Lazer

3
value >> shift for signed values leave the top bit unchanged (sign extend) 你确定吗?我认为这是实现定义的行为,至少对于负有符号的值而言。
2012年

@丹:刚刚看了最近的标准,它确实声明了它是由实现定义的-我想这是在万一有一个CPU没有带符号扩展指令的右移的情况下。
Skizz'2


1
浮点通常会饱和(到-/ + Inf),而不是自动换行。您可能希望无符号减法溢出会饱和到0.0,或者可能是Inf或NaN。或者只是保持未定义的行为,就像在对问题进行编辑时建议的操作。关于:触发函数:因此,请勿定义等的无符号输入版本sin,并确保将其返回值视为带符号。问题不是建议无符号浮点数替换浮点数,而只是添加unsigned float为新类型。
Peter Cordes

9

(顺便说一句,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 ...不会解析。
短暂

8

我相信创建unsigned int的原因是,需要更高的价值保证金,而不是signed int所能提供的。

浮点数的边距要大得多,因此从来没有“实际”需要未签名的浮点数。正如您在自己的问题中指出的那样,额外的1位精度无济于事。

编辑: 在阅读了Brian R. Bondy答案之后,我必须修改我的答案:他绝对是对​​的,底层CPU没有未签名的浮点运算。但是,我仍然坚信这是基于我上述原因的设计决定;-)


2
同样,整数的加减是相同的有符号或无符号-浮点数,不是那么多。鉴于这种功能的边际效用相对较低,谁会做额外的工作来支持有符号和无符号浮点数?
ephemient

7

我认为Treb步入正轨。对于整数来说,具有无符号的对应类型更为重要。这些是用于位移位图的。一个标志位刚刚进入。例如,右移负值,结果值是C ++中定义的实现。使用无符号整数或溢出这样的整数具有完美定义的语义,因为没有这种方式。

因此,至少对于整数,对单独的无符号类型的需求比仅发出警告要强。浮点数不需要考虑所有上述几点。因此,我认为实际上并不需要它们的硬件支持,并且C那时将不支持它们。


5

平方根一定永远不会返回负数。在其他地方,负浮点值也没有意义。无符号浮动的完美候选人。

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。


是的,但是如果使用复数,则可以使用其他名称调用函数。返回类型也不同。好点!
Nils Pipenbrinck,2009年

4
sqrt(i)的结果是一个复数。而且由于没有指定复数,所以不能说复数是负数(即<0)
quinmars

1
quinmars,确定不是csqrt吗?还是谈论数学而不是C?无论如何,我同意这是一个好点子:)
Johannes Schaub-litb

确实,我在谈论数学。我从来没有处理过c中的复数。
quinmars,

1
“平方根一定永远不会返回负数。” -> sqrt(-0.0)经常产生-0.0。当然-0.0不是负
chux-恢复莫妮卡2015年

4

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

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

编辑:另外,正如其他人所指出的那样,大多数硬件不支持非负浮点数,因此,由于有硬件支持,因此常规浮点数的执行效率更高。


C是在IEEE-754标准出现之前很久才引入的
phuclv

@phuclv都不是常见的浮点硬件。几年后,它被纳入标准C。互联网上可能有一些关于它的文档。(此外,维基百科文章提到C99)。
TobiasWärre19年

我不明白你的意思。您的答案没有“硬件”,IEEE-754诞生于C之后,因此C中的浮点类型不能依赖IEEE-754标准,除非这些类型在以后
很久

@phuclv C也被称为可移植程序集,因此它与硬件非常接近。这些年来,语言获得了很多功能,即使(在我之前)浮动工具是用C实现的,它也可能是基于软件的操作,而且非常昂贵。在回答这个问题时,显然我比现在更了解我要解释的内容。而且,如果您查看接受的答案,您可能会理解为什么我提到IEE754标准。我不明白的是,您是根据10岁的答案挑剔的,这不是被接受的答案吗?
TobiasWärre19年

3

我认为主要原因是,与无符号整数相比,无符号浮点数的使用实际上是有限的。我不认为这是因为硬件不支持它。较旧的处理器根本没有浮点功能,所有功能都在软件中进行了仿真。如果未签名的浮点数有用,那么它们将首先在软件中实现,而硬件将紧随其后。


4
C的第一个平台PDP-7具有可选的硬件浮点单元。C的下一个平台PDP-11在硬件上具有32位浮点数。80x86出现了一代,但有些技术却落后了一代。
短暂的

3

C中的无符号整数类型的定义应遵循抽象代数环的规则。例如,对于任何值X和Y,将XY加到Y都会得到X。无符号整数类型在所有不涉及到任何其他数字类型(或不同大小的无符号类型)的转换的所有情况下,都应遵守这些规则。 ,并且保证是此类类型最重要的功能之一。在某些情况下,值得放弃以负数表示的能力,以换取只有无符号类型可以提供的额外保证。浮点类型(无论是否带符号)都不能遵守代数环的所有规则(例如,它们不能保证X + YY等于X),而IEEE确实不 甚至允许他们遵守等价类的规则(通过要求某些值与其自身不相等进行比较)。我认为“无符号”浮点类型不能遵守普通浮点类型不能做到的任何公理,因此我不确定它会带来什么好处。


1

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

对于整数类型,在大多数情况下,我们可以使用2的补码的nice属性相同的逻辑单元用于有符号和无符号整数运算,因为在那些情况下,加,子,非加宽mul和大多数按位运算的结果是相同的。对于区分有符号版本和无符号版本的操作,我们仍然可以共享大多数逻辑。例如

  • 算术和逻辑移位仅需对最高位的填充符稍作更改
  • 扩展乘法可以在主要部分使用相同的硬件,然后使用一些单独的逻辑来调整结果以更改符号性。并不是说它在实数乘法器中使用,但是有可能做到
  • 可以通过切换最高位或添加,将带INT_MIN符号的比较轻松地转换为无符号的比较,反之亦然。从理论上讲也是可能的,它可能不用于硬件,但是在仅支持一种比较类型的系统(例如8080或8051)上很有用

使用1的补码的系统也只需要对逻辑做一点修改,因为它只是将进位位包裹在最低有效位上。不确定信号量级系统,但似乎它们在内部使用1的补码,因此也适用

不幸的是,对于浮点类型,我们并不是那么奢侈。通过简单地释放符号位,我们将获得未签名的版本。但是那该怎么用呢?

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

但是,两个选择都需要一个更大的加法器,以适应更大的价值范围。这会增加逻辑的复杂性,而加法器的高位大部分时间都坐在那儿。乘法,除法或其他复杂运算将需要更多电路

在使用软件浮点的系统上,每个功能都需要2个版本,而这在内存非常昂贵的时候是无法预期的,或者您必须找到某种“棘手的”方式来共享有符号和无符号功能的一部分

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

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


0

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


底层处理器是否具有处理带符号浮点数的好方法?当浮点辅助处理器特有且几乎不通用时,C变得流行起来。
David Thornley,

1
我不知道所有的历史时间表,但是对签名浮点数有新兴的硬件支持,尽管您指出的很少。语言设计人员可以合并对它的支持,而编译器后端则根据目标体系结构提供不同级别的支持。
Brian Ensink 09年

0

好问题。

如您所说,如果仅用于编译时警告,并且不对其行为进行任何更改,则基础硬件不会受到影响,因此仅是C ++ / Compiler更改。

我以前也曾想过,但事实是:这没有太大帮助。充其量,编译器可以找到静态分配。

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”将是候选人。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.