2012年4月13日 星期五

Transact-SQL IEEE 754 反轉程式 -- 32 bits (Convert IEEE 754 Binary Floating-Point To Decimal)


此 Stored Procedure 可將輸入的 IEEE 754 標準二進制浮點格式 (32 bits) 轉換為十進制數字輸出。




IEEE 754 共分為 S (Sign)、E (Exponent)、M (Mantisa) 三個部分 -




計算方式:以 11000010011100010000000000000000 為例


1) 將此數分為 S、E、M 三部分 (S = 1、E = 10000100、M = 11100010000000000000000)


2) S = 1 得知此數為負數 (若為正數則設定為 0)


3) 將 E 轉換為十進制再減去 127,得到 5 (此為 2 的乘方)。


4) 將 M 的左方加上 "1." (即 1.1110001) 後,將小數點右移 5 位,得到 111100.01。


5) 將 111100.01 轉為十進制,得到 60.25 。


6) 最後結果為 -60.25


程式碼

NOTE 欲複製程式碼的讀者,先將滑鼠指標移至程式碼區,雙擊 (Double-Click) 滑鼠左鍵,此時該區程式碼應會呈現 Highlight 狀態;再按下滑鼠右鍵,於出現的選單中選擇複製。若程式碼未出現 Highlight 狀態,請重整 (Refresh) 本網頁後,再重複上述動作。

if object_id ('usp_IEEE754_Restore') is not null
   drop proc usp_IEEE754_Restore
go


create proc usp_IEEE754_Restore @prm_IEEE_754_string   varchar(32), 
                                @rtn_decimal_number    float output
/*----------------------------------------------------------------------------------+
 | Author: Wei-jie Yang                                                             |
 | Date:   2012/05/10                                                               |
 | All Rights Reserved                                                              |
 |                                                                                  |
 | [Description]                                                                    |
 |  This stored procedure is used to convert IEEE 754 binary floating number to     |
 |  decimal floating number.                                                        |
 |                                                                                  |
 | [Parameters]                                                                     |
 |  (1).INPUT Parameter                                                             |
 |      - @prm_IEEE_754_string: The IEEE 754 number that you want to convert.       |
 |  (2).OUTPUT Parameter                                                            |
 |      - @rtn_decimal_number : Converted decimal number                            |
 |                                                                                  |
 | [Usage]                                                                          |
 |  declare @return_1 float                                                         |
 |  exec usp_IEEE754_Restore '11000010111100000100000000000000', @return_1 output   |
 |  select @return_1 as Decimal_Mode                                                |
 +----------------------------------------------------------------------------------*/
as

declare @abs_binary_exp                     int,
        @add_digit_number                   tinyint,

        @dot_left_digits                    int,
        @dot_position                       int,
        @dot_right_digits                   int,

        @binary_exp                         int,
        @binary_exp_string                  varchar(8),
        @binary_float_string                varchar(50),
        @binary_float_string_dot_left_part  varchar(50),
        @binary_float_string_dot_right_part varchar(50),
        @binary_float_string_length         int,

        @decimal_fraction                   float,
        @decimal_integer                    int,
        @digit_counter                      int,
        
        @i                                  tinyint,
        @j                                  tinyint,
        @k                                  tinyint,
  
        @shift_string                       varchar(50),
        @single_digit                       char(1)
         
         
-- Binary Exponential -----------------------------------------------
select @binary_exp_string = substring(@prm_IEEE_754_string, 2, 8)

select @binary_exp = 0

select @i = 8

while @i >= 1
 begin
    select @single_digit = substring(@binary_exp_string, @i, 1)
    
    select @binary_exp = @binary_exp + power(2, 8 - @i) * cast(@single_digit as tinyint)
    
    select @i = @i - 1
 end

select @binary_exp = @binary_exp - 127
---------------------------------------------------------------------


-- Binary Float -----------------------------------------------------
select @binary_float_string = '1.' + substring(@prm_IEEE_754_string, 10, 23)

