深入理解计算机系统第三版 家庭作业 第2章

2.55 在你能够访问的不同机器上使用show_bytes 编译并运行实例代码。确定这些机器使用的字节顺序。

#include <stdio.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len);
void show_int(int x);
void show_short(short x);
void show_float(float x);
void show_double(double x);
void show_pointer(void *x);

int main()
{
    int ival = 0x12345678;
    short sval = (short) ival;
    float fval = (float) ival;
    double dval = (double) fval;
    int *pval = &ival;
    show_short(sval);
    show_int(ival);
    show_float(fval);
    show_double(dval);
    show_pointer(pval);

    return 0;
}

void show_bytes(byte_pointer start, size_t len)
{
    size_t i;
    for (i = 0; i < len; ++i)
        printf(" %.2x", start[i]);
    printf("\n");
}

void show_short(short x)
{
    show_bytes((byte_pointer) &x, sizeof(short));
}

void show_int(int x)
{
    show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x)
{
    show_bytes((byte_pointer) &x, sizeof(float));
}

void show_double(double x)
{
    show_bytes((byte_pointer) &x, sizeof(double));
}

void show_pointer(void *x)
{
    show_bytes((byte_pointer) &x, sizeof(void *));
}

 
2.58 编写过程 is_little_endian, 当在小端机器上编译和运行的时候返回1,在打断机器上运行时返回0。这个程序应该可以运行在任何机器上,无论机器的字长是多少。

#include <stdio.h>

int is_little_endian(void);

int main()
{
    printf("%d\n",is_little_endian());
    return 0;
}

int is_little_endian(void)
{
    short x = 0x1234;
    char low, high;

    low = *((char *) &x);
    high = *((char *) &x + 1);

    if (low == 0x34)
        return 1;
    else
        return 0;
}

 
2.59 编写一个C表达式,生成一个字,由 x 的最低有效字节和 y 中剩下的字节组成。对于运算数x=0x89ABCDEF 和 y=76543210, 就得到0x765432EF。

#include <stdio.h>

int main()
{
    int x = 0x89ABCDEF;
    int y = 0x76543210;
    int c;

    c = (x & 0xFF) | (y & ~0xFF);
    printf("0x%X\n", c);

    return 0;
}

 
2.60 假设我们将一个w位字节中从0到w/8-1编号,写出下面C函数的代码,它会返回一个无符号值,其中参数x的字节i被替换成字节b:
unsigned replace_byte (unsigne x, int i, unsigned char b);
一下示例,说明了这个函数该如何工作:
replace_byte(0x12345678,2,0xAB) --> 0x12AB5678
replace_byte(0x12345678,0,0xAB) --> 0x123456AB

#include <stdio.h>

unsigned replace_byte(unsigned x, int i, unsigned char b);

int main()
{

    printf("0x%x\n", replace_byte(0x12345678, 2, 0xAB));
    printf("0x%x\n", replace_byte(0x12345678, 0, 0xAB));

    return 0;
}

unsigned replace_byte(unsigned x, int i, unsigned char b)
{
    unsigned result;
    unsigned mask1, mask2;

    mask1 = (unsigned)0xFF << (i << 3);
    mask2 = (unsigned) b << (i << 3);
    
    result = (x & (~mask1)) | mask2;
    return result;
}

 
2.61 编写一个C表达式,在下列描述的条件下产生1,而在其他条件下得到0。假设x为int类型。
A. x的任何位都等于1
B. x的任何位都等于0
C. x的最低有效字节中的位等于1
D. x的最高有效字节中的位等于0

#include <stdio.h>

int main()
{
    int x, A, B, C, D;
    
    A = !(~x);
    B = !x;
    C = !(~(~0xFF | x));
    D = !(x >> ((sizeof(int) - 1) << 3));
    return 0;
}

 
2.62 编写一个函数 int_shifts_are_arithmetic(), 对于int类型数使用算数右移的机器上这个函数生成1, 其他情况下生成0.

#include <stdio.h>

int int_shifts_are_arithmetic(void);

int main()
{
    
    printf("%d\n", int_shifts_are_arithmetic());
    return 0;
}

int int_shifts_are_arithmetic(void)
{
    int x = ~0;
    int y = x >> ((sizeof(int) - 1) << 3);
    return !(x^y);
}

2.63 将下面C函数补充完整。函数 srl 用算术右移(其值由xsra给出)来完成逻辑右移, 后面的其他操作不包括右移或者除法。 函数sra用逻辑右移来完成算术右移,后面不包括右移或者除法。

#include <stdio.h>

unsigned srl(unsigned x, int k);
int sra(int x, int k);

int main()
{
    unsigned x = 0xFFFFFFFF;
    printf("0x%x srl is 0x%x\n", x, srl(x, 8));    
    printf("0x%x sra is 0x%x\n", x, sra(x, 8));    
    return 0;
}

unsigned srl(unsigned x, int k)
{
    unsigned xsra = (int) x >> k;
    int restbit = (sizeof(int) << 3) - k;
    unsigned mask = ~((~0) << restbit); 
    return xsra & mask;
}

int sra(int x, int k)
{
    int xsrl = (unsigned) x >> k;
    int restbit = (sizeof(int) << 3) - k;
    int msb = !(!((1 << ((sizeof(int) << 3 ) - 1)) & x));
    int mask = ~((~0) + msb) << restbit;
    return xsrl | mask;
}

