2

我不太了解 C 中的强制转换。任何人都可以帮助我解决《计算机系统:程序员的观点》一书中的问题:


我们生成任意整数值 x、y 和 z,并将它们转换为 double 类型的值,如下所示:

int x = random();
int y = random();
int z = random();

double dx = (double) x;
double dy = (double) y;
double dz = (double) z;

对于以下每个 C 表达式,你要指出该表达式是否总是产生 1。如果它总是产生 1,请描述基本的数学原理。否则,给出一个使其产生 0 的参数示例

A. (float) x == (float) dx
B. dx - dy == (double) (x-y)
C. (dx + dy) + dz == dx + (dy + dz)
D. (dx * dy) * dz == dx * (dy * dz)
E. dx / dx == dz / dz
4

3 回答 3

3

铸造所做的是将一件事转换为另一件事。它可能是 8 位整数到 16 位或 32 位

unsigned int x;
unsigned char y;

y = 7;
x = (unsigned int) y;

如果你做了 x=y; 这当然是暗示的。假设这里有 8 位和 32 位类型定义。从位模式 0x07 到 0x00000007。

这实际上是一个非常有趣的练习。让我们组成一个格式,然后思考问题。

浮点格式通常会做这样的事情,我们可以纯粹以正数来思考并完成大部分练习。以 10 为底的数字 1234,我们在小学谈论科学记数法,即 1.234 乘以 10 的 3 次方。基于两种计算机格式的工作方式相同,之所以称为浮点数是有原因的。小数在虚拟意义上移动。因此,浮点数中的数字 9 0b1001 你会想要找到最重要的一个,然后将小数放在它之后 1.001 乘以 2 的 3 次幂。数字 17 是 1.0001 乘以 2 的 4 次幂。浮点必须覆盖符号,它“分数”或尾数必须有一些位,因为即使是整数,我们也要强制分数。然后是指数。我们不一定需要保留可以在某些格式中假定的小数点之前的那个。当然,零是一个特殊问题,浮点格式需要一个特殊的异常或模式。该格式还需要一些位数来表示所应用的 2 的幂。对于初学者,我们假设只生成正整数,我们的整数是 8 位,所以 0 到 0xFF 是我们整个整数世界。我们的双精度格式有 12 个小数位,我们的单精度格式有 5 个以供争论。对于初学者,我们假设只生成正整数,我们的整数是 8 位,所以 0 到 0xFF 是我们整个整数世界。我们的双精度格式有 12 个小数位,我们的单精度格式有 5 个以供争论。对于初学者,我们假设只生成正整数,我们的整数是 8 位,所以 0 到 0xFF 是我们整个整数世界。我们的双精度格式有 12 个小数位,我们的单精度格式有 5 个以供争论。

那么我们最坏情况下的数字 0xFF 是多少,我们可以用 12 个小数位 1.111111100000 乘以 2 的 7 次方轻松表示,并假设我们的格式有足够多的指数位来涵盖整个练习。

所以我们的 double 可以保存我们的每一个整数,在 C

dx = 双(x);

只是意味着转换格式,如果我们从位模式 00001001 开始,它是数字 9,在我们的双倍中,这将是 1.001000000000 乘以 2 的幂 3 幂是我们以我们的格式存储的更多位,但与这个问题无关.

在我们组合的单曲中,数字 9 是 1.00100 乘以 2 的 3 次方。

但是我们单精度中的数字 0xFF 是 1.11111 乘以 2 的 7 次方,当我们将其转换回整数时,它是 0xFC 而不是 0xFF,我们在转换中丢失了一些位。假设没有四舍五入。以 10 1/27 为底数是 0.0307307307 ...如果我们将其截断为 4 位数,我们将得到 0.0307,这有点低。但是如果我们取 3 0.031 并四舍五入,那就有点太高了。如果我们看一下单曲中的 0xFF,它是 1.11111,接下来被扔掉的两位是 11,这是一半以上,所以如果我们将 10.00000 乘以 2 的 7 次归一化为 1.00000 乘以 2 的 8 次方怎么办。0xFF 基本上舍入到 0x100。我们可以用 0x100 代表 0xff 或 0xFC 代表 0xFF 有点高或有点低但不准确,当我们转换回来时,我们要么有点高要么有点低。