-- Use '.' to split binary string into two parts
select @dot_position = charindex('.', @binary_float_string)

if @dot_position > 0
 begin
    select @binary_float_string_length = len(@binary_float_string)
    
    select @binary_float_string_dot_left_part  = substring(@binary_float_string, 1, @dot_position - 1)
    
    select @binary_float_string_dot_right_part = substring(@binary_float_string, @dot_position + 1, @binary_float_string_length - @dot_position)
 end
else
 begin
    select @binary_float_string_dot_left_part = @binary_float_string
    
    select @binary_float_string_dot_right_part = ''
 end
  
-- Process Binary Exponential
if @binary_exp > 0
 begin
    select @shift_string = substring(@binary_float_string, @dot_position + 1, @binary_exp)
 
    select @binary_float_string_dot_left_part = @binary_float_string_dot_left_part + @shift_string
    
    if len(@binary_float_string_dot_right_part) = @binary_exp
     begin
        select @binary_float_string_dot_right_part = ''
     end
    else
     begin

        if len(@binary_float_string_dot_right_part) < @binary_exp  -- range exceeded
         begin
   
            if substring(@prm_IEEE_754_string, 1, 1) = '1'
             begin
                select @rtn_decimal_number = 1073741824 * -1
                goto Error_Handler
             end
            else
             begin
                select @rtn_decimal_number = 1073741824
                goto Error_Handler    
             end
         end

        select @binary_float_string_dot_right_part = 
           substring(@binary_float_string_dot_right_part, @binary_exp + 1, len(@binary_float_string_dot_right_part) - @binary_exp)
     end
 end
else if @binary_exp < 0
 begin
    select @binary_float_string_dot_left_part = ''

    select @add_digit_number = 1

    select @abs_binary_exp = abs(@binary_exp)
    
    while @add_digit_number <= abs(@binary_exp)
     begin
        if @add_digit_number = 1
         begin
            select @binary_float_string_dot_right_part = '1' + @binary_float_string_dot_right_part
         end
        else
         begin
     select @binary_float_string_dot_right_part = '0' + @binary_float_string_dot_right_part 
         end

        select @add_digit_number = @add_digit_number + 1
     end    
 end
---------------------------------------------------------------------


-- Generate Decimal Integer -----------------------------------------
select @dot_left_digits = len(@binary_float_string_dot_left_part)

select @decimal_integer = 0

select @j = @dot_left_digits

select @digit_counter = 1

while @j >= 1
 begin
    select @single_digit    = substring(@binary_float_string_dot_left_part, @digit_counter, 1)
    
    select @decimal_integer = @decimal_integer + power(2, @j - 1) * cast(@single_digit as tinyint)
    
    select @j = @j - 1
    
    select @digit_counter = @digit_counter + 1
 end
 --------------------------------------------------------------------


-- Generate Decimal Fraction ----------------------------------------
select @dot_right_digits = len(@binary_float_string_dot_right_part)

select @decimal_fraction = 0

select @k = 1

while @k <= @dot_right_digits
 begin
    select @single_digit     = substring(@binary_float_string_dot_right_part, @k, 1)

    if @single_digit <> 0
     begin

        if @k <= 30
         begin
            select @decimal_fraction = @decimal_fraction + (1 / (power (2, @k) * 1.0)) * cast(@single_digit as tinyint)
         end   
        else    -- exceed range
         begin
            select @decimal_fraction = @decimal_fraction + (1 / (power (2, 30) * 1.0)) * cast(@single_digit as tinyint)
         end       
     end

    select @k = @k + 1
 end
---------------------------------------------------------------------

if substring(@prm_IEEE_754_string, 1, 1) = '1'
   select @rtn_decimal_number = (@decimal_integer + @decimal_fraction) * -1
else
   select @rtn_decimal_number = (@decimal_integer + @decimal_fraction)

return