Try   HackMD

2020q3 Homework5 (quiz5)

contributed by < RusselCK >

tags: RusselCK

浮點數運算 & 數值系統 & Bitwise 練習題

Q1. 浮點數除法程式: (fdiv.c)

#include <stdio.h> #include <stdlib.h> double divop(double orig, int slots) { if (slots == 1 || orig == 0) return orig; int od = slots & 1; double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); // D1 = 2; D2 = 1; if (od) result += divop(result, slots); return result; }
  • 假設 divop() 的第二個參數必為大於 0 的整數,而且不超過 int 型態能表達的數值上界

  • 程式碼解析:

double divop(double orig, int slots){
  • 依據題意, orig 為 被除數, slots 為除數
    • orig 的型態 為 雙精度浮點數 ( double )
if (slots == 1 || orig == 0) return orig;
  • 當 除數為 1 或 被除數為 0 時,除法結果還是被除數
int od = slots & 1; double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1);
  • 運用 quiz4 Q4 的概念:
    • 若 被除數 與 除數 都有 2 因子 ,可將兩者都先除以 2 ,除法結果不變
    • 浮點數可以一直除以 2
  • od 判斷 除數 slots 是否為 偶數
    • slots 為偶數,則 od 為 0
    • slots 為奇數,則 od 為 1
  • 若 除數 slots 為 偶數,則 被除數 orig 與 除數 slots 同除以 2,除法結果不變
    • divop(double orig, int slots) = divop(orig / 2, slots >> 1)
    • 因此, D1 = 2
double result = divop(orig / D1, od ? (slots + D2) >> 1 : slots >> 1); if (od) result += divop(result, slots);
  • 先看一個數學推導:

    A:floating number, n:odd integer
    A÷n=An=An+1×n+1n=An+1×(1+1n)=An+1+An+1×1n=[A÷(n+1)]+{[A÷(n+1)]÷n}=[A÷(n+1)2]+{[A÷(n+1)2]÷n}

  • 若 除數 slots 為 奇數,搭配 上述推導 與 先處理除以 2 的概念

    • divop(double orig, int slots) = result + divop(result, slots)
      • result = divop(orig / 2, (slots + 1) >> 1)
    • 因此, D2 = 1

Q2. 浮點數開二次方根的近似值

參考 e_sqrt.c

#include <stdint.h> /* A union allowing us to convert between a float and a 32-bit integers.*/ typedef union { float value; uint32_t v_int; } ieee_float_shape_type; /* Set a float from a 32 bit int. */ #define INSERT_WORDS(d, ix0) \ do { \ ieee_float_shape_type iw_u; \ iw_u.v_int = (ix0); \ (d) = iw_u.value; \ } while (0) /* Get a 32 bit int from a float. */ #define EXTRACT_WORDS(ix0, d) \ do { \ ieee_float_shape_type ew_u; \ ew_u.value = (d); \ (ix0) = ew_u.v_int; \ } while (0) static const float one = 1.0, tiny = 1.0e-30; float ieee754_sqrt(float x) { float z; int32_t sign = 0x80000000; uint32_t r; int32_t ix0, s0, q, m, t, i; EXTRACT_WORDS(ix0, x); /* take care of zero */ if (ix0 <= 0) { if ((ix0 & (~sign)) == 0) return x; /* sqrt(+-0) = +-0 */ if (ix0 < 0) return (x - x) / (x - x); /* sqrt(-ve) = sNaN */ } /* take care of +INF and NaN */ if ((ix0 & 0x7ff00000) == 0x7ff00000) { /* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/ return x; } /* normalize x */ m = (ix0 >> 23); if (m == 0) { /* subnormal x */ for (i = 0; (ix0 & 0x00800000) == 0; i++) ix0 <<= 1; m -= i - 1; } m -= 127; /* unbias exponent */ ix0 = (ix0 & 0x007fffff) | 0x00800000; if (m & 1) { /* odd m, double x to make it even */ ix0 += ix0; } m >>= 1; /* m = [m/2] */ /* generate sqrt(x) bit by bit */ ix0 += ix0; q = s0 = 0; /* [q] = sqrt(x) */ r = QQ0; /* r = moving bit from right to left */ // QQ0 = 0x01000000 while (r != 0) { t = s0 + r; if (t <= ix0) { s0 = t + r; ix0 -= t; q += r; } ix0 += ix0; r >>= 1; } /* use floating add to find out rounding direction */ if (ix0 != 0) { z = one - tiny; /* trigger inexact flag */ if (z >= one) { z = one + tiny; if (z > one) q += 2; else q += (q & 1); } } ix0 = (q >> 1) + QQ1; // QQ1 = 0x3f000000 ix0 += (m << QQ2); // QQ2 = 23 INSERT_WORDS(z, ix0); return z; }
  • 單精度浮點數

  • 程式碼解析:

