# 计算机组成原理 第3次作业 (March 15, 2023)

1. **通过编程验证 **`**float**`** 和 **`**double**`** 类型的精度（即十进制有效位的位数），在实验报告中给出结果及解释；编程计算 **`**-8.0/0**`** 、 **`**sqrt(-4,0)**`** 的运算结果，并对结果给予解释。**

```java
static void testPrecision() {
    float f = 123456789.123456789f;
    double d = 123456789.123456789;
    System.out.println("float f = " + f);
    System.out.println("double d = " + d);
}
```

运行结果：

```java
float f = 1.2345679E8
double d = 1.2345678912345679E8
```

在上述代码中，我们分别定义了一个 `float` 类型变量 `f` 和一个 `double` 类型变量 `d`。`f` 只保留了小数点后的前 7 位有效数字，而 `d` 则保留了小数点后的前 15 到 16 位有效数字。

`float` 类型使用 32 位（4 字节）来存储浮点数，其中 1 位用于表示符号位，8 位用于表示指数，剩余的 23 位用于表示尾数。这意味着 `float` 类型最多可以表示 $ 2^{23} $ 个不同的数，也就是大约 $ 8 $ 位的有效数字。因此，`float` 类型只能精确表示到第 7 位有效数字。

`double` 类型使用 64 位（8 字节）来存储浮点数，其中 1 位用于表示符号位，11 位用于表示指数，剩余的 52 位用于表示尾数。这意味着 `double` 类型最多可以表示 $ 2^{52} $ 个不同的数，也就是大约 $ 16 $ 位的有效数字。因此，`double` 类型可以精确表示到第 15 到 16 位有效数字。

```java
static void testCaculation() {
    double result1 = -8.0 / 0;
    double result2 = Math.sqrt(-4.0);
    System.out.println("-8.0/0 = " + result1);
    System.out.println("sqrt(-4,0) = " + result2);
}
```

运行结果：

```java
-8.0/0 = -Infinity
sqrt(-4,0) = NaN
```

在计算机中，当执行除以 0 的操作时，根据 IEEE 754 浮点数标准，结果会被定义为正无穷大、负无穷大或 NaN（不是一个数字）中的一个。

当被除数为正数时，除以 0 的结果为正无穷大（`+Infinity`）。当被除数为负数时，除以 0 的结果为负无穷大（`-Infinity`）。当被除数为 0 时，除以 0 的结果为 NaN。

因此，对于表达式 `-8.0/0`，被除数为负数 `-8.0`，因此结果为负无穷大（`-Infinity`）。

对于表达式 `sqrt(-4.0)`，它是求负数的平方根，而在实数域中，负数的平方根是一个虚数，不能用实数来表示。在计算机中，`sqrt()` 函数不支持计算负数的平方根，会返回 NaN（不是一个数字），表示计算无法完成。

2. **令 **`**float a = (100 + 1.0/3) - 100**`** ， **`**float b = 1.0 / 3**`** ；请回答逻辑表达式 **`**a==b**`** 的取值是什么？如果变成 **`**double a = (100 + 1.0/3) - 100**`** ， **`**double b = 1.0 / 3**`** ， **`**a==b**`** 的取值又是什么？通过程序计算和检验，对结果进行说明。**

```java
static void testEquality() {
    float aFloat = (100 + 1.0f / 3) - 100;
    float bFloat = 1.0f / 3;
    double aDouble = (100 + 1.0 / 3) - 100;
    double bDouble = 1.0 / 3;
    System.out.println("float a == b: " + (aFloat == bFloat));
    System.out.println("double a == b: " + (aDouble == bDouble));
}
```

运行结果：

```java
float a == b: false
double a == b: false
```

浮点数在计算机内部以二进制形式表示时，会存在精度损失。当我们使用等于操作符（==）比较两个浮点数时，它会将它们的二进制表示形式进行比较。由于精度损失，这样的比较可能会产生不准确的结果。