2.64 写出代码实现如下函数:
  /* Return 1 when any odd bit of x equals 1; 0 otherwise. Assume w=32 */
  int any_odd_one(unsigned x)

#include <stdio.h>


int main()
{
    return 0;
}

int any_odd_one(unsigned x)
{
    unsigned mask = 0xAAAAAAAA;
    return !(!(x & mask));
}

2.65 写出代码实现如下函数:
  /* Return 1 when x contains an odd number of 1s; 0 otherwise.  Assume w=32 */
  int odd_ones(unsigned x)

#include <stdio.h>

int odd_one(unsigned x);

int main()
{
    printf("%d\n", odd_one(0xFEFFFFFF));
    return 0;
}

int odd_one(unsigned x)
{
    x ^= x >> 16;
    x ^= x >> 8;
    x ^= x >> 4;
    x ^= x >> 2;
    x ^= x >> 1;
    x &= 0x01;
    return x;
}

2.66 写出代码实现如下函数:
  /* Generate mask indicating leftmost 1 in x. Assume w=32
   * For example, 0xFF00 -> 0x8000, and 0x6600 -> 0x4000.
   * If x=0, then return 0.
   */
  int leftmost_one(unsigned x);

#include <stdio.h>

int leftmost_one(unsigned x);

int main()
{
    printf("0x%x\n",leftmost_one(0xFF00));
    printf("0x%x\n",leftmost_one(0x6600));
    printf("0x%x\n",leftmost_one(0));
    return 0;
}

int leftmost_one(unsigned x)
{
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;

    return (x >> 1) + (x && 1);
}

2.67 给你一个任务,编写一个过程int_size_is_32(), 挡在一个int是32位的机器上运行时,该程序产生1,而其他情况下则生成0。 不允许使用sizeof运算符。下面是开始时的尝试。
  /* The following code does not run properly on some machines */
  int bad_int_size_is_32() {
    int set_msb = 1 << 31;
    int beyond_msb = 1 << 32;
    return set_msb && !beyong_msb; 
  }
  当在SUN SPARC这样的32位机器上编译并运行时候,这个过程返回的却是0。编译器给了一个提示: Warning: left shift count >= width of type
  A. 我的代码在哪个方面没有遵循C语言标准。
    答: 查阅手册发现, 移位超过最大位数的时候,结果是不确定的。
  B. 修改代码,使得它在int至少为32位的任何机器上都能正确运行。
    答: 查看代码。
  C.修改代码, 使得它在int至少为16位的任何机器上都能正确运行。
    答: 查看代码。

#include <stdio.h>

int int_size_is_32(void);
int int_size_is_32_for_16bit(void);

int main()
{
    printf("%d\n", int_size_is_32());
    printf("%d\n", int_size_is_32_for_16bit());
    return 0;
}

int int_size_is_32(void)
{
    int set_msb = 1 << 31;
    int beyond_msb = set_msb << 1;

    return set_msb && !beyond_msb;
}

int int_size_is_32_for_16bit(void)
{
    int set_msb = 1 << 15 << 15 << 1;
    int beyond_msb = set_msb << 1;

    return set_msb && !beyond_msb;
}

 
2.68 写出具有如下原型的函数的代码:
  /*
   * Mask with least signficant n bits set to 1
   * Example: n = 6 -> 0x3F, n = 17 -> 0x1FFFF
   * Assume 1 <= n <= w
   */
  int lower_one_mask(int n)

#include <stdio.h>

int lower_one_mask(int n);

int main()
{
    printf("0x%x\n", lower_one_mask(1));
    printf("0x%x\n", lower_one_mask(32));
    return 0;
}

int lower_one_mask(int n)
{
    int mask = 1 << (n - 1) << 1;
    return mask - 1;
}

 
2.69 写出具有如下原型的函数的代码:
  /*
   * Do rotating left shift. Assume 0 <= n < w
   * Example when x = 0x12345678 and w = 32;
   * n = 4 -> 0x2345671, n=20 -> 0x67812345
   */
  unsigned rotate_left(unsigned x, int n);

#include <stdio.h>

unsigned rotate_left(unsigned x, int n);

int main()
{
    printf("0x%x\n", rotate_left(0x12345678, 0));
    printf("0x%x\n", rotate_left(0x12345678, 20));
    return 0;
}

unsigned rotate_left(unsigned x, int n)
{
    int w = sizeof(int) << 3;
    unsigned mask1 = x >> ((w - n) & 0x1F);
    unsigned mask2 = x << n;
    return mask1 | mask2;
}

2.70 写出具有如下原型的函数的代码:
  /*
   * Return 1 when x can be represented as an n-bit, 2's-complement
   * number; 0 otherwise
   * Assume 1 <=n <=w
   */

#include <stdio.h>

int fits_bits(int x, int n);

int main()
{
    printf("%d\n", fits_bits(255, 8));
    return 0;
}

int fits_bits(int x, int n)
{
    int mask = (1 << (n - 1) << 1) - 1;
    return x == (x & mask);
}

