CnPack Forum


 
Subject: MicroTip#5 内存字节转为浮点数
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-22 16:51  Profile | Blog | P.M.  | QQ
MicroTip#5 内存字节转为浮点数

//====================================================================
//MicroTip#5 内存字节转为浮点数
// Test in Delphi6 up2
// Wrtten by SkyJacker 2007.03.22
// QQ Discuss Group: 130970
// 注:本人发布的源码,仅供参考,不保证无Bug。
// 非常欢迎讨论 Code 或 Bug 问题。
// 您出了个优化版本后,别忘了发给我一份啊。谢谢! 我的 QQ: 6705517
//====================================================================

函数需求:内存字节转为浮点数
方法实现:IEEE标准754 或 类型转换
采用方法:IEEE标准754

类型定义:
  //4内存字节转为浮点数Single
  TByte4 = packed record
    F1: Byte;
    F2: Byte;
    F3: Byte;
    F4: Byte; //1-4 由低字节到高字节
  end;
  //8字节 64 位的浮点数有必要定义一个数组
  //注意Btyte8 数组索引小的存放高字节
  TByte8 = array[1..64] of Byte; //字节由 高 到 低 便于计算
  
{
函数功能:将4个内存字节(低位在前,高位在后)转为浮点数 Single
创建人:SkyJacker
创建日期:2006-09-22 14:17:45
修改记录:常量需要优化 bias
函数来历:IEEE标准754:浮点数表示----单精度
单精度:N共32位,其中S占1位,E占8位,M占23位。
公式:n=(-1)^s * 2^e * m
n,s,e,m     分别为N,S,E,M对应的实际数值,而N,S,E,M仅仅是一串二进制位。
S(sign)     表示N的符号位。对应值s满足:n>0时,s=0; n<0时,s=1。
E(exponent) 表示N的指数位,位于S和M之间的若干位。对应值e值也可正可负。
M(mantissa) 表示N的尾数位,恰好,它位于N末尾。M也叫有效数字位(sinificand)、
            系数位(coefficient), 甚至被称作“小数”。
}
function ByteToFloat(const Bytes: TByte4): Single;
const
  k = 8; //幂位数 Single=8
var
  SByte: Byte; //含有符号位的字节
  //公式相关
  EByte: Byte; //存放幂E
  MByte: Byte; //二进制位暂存
  n: Single; //结果
  s: Integer; //符号位数字表示 +1 -1
  e: Integer; //幂值
  m: Single; //位数值
  //子公式相关
  //规格化
  bias: Integer; //偏置
  En: Integer; //幂的值
  i: Integer; //位数 m 23位