3.  **类型转换和移位操作。**

编程以实现以下各种操作：

+ 给定一个short型数据-12345，分别转为int、unsigned short、unsigned int、float类型的数据
+ 给定一个int型数据2147483647，分别转为short、unsigned short、unsigned int、float类型的数据
+ 给定一个float型数据123456.789e5，转换成double型数据
+ 给定一个double型数据123456.789e5，转换成float型数据
+ 按short和unsigned short类型分别对-12345进行左移2位和右移2位操作

要求分别用十进制和十六进制形式打印输出以上各种操作的结果，并根据实验结果，回答下列问题： 

+ float型数据是否总能转换成等值的double型数据？
+ 长数被截断成短数后可能发生什么现象？为什么？
+ C语言中移位操作规则与操作对象的数据类型有关吗？
+ 左移2位和右移2位操作分别相当于扩大和缩小几倍？

```java
static void testTypeConversion1() {
    short s = -12345;
    int i = s;
    int us = s & 0xffff;
    long ui = s & 0xffffL;
    float f = s;
    System.out.println("int: " + i + " (0x" + Integer.toHexString(i) + ")");
    System.out.println("unsigned short: " + us + " (0x" + Integer.toHexString(us) + ")");
    System.out.println("unsigned int: " + ui + " (0x" + Long.toHexString(ui) + ")");
    System.out.println("float: " + f + " (" + Float.toHexString(f) + ")");
}

static void testTypeConversion2() {
    int i = 2147483647;
    short s = (short) i;
    int us = i & 0xffff;
    long ui = i & 0xffffffffL;
    float f = i;
    System.out.println("short: " + s + " (0x" + Integer.toHexString(s) + ")");
    System.out.println("unsigned short: " + us + " (0x" + Integer.toHexString(us) + ")");
    System.out.println("unsigned int: " + ui + " (0x" + Long.toHexString(ui) + ")");
    System.out.println("float: " + f + " (" + Float.toHexString(f) + ")");
}

static void testTypeConversion3() {
    float f = 123456.789e5f;
    double d = f;
    System.out.println("double: " + d + " (" + Double.toHexString(d) + ")");
}

static void testTypeConversion4() {
    double d = 123456.789e5;
    float f = (float) d;
    System.out.println("float: " + f + " (" + Float.toHexString(f) + ")");
}

static void testTypeConversion5() {
    short s = -12345;
    short s2 = (short) (s << 2);
    int us = s & 0xffff;
    int us2 = us << 2;
    System.out.println("short: " + s2 + " (0x" + Integer.toHexString(s2) + ")");
    System.out.println("unsigned short: " + us2 + " (0x" + Integer.toHexString(us2) + ")");
}
```

运行结果：

```java
int: -12345 (0xffffcfc7)
unsigned short: 53191 (0xcfc7)
unsigned int: 53191 (0xcfc7)
float: -12345.0 (-0x1.81c8p13)
short: -1 (0xffffffff)
unsigned short: 65535 (0xffff)
unsigned int: 2147483647 (0x7fffffff)
float: 2.1474836E9 (0x1.0p31)
double: 1.2345678848E10 (0x1.6fee0ep33)
float: 1.2345679E10 (0x1.6fee0ep33)
short: 16156 (0x3f1c)
unsigned short: 212764 (0x33f1c)
```

---

由于没有指定语言，所以一开始用了Java，但是Java中没有unsigned的数据类型，只好手动模拟，于是又用C语言实现了一遍：

**给定一个short型数据-12345，分别转为int、unsigned short、unsigned int、float类型的数据**

```c
#include <stdio.h>

int main() {
    short s = -12345;
    int i = s;
    unsigned short us = s;
    unsigned int ui = s;
    float f = s;
    printf("s = %d, %#x\n", s, s);
    printf("i = %d, %#x\n", i, i);
    printf("us = %u, %#x\n", us, us);
    printf("ui = %u, %#x\n", ui, ui);
    printf("f = %f\n", f);
    return 0;
}
```