所以看看第一种情况 A (float)x vs (float)((double)x) 1.00100 乘以 2 的幂 3 vs 1.001000000000 乘以 2 的幂 3. 覆盖 float x vs double x 然后必须转换双精度,浮点格式之间使用的剪裁和舍入是否与整数浮动相同?取决于硬件,但人们希望 11111111 转换与 1.111111100000 相同,但也许它不会,在理想的世界中它会。

C 是一个有趣的例子,会这样说,表示一个两位数加上一个两位数需要多少位?正数 最坏情况 3 + 3 = 6 多少位扩大 0xFF 加上 0xFF 现在很多位在我们的双格式中是否超过 12?取 0xFF 加上 0xFF 加上 0xFF 这需要多少位?是不是超过 12 个?重新安排分组会改变吗?

D 两位数 3*3 = 9 每个操作数的两位输入多少位?0xFF 乘以 0xFF?那么 0xFF 乘以 0xFF 乘以 0xFF 是否需要超过 12 位?是第一个问题。第二个问题如果是这样,那么裁剪和舍入如何工作?剪裁和舍入是否受分组影响。那是迄今为止的脑筋急转弯,我可能必须编写一个简单的程序才能理解它。

而且E并不像我最初想的那么讨厌,重新阅读它。我将位模式与确切的位模式分开。除法的最大问题是什么,不仅是计算机,而且一般来说,什么整数给我们带来了问题并解决了其他问题?如果我们在这里允许有符号数正x除以正x和负z除以负z怎么办?

所以在维基百科搜索双精度浮点然后搜索单精度。double 有多少个“分数”位?并假设一个 32 位或 31 和一个 int 的符号,所有有效数字都适合吗?考虑正数,我们将有 2 到 31 的幂现在需要许多指数位来表示数字 31?是否有足够?那么,你可以在分数中保存 31 或 30 个有效位吗?你能用指数表示正负 31 的幂吗?

编辑

于是想着案例DI写了一个程序。我使用了 8 位小数

//5 9 f 020140 030120 0301E0 090151 090152
(5 * 9) * 15 vs 5 * (9 * 15)

所以 5 是 1.01000000 或 0x1.40,9 是 1.00100000 或 0x1.20,0xF 是 1.11100000 或 0x1.E0。

float 中的乘法与您想象的不一样,这会让您的大脑有些受伤,因为我们不是直接将 5 乘以 9,而是将所有内容都移动了,因此它是 1.something 所以它是

0x120 * 0x140 = 0x16800

由于标准化,你砍掉了 8 位,这就是我们将看到的四舍五入的地方,在这种情况下,结果是 0x168,不需要标准化

5*9 = 0x2D = 101101
1.01101000 0x1.68

我不需要关心指数,但他们只是加 5 的指数为 2,9 的指数为 8,所以结果是 0x168,指数为 5 所以 0x168 乘以 0x1E0 = 0x2A300 由于性质,我们立即切断 8 位 0x2A3的乘法。现在我们标准化我们需要 1.soemthing 而不是 2.something 所以我们向右移动并增加指数所以指数是 5+3=8 我们再给它一个 9 但注意到一些东西 0x2A3 0x1010100011 我们要丢掉一点精度而不是 0x1.51 半位,我们有 0x1.51 基数 2 浮点的性质。现在这应该是为了总结答案吗?也许。如果是这样,那么答案 0x1.52

Take it the other way 5*(9*15)
0x120*0x1E0 = 0x21C00
or 0x21C  1000011100
0x10E
0x140*0x10E = 0x15180
we are going to lose a bit here
is it 0x151 or 0x152?

这些是等效的四舍五入问题吗?两条路径是否导致 0x152 使它们相等,或者是一种截断位的视图与另一种不同?如果我们根本不四舍五入,只剪辑两个答案都是 0x152。

3 11 1f 010180 040110 0401F0 0A018B 0A018A
(3*17)*31 vs 3*(17*31)  no rounding just clipping
(3*17)*31
0x180*0x110 = 0x19800
0x198
0x198*0x1F0 = 0x31680  0x316 0x1100010110
0x18B
3*(17*31)
0x110*0x1F0 = 0x20f00
0x107
0x180*0x107 = 0x18A80
0x18A
0x18B != 0x18A

一条路径我们剪掉了两位,另一条路径只有一条。那公平吗?0x31680 作为一个整体