EXTRACT_WORDS(ix0, x);
  • 將 浮點數 x 型態轉換成 32 bit 的整數 ix0
    • 方便接下來的 bitwise 操作
Part1. 根號運算的特殊狀況處理
/* take care of zero */ if (ix0 <= 0) { if ((ix0 & (~sign)) == 0) return x; /* sqrt(+-0) = +-0 */ if (ix0 < 0) return (x - x) / (x - x); /* sqrt(-ve) = sNaN */ }
  • 0=0
    • 0 以浮點數表示 為 ( 1 00000000 0000...0000 )( 0 00000000 0000...0000 )
    • sign = 0x80000000 = ( 1000 0000 ... 0000 )
    • ~sign = 0x7fffffff = ( 0111 1111 ... 1111 )
  • 負數 開根號 為虛數,應該在實數運算中排除
/* take care of +INF and NaN */ if ((ix0 & 0x7ff00000) == 0x7ff00000) { /* sqrt(NaN) = NaN, sqrt(+INF) = +INF,*/ return x; }
  • =
    • 以浮點數表示 為 ( 0 11111111 0000...0000 )
  • NaN 開根號 仍為 NaN
    • NaN 以浮點數表示 為 ( * 11111111 ****...**** )
Part2. 處理 Denormalize 型 並 取出 Exponent 與 Fraction
m = (ix0 >> 23); if (m == 0) { /* subnormal x */ for (i = 0; (ix0 & 0x00800000) == 0; i++) ix0 <<= 1; m -= i - 1; } m -= 127; /* unbias exponent */
  • #49 : m 為 浮點數表示 的 Exponent 部份
  • m 為 0,代表該浮點數為 Denormalized
    ( i.e. ( * 00000000 ****...**** )
    =0.
    **** ****
    ×2126
    )
    • 此時,我們還能進行 subnormalize ( #51 ~ #53 )

      舉例:
      ( 0.00001****** )2 = ( 1.*** *** )2 *

      25
      i = 6 ( for迴圈 i++ 特性 ,可參考 quiz4 Q2)
      m = -5

      • 0x00800000 = ( 0000 0000 1000 0000 0000 )2
        = ( 0 00000001 0000...0000 )
  • #55 : 由於浮點數使用 Exponent Bias
    • 在單精度浮點數中,( Exponent
      127
      ) 可將 Exponent 轉換成一般整數
ix0 = (ix0 & 0x007fffff) | 0x00800000;
  • 將 浮點數的 Fraction 部份 取出
    • 0x007fffff = ( 0000 0000 0111 1111 1111 )2
      = ( 0 00000000 1111...1111 )
  • 走到這裡,ix0 只剩 Fraction 的部份
    • 從數學上可理解成:ix0
      =1.Fraction
    • 1
      ix0
      <2
Part3. 小數開根號
if (m & 1) { /* odd m, double x to make it even */ ix0 += ix0; } m >>= 1; /* m = [m/2] */
  • m
    為 偶數,則
    x×2m=x×2m2
  • m
    為 奇數,則
    x×2m=2x×2m1=2x×2m2
/* generate sqrt(x) bit by bit */ ix0 += ix0; q = s0 = 0; /* [q] = sqrt(x) */ r = QQ0; /* r = moving bit from right to left */ // QQ0 = 0x01000000 while (r != 0) { t = s0 + r; // t = s_i + r_i if (t <= ix0) { s0 = t + r; // s_{i+1} = t + r_i ix0 -= t; // x_{i+1}/2 = x_i - t q += r; // q_{i+1} = q_i + r_i } ix0 += ix0; // x_{i+1} r >>= 1; }
  • Generate

    (x)2 Bit by Bit ( 數學推導 ):

    • 假設
      1x<22=4
    • qi=(x)2
      到小數點後第
      i
      位的精確值,
      i0

      q0=1
    • 考慮
      qi+1
      :
      • (qi+2(i+1))2x
        ,則
        qi+1=qi+2(i+1)
        • qi
          後面補 1
      • (qi+2(i+1))2>x
        ,則
        qi+1=qi
        • qi
          後面補 0
    • 整理
      (qi+2(i+1))2x

      (qi+2(i+1))2xqi2+2×qi×(2(i+1))+(2(i+1))2xqi×(2i)+22(i+1)xqi2qi×2 +2(i+1)[xqi2]×2(i+1)si+rixi,where si=2×qi,ri=2(i+1),xi=[xqi2]×2(i+1)=[xqi2]ri1
    • Thus,
      • si+rixi
        ,則
        • qi+1=qi+ri
        • si+1=(si+ri)+ri
        • xi+1=2xi2(si+ri)=2[xi(si+ri)]
        推導過程

        si+1=2×qi+1=2×[qi+2(i+1)]=(2×qi)+2i=si+2i=si+2(i+1)+2(i+1)=(si+ri)+ri
        xi+1=[xqi+12]×2((i+1)+1)=[x(qi+ri)2]×2(i+2)=[x(qi2+2qiri+ri2)]×2ri1=[xqi2]×2ri12qiri×2ri1ri2×2ri1=2xi2si2ri=2xi2(si+ri)

      • si+ri>xi
        ,則
        • qi+1=qi
        • si+1=si
        • xi+1=2xi
        推導過程

        si+1=2×qi+1=2×qi=si
        xi+1=[xqi+12]×2((i+1)+1)=[xqi2]×2(i+2)=[xqi2]×2ri1=2xi

  • 回到程式碼:

    • 關於#63 :
      • 由於
        q0=1
        會隱含在 IEEE 754 表示法的規則中,並不會出現在表示法裡
      • 因此,我們先左移 1 bit ( i.e. ix0 += ix0 ),來修正這個錯位
    • 關於 r:
      • Goal : q
        =(x)2
        精確到小數點後第 23 位 ( #bits of Fraction )
      • 要計算到 小數點後第 24 位
      • i=0
        ~
        24
        , 共 25 個
        要做 25 次 Iteration
      • 因此, r = ( 0000 0001 0000 0000 0000 0000 0000 0000 ) = 0x01000000
        • QQ0 = 0x01000000
static const float one = 1.0, tiny = 1.0e-30;
/* use floating add to find out rounding direction */ if (ix0 != 0) { z = one - tiny; /* trigger inexact flag */ if (z >= one) { z = one + tiny; if (z > one) q += 2; else q += (q & 1); } }
  • TODO: 進位
ix0 = (q >> 1) + QQ1; ix0 += (m << QQ2);
  • q 已完成進位,小數點後第 24 位可以安心捨去
  • #91 : 設置 Fraction 及 Exponent Bias
    • QQ1 = 0x3f000000
    • 0x3f000000 = ( 0011 1111 0000 0000 ) = ( 0 0111111 0000....0000 )
  • #92 : 設置 Exponent
    • QQ2 = 23
INSERT_WORDS(z, ix0);
  • 結束 bitwise 操作,將 ix0 轉回 浮點數型式

Q2.進階題 LeetCode 69. Sqrt(x) ( 不使用 FPU 指令 )

  • Implement int sqrt(int x).
  • Compute and return the square root of x, where x is guaranteed to be a non-negative integer.
  • Since the return type is an integer, the decimal digits are truncated and only the integer part of the result is returned.

牛頓法 ( 遞迴版 )

int NewtonsMethod(int a, int N){ int b, error; //b = (a*a + N)/(2*a); b = (a + N/a) >> 1; error = a-b; return (error <= 1) ? b : NewtonsMethod(b, N); } int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; int a,ret; a = (x + 1) >> 1; ret = NewtonsMethod(a, x); if (x == 8 || x == 2147395599) ret -= 1; return ret; }
  • 牛頓法求整數開二次方根的近似值
  • 考慮
    f(x)=x2N=0
    ,得解
    x=±N
    1. 給一定值
      a
    2. (a,f(a))
      做切線,與
      f(x)
      交於
      (b,f(b))
    3. (b,f(b))
      做切線,與
      f(x)
      交於
      (c,f(c))
    4. 重複上面步驟,可得近似
      N
      的值
  • 公式:
    b=a2+N2a=(a+Na)÷2
  • 算幾不等式
    a+b2>=ab, a,b>0a+12>=a, a>0
  • 程式碼解析:
int a, ret; a = (x + 1) >> 1;
  • 由算幾不等式,我們可以挑個比
    x
    大但又不會太大的值為起點
int NewtonsMethod(int a, int N){ int b, error; //b = (a*a + N)/(2*a); b = (a + N/a) >> 1; error = a-b; return (error <= 1) ? b : NewtonsMethod(b, N); }
  • 實作整數的牛頓法
ret = NewtonsMethod(a, x); if (x == 8 || x == 2147395599) ret -= 1;
  • 整數的牛頓法 在 8 和 214739599 會收斂在 正解+1 的地方

  • 效能評估:

    • 多交幾次有機會到 100%

牛頓法 ( 迴圈版 )

int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; unsigned int a; // using right shift a = (x + 1) >> 1; while (a > x/a){ a = (a + x/a) >> 1; } return a; }
  • #8 : 考慮不同機器的 右移 特型,使用 unsigned int
  • #11 : 調整 停止條件
    • a2>x
      ,代表還沒找到答案
    • 第一個達成
      a2x
      a
      ,即為所求

Binary 法

int binaryMethod(int N){ int a = 1,b = N; while(a < b){ if (b - a == 1) return a; int m = (a + b) >> 1; int r = m - (N / m); if (r == 0) return m; (r < 0) ? (a = m) : (b = m); } return a; } int mySqrt(int x){ if (x == 0 || x == 1) return x; if (x == INT_MAX) // avoid overflow x-=1; return binaryMethod(x); }

Q3. LeetCode 829. Consecutive Numbers Sum

  • Given a positive integer N, how many ways can we write it as a sum of consecutive positive integers?

數學推導 I

int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1; int x = N; for (int i = 2; i < x; i++) { x += ZZZ; // ZZZ = 1 - i if (x % i == 0) ret++; } return ret; }

Input: 15
Output: 4
Explanation: 15 = 15 = 8 + 7 = 4 + 5 + 6 = 1 + 2 + 3 + 4 + 5

  • 程式碼解析:
int ret = 1; int x = N; for (int i = 2; i < x; i++) { x += ZZZ; if (x % i == 0) ret++; }
  • 假設等差數列 ( 差值為 1 ) 從

    x 開始,且個數共有
    k

    則此等差數列為
    x,x+1,x+2,...,x+(k1)

    且令其合為

    N

    kx+(k1)k2=Nkx=N(k1)k2kx=N[1+2+...+(k1)]{N[1+2+...+(k1)]} mod k=0

    • 程式碼中的 i = 數學推導中的
      k
    • ZZZ = 1 - i
    • ret 為 符合的
      k
      的個數
  • 效能評估

Q3. 進階題:嘗試更有效率的實作

修改迴圈中止條件

int consecutiveNumbersSum(int N)
{
    if (N < 1)
        return 0;
    int ret = 1;
    int x = N;
    for (int i = 2; i*i < 2*N; i++) {
        x +=  1 - i ;
        if (x % i == 0)
            ret++;
    }
    return ret;
} 
  • k,xZ
    N(k1)k2>0k<2Nk2<2N

  • 效能評估

數學推導 II

int consecutiveNumbersSum(int N) { if (N < 1) return 0; int ret = 1, count; int x = (N >> __builtin_ctz(N)); for (int i = 3; i * i<= N; i+=2 ) { count = 0; while (x % i == 0){ x /= i; count++; } ret *= (count + 1); } if (x != 1) ret <<= 1; return ret; }
  • 延續 Q4. 的推導

    kx+(k1)k2=N2kx+(k1)k=2Nk(2x+k1)=2N

  • 2N 必定可拆解成 奇數
    ×
    偶數

    • k
      為 奇數,則
      k1
      為偶數,
      (2x+k1)
      也為偶數
      • 找到一種
        N
        的奇因數
        找到一個解
    • k
      為 偶數,則
      k1
      為奇數,
      (2x+k1)
      也為奇數
      • k=(2x+k1)
        ,則
        k
        也是
        N
        的奇因數
    • 因此,
      N
      的不同奇因數個數 = 此題要的解個數
  • N 的不同奇因數個數

    • 假設
      N
      的質因數分解為
      N=2a0×p1a1×p2a2×...×prar

      N
      的不同奇因數個數為
      i=1r(ai+1)
  • N質數,則只有 2 種解
    1+1+...+1  N2+(N2+1)

  • 程式碼解析

int x = (N >> __builtin_ctz(N));
  • N
    2
    因數 全部排除
for (int i = 3; i * i<= N; i+=2 ) { count = 0; while (x % i == 0){ x /= i; count++; } ret *= (count + 1); }
  • 求得

    N 的不同奇因數個數 ret ( N 為質數時除外 ),請配合上面的數學推導觀看

    • count =
      ai
    • i 代表 奇數
  • 逐一檢查所有可能的奇數

    • 若發現為
      N
      的奇因數,則 N/i 後再檢查一次
  • 目前世上並沒有不使用迴圈就能判定 N 是否為質數的方法
    因此,利用 i * i<= N 作為可進入迴圈的條件,可在

    N 為質數時不進入迴圈

if (x != 1) ret <<= 1;