ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

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

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

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

编辑:

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

有趣的是,您能否发布一个签名类型检查有用的案例示例?
litb,你的评论是针对我的吗?如果是这样,我不明白
Iraimbilanja 是的 :) fabs 不能返回负数,因为它返回其参数的绝对值
对。我没有问假设的 unsignedfloat 如何帮助正确性。我问的是:在什么情况下 pipenbrinck 发现 Int 签名类型检查有帮助(导致他为浮点数寻求相同的机制)。我问的原因是我发现 unsigneds 完全没用关于类型安全
范围内检查有一个无符号微优化:((unsigned)(p-min))<(max-min),它只有一个分支,但一如既往,最好进行分析以查看是否它真的很有帮助(我主要在 386 核上使用它,所以我不知道现代 CPU 是如何应对的)。

B
Brian R. Bondy

为什么 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,以确保永远不会执行某些无意义的操作。无论您的浮点数是否已签名,生成的机器代码和性能都可能相同。
T
Tim Cooper

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 计算。


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

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

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

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

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

[编辑]

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


C
Community

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

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

编辑: 阅读answer by Brian R. Bondy后,我必须修改我的答案:他绝对正确,底层 CPU 没有无符号浮点操作。但是,我坚持认为这是基于我上述原因的设计决定;-)


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

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

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


P
Pete Kirkham

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

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

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

Wikipedia article on IEEE-754 floating-point numbers

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


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

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


F
Ferruccio

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


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

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


A
ABaumstumpf

好问题。

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

最后,这不会是定点数的问题,因为模数是明确定义的。


D
Dwayne Robinson

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

存在无符号浮点数。请参阅 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 继续实现更多奇特的类型,而无需编译器帮助。


B
Brian Ensink

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


底层处理器是否有处理有符号浮点数的好方法?当浮点辅助处理器具有特殊性并且几乎没有通用性时,C 变得流行起来。
我不知道所有的历史时间表,但有新兴的硬件支持签名花车,虽然你指出很少见。语言设计者可以合并对它的支持,而编译器后端根据目标架构提供不同级别的支持。