2.71 你刚刚开始在一家公司工作,他们要实现一组过程来操作一个数据结构,要将4个有符号字节封装在一个32位unsigned。 一个字中的字节从0(最低有效字节)编号到3(最高有效字节)。分配给你的任务是 为一个使用补码运算和算数右移的机器编写一个具有如下原型的函数:
  /* Declaration of data type where 4 bytes are packed into an unsigned */
  typedef unsigned packed_t;
  /*Extract byte from the word, Return as signed integer */
  int xbytes(packed_t word, int bytenum)
  也就是说,函数会抽取指定字节,再把它符号扩展为一个32位int
  你的前任因为水平不高被解雇了,编写了下面的代码:
  /*Fail attempt at xbyte*/
  int xbyte(packed_t word, int bytenum)
  {
    return (word >> (bytenum <<3)) & 0xFF;
  }
  A 这段代码错在哪里
    答: 如果该字节为负数,按照这样读取是错误的。
  B 给出函数的正确实现,只能使用左右移位和减法
    答: 查看代码。

#include <stdio.h>

typedef unsigned packed_t;

int xbyte(packed_t word, int bytenum);
int xbyte_bug(packed_t word, int bytenum);

int main()
{
    unsigned con = 0xFFFEFDFC;
    printf("%d\n", xbyte(con, 0));
    printf("%d\n", xbyte(con, 1));
    printf("%d\n", xbyte(con, 2));
    printf("%d\n", xbyte(con, 3));
    printf("%d\n", xbyte_bug(con, 0));
    printf("%d\n", xbyte_bug(con, 1));
    printf("%d\n", xbyte_bug(con, 2));
    printf("%d\n", xbyte_bug(con, 3));
    return 0;
}

int xbyte(packed_t word, int bytenum)
{
    int left_shift = (sizeof(int) - 1 - bytenum) << 3;
    int right_shift = (sizeof(int) - 1) << 3;

    return (int)word << left_shift >> right_shift;
}

int xbyte_bug(packed_t word, int bytenum)
{
    return (word >> (bytenum << 3)) & 0xFF;
}

2.72 给你一个任务,写一个函数,将整数val复制到缓冲区buf中,但是只有缓冲区有足够可用的空间时,才执行复制。你写的代码如下:
  /*Copy integer into buffer if space is available*/
  /*WARRING: The following code is buggy */
  void copy_int(int val, void * buf, int maxbytes)
  {

    if (maxbytes -sizeof(val) >= 0)
      memcpy(buf, (void *) &val, sizeof(val));
  }
  这段代码使用了库函数memcpy。虽然在这里使用这个函数有些刻意,但是它说明了复制较大数据结构的常用方法。
  你仔细调试了这段代码后发现,哪怕maxbytes很小的时候,它也能够把值复制到缓冲区中。
  A 解释为什么代码中的条件测试总是成功。 提示: 运算符sizeof返回的类型为size_t
    答: maxbytes是int类型, sizeof是unsigned类型,两者计算前,maxbytes会转化成unsigned类型,所以运算结果总是大于0,即总是成功。
  B 你该如何重写这个条件测试,,使之工作正确。
    答: 查看代码

#include <stdio.h>
#include <string.h>

#define MAXBYTES 3

char temp1[MAXBYTES];
char buffer[MAXBYTES];
char temp2[MAXBYTES];

void copy_int_bug(int val, void *buf, int maxbytes)
{
    if (maxbytes - sizeof(val) >= 0)
        memcpy(buf, (void *) &val, sizeof(val));
}

void copy_int(int val, void *buf, int maxbytes)
{
    if (maxbytes - (int)sizeof(val) >= 0)
        memcpy(buf, (void *) &val, sizeof(val));
}

int main()
{
    int i;
    copy_int(0x12345678, buffer, MAXBYTES);
    for(i = 0; i < MAXBYTES; ++i)
        printf("%x\n", temp1[i]);
    for(i = 0; i < MAXBYTES; ++i)
        printf("%x\n", buffer[i]);
    for(i = 0; i < MAXBYTES; ++i)
        printf("%x\n", temp2[i]);
    return 0;
}

 
2.73 写出具有如下原型的函数的代码:
  /*Addition that saturates to TMin or TMAax */
  int saturating_add(int x, int y)
  同正常补码加法溢出的方式不同,当正溢出时, 饱和加法返回,负溢出时, 返回。饱和运算常常用于执行数字信号的处理函数中。

#include <stdio.h>

int saturating_add(int x, int y);

int main()
{
    printf("%d\n", saturating_add(123, 456));
    printf("%d\n", saturating_add(2147483647, 5));
    printf("%d\n", saturating_add(-2147483648, -6));
    return 0;
}

int saturating_add(int x, int y)
{
    int sum, pos_over, neg_over;
    int TMIN = 1 << ((sizeof(int) << 3) - 1);
    int TMAX = TMIN - 1;

    sum = x + y;
    pos_over = !(x & TMIN) && !(y & TMIN) && (sum & TMIN);
    neg_over = (x & TMIN) && (y & TMIN) && !(sum & TMIN);

    (pos_over && (sum = TMAX)) || (neg_over && (sum = TMIN));

    return sum;
}

 
2.74 写出具有如下原型的函数的代码:
  /*Determine whether arguments can be subtracted without overflow */
  int tsub_ok(int x, int y)
  如果计算不溢出出,这个函数就返回1。

注意:  当 y 为 TMIN, 无论是 x 是正还是负,运算都溢出。 因此要单独列出。

#include<stdio.h>

int tsub_ok(int x, int y);

int main()
{
    printf("%d\n", tsub_ok(-1,0x80000000));    
    return 0;
}