begin
  //符号相关
  SByte := Bytes.F4;
  if (SByte and $80) = $00 then
    s := 1
  else
    s := -1;
  //初始化取值
  EByte := (SByte shl 1) or (Bytes.F3 shr 7); //取8位幂E
  bias := Pow(2, (k - 1)) - 1;
  En := EByte;
  //e     := En - bias;  //根据是否规格化判断
  //是否规格化判断
  if EByte = $00 then
  begin
     {
       当E的二进制位全部为0时,N为非规格化形式。此时e,m的计算都非常简单。
       此时: e:=1-bias;  m=|0.M|
       为什么e会等于(1-bias)而不是(-bias),
       这主要是为规格化数值、非规格化数值之间的平滑过渡设计的。
     }
    e := 1 - bias;
    m := 0; //m的初始值 非规格化
  end
  else if EByte = $FF then
  begin
    {
      特殊数值: 当E的二进制位全为1时为特殊数值。
      此时,若M的二进制位全为0,则n表示无穷大,
      若S为1则为负无穷大,若S为0则为正无穷大;
      若M的二进制位不全为0时,表示NaN(Not a Number),
      表示这不是一个合法实数或无穷,或者该数未经初始化。
    }
    {
     raise ERangeError.Create(#13 + '浮点数溢出或数据未初始化.' + #13);
     Single 1.5 x 10^?5 .. 3.4 x 10^38
    }
    Result := 0; //防止溢出或未初始化
    Exit;
  end
  else
  begin //规格化计算方法
    {
      当E的二进制位不全为0,也不全为1时,N为规格化形式。
      e=|E| - bias
      bias=2^(k-1) - 1
      k则表示E的位数,
      对单精度来说,k=8,则bias=127,
      对双精度来说,k=11,则bias=1023。
      |E|表示E的二进制序列表示的整数值,例如E为"10000100",则|E|=132,e=132-127=5。
      此时e被解释为表示偏置(biased)形式的整数
      m=|1.M|
      标准规定此时小数点左侧的隐含位为1,那么m=|1.M|。
      如M="101",则|1.M|=|1.101|=1.625,即 m=1.625
      总公式:n=(-1)^s * 2^e * m
    }
    e := En - bias;
    m := 1; //m的初始值 规格化
  end;
  //计算m  通过字节移位的方式
  MByte := Bytes.F3 shl 1; //去掉1个幂位,即幂位清0
  MByte := MByte shr 1; ; //再移回
  for I := 7 downto 1 do
  begin
    m := m + (MByte and $01) / pow(2, I); //2^(-7) + ~ + 2^(-1)
    MByte := MByte shr 1;
  end;
  //后面23-7位数小数不能忽略不计
  //第3个尾数字节
  MByte := Bytes.F2;
  for I := 15 downto 8 do
  begin
    m := m + (MByte and $01) / pow(2, I); //2^(-17) + ~ + 2^(-8)
    MByte := MByte shr 1;
  end;
  //第4个尾数字节  如果全为0,可以调过
  MByte := Bytes.F1;
  if MByte <> $00 then
  begin
    for I := 23 downto 16 do
    begin
      m := m + (MByte and $01) / pow(2, I); //2^(-23) + ~ + 2^(-16)
      MByte := MByte shr 1;
    end;
  end;
  n := s * Power(2, e) * m;
  Result := n;
end;


{
函数功能:将8个内存字节(低位在前,高位在后)转为浮点数 Double
创建人:SkyJacker
创建日期:2006-09-22 17:46:58
函数来历:IEEE标准754:浮点数表示----单精度
双精度:N共32位,其中S占1位,E占11位,M占52位。
公式:n=(-1)^s * 2^e * m
//注意Btyte8 数组索引小的存放高字节
}
function ByteToFloat(const Bytes: TByte8): Double;
const
  k = 11; //幂位数 Double=11
var
  TmpByte: Byte; //临时字节
  //公式相关
  n: Double; //结果
  s: Integer; //符号位数字表示 +1 -1
  e: Integer; //幂值
  m: Double; //位数值
  //子公式相关
  //规格化
  bias: Integer; //偏置
  En: Integer; //幂的值
  I: Integer; //位数 m 23位
  A: Integer; //3~8个字节
  B: Integer; //TBits偏移 从5开始
  Bits: TBits; //存放二进制位 E=11 M=52
  //判断是否规格化
  //Result=$00非规格化 =$FF 特殊数值 其他:规格化
  function NormalizedStatus(const Bits: TBits): Byte;
  var
    I: Integer;
    a, b: Integer;
  begin
    a := 0;
    b := 0;
    for I := 11 downto 1 do //Error:for I:=Bits.size(=12) downto 1 do
    begin
      if Bits.Bits[I] then
        Inc(b)
      else
        Inc(a);
    end;
    if (Bits.Size - 1) = a then //为什么Bits.Size大小变为12
      Result := $00
    else if (Bits.Size - 1) = b then
      Result := $FF
    else Result := $01; //规格化
  end;
begin
  //符号相关
  if (Bytes[1] and $80) = $00 then
    s := 1
  else
    s := -1;
  bias := pow(2, (k - 1)) - 1;
  //整理11位二进制幂值 E
  Bits := TBits.Create;
  Bits.Size := 11;
  TmpByte := Bytes[1];
  TmpByte := TmpByte shl 1; //移去符号位
  for I := 1 to 7 do
  begin
    Bits[I] := ((TmpByte and $80) = $80);
    TmpByte := TmpByte shl 1;
  end;
  TmpByte := Bytes[2];
  for I := 1 to 4 do
  begin
    Bits[I + 7] := ((TmpByte and $80) = $80);
    TmpByte := TmpByte shl 1;
  end;
  //求幂值
  En := 0;
  for I := 11 downto 1 do
  begin
    if Bits.Bits[12 - I] then
      En := En + pow(2, (I - 1));
  end;
  //e     := En - bias;  //根据是否规格化判断
  TmpByte := NormalizedStatus(Bits);
  //是否规格化判断
  if TmpByte = $00 then
  begin
    e := 1 - bias;
    m := 0; //m的初始值 非规格化
  end
  else if TmpByte = $FF then
  begin
    //特殊数值: 当E的二进制位全为1时为特殊数值。
    Result := 0; //防止溢出或未初始化
    Exit;
  end
  else
  begin //规格化计算方法
    e := En - bias;
    m := 1; //m的初始值 规格化
  end;
  //计算m  通过字节移位的方式
  //整理52位尾数二进制M
  Bits.Size := 52; //Size自动复值了以前11个值,由于重新赋值,因为不会影响结果。
  TmpByte := Bytes[2]; //5~8 bit
  TmpByte := TmpByte shl 4;
  for I := 1 to 4 do
  begin
    Bits[I] := ((TmpByte and $80) = $80);
    TmpByte := TmpByte shl 1;
  end;
  //3到8个字节位存入Bits
  B := 5;
  for A := 3 to 8 do
  begin
    TmpByte := Bytes[A];
    for I := 1 to 8 do
    begin
      Bits[B] := ((TmpByte and $80) = $80);
      TmpByte := TmpByte shl 1;
      Inc(B);
    end;
  end;
  //求m值
  for I := 1 to 52 do
  begin
    if Bits.Bits[I] then
      m := m + 1 / power(2, I);
  end;
  n := s * Power(2, e) * m;
  Bits.Free;
  Result := n;
end;


New:
写了个小程序,有兴趣可以测试一下(Exe + Source)。

[ 本帖最后由 skyjacker 于 2007-3-22 22:59 编辑 ]


Attachment: [Test Demo] FloatAsciiEx.rar (2007-3-22 22:40, 184.42 K)
Download count 432, Reading Access 1




一壶清茶煮青春.
Top
Passion (LiuXiao)
管理员
Rank: 9Rank: 9Rank: 9


UID 359
Digest Posts 19
Credits 6760
Posts 3556
点点分 6760
Reading Access 102
Registered 2004-3-28
Status Offline
Post at 2007-3-22 19:37  Profile | Blog | P.M. 
如果用PSingle等来进行强制类型转换的话会有什么副作用?
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-22 22:20  Profile | Blog | P.M.  | QQ
文中有处错误:
TByte8 = array[1..64] of Byte; //字节由 高 到 低 便于计算
应为:
TByte8 = array[1..8] of Byte




一壶清茶煮青春.
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-22 22:29  Profile | Blog | P.M.  | QQ
"如果用PSingle等来进行强制类型转换的话会有什么副作用?"

有点担心它的边界情况,测试了一下:

果然有要注意的地方:
var
  B: TByte8;
  C: TByte4;
  S: Single;
  F: Double;
begin
  //验证字节转浮点数
  //4内存字节
  //C=12.156
  //41 42 7E FA
  C.F1 := $FA;
  C.F2 := $7E;
  C.F3 := $42;
  C.F4 := $41;

  //8内存字节
  //B=1234.56;
  B[1] := $40;
  B[2] := $93;
  B[3] := $4A;
  B[4] := $3D;
  B[5] := $70;
  B[6] := $A3;
  B[7] := $D7;
  B[8] := $0A;

  B[1] := $FF;
  B[2] := $FF;
  B[3] := $FF;
  B[4] := $FF;
  B[5] := $FF;
  B[6] := $FF;
  B[7] := $FF;
  B[8] := $FF;


  Log('FFFFFF内存字节=' + FloatToStr(PDouble(@B[1])^));

  S := ByteToFloat(C);
  F := ByteToFloat(B);

  Log('4内存字节=' + Format(cnFloatFmt, [S]));
  Log('8内存字节=' + Format(cnFloatFmt, [F]));
end;

结果:
FFFFFF内存字节=NAN   // 这个结果用户或计算不会接受吧
4内存字节=12.156000137329100800
8内存字节=0.000000000000000000

因此:
即使使用强制转换,在不能保证内存数据的情况下,需要检测一下内存。

上面的函数处理了这种IEEE中叫做的非规格化和溢出处理,默认结果为零。
当然,也可以用自定义的特殊数字。
  //是否规格化判断
  if TmpByte = $00 then
  begin
    e := 1 - bias;
    m := 0; //m的初始值 非规格化
  end
  else if TmpByte = $FF then
  begin
    //特殊数值: 当E的二进制位全为1时为特殊数值。
    Result := 0; //防止溢出或未初始化
    Exit;
  end
  else
  begin //规格化计算方法
    e := En - bias;
    m := 1; //m的初始值 规格化
  end;




一壶清茶煮青春.
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-22 22:30  Profile | Blog | P.M.  | QQ
当然, Delphi 也没有错。它严格遵守了ieee。  比如NAN。
else if EByte = $FF then
  begin
    {
      特殊数值: 当E的二进制位全为1时为特殊数值。

      此时,若M的二进制位全为0,则n表示无穷大,
      若S为1则为负无穷大,若S为0则为正无穷大;
      若M的二进制位不全为0时,表示NaN(Not a Number),
      表示这不是一个合法实数或无穷,或者该数未经初始化。
    }
    {
     raise ERangeError.Create(#13 + '浮点数溢出或数据未初始化.' + #13);
     Single        1.5 x 10^?5 .. 3.4 x 10^38
    }
    Result := 0; //防止溢出或未初始化
    Exit;




一壶清茶煮青春.
Top
skyjacker
版主
Rank: 7Rank: 7Rank: 7
茶农


UID 2239
Digest Posts 9
Credits 617
Posts 269
点点分 617
Reading Access 100
Registered 2006-6-8
Status Offline
Post at 2007-3-22 22:33  Profile | Blog | P.M.  | QQ
IF FloatToStr(PDouble(@B[1])^ = 'NAN' then

这样判断一下也行啊。除了NAN,无穷大的情况,其他的都应该正常。




一壶清茶煮青春.
Top
kendling (小冬)
高级版主
Rank: 8Rank: 8
MyvNet


Medal No.1  
UID 703
Digest Posts 5
Credits 978
Posts 580
点点分 978
Reading Access 101
Registered 2005-2-18
Location 广东
Status Offline
Post at 2007-3-28 12:07  Profile | Site | Blog | P.M.  | QQ | Yahoo!
哈哈,好文章!!!




小冬
http://MyvNet.com
Top
 




All times are GMT++8, the time now is 2024-5-1 05:38

    本论坛支付平台由支付宝提供
携手打造安全诚信的交易社区 Powered by Discuz! 5.0.0  © 2001-2006 Comsenz Inc.
Processed in 0.011829 second(s), 8 queries , Gzip enabled

Clear Cookies - Contact Us - CnPack Website - Archiver - WAP