Try   HackMD

2018q1 Homework (quiz5-2)

tags: linyunwen raygoah

contributed by <LinYunWen, raygoah>

Q1

已知在 x86_64 架構,以下程式碼的執行輸出是 jserv++C:

#include <stdio.h>
int main() {
    puts((char *) &(double []){ 3823806048287157.0, 96 });
}

考慮以下程式碼,假設 puts 函式總是可正確運作,那麼程式輸出為何?

#include <stdio.h>
double m[] = { 3823806048287157.0, 96 };
void gen() {
    if (m[1]--) {
        m[0] *= 2;
        gen();
    } else
        puts((char *) m);
}
int main() { gen(); return 0; }
  • 首先先將 3823806048287157.0 以二進位表示,根據 IEEE754 double 的儲存方式如下圖,因此我們先將 3823806048287157 轉成二進位

    (3823806048287157)10=(11011001 01011011 10110011 10010011 00101011 10011011 0101)2

    以科學符號表示為

    1.1011001...×251
    將小數點後的所有值拿出來從 fraction 的 high bit 開始放
    51| 1011001 01011011 10110011 10010011 00101011 10011011 0101 0 |0
    (最後面補上一個 0 )

    (2101) + 51
    = 1023 + 51
    =
    (1074)10

    =
    (1000 0110010)2

    放進 exponent 裏
    62| 01000 0110010 |52

    (前面要補上 0)

    因此完成結果為01000011 00101011 00101011 01110110 01110010 01100101 01110011 01101010

  • 因為強制轉型成 char * ,因此接著要將這一長串 0 和 1 轉換成 char ,因此將每 8 個 bits 一組形成一個字元,最後呈現的結果便是 C++vresj,但是因為使用的機器其儲存方式為 little-endian,所以顯示的結果會相反:jserv++C

  • 可以看到在 gen() 中,會將 m[0] 的值乘上 2,而因為遞迴的關係,最後 m[0] 會乘上

    296

  • 對於乘上

    296 在 IEEE 754 的規定中,即是要將第 52 到第 62 共 11 個 bits 的 exponent 加上 96,因此會得到 01001001 00101011 00101011 01110110 01110010 01100101 01110011 01101010,轉換成字元後及為:jserv++I

延伸題目:

  1. 修改程式,允許輸出你的 GitHub 帳號名稱
  2. 承上,修改 gen() 函式,透過特定的轉換,讓 m[] 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼
  3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
  1. 修改程式,允許輸出你的 GitHub 帳號名稱

    ​​​​#include <stdio.h> ​​​​int main() { ​​​​ // output: raygoah ​​​​ puts((char *) &(double []){ 1.08497298767257192667014240249E-306}); ​​​​ // output: LinYunWe ​​​​ puts((char *) &(double []){ 1.5192075502270189308379629358E180}); ​​​​}
  2. 修改 gen() 函式,透過特定的轉換,讓 m[] 的內容變為你的英文姓氏 (last name)

    • 將 raygoah 改成 LEI
      • raygoah:
        0 00000000110 1000011000010110111101100111011110010110000101110010

      • LEI_____:
        0 10111110101 1111010111110101111101011111010010010100010101001100
        由上面比較可知,sign bit 不變,而 exponent 需要加上 1519,因此乘上

        221519
        乘完後,在次方相同的情況下,加上兩者的差距,即可順利轉換成要的結果

    • 程式碼如下方所示:
    ​​​​#include <stdio.h> ​​​​double m[] = { 1.08497298767257192667014240249E-306, 1519 }; ​​​​void gen() {&nbsp;&nbsp;&nbsp;&nbsp; ​​​​ if (m[1]--) { ​​​​ m[0] *= 2; ​​​​ gen(); ​​​​ } else { ​​​​ m[0] = m[0] + (0.57218400314947140020383950500E151); ​​​​ puts((char *) m); ​​​​ } ​​​​} ​​​​int main() { gen(); return 0; }
  3. 在 big-endian 的環境中,要如何改寫

    • 使用 codebeautify 可以發現到,要讓最後程式輸出得到 raygoah,一開始轉換時要輸入 haogyar,因此在 big_endian 環境中要得到正確結果的話,一開始轉換時就要注意

    參考 stackoverflow 。也可以不改變數值,但是另外用一個函式ReverseFloat() 一個一個 char 轉過來。(注意網址中是只有32 bits ,因此只有 4個 char 空間,而若是適用於這題需要 0~7 ,8個空間。)
    rex662624

    在不更改程式碼的前提下,撰寫 Convert "Little-Endian" to "Big-Endian" 的函式,也是可行的做法:
    自己撰寫一個:
    workat60474

    ​​​​static void Little_to_Big_swap(void *v) ​​​​{ ​​​​ char in[8], out[8]; ​​​​ memcpy(in, v, 8); ​​​​ out[0] = in[7]; ​​​​ out[1] = in[6]; ​​​​ out[2] = in[5]; ​​​​ out[3] = in[4]; ​​​​ out[4] = in[3]; ​​​​ out[5] = in[2]; ​​​​ out[6] = in[1]; ​​​​ out[7] = in[0]; ​​​​ memcpy(v, out, 8); ​​​​}