---
tags: linyunwen, raygoah
---
# 2018q1 Homework (quiz5-2)
###### tags: `linyunwen` `raygoah`
contributed by <`LinYunWen`, `raygoah`>
- [2018q1 Homework3 (作業區)](https://hackmd.io/Ex6mTFOvS3K6NizRwX-HVQ)
- [第五周練習題 ( 中 )](https://hackmd.io/s/HkQjgqI5G)
- [Youtube](https://www.youtube.com/watch?v=zaA9jzWvMlg&list=PLFSyL7YoFilT_HugG5YUnSqslvFrwuIEp)
### Q1
已知在 x86_64 架構,以下程式碼的執行輸出是 `jserv++C`:
```clike
#include <stdio.h>
int main() {
puts((char *) &(double []){ 3823806048287157.0, 96 });
}
```
考慮以下程式碼,假設 puts 函式總是可正確運作,那麼程式輸出為何?
```clike
#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... \times 2^{51}$
> 將小數點後的所有值拿出來從 fraction 的 high bit 開始放
> $^{51}|\ 1011001\ 01011011\ 10110011\ 10010011\ 00101011\ 10011011\ 0101\ 0\ |^0$ (最後面補上一個 0 )
> $(2^{10}-1)$ + **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] 會乘上 $2^ {96}$
* 對於乘上 $2^ {96}$ 在 IEEE 754 的規定中,即是要將第 52 到第 62 共 11 個 bits 的 exponent 加上 96,因此會得到 0**1001001 0010**1011 00101011 01110110 01110010 01100101 01110011 01101010,轉換成字元後及為:jserv++I
:::success
延伸題目:
1. 修改程式,允許輸出你的 GitHub 帳號名稱
2. 承上,修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name),程式碼應該越短越好,而且不得以字串的形式存在於原始程式碼
3. 如果在 big-endian 的環境中,要達到一樣的效果,我們該如何改寫?
:::
:::success
1. 修改程式,允許輸出你的 GitHub 帳號名稱
```clike=
#include <stdio.h>
int main() {
// output: raygoah
puts((char *) &(double []){ 1.08497298767257192667014240249E-306});
// output: LinYunWe
puts((char *) &(double []){ 1.5192075502270189308379629358E180});
}
```
- [binary converter](http://www.binaryconvert.com/convert_double.html)
2. 修改 gen() 函式,透過特定的轉換,讓 `m[]` 的內容變為你的英文姓氏 (last name)
* 將 raygoah 改成 LEI
* raygoah:
0 **00000000110** 1000011000010110111101100111011110010110000101110010
* LEI_____:
0 **10111110101** 1111010111110101111101011111010010010100010101001100
由上面比較可知,sign bit 不變,而 exponent 需要加上 1519,因此乘上 $2^{21519}$,
乘完後,在次方相同的情況下,加上兩者的差距,即可順利轉換成要的結果
* 程式碼如下方所示:
```clike=
#include <stdio.h>
double m[] = { 1.08497298767257192667014240249E-306, 1519 };
void gen() {
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](https://codebeautify.org/binary-string-converter) 可以發現到,要讓最後程式輸出得到 raygoah,一開始轉換時要輸入 haogyar,因此在 big_endian 環境中要得到正確結果的話,一開始轉換時就要注意
> 參考[ stackoverflow ](https://stackoverflow.com/questions/2782725/converting-float-values-from-big-endian-to-little-endian)。也可以不改變數值,但是另外用一個函式ReverseFloat() 一個一個 char 轉過來。(注意網址中是只有32 bits ,因此只有 4個 char 空間,而若是適用於這題需要 0~7 ,8個空間。)
> [name=rex662624]
> 在不更改程式碼的前提下,撰寫 Convert "Little-Endian" to "Big-Endian" 的函式,也是可行的做法:
> 自己撰寫一個:
> [name=workat60474]
```clike=
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);
}
```
:::