int tsub_ok(int x, int y)
{
    int pos_over, neg_over;
    int TMIN = 1 << ((sizeof(int) << 3) - 1);
    int sub = x - y;

    pos_over = !(x & TMIN) && (y & TMIN) && (sub & TMIN);
    neg_over = (x & TMIN) && !(y & TMIN) && !(sub & TMIN);

    return !(y == TMIN) && !pos_over && !neg_over;
}

 
2.75 假设我们想要计算x * y 的完整位表示, 其中x和y都是无符号,并且运行在类型unsigned是w位的机器上,乘积的低w位能够用表达式计算x*y, 所以, 我们只需要一个具有如下原型的函数:
unsigned unsigned_high_prod(unsigned x, unsigned y);
这个函数计算无符号变量x*y的高w位。
  我们使用一个具有下面原型的库函数:
int signed_high_prod(int x, int y)
它计算在和采用补码形式的情况下,x*y的高w位。编写代码调用这个过程,以实现无符号数为参数的函数。验证你的解答的正确性。
  提示: 看看等式(2.18)的推导, 有符号乘积 x * y 和无符号乘积 x' * y' 之间的关系。

注意: 等式2.18的两边去掉求模运算,就是完整的有符号和无符号乘积转化。
  可以参考下面页面中3位二进制无符号和有符号进行转化的例子。 可以加对公式的理解。
  https://stackoverflow.com/questions/37005446/get-the-32-high-bits-of-multiplying-two-unsigned-integers-hw

#include <stdio.h>
#include <inttypes.h>

int signed_high_prod(int x, int y);
unsigned unsigned_high_prod(unsigned x, unsigned y);
unsigned unsigned_high_prod_ref(unsigned x, unsigned y);

int main()
{
    printf("%x\n", unsigned_high_prod(0x12345678, 0x12345678));
    printf("%x\n", unsigned_high_prod_ref(0x12345678, 0x12345678));
    return 0;
}

int signed_high_prod(int x, int y)
{
    int64_t mul = (int64_t) x * y;
    return mul >> 32;
}

unsigned unsigned_high_prod(unsigned x, unsigned y)
{
    int w = sizeof(int) << 3;
    uint64_t msb_x = (uint64_t)(x >> w - 1);
    uint64_t msb_y = (uint64_t)(y >> w - 1);
    uint64_t prod_high = (uint64_t)signed_high_prod((int)x, (int)y) << w;
    uint64_t prod_low = (int)x * (int)y;
    uint64_t prod = prod_high + prod_low + ((msb_x * y + msb_y * x) << w) + ((msb_x * msb_y) << (2 * w - 1) << 1);
    return prod >> w;
}

unsigned unsigned_high_prod_ref(unsigned x, unsigned y)
{
    uint64_t mul = (uint64_t) x * y;
    return mul >> 32;
}

 
2.76 库函数calloc有如下声明:
void * calloc(size_t nmemb, size_t size);
根据库文档: “ 函数 calloc 为一个数组分配内存, 该数组有 nmemb  个元素, 每个元素为size字节。内存设置为 0。 如果 nmemb 或 size 为 0,则 calloc 返回 NULL。”
  编写calloc的实现,通过调用 malloc执行分配, 调用 memset 将内存设置为0。你的代码应该没有任何算术溢出引起的漏洞, 且无论数据类型 size_t 用多少位表示,代码都应该正常工作。
  作为参考,malloc 和 memset 声明如下:
void *malloc(size_t size)
void *memset(void * s, int c, size_t n);

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void *calloc(size_t nmemb, size_t size);

int main()
{
    printf("%p\n", calloc(0,16));    
    printf("%p\n", calloc(1,0));    
    printf("%p\n", calloc(1048577, 4096));    
    printf("%p\n", calloc(1024, 4));    
    return 0;
}

void *calloc(size_t nmemb, size_t size)
{
    void *p;

    if (nmemb == 0 || size == 0)
        return NULL;

    size_t request_size = nmemb * size;

    if (nmemb != request_size / size)
        return NULL;
    else {
        p = malloc(request_size);
        if (p == NULL)
            return NULL;
        memset(p, 0, request_size);
    }
    return p;
}

 
2.77 假设我们有一个任务: 生成一段代码, 将整数变量乘以不同的常数因子K。 为了提高效率, 我们只想使用+ - << 运算。 对于下面K值, 写出执行乘法运算的C表达式, 每个表达式中最多使用3个运算。
  A  K=17
  B  K=-7
  C  K=60
  D  K=-112

#include <stdio.h>

int main()
{
    int x = 1;
    int A = (x << 4) + x;
    int B = x - (x << 3);
    int C = (x << 6) - (x << 2);
    int D = (x << 4) - (x << 7);
    printf("%d\n", A);
    printf("%d\n", B);
    printf("%d\n", C);
    printf("%d\n", D);
}


2.78 写出具有如下原型的函数的代码:
/* Devide by power of 2. Assume 0 <= k <= w-1*/
int devide_power2(int x, int k)

#include <stdio.h>

int devide_power2(int x, int k);

int main()
{
    printf("%d\n", devide_power2(-12340, 4));
    return 0;
}

int devide_power2(int x, int k)
{
    int w = sizeof(int) << 3;
    int bias = (x >> w - 1) & ((1 << k) - 1);
    return (x + bias) >> k;
}


2.79 写出函数mul3dev4的代码, 对于整数参数x, 计算3*x/4, 但是要遵循位级整数编码规则。你的代码计算3*x也会产生溢出。

#include <stdio.h>

int devide_power2(int x, int k);
int mul3dev4(int x);

int main()
{
    printf("%d\n", mul3dev4(-12341));
    return 0;
}

