[Computer Architecture] Assignment 3 - Float

"Floating-Point Numbers"

[Objective]

Half-Precision Floating-Point 형식의 Sign, Exponent, Fraction Bit를 직접 조작하여 사칙 연산을 구현하고 IEEE Floating-Point 동작 원리를 이해하기

[Background]

▣ Infinity
· Exponent : 11111 & Fraction = 0

▣ NaN(Not a Number)
· Exponent : 11111 & Fraction ≠ 0

▣ Zero
· Exponent = 00000 & Fraction = 0

▣ Normalized Number
· Exponent : 00001 ~ 11110
→ 1.Fraction
→ (-1)Sign x 2(Exponent - 15) x (1 + Fraction)

▣ Denormalized Number
· Exponent = 00000 & Fraction ≠ 0
→ 0.Fraction
→ (-1)Sign x 2(Exponent - 15) x (Fraction)

[Implementation]

▣ Add Operation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
_float_ _float_::operator+(const _float_ &y) {
    _float_ r;
 
    bool x_sign = data >> 15;                           // Sign of x Value
    uint16_t x_exp = (data >> 10& 0x1f;               // Exponent of x Value
    uint16_t x_frac = data & 0x3ff;                     // Fraction of x Value
 
    bool y_sign = y.data >> 15;                         // Sign of y Value
    uint16_t y_exp = (y.data >> 10& 0x1f;             // Exponent of y Value
    uint16_t y_frac = y.data & 0x3ff;                   // Fraction of y Value
 
    bool r_sign = 0;                                    // Sign of Result Value
    uint16_t r_exp = 0;                                 // Exponent of Result Value
    uint16_t r_frac = 0;                                // Fraction of Result Value
 
    // NaN (Exponent == 11111, Fraction != 0)
    if ((x_exp == 0x1f && x_frac !=0|| (y_exp == 0x1f && y_frac != 0)) {
        r.data = 0x7e00;                                // NaN
        return r;
    }
 
    // Infinity + Infinity (Exponent == 11111, Fraction == 0)
    if (x_exp == 0x1f && y_exp == 0x1f) {
        if (x_sign != y_sign) {                         // (+/- Infinity) + (-/+ Infinity)
            r.data = 0x7e00;                            // NaN
        } else {                                        // (+/- Infinity) + (+/- Infinity)
            r.data = (x_sign << 15| (0x1f << 10);     // +/- Infinity
        }
        return r;
    }
 
    // x = Infinity
    if (x_exp == 0x1f && x_frac == 0) {                 // x = Infinity
        r.data = data;                                  // Result = x = Infinity
        return r;
    }
 
    // y = Infinity
    if (y_exp == 0x1f && y_frac == 0) {                 // y = Infinity
        r.data = y.data;                                // Result = y = Infinity
        return r;
    }
 
    // x = 0
    if (x_exp == 0 && x_frac == 0) {                    // x = 0
        r.data = y.data;                                // Result = 0 + y = y
        return r;
    }
 
    // y = 0
    if (y_exp == 0 && y_frac == 0) {                    // y = 0
        r.data = data;                                  // Result = x + 0 = x
        return r;
    }
 
    // Consider Hidden Bit
    uint16_t x_val;
    uint16_t y_val;
    uint16_t r_val;
 
    if (x_exp == 0) {                                   // Denormalize
        x_val = x_frac;                                 // 0.xxxx
    } else {                                            // Normalize
        x_val = (1 << 10| x_frac;                     // 1.xxxx
    }
 
    if (y_exp == 0) {                                   // Denormalize
        y_val = y_frac;                                 // 0.xxxx
    } else {                                            // Normalize
        y_val = (1 << 10| y_frac;                     // 1.xxxx
    }
 
    // Shift To Add Guard-Bit & Round Bit
    x_val <<= 2;
    y_val <<= 2;
 
    // Denormalize -> Exponent = 1
    x_exp = (x_exp == 0) ? 1 : x_exp;
    y_exp = (y_exp == 0) ? 1 : y_exp;
 
    // Match Exponent Equally to Calculate
    if (x_exp > y_exp) {
        y_val >>= x_exp - y_exp;                        // y_exp Change Same As x_exp
        r_exp = x_exp;                                  // Result Exponent = x_exp
    } else if (y_exp > x_exp) {
        x_val >>= y_exp - x_exp;                        // x_exp Change Same As y_exp
        r_exp = y_exp;                                  // Result Exponent = y_exp
    } else {                                            // x_exp == y_exp
        r_exp = x_exp;                                  // Either x_exp or y_exp OK
    }
 
    if (x_sign == y_sign) {                             // Same Sign Bit
        r_val = x_val + y_val;                          // Same Exponent By Above
        r_sign = x_sign;                                // Either x_sign or y_sign OK
 
        if (r_val & (1 << 13)) {                        // 1x.xxxx
            r_val >>= 1;                                // 1.xxxxx Format
            r_exp++;                                    // Exponent + 1
        }
    } else {                                            // Different Sign Bit
        if (x_val > y_val) {                            // |x| > |y|
            r_val = x_val - y_val;                      // x - y
            r_sign = x_sign;                            // |x| Sign Bit
        } else if (y_val > x_val) {                     // |y| > |x|
            r_val = y_val - x_val;                      // y - x
            r_sign = y_sign;                            // |y| Sign Bit
        } else {                                        // |x| = |y|
            r.data = 0;                                 // Result = 0
            return r;
        }
    }
 
    // Normalize
    while ((r_val & (1 << 12)) == 0 && r_exp > 1) {     // Until 0.01xxx -> 1.xxx
        r_val <<= 1;                                    // 0.1xxx
        r_exp--;                                        // Exponent - 1
    }
    if ((r_val & (1 << 12)) == 0) {                     // 1.xxxx
        r_exp = 0;                                      // Exponent = 0
    }
 
    // Rounding
    uint16_t guard_round = r_val & 0x3;                 // {LSB - 1, LSB}
    r_val >>= 2;                                        // Remove Guard Bit & Round Bit
    if (guard_round > 0x2 || (guard_round == 0x2 && (r_val & 1))) {
        r_val++;                                        // Round Up
        if (r_exp > 0 && (r_val & (1 << 11))) {         // 10.xxxx
            r_val >>= 1;                                // 1.0xxxx
            r_exp++;                                    // Exponent + 1
        } else if (r_exp == 0 && (r_val & (1 << 10))) {
            r_exp = 1;                                  // Exponent = 1
        }
    }
 
    // Overflow -> Infinity
    if (r_exp >= 31) {                                  // Overflow
        r.data = (r_sign << 15| (0x1f << 10);         // Infinity
        return r;
    }
 
    // Combine Result Value
    if (r_exp > 0) {                                            // Normalize
        r_frac = r_val & 0x3ff;                                 // Leave 10-Bit For Fraction
        r.data = (r_sign << 15| (r_exp << 10| r_frac;       // Combine
    } else {                                                    // Denormalize
        r.data = (r_sign << 15| (r_val & 0x3ff);              // Exponent = 0
    }
    return r;
}
cs

① x, y의 Sign / Exponent / Fraction 분리 후, Special Case 처리
    · NaN : Result = NaN
    · Infinity + Infinity : 부호 동일 O → Result = Infinity / 부호 동일 X → Result = NaN
    · Infinity + y : Result = Infinity
    · x + Infinity : Result = Infinity
    · x = 0 : Result = y
    · y = 0 : Result = x
② Exponent = 00000 : 0.Fraction → Hidden Bit = 0 / Exponent ≠ 00000 : 1.Fraction → Hidden Bit = 1
③ Guard Bit, Round Bit를 사용하기 위해 Fraction을 왼쪽으로 2-bit Shift
④ Denormalized Number는 저장된 Exponent가 0이지만, 실제 Exponent 계산에서는 (1 - Bias) → 0이 아닌 1로 취급
⑤ 덧셈 연산을 위해 두 수의 Exponent를 일치시킴
⑥ Sign이 같으면 덧셈, 다르면 뺄셈
⑦ Normalized 형태(1.xxxx)가 될 때까지 Shift & Exponent-- (더 이상 Exponent를 줄일 수 없으면 Denormalized)
⑧ Rounding (Guard + Round < 10 : 버림 / Guard + Round > 10 : 올림 / Guard + Round == 10 : Tie)
    → Tie : LSB = 1 ▶ 올림 / LSB = 0 ▶ 유지
⑨ Rounding 후 다시 Normalized 형태(1.xxxx)로 변환
⑩ Overflow → Infinity / Normalized → {Sign, Exponent, Fraction} / Denormalized → {Sign, Exponent = 00000, Fraction}

▣ Subtract Operation
1
2
3
4
5
6
7
8
_float_ _float_::operator-(const _float_ &y) {
    _float_ neg_y = y;                                          // Copy y Value
    neg_y.data ^= 0x8000;                                       // Change Sign Bit By Using XOR
 
    _float_ r;
    r = *this + neg_y;                                          // x - y == x + (-y)
    return r;
}
cs

① y 값을 복사 후, Sign Bit를 반전시켜 부호 전환
② 덧셈 연산자로 계산

▣ Multiply Operation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
_float_ _float_::operator*(const _float_ &y) {
    bool x_sign = data >> 15;                                   // Sign of x Value
    uint16_t x_exp = (data >> 10& 0x1f;                       // Exponent of x Value
    uint16_t x_frac = data & 0x3ff;                             // Fraction of x Value
 
    bool y_sign = y.data >> 15;                                 // Sign of y Value
    uint16_t y_exp = (y.data >> 10& 0x1f;                     // Exponent of y Value
    uint16_t y_frac = y.data & 0x3ff;                           // Fraction of y Value
 
    _float_ r;
    bool r_sign = x_sign ^ y_sign;                              // Sign of Result Value
 
    // NaN (Exponent == 11111, Fraction != 0)
    if ((x_exp == 0x1f && x_frac != 0|| (y_exp == 0x1f && y_frac != 0)) {
        r.data = 0x7e00;                                        // NaN
        return r;
    }
 
    // Infinity x 0
    if (((x_exp == 0x1f && x_frac == 0&& (y_exp == 0 && y_frac == 0)) ||      // Infinity x 0
        ((x_exp == 0 && x_frac == 0&& (y_exp == 0x1f && y_frac == 0))) {      // 0 x Infinity
        r.data = 0x7e00;                                        // NaN
        return r;
    }
 
    // Infinity (Exponent == 11111, Fraction == 0)
    if ((x_exp == 0x1f && x_frac == 0|| (y_exp == 0x1f && y_frac == 0)) {
        r.data = (r_sign << 15| (0x1f << 10);                 // Infinity
        return r;
    }
 
    // 0
    if ((x_exp == 0 && x_frac == 0|| (y_exp == 0 && y_frac == 0)) {
        r.data = (r_sign << 15);                                // +/- 0
        return r;
    }
 
    // Consider Hidden Bit
    uint16_t x_val = (x_exp == 0) ? x_frac : ((1 << 10| x_frac);
    uint16_t y_val = (y_exp == 0) ? y_frac : ((1 << 10| y_frac);
    uint32_t r_val = x_val * y_val;                             // 11-bit x 11-bit = 22-bit
 
    // Denormalize -> Exponent = 1
    x_exp = (x_exp == 0) ? 1 : x_exp;
    y_exp = (y_exp == 0) ? 1 : y_exp;
    int r_exp = x_exp + y_exp - 15;                             // Subtract Exponent Bias
 
    if (r_val & (1 << 21)) {                                    // x_val * y_val = 10.xxxx
        r_val >>= 9;                                            // 1.0xxxx (Guard & Round Bit -> 12-bit)
        r_exp++;                                                // Exponent + 1
    } else {                                                    // x_hidden * y_hidden = 1.xxxx
        r_val >>= 8;                                            // 1.xxxx (Guard & Round Bit -> 12-bit)
    }
 
    // Denormalize
    if (r_exp < 1) {
        r_val >>= 1 - r_exp;                                    // Shift Fraction
        r_exp = 0;                                              // Exponent = 0
    }
 
    // Rounding
    uint16_t guard_round = r_val & 0x3;                         // {LSB - 1, LSB}
    r_val >>= 2;                                                // Remove Guard Bit & Round Bit
    if (guard_round > 0x2 || (guard_round == 0x2 && (r_val & 1))) {
        r_val++;                                                // Round Up
        if (r_exp > 0 && (r_val & (1 << 11))) {                 // 10.xxxx
            r_val >>= 1;                                        // 1.0xxxx
            r_exp++;                                            // Exponent + 1
        } else if (r_exp == 0 && (r_val & (1 << 10))) {
            r_exp = 1;                                          // Exponent = 1
        }
    }
 
    // Overflow -> Infinity
    if (r_exp >= 31) {                                          // Overflow
        r.data = (r_sign << 15| (0x1f << 10);                 // Infinity
        return r;
    }
 
    // Denormalize / Underflow
    if (r_exp == 0) {
        r.data = (r_sign << 15| (r_val & 0x3ff);              // Exponent == 0
        return r;
    }
 
    uint16_t r_frac = r_val & 0x3ff;                            // Leave 10-Bit For Fraction
    r.data = (r_sign << 15| (r_exp << 10| (r_frac);         // Combine
    return r;
}
cs

① x, y의 Sign / Exponent / Fraction 분리 & Result Sign 계산 후, Special Case 처리
    · NaN : Result = NaN
    · Infinity x 0 : Result = NaN
    · Infinity : Result = Infinity
    · 0 : Result = 0
② Exponent = 00000 : 0.Fraction → Hidden Bit = 0 / Exponent ≠ 00000 : 1.Fraction → Hidden Bit = 1
    → Result Value는 32-bit로 선언 (x_val x y_val = 11-bit x 11-bit = 22-bit)
③ Denormalized Number는 저장된 Exponent가 0이지만, 실제 Exponent 계산에서는 (1 - Bias) → 0이 아닌 1로 취급
④ (x_exp + y_exp)는 Bias를 2번 더하므로 Result Exponent는 (- Bias) 필요
⑤ x_val x y_val = 1x.xxxx : 오른쪽으로 Shift & Exponent++ / x_val x y_val = 1.xxxx : 오른쪽으로 Shift & Exponent 유지
    → Guard & Round Bit를 포함해야 하므로 Fraction이 12-bit가 남도록 Shift
⑥ Denormalized인 경우 (1 - r_exp)만큼 오른쪽으로 Shift하고, r_exp는 최솟값인 0으로 할당
⑦ Rounding (Guard + Round < 10 : 버림 / Guard + Round > 10 : 올림 / Guard + Round == 10 : Tie)
    → Tie : LSB = 1 ▶ 올림 / LSB = 0 ▶ 유지
⑧ Rounding 후 다시 Normalized 형태(1.xxxx)로 변환
⑨ Overflow → Infinity / Normalized → {Sign, Exponent, Fraction} / Denormalized → {Sign, Exponent = 00000, Fraction}

** 덧셈 : Exponent 일치시킬 때 하위 Bit들이 버려져서 정확도 감소 → Guard & Round Bit를 위해 Shift
** 곱셈 : 결과 자체가 Bit를 많이 생성하여 하위 Bit를 사용 Guard & Round Bit로 바로 사용 가능

▣ Divide Operation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
_float_ _float_::operator/(const _float_ &y) {
    bool x_sign = data >> 15;                                   // Sign of x Value
    uint16_t x_exp = (data >> 10& 0x1f;                       // Exponent of x Value
    uint16_t x_frac = data & 0x3ff;                             // Fraction of x Value
 
    bool y_sign = y.data >> 15;                                 // Sign of y Value
    uint16_t y_exp = (y.data >> 10& 0x1f;                     // Exponent of y Value
    uint16_t y_frac = y.data & 0x3ff;                           // Fraction of y Value
 
    _float_ r;
    bool r_sign = x_sign ^ y_sign;                              // Sign of Result Value
 
    // NaN (Exponent == 11111, Fraction != 0)
    if ((x_exp == 0x1f && x_frac != 0|| (y_exp == 0x1f && y_frac != 0)) {
        r.data = 0x7e00;                                        // NaN
        return r;
    }
 
    // 0/0
    if ((x_exp == 0 && x_frac == 0&& (y_exp == 0 && y_frac == 0)) {
        r.data = 0x7e00;                                        // NaN
        return r;
    }
 
    // Infinity / Infinity
    if ((x_exp == 0x1f && x_frac == 0&& (y_exp == 0x1f && y_frac == 0)) {
        r.data = 0x7e00;                                        // NaN
        return r;
    }
 
    // x/0 (y == 0)
    if (y_exp == 0 && y_frac == 0) {
        r.data = (r_sign << 15| (0x1f << 10);                 // Infinity
        return r;
    }
 
    // 0/y (x == 0)
    if (x_exp == 0 && x_frac == 0) {
        r.data = (r_sign << 15);                                // +/- 0
        return r;
    }
 
    // Infinity / Finite
    if (x_exp == 0x1f && x_frac == 0) {
        r.data = (r_sign << 15| (0x1f << 10);                 // Infinity
        return r;
    }
 
    // Finite / Infinity
    if (y_exp == 0x1f) {
        r.data = (r_sign << 15);                                // +/- 0
        return r;
    }
 
    // Consider Hidden Bit
    uint16_t x_val = (x_exp == 0) ? x_frac : ((1 << 10| x_frac);
    uint16_t y_val = (y_exp == 0) ? y_frac : ((1 << 10| y_frac);
    uint32_t r_val = (x_val << 12/ y_val;                     // Shift To Maintain 12-Bit
 
    // Denormalize -> Exponent = 1
    x_exp = (x_exp == 0) ? 1 : x_exp;
    y_exp = (y_exp == 0) ? 1 : y_exp;
    int r_exp = x_exp - y_exp + 15;                             // Add Bias
 
    // Denormalize
    if (r_exp < 1) {
        r_val >>= 1 - r_exp;                                    // Shift Fraction
        r_exp = 0;                                              // Exponent = 0
    }
 
    // Normalize (10.xxxx)
    if (r_val & (1 << 13)) {                                    // 10.xxxx
        r_val >>= 1;                                            // 1.0xxxx
        r_exp++;                                                // Exponent + 1
    }
 
    // Normalize (0.1xxx)
    while ((r_val & (1 << 12)) == 0 && r_exp > 1 && r_val != 0) {       // Until 0.01xxx -> 1.xxx
        r_val <<= 1;                                            // 0.1xxx
        r_exp--;                                                // Exponent - 1
    }
    if ((r_val & (1 << 12)) == 0) {                             // 1.xxxx
        r_exp = 0;                                              // Exponent = 0
    }
 
    // Rounding
    uint16_t guard_round = r_val & 0x3;                         // {LSB - 1, LSB}
    r_val >>= 2;                                                // Remove Guard Bit & Round Bit
 
    if (guard_round > 0x2 || (guard_round == 0x2 && (r_val & 1))) {
        r_val++;                                                // Round Up
        if (r_exp > 0 && (r_val & (1 << 11))) {                 // 10.xxxx
            r_val >>= 1;                                        // 1.0xxxx
            r_exp++;                                            // Exponent + 1
        } else if (r_exp == 0 && (r_val & (1 << 10))) {
            r_exp = 1;                                          // Exponent = 1
        }
    }
 
    // Overflow -> Infinity
    if (r_exp >= 31) {                                          // Overflow
        r.data = (r_sign << 15| (0x1f << 10);                 // Infinity
        return r;
    }
 
    // Denormalize / Underflow
    if (r_exp == 0) {
        r.data = (r_sign << 15| (r_val & 0x3ff);              // Exponent == 0
        return r;
    }
 
    uint16_t r_frac = r_val & 0x3ff;                            // Leave 10-Bit For Fraction
    r.data = (r_sign << 15| (r_exp << 10| r_frac;           // Combine
    return r;
}
cs

① x, y의 Sign / Exponent / Fraction 분리 & Result Sign 계산 후, Special Case 처리
    · NaN : Result = NaN
    · 0 / 0 : Result = NaN
    · Infinity / Infinity : Result = NaN
    · x / 0 : Result = Infinity
    · 0 / y : Result = 0
    · Infinity / Finite : Result = Infinity
    · Finite / Infinity : Result = 0
② Exponent = 00000 : 0.Fraction → Hidden Bit = 0 / Exponent ≠ 00000 : 1.Fraction → Hidden Bit = 1
    → Result Value는 정수 나눗셈이라 소수 부분이 사라지므로 x_val을 왼쪽으로 Shift하여 나눗셈 결과 Fraction 부분을 보존
③ Denormalized Number는 저장된 Exponent가 0이지만, 실제 Exponent 계산에서는 (1 - Bias) → 0이 아닌 1로 취급
④ (x_exp - y_exp)는 Bias가 서로 상쇄되므로 Result Exponent는 (+ Bias) 필요
⑤ Denormalized인 경우 (1 - r_exp)만큼 오른쪽으로 Shift하고, r_exp는 최솟값인 0으로 할당
⑥ x_val / y_val = 1x.xxxx : 오른쪽으로 Shift & Exponent++ / x_val / y_val = 0.01xxxx : 왼쪽으로 Shift & Exponent--
    → 더 이상 Exponent를 줄일 수 없으면 Denormalized
⑦ Rounding (Guard + Round < 10 : 버림 / Guard + Round > 10 : 올림 / Guard + Round == 10 : Tie)
    → Tie : LSB = 1 ▶ 올림 / LSB = 0 ▶ 유지
⑧ Rounding 후 다시 Normalized 형태(1.xxxx)로 변환
⑨ Overflow → Infinity / Normalized → {Sign, Exponent, Fraction} / Denormalized → {Sign, Exponent = 00000, Fraction}

[Result]

· main.cc에서 "_float_ x, y;"를 "_Float16 x, y;"로 변경하면 결과 비교 가능

[Reference]

· float (Computer Architecture) - William J. Song

댓글

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)