110001011010000000
110001011 010000000 with discarded bits on the right

因此,以这种方式看,01 或 010 或 0100 不到一半,不超过 3 或 2 的四舍五入 1.33 不会以 10 为基数四舍五入到 1.4。

但是 0x20F00

100000111100000000
100000111 100000000

就在中间点 1/10, 10/100, 100/1000 是一半。

那应该是0x108吗?

0x180*0x108 = 0x18C00
0x18C
0x18C != 0x18B

所以以这种方式查看舍入,它仍然不匹配排序产生了影响。

也许你认为我做错了舍入,这是公平的,如果是这样,这是否会使所有可能的整数模式都起作用?假设 int 是 32 位,double 是 IEEE754 和 52 位尾数,我们将溢出它并且必须砍掉位,因此会发生斩波和舍入,排序重要吗?

于 2016-12-28T04:57:55.110 回答
2

C 是一种非常可移植的语言,适用于许多平台——各种处理器和计算机。5 次相等性测试的结果因平台而异。让我们看看 C 规范。


int x = random();就像对以下每个测试说:“每个测试的结果如何int?”。让我们仔细看看。

random()不是标准 C 库函数。名称“随机”暗示函数结果是整个int范围:[INT_MIN...INT_MAX][0...INT_MAX]。选择任何一个。

==================================================== ====================

int
C 指定的最小范围int是 -(2 15 -1) 到 -(2 15 -1) 或 -32,767 到 32,767。
通常,当然不是全部,平台使用范围 -(2 31 ) 到 -(2 31 -1) 或 -2,147,483,648 到 2,147,483,647。
有些使用范围 -(2 63 ) 到 (2 63 -1) 或大约-9*10 18到 9*10 18
C 没有指定范围的最大大小。

double
C 指定的最小范围double是 -10 37到 10 37
通常,当然不是全部,平台使用大约 -1.8*10 308到 1.8*10 308的范围。
C 没有指定范围的最大大小。

double是浮点数。它表示达到一定精度的数字。范围的大整数并非都可以表示。

C 指定可精确表示的整数的最小范围是 -10 9到 10 9。 通常,这个可精确表示的整数范围约为 -9*10 15到 9*10 15 double

IEEE 标签提示使用了binary64。我们可以这样做,但是 FP 选择不会对结果有太大的改变,因为宽度int范围与double' 的完全可表示的整数是关键问题。任何一个都可能比另一个更宽。 int通常较窄。


使用double dx = (double) x,在许多平台上的转换是精确的。在范围比 a的可精确表示的整数int范围更宽的稀有平台上,这并不准确。double

使用(float) x时,在 范围内的许多平台上,转换通常是不精确的int,因为float范围不超过double并且通常更窄。

int当到float或发生不精确的转换double时,结果通常是最接近的可能结果。


(float) x == (float) dx在浮点范围完全可表示的整数和不同且float两者都是的子范围的平台上可能为假。很可能会失败。对于 binary64 和 32 位(OP 的情况?),这种相等性应该始终为真。doubleintINT_MAXint

dx - dy == (double) (x-y)由于与上述相同的原因和另外一个原因,可能为假:在 C 中,int 溢出未定义,因此INT_MIN - 1未定义。任何结果都是可能的。

(dx + dy) + dz == dx + (dy + dz)int当范围大于double(不常见)时可能为假,并且dx是一个舍入值,其下一个较高的可表示值大于 2。 (dx + 1)可以四舍五入到dx,但dx + 2可以表示。所以(dx + 1) + 1 != dx + (1 + 1)

(dx * dy) * dz == dx * (dy * dz)当子乘积和/或乘积不精确时很容易出错 - 通常当数学乘积超过 的可精确表示的整数double

dx / dx == dz / dz可以简单地为假,dx并且0未定义除以 0。

于 2016-12-28T10:50:58.353 回答
-1

由于您只是将 x,y,z 转换为它们的 double 形式,因此 dx,dy,dz 只不过是 x,y,z 在小数点后有一些零。根据编译器的不同,可能会添加一些其他数字而不是零。在这种情况下,平等将不成立。例如:x=5 和 dx=5.000000 但可能会发生 dx 被设为 5.000001,因为它仍然几乎等于 x。我还建议不要讨论浮点数相等这样毫无意义的话题。

于 2016-12-28T04:20:14.507 回答