int devide_power2(int x, int k)
{
    int w = sizeof(int) << 3;    
    int bias = (x >> w - 1) & ((1 << k) - 1);
    
    return (x + bias) >> k;
}

int mul3dev4(int x)
{
    x += x << 1;
    return devide_power2(x, 2);
}

 
2.80 写出函数 threefourths 的代码, 对于证书参数x, 计算 3/4x的值,向0舍入。它不会溢出。 函数应该遵循位级别整数编码规则。
注意: 虽然大体方向是先右移再左移后就不会溢出,但是存在一个问题,尾部的小数字原本在左移后是可以进位的,但是在右移的过程中会直接舍去!

#include <stdio.h>

int threefourths_bug(int x);
int threefourths(int x);

int main()
{
    int i;
    printf("test positive numbers\n");
    for (i = 8; i <= 12; ++i)
        printf("%d\n", threefourths_bug(i));
    printf("---------------\n");
    for (i = 8; i <= 12; ++i)
        printf("%d\n", threefourths(i));
    printf("test negative numbers");
    for (i = -12; i <= -8; ++i)
        printf("%d\n", threefourths_bug(i));
    printf("---------------\n");
    for (i = -12; i <= -8; ++i)
        printf("%d\n", threefourths(i));
    return 0;
}

/*Some trailing digits will get cut before they multiply 3*/
int threefourths_bug(int x)
{
    int w = sizeof(int) << 3;
    int bias = (x >> w - 1) & ((1 << 2) - 1);
    int dev4 = (x + bias) >> 2;
    int mul3 = (dev4 << 1) + dev4;
    
    return mul3;
}
/*Separate the x into former 30 bits, and later 2 bits. Multiply the later part with 3 first before they get devided */
int threefourths(int x)
{
    int w = sizeof(int) << 3;
    int former_bits = x & (~0x3);
    int later_bits = x & 0x3;

    former_bits = former_bits >> 2;
    former_bits = (former_bits << 1) + former_bits;
    
    later_bits = (later_bits << 1) + later_bits;
    int bias = (x >> w - 1) & ((1 << 2) - 1);//whether add bias or not is depends on the msb of x, not on later_bits
    later_bits = (later_bits + bias) >> 2;

    return former_bits + later_bits;
}


 2.81 编写C表达式产生如下模式: 其中 a^k 表示符号 a 重复 k 次。 假设一个 w 位的数据类型。代码可以包含j 和k,他们分别表示 j 和 k 的值, 但是不嫩使用表示表示w的参数。
  A 1^(w-k)0^k
  B 0^(w-k-j)1^k0^j

#include <stdio.h>

void gen_bits(int k, int j);

int main()
{
    gen_bits(8,8);    
}

void gen_bits(int k, int j)
{
    int A = (~0) << k;
    int B = (~((~0) << (k + j))) & ((~0) << j);
    printf("%x\n", A);
    printf("%x\n", B);
}

2.82 对于下面的每个C表达式,是否总是成立。
/* Create some arbitrary values */
int x = random();
int y = random();
/*Convert to unsigned *
unsigned ux = (unsigned) x;
unsigned uy = (unsigned) y;
  
A (x < y) == (-x > -y)
B ((x + y) << 4) + y -x  = 17*y + 15*x
C ~x+~y+1==~(x+y)
D (ux - uy)== -(unsigned) (y-x)
E ((x>>2)<<2)<=x

答:
  A 错误。 例如x=-1, y=TMIN
  B 正确。推导如下: x << 4 + y << 4 + y - x = (x << 4 - x) + (y << 4 + y) = x * (16-1) + y *(16+1) = 15x+16y
  C 正确。 推导如下: ~x +1 + ~y +1 - 1 = -x + -y  - 1=  -(x + y) -1 = ~(x + y) + 1 - 1 = ~(x+y)
  D 正确。 推导如下: ux - uy = (uinsigned) (x - y) =  -(unsigned)(y - x)
  E 正确。 推导如下: (x >> 2) << 2 = x & (~0x3) , 末尾的2位丢失了,所以小于或等于原来的值。

2.83  一些数字的二进制表示是由形如0.yyy...无穷串组成的, 其中 y 是一个k位的序列。 例如1/3 的二进制数字表示是0.010101...(y=01), 而1/5的二进制表示是0.00110011...(y=0011)
A 设 Y = B2U(y), 也就是说, 这个数字具有二进制表示y。给出一个由Y和k 组成的公式表示这个无穷串的值。
B 对于下面的y值, 串的数值是多少?
a) 101
b) 0110
c) 010011
答:
A  设 n 为原来的数。 n << k = Y + n 即: n = Y/(2^k -1)
B
a)  Y 为 101 时, 即 Y =  5, k = 3 ,带入公式得:5/7
b)  Y 为 0110时, 即 Y = 6, k = 4,带入公式得: 2/5
c)  Y 为010011时, 即Y = 19, k = 6, 带入公式得: 19/63

 
2.84 填写下列程序的返回值, 这个程序测试它的第一个参数是否小于或者等于第二个参数。 假定函数f2u返回一个32位无符号数字,其位表示与它的浮点参数相同。 你可以假设两个参数都不是NaN, 两种0, +0,-0被认为是相等的。
int float_le(float x, float y) {
  unsigned ux = f2u(x);
  unsigned uy = f2u(y);

  /*Get the sign bits*/
  unsigned sx = ux >> 31;
  unsigned sy = uy >> 31;

  /*Give an expression using only ux, uy, sx, and sy*/
  return  ;
}