输出结果为：

```c
s = -12345, 0xffffcfc7
i = -12345, 0xffffcfc7
us = 53191, 0xcfc7
ui = 4294954951, 0xffffcfc7
f = -12345.000000
```

**给定一个int型数据2147483647，分别转为short、unsigned short、unsigned int、float类型的数据**

```c
#include <stdio.h>

int main() {
    int i = 2147483647;
    short s = i;
    unsigned short us = i;
    unsigned int ui = i;
    float f = i;
    printf("i = %d, %#x\n", i, i);
    printf("s = %d, %#x\n", s, s);
    printf("us = %u, %#x\n", us, us);
    printf("ui = %u, %#x\n", ui, ui);
    printf("f = %f\n", f);
    return 0;
}
```

输出结果为：

```c
i = 2147483647, 0x7fffffff
s = -1, 0xffffffff
us = 65535, 0xffff
ui = 2147483647, 0x7fffffff
f = 2147483648.000000
```

**给定一个float型数据123456.789e5，转换成double型数据**

```c
#include <stdio.h>

int main() {
    float f = 123456.789e5;
    double d = f;
    printf("f = %f, %a\n", f, f);
    printf("d = %f, %a\n", d, d);
    return 0;
}
```

输出结果为：

```c
f = 12345678848.000000, 0x1.6fee0ep+33
    d = 12345678848.000000, 0x1.6fee0ep+33
```

**给定一个double型数据123456.789e5，转换成float型数据**

```c
#include <stdio.h>

int main() {
    double d = 123456.789e5;
    float f = d;
    printf("d = %f, %a\n", d, d);
    printf("f = %f, %a\n", f, f);
    return 0;
}
```

输出结果为：

```c
d = 12345678900.000000, 0x1.6fee0e1ap+33
    f = 12345678848.000000, 0x1.6fee0ep+33
```

**按short和unsigned short类型分别对-12345进行左移2位和右移2位操作**

```c
#include <stdio.h>

int main() {
    short s = -12345;
    unsigned short us = s;
    printf("s = %d, %#x\n", s, s);
    printf("s左移2位: %d, %#x\n", s << 2, s << 2);
    printf("s右移2位: %d, %#x\n", s >> 2, s >> 2);
    printf("us = %u, %#x\n", us, us);
    printf("us左移2位: %u, %#x\n", us << 2, us << 2);
    printf("us右移2位: %u, %#x\n", us >> 2, us >> 2);
    return 0;
}
```

输出结果为：

```c
s = -12345, 0xffffcfc7
s左移2位: -49380, 0xffff3f1c
s右移2位: -3087, 0xfffff3f1
us = 53191, 0xcfc7
us左移2位: 212764, 0x33f1c
us右移2位: 13297, 0x33f1
```

**float型数据是否总能转换成等值的double型数据？**

是的，float类型的数据可以转换为double类型的数据，但是可能会精度丢失。

**长数被截断成短数后可能发生什么现象？为什么？**

当一个长数被截断成短数后，如果截断的部分超过了短数所能表示的范围，就会发生数据溢出，即结果不再准确。这是因为长数和短数的字节数不一样，长数通常占用4个字节或8个字节，而短数通常只占用2个字节，因此短数能够表示的数值范围比长数小。

**C语言中移位操作规则与操作对象的数据类型有关吗？**

是的，C语言中移位操作的规则与操作对象的数据类型有关。对于有符号数，右移操作会保留符号位（即最高位），而左移操作不会。对于无符号数，左右移操作都不会保留符号位。另外，移位操作会将超出数据类型位数的位数截断。

**左移2位和右移2位操作分别相当于扩大和缩小几倍？**

左移n位相当于将数值乘以2的n次方，右移n位相当于将数值除以2的n次方。因此，左移2位相当于将数值扩大4倍，右移2位相当于将数值缩小4倍。