注意点: 需要思考一下可否直接返回ux < uy。 其实是不可以的,这两个无符号数是位级别的数字,如果连正负都没看,直接比较是没有意义的。因此要判断这两个数字的大小,需要分类讨论一下两者的正负,其次再比较大小。

#include <stdio.h>

unsigned f2u(float x);
int float_le(float x, float y);

int main()
{
    printf("-0 <= +0 %d\n", float_le(-0, +0));
    printf("+0 <= -0 %d\n", float_le(+0, -0));
    printf("0 <= 3 %d\n", float_le(0, 3));
    printf("-4 <= -1 %d\n", float_le(-4, -1));
    printf("-4 <= 4 %d\n", float_le(-4, 4));
    return 0;
}

int float_le(float x, float y)
{
    unsigned ux = f2u(x);
    unsigned uy = f2u(y);

    unsigned sx = ux >> 31;
    unsigned sy = uy >> 31;

    return (ux << 1 == 0 && uy << 1 == 0) ||
             (sx && !sy) ||
            (!sx && !sy && ux <= uy) ||
            ( sx && sy && ux >= uy);
}

unsigned f2u(float x)
{
    return *(unsigned *)&x;
}

 
2.85 给定一个浮点公式, 有 k 位指数和 n 位小数, 对于下列数,写出阶码 E, 尾数 M,小数 f 和值 V 的公式。另外, 请描述其位表示。
A 数7.0。
B 能够准确描述的最大奇整数。
C 最小的规格化数的倒数。

答:
bias = 2^(k-1) - 1  V = 2^E * M
(以下二进制中连续的3个小数点表示重复上一个位)
A
7.0 -> 111.00...  -> 1.1100... * 2^2
E = 2,  M = 1.1100...  f = 0.1100...  V = 2^2 * M
0  10...01  1100...

B
最大奇整数可以这样思考,: 1)n位无符号数的最大取值是111...111(n个1); 2) n个1(全1)的无符号数是个奇数,左移一位会变成偶数。
E = n, M = 1.111...(小数点后面n个1), f = 0.111...(小数点后面n个1), V = 2
0  n+bias  1111...

C
最小规格化数:
E = 1-bias, M = 1.00... f = 0.00... V = 2^(1-bias)
最小规格化的倒数:
V = 2^(bias - 1), E = bias-1, M = 1.00... f = 0.00...
0  11...101  00...


2.86 扩展精度为80位浮点数。其中,1个符号位, 15个阶码位, 1个单独整数位, 63个小数位。单独的整数位是小数位隐式的1显示化,即规格化数总为1, 非规格化数总为0. 填写下面数字。
最小正非规格化数
最小正规格化数
最大规格化数

答:
bias = 2^14 - 1
最小正非规格化数:  0  0...0  0  00...1  2^(1-bias) * 2^(-63) = 2^(1-bias-63)
最小正规格化数:   0  0...1  1  00...0  2^(1-bias)  * 1 = 2^(1-bias)
最大规格化数:    0  1...0  1  11...1  2^(bias) * (1 + 1 - 2^-63) = 2^(bias) * (2 - 2^-63)

2.87 16位半精度浮点为: 1个符号位, 5位阶码位, 10位小数位。 填写下面表格

  描述      Hex      M      E      V   
------------------------------------------------------------------------------------------------ 
  -0      0x8000      0      -14       -0    
最小>2的值     0x4001   1025/1024       1   1025/512   
  512      0x6000      1       24     512
最大非规格化数  0x3FFF   1023/1024      -14   1023/(2^24)
负无穷      0xFC00     -----       -----    负无穷
 0x3BB0     0x3BB0    123/64        -1    123/128  

2.88 两种浮点格式的转换
A 1个符号位, 5个阶码位, 3个小数位
B 1个符号位, 4个阶码位, 4个小数位
A到B的转换过程中, 要转化成最接近A的格式。 如果需要舍入,你要向正无穷舍入。
    格式A          格式B
  位    值      位        值
1 01110 001     -9/16  1 0110 0010     -9/16
0 10110 101
1 00111 110
0 00000 101
1 11011 000
0 11000 100


答:
思路: 先考虑阶码,能否直接转换, 使得小数隐含1位置保持不变。
   如果阶码小于下界, 向左移动小数点, 调节阶码回到范围内(注意:向左移动小数点后,小数隐含位置数就变成了0,即非规格化数)。 如果向左调还是不行,  就取一个最小的值(虽然数值很小,但是不是0, 并且题意中明确要取一个最接近的数值)。
  如果解码大于上届, 小数点不能向右移动,因为向右移动后,小数隐含位置的规则就被破坏了。 此时, 就要做无穷大处理。

    格式A          格式B
  位      值      位        值
1 01110 001  -9/16  1 0110 0010       -9/16
0 10110 101  208    0 1110 1010       208
1 00111 110 -7/1024    1 0000 0111      -7/1024
0 00000 101 13/(2^17) 0 0000 0001      1/(2^10)    (取一个最接近的数值,即一个最小正数) 
1 11011 000  -2^12  1 1110 1111      -31*(2^3)       (取一个最接近的数值,即一个最小负数)
0 11000 100  3*(2^8) 0 1111 0000      正无穷     (做无穷大处理)

2.89 判断下面等式是否总成立
/*Create some arbitrary values*/
int x = random();
int y = random();
int z = random();

/*Convert to double*/
double dx = (double)x;
double dy = (double)y;
double dz = (double)z;

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

答:
A总成立。
B 不总成立。 例如,当y=TMIN, x= TMAX
C 不总成立。 因为浮点数不满足交换律
D 不总成立。 因为浮点数不满足交换律
E 不总成立。 例如,x = 1, z = 0

2.90 编写2^x。 创建的结果要符合IEEE单精度表示。太小返回0, 太大返回正无穷。 u2f返回的浮点值和它的无符号参数有相同的位表示。

#include <stdio.h>
#include <math.h>

float fpwr2(int x);
float u2f(unsigned x);

int main()
{
    printf("%f\t%f\n", fpwr2(0), powf(2, 0));
    printf("%f\t%f\n", fpwr2(-150), powf(2, -150));
    printf("%f\t%f\n", fpwr2(-100), powf(2, -100));
    printf("%f\t%f\n", fpwr2(100), powf(2, 100));
    printf("%f\t%f\n", fpwr2(10000), powf(2, 10000));
    return 0;
}

float fpwr2(int x)
{
    unsigned exp, frac;
    unsigned u;

    if (x < -126 - 23) { // < 0 00000000 00000000000000000000001
        exp = 0;
        frac = 0;
    } else if (x < -126) {
        exp = 0;
        frac = 1 << (149 + x);
    } else if (x < 128) {
        exp = x + 127;
        frac = 0;
    } else {
        exp = 255;
        frac = 0;
    }

    u = exp << 23 | frac;
    return u2f(u);
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.91 223/71 < pi < 22/7, pi 的单精度浮点近似值的十六进制表示为0x40490FDB.
A 这个浮点值表示的二进制小数是多少?
B 22/7 的二进制小数表示什么?
C 这两个 pi 的近似值从哪位(相对与二进制小数点)开始不同?

答。
A
0 10000000 10010010000111111011011
B
22/7 小数部分为 1/7, 根据2.83公式得到 y = 001
11 . 001(001循环)
C
比较的AB中的两个小数的不同。 A 要先进行转化。
从第9位开始不同。

2.92 遵循位级别浮点编码规则, 实现具有如下原型的函数:
/* Computer -f. If f is NaN, then return f. */
float_bits float_negate(float_bits f);

实际测试,因为运行时间过长,只测试了一部分数据。暂时没发现问题。

#include <stdio.h>
#include <limits.h>

typedef unsigned float_bits;

float_bits float_negate(float_bits f);
float u2f(unsigned x);

int main()
{
    unsigned i;
    for (i = 0; i < UINT_MAX; ++i) {
        if (-u2f(1) != u2f(float_negate(1))) {
            printf("%u\n", i);
            break;
        }
    }

    return 0;
}

float_bits float_negate(float_bits f)
{
    unsigned sign = f >> 31;
    unsigned exp = f >> 23 & 0xFF;
    unsigned frac = f & 0x7FFFFF;

    if (exp == 0xFF && frac != 0)
        return f;
    else 
        return (~sign << 31) | (exp << 23) | frac;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.93 遵循位级浮点编码规则, 实现具有如下原型的函数:
/* Compute |f|. If f is NaN, then return f. */
float_bits float_absval(float_bits f);

#include <stdio.h>
#include <math.h>
#include <limits.h>

typedef unsigned float_bits;

float_bits float_absval(float_bits f);
float u2f(unsigned x);

int main()
{
    unsigned i = 0xFF000000;
    printf("%g\t%g\n", fabs(u2f(i)), u2f(float_absval(i)));

    return 0;
}

float_bits float_absval(float_bits f)
{
    unsigned sign = f >> 31;
    unsigned exp = f >> 23 & 0xFF;
    unsigned frac = f & 0x7FFFFF;

    if (exp == 0xFF && frac != 0)
        return f;
    else 
        return (0 << 31) | (exp << 23) | frac;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.94 遵循位级浮点编码规则, 实现具有如下原型的函数:
/*Compute 2*f. If f is NaN , then return f*/
float_bits float_twice(float_bits f)

#include <stdio.h>
#include <float.h>

typedef unsigned float_bits;

float_bits float_twice(float_bits f);
float u2f(unsigned x);

int main()
{
    printf("%g\n", 2*FLT_MAX);
    unsigned i = 0x00000001;
    printf("%g\t%g\n", 2*u2f(i), u2f(float_twice(i)));
    i = 0x7F7FFFFF;
    printf("%g\t%g\n", 2*u2f(i), u2f(float_twice(i)));
    i = 0x08000000;
    printf("%g\t%g\n", 2*u2f(i), u2f(float_twice(i)));
    i = 0xFFFFFFFF;
    printf("%g\t%g\n", 2*u2f(i), u2f(float_twice(i)));

    return 0;
}

float_bits float_twice(float_bits f)
{
    unsigned sign = f >> 31;
    unsigned exp = f >> 23 & 0xFF;
    unsigned frac = f & 0x7FFFFF;

    if (exp == 0xFF && frac != 0)
        return f;
    else {
        if (exp == 0) {// 非规格化数
            if (!(frac >> 22)) // *2仍然非规格化数
                frac <<= 1;
            else { // 非规格化数向规格化数转变
                exp = 1;
                frac = (frac << 1) & 0x7FFFFF;
            }
        } else { // 规格化数
            ++exp;
            if (exp == 255)// 阶码超出最大值,做无穷大处理
                frac = 0;
        }
    }
    return (sign << 31) | (exp << 23) | frac;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.95 遵循位级浮点编码规则, 实现具有如下原型的函数:
/*Compute 0.5*f. If f is NaN, then return f. */
float_bits float_half(float_bits);

#include <stdio.h>

typedef unsigned float_bits;

float_bits float_half(float_bits f);
float u2f(unsigned x);

int main()
{
    unsigned i = 0x00000001;
    printf("%g\t%g\n", 0.5*u2f(i), u2f(float_half(i)));
    i = 0x00800000;
    printf("%g\t%g\n", 0.5*u2f(i), u2f(float_half(i)));
    i = 0x08000000;
    printf("%g\t%g\n", 0.5*u2f(i), u2f(float_half(i)));
    i = 0xFFFFFFFF;
    printf("%g\t%g\n", 0.5*u2f(i), u2f(float_half(i)));

    return 0;
}

float_bits float_half(float_bits f)
{
    unsigned sign = f >> 31;
    unsigned exp = f >> 23 & 0xFF;
    unsigned frac = f & 0x7FFFFF;
    unsigned adition;
    adition = (frac & 0x3) == 0x3;//最后两位若是11移位后要舍入,这不是向偶数舍入!

    if (exp == 0xFF) //无穷大和NaN返回原来的值
        return f;
    else {
        if (exp == 0) { //非规格化数
            frac >>= 1;
            frac += adition;
        }else if (exp == 1) { // 规格化数变为非规格化数字
            exp = 0;
            frac >>= 1;
            frac += adition;
            frac |= 0x400000; // 小数点左边的1移到右边
        }else //规格化数
            exp -= 1;
    }
    return (sign << 31) | (exp << 23) | frac;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.96 遵循浮点位级编码规则, 实现具有如下原型的函数。
/* Computer (int)f. If coversion casuse overflow or f is NaN, return 0x80000000*/
int float_f2i(float_bits f);

#include <stdio.h>

typedef unsigned float_bits;

float_bits float_f2i(float_bits f);
float u2f(unsigned x);

int main()
{
    unsigned i = 0x00000001;
    printf("%d\t%d\n", (int)u2f(i), float_f2i(i));
    i = 0x43800000;
    printf("%d\t%d\n", (int)u2f(i), float_f2i(i));
    i = 0x4E800000;
    printf("%d\t%d\n", (int)u2f(i), float_f2i(i));
    i = 0xFFFFFFFF;
    printf("%d\t%d\n", (int)u2f(i), float_f2i(i));

    return 0;
}

float_bits float_f2i(float_bits f)
{
    unsigned sign = f >> 31;
    unsigned exp = f >> 23 & 0xFF;
    unsigned frac = f & 0x7FFFFF;
    unsigned bias = 127;
    unsigned num;
    unsigned E, M;

    if (exp >= 0 && exp < 0 + bias)
        num = 0;
    else if (exp >= 31 + bias)
        num = 0x80000000;
    else {
        E = exp - bias;
        M = frac | 0x800000;
        if (E > 23)
            num = M << (E -23);
        else 
            num = M >> (23 - E);
    }
    return sign ? -num : num; 
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

2.97  遵循位级浮点编码规则, 实现具有如下原型的函数:
/*Compute (float)i */
float_bits float_i2f(int i);

#include <stdio.h>
#include <limits.h>

typedef unsigned float_bits;

float_bits float_i2f(int i);
unsigned bits_length(int x);
unsigned bits_mask(unsigned x);
float u2f(unsigned x);

int main()
{
    int i = 123;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = -123;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = 0;
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = (~0);
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));
    i = (1 << 31);
    printf("%f\t%f\n", (float)i, u2f(float_i2f(i)));

    return 0;
}

float_bits float_i2f(int i)
{
    unsigned sign, exp, frac, bias;
    bias = 127;
    
    if (i == 0) 
        return 0;
    if (i == INT_MIN) { // -1
        sign = 1;
        exp = 31 + bias; 
        frac = 0; // -1是整数,没有小数部分
        return sign << 31 | exp << 23 | frac;
    }

    sign = i > 0 ? 0 : 1;
    
    if (i < 0)
        i = -i;
    
    unsigned bits_num = bits_length(i);
    unsigned fbits_num = bits_num - 1;
    unsigned fbits;

    exp = bias + fbits_num;

    fbits = i & bits_mask(1 << fbits_num - 1);
    
    if (fbits_num <= 23)
        frac = fbits << (23 - fbits_num);
    else {
        unsigned offset = fbits_num - 23;
        frac = fbits >> offset;
        unsigned round_mid = 1 << (offset - 1);
        unsigned round_part = fbits & bits_mask(1 << offset - 1);
        if (round_part > round_mid)
            ++frac;
        else if (round_part == round_mid) {
            if (frac & 0x1)
                ++frac;
        }
    }
    return sign << 31 | exp << 23 | frac;
}

unsigned bits_length(int x)
{
    unsigned ux = (unsigned) x;
    unsigned count = 0;
    while (ux > 0) {
        ux >>= 1;
        ++count;
    } 
    return count;
}

unsigned bits_mask(unsigned x)
{
    x |= x >> 1;
    x |= x >> 2;
    x |= x >> 4;
    x |= x >> 8;
    x |= x >> 16;

    return x;
}

float u2f(unsigned x)
{
    return *(float *)&x;
}

猜你喜欢

转载自www.cnblogs.com/chritran-dlay/p/9279184.html