• IEEE754、VAX、IBM浮点型介绍和.NET中互相转换


    【题外话】

    最近在做C3D文件的解析,好奇怪的是文件中竟然存储了CPU的类型,原本不以为然,结果后来读取一个文件发现浮点数全部读取错误。查了下发现虽然在上世纪80年代就提出了IEEE754要统一浮点数标准,但是到现在仍然有计算机采用不同方式存储浮点数。在某些非IEEE754标准的计算机产生的二进制文件中,如果拿到其他计算机中读取,如果不进行专门的转换,可能导致数据错误等问题。

    【文章索引】

    1. IEEE754标准浮点数字的存储详解
    2. VAX及IBM浮点数字的存储和转换
    3. 双精度浮点数的处理

    【一、IEEE754标准浮点数字的存储详解】

    对于x86等常见的CPU,都是采用IEEE754存储和计算浮点型的,当然在.NET平台中浮点型也是IEEE754标准的。首先回顾下本科时学过的计算机组成原理,查了下课本发现是如下介绍IEEE754浮点数的存储的(唐朔飞版课本233页):

    其中,S为数符,它表示浮点数的正负,但与其有效位(尾数)是分开的。阶码用移码表示,阶码的真值都被加上一个常数(偏移量),如短实数、长实数和临时实数的偏移量用十六进制表示分别为7FH、3FFH和3FFFH。尾数部分通常都是规格化表示,即非“0”的有效位最高位总是1。

    以单精度浮点数为例,如果字节查看应该是如下这个样子的,数符占第1字节的第1位,阶码占第1字节的后7位及第二字节的第1位,其余都是尾数。

    SEF      S        EEEEEEEE        FFFFFFF        FFFFFFFF        FFFFFFFF
    bits     1        2      9        10                                    32
    bytes    byte1           byte2                   byte3           byte4

    如果设数符为S,阶码为E,尾数的小数部分为F,那么可以通过位运算得到这三位:

    Double S = (byte1 & 0x80) >> 7;
    Double E = ((byte1 & 0x7F) << 1) + ((byte2 & 0x80) >> 7);
    Double F = ((byte2 & 0x7F) << 16) + (byte3 << 8) + byte4;

    由于阶码用移码表示,所以真实的阶码则是E - 0x7F。而尾数由于是规格化的表示,即将尾数规范化为(1.FFFFF……FF)2,但只存小数点之后的部分。由于1 / 2 + 1 / 4 + 1 / 8 + ... + 1 / n = 1 - 1 / 2n,所以可知尾数M(M = 1.0 + F)的范围为1 <= M <= 2 - 1 / 223

    所以可通过如下的公式来计算浮点数的值,其中,C是尾数规范化后减去的常量,B是移码的偏移量,可知A、B、C分别为A = 2、B = 0x7F以及C = 1.0。

    V = (-1)^S * (F + C) * A^(E - B)

    可见,浮点数就不存在0的概念了,所以只能用极限小来表示,同时为了表示无穷大,规定E取值范围为0 < E < 0xFF,即-0x7F < (E - B) < 0x80。

    所以,当E = 0xFF时,指数最大,规定F = 0时为无穷值,其中又有S = 0为正无穷以及S = 1为负无穷;而F != 0时为无效数字(NaN)。

    当E = 0时,指数最小,规定F = 0时为0,其中又有S = 0为正0以及S = 1时为-0。

    不过表示非常小的数字,允许当E = 0时非规范化的尾数存在。即当E = 0且F !=0时,V = (-1)^S * F * A^-126。

    二进制表示 十六进制表示 含义 十进制表示
    0 11111111 00000000000000000000000 7F 80 00 00 正无穷 +∞ 
    1 11111111 00000000000000000000000 FF 80 00 00 负无穷 -∞ 
    0 00000000 00000000000000000000000 00 00 00 00 +0 0
    1 00000000 00000000000000000000000 80 00 00 00 -0 0
    0 00000000 00000000000000000000001 00 00 00 01  最小正数  1.401298E-45
    0 11111110 11111111111111111111111 7F 7F FF FF 最大值 3.402823E+38
    1 11111110 11111111111111111111111 FF 7F FF FF 最小值 -3.402823E+38
    0 01111111 00000000000000000000000
    3F 80 00 00
    +1 1

    而二进制小数转十进制小数的计算可以直接按整数的转换来做,然后除以2n即可,n在这里其实就是尾数的长度,为23。

    所以,有了以上的这些信息,我们就可以将浮点数字与字节数组相互转换了(本文假定给定的字节数组都是Litten-Endian):

     1 Single ToSingle(Byte[] data)
     2 {
     3     Double a = 2.0;
     4     Double b = 127.0;
     5     Double c = 1.0;
     6     Double d = -126.0;
     7 
     8     Byte byte1 = data[3];
     9     Byte byte2 = data[2];
    10     Byte byte3 = data[1];
    11     Byte byte4 = data[0];
    12 
    13     Double s = (byte1 & 0x80) >> 7;
    14     Double e = ((byte1 & 0x7F) << 1) + ((byte2 & 0x80) >> 7);
    15     Double f = ((byte2 & 0x7F) << 16) + (byte3 << 8) + byte4;
    16     Double m = f / Math.Pow(2, 23);
    17 
    18     if (e == 0xFF && f == 0) return (s == 0 ? Single.PositiveInfinity : Single.NegativeInfinity);
    19     if (e == 0xFF && f != 0) return Single.NaN;
    20     if (e == 0x00 && f == 0) return 0;
    21     if (e == 0x00 && f != 0) return (Single)((s == 0 ? 1.0 : -1.0) * m * Math.Pow(a, d));
    22 
    23     return (Single)((s == 0 ? 1.0 : -1.0) * (c + m) * Math.Pow(a, e - b));
    24 }
    25 
    26 Byte[] GetBytes(Single num)
    27 {
    28     Double a = 2.0;
    29     Double b = 127.0;
    30     Double c = 1.0;
    31     Double d = Math.Log(2);
    32 
    33     Int32 s = (num >= 0 ? 0 : 1);
    34 
    35     Double v = Math.Abs(num);
    36     Int32 e = (Int32)(Math.Log(v) / d + b);
    37 
    38     Double m = (v / Math.Pow(a, e - b)) - c;
    39     Int32 f = (Int32)(m * Math.Pow(2, 23));
    40 
    41     Byte[] data = new Byte[4];
    42     data[3] = (Byte)((s << 7) + ((e & 0xFE) >> 1));
    43     data[2] = (Byte)(((e & 0x01) << 7) + ((f & 0x007F0000) >> 16));
    44     data[1] = (Byte)((f & 0x0000FF00) >> 8);
    45     data[0] = (Byte)(f & 0x000000FF);
    46 
    47     return data;
    48 }

    上述的浮点数转字节数组不能支持NaN和非规范化的情况,当然也可以自己判断下。当然了,上边说了这么多还是为了介绍下边两种浮点数做铺垫。如果实现系统浮点数与字节数组转换的话,用上边这种方法转换就不如用System.BitConverter来的方便了。

    【二、VAX及IBM浮点数字的存储和转换】

    首先还是按字节看下VAX和IBM浮点型的存储:

    VAX单精度浮点:

    SEF         S        EEEEEEEE        FFFFFFF        FFFFFFFF        FFFFFFFF
    bits        1        2      9        10                                    32
    bytes       byte2           byte3                   byte0           byte1

    IBM单精度浮点:

    SEF         S        EEEEEEE        FFFFFFFF        FFFFFFFF        FFFFFFFF
    bits        1        2     8        9                                      32
    bytes       byte1                   byte2           byte3           byte4

    非常有意思的是,VAX存储的结构并不是按顺序存储的,而是采用了一种叫做Middle-Endian的存储方式来存储(并非字节序):对于四字节而言其顺序就是2301,八字节为23016745,十六字节为23016745AB89EFCD。不过总体来说,VAX浮点型与IEEE754还是很类似的,比如VAX也要进行规范化,但是其规范化为(0.1FFFFF..FF)2,所以上述的C就为0.5,其尾数M的范围即为1/2 <= M <= 1 - 1 / 224;而同时其也并没有规定无穷大,不需要单独为无限大留出最大的阶码,所以上述的B为0x80。

    而IBM单精度浮点则与上述两种差别更大。首先其阶码并不是8位,而是7位,由于还是使用移码存储的阶码,所以其减去的不能是127或者128,而是64,所以其与VAX一样,也没有无穷值的表示。除此之外,其也不是以2为底计算阶码的,而是以16为底,并且其没有规范化尾数的要求(当然这也与其以16为底有关),所以不需要对尾数进行加减运算,其范围为1/16 <= M <= 1- 1 / 224

    以下是实现VAX浮点字节数组与系统浮点数字相互转化的类:

     1 using System;
     2 
     3 namespace DotMaysWind.Numerics
     4 {
     5     /// <summary>
     6     /// VAX单精度浮点数字
     7     /// </summary>
     8     /// <remarks>
     9     /// SEF         S        EEEEEEEE        FFFFFFF        FFFFFFFF        FFFFFFFF
    10     /// bits        1        2      9        10                                    32          
    11     /// bytes       byte2           byte1                   byte4           byte3
    12     /// </remarks>
    13     public struct VAXSingle
    14     {
    15         #region 常量
    16         private const Int32 LENGTH = 4;
    17         private const Double BASE = 2.0;
    18         private const Double EXPONENT_BIAS = 128.0;
    19         private const Double MANTISSA_CONSTANT = 0.5;
    20         private const Double E24 = 16777216.0;
    21         #endregion
    22 
    23         #region 字段
    24         private Byte[] _data;
    25         #endregion
    26 
    27         #region 构造方法
    28         /// <summary>
    29         /// 初始化新的VAX单精度浮点数字
    30         /// </summary>
    31         /// <param name="data">VAX单精度浮点数字字节数组</param>
    32         /// <param name="startIndex">数据起始位置</param>
    33         public VAXSingle(Byte[] data, Int32 startIndex)
    34         {
    35             this._data = new Byte[VAXSingle.LENGTH];
    36             Array.Copy(data, startIndex, this._data, 0, VAXSingle.LENGTH);
    37         }
    38 
    39         /// <summary>
    40         /// 初始化新的VAX单精度浮点数字
    41         /// </summary>
    42         /// <param name="num">系统标准的单精度浮点数字</param>
    43         public VAXSingle(Single num)
    44         {
    45             Int32 s = (num >= 0 ? 0 : 1);
    46 
    47             Double v = Math.Abs(num);
    48             Int32 e = (Int32)(Math.Log(v) / Math.Log(2.0) + 1.0 + VAXSingle.EXPONENT_BIAS);
    49 
    50             Double m = (v / Math.Pow(VAXSingle.BASE, e - VAXSingle.EXPONENT_BIAS)) - VAXSingle.MANTISSA_CONSTANT;
    51             Int32 f = (Int32)(m * VAXSingle.E24);
    52 
    53             this._data = new Byte[VAXSingle.LENGTH];
    54             this._data[1] = (Byte)((s << 7) + ((e & 0xFE) >> 1));
    55             this._data[0] = (Byte)(((e & 0x01) << 7) + ((f & 0x007F0000) >> 16));
    56             this._data[3] = (Byte)((f & 0x0000FF00) >> 8);
    57             this._data[2] = (Byte)(f & 0x000000FF);
    58         }
    59         #endregion
    60 
    61         #region 方法
    62         /// <summary>
    63         /// 获取系统标准的单精度浮点数字
    64         /// </summary>
    65         /// <returns>系统标准的单精度浮点数字</returns>
    66         public Single ToSingle()
    67         {
    68             Byte b1 = this._data[1];
    69             Byte b2 = this._data[0];
    70             Byte b3 = this._data[3];
    71             Byte b4 = this._data[2];
    72 
    73             Double s = (b1 & 0x80) >> 7;
    74             Double e = ((b1 & 0x7F) << 1) + ((b2 & 0x80) >> 7);
    75             Double f = ((b2 & 0x7F) << 16) + (b3 << 8) + b4;
    76             Double m = f / VAXSingle.E24;
    77 
    78             if (e == 0 && s == 0) return 0;
    79             if (e == 0 && s == 1) return Single.NaN;
    80 
    81             return (Single)((s == 0 ? 1.0 : -1.0) * (VAXSingle.MANTISSA_CONSTANT + m) * Math.Pow(VAXSingle.BASE, e - VAXSingle.EXPONENT_BIAS));
    82         }
    83 
    84         /// <summary>
    85         /// 获取VAX单精度浮点数据字节数组
    86         /// </summary>
    87         /// <returns>字节数组</returns>
    88         public Byte[] ToArray()
    89         {
    90             Byte[] data = new Byte[VAXSingle.LENGTH];
    91 
    92             Array.Copy(this._data, data, VAXSingle.LENGTH);
    93 
    94             return data;
    95         }
    96         #endregion
    97     }
    98 }
    View Code

    以下是实现IBM浮点字节数组与系统浮点数字相互转化的类:

     1 using System;
     2 
     3 namespace DotMaysWind.Numerics
     4 {
     5     /// <summary>
     6     /// IBM单精度浮点数字
     7     /// </summary>
     8     /// <remarks>
     9     /// SEF         S        EEEEEEE        FFFFFFFF        FFFFFFFF        FFFFFFFF
    10     /// bits        1        2     8        9                                      32
    11     /// bytes       byte1                   byte2           byte3           byte4
    12     /// </remarks>
    13     public struct IBMSingle
    14     {
    15         #region 常量
    16         private const Int32 LENGTH = 4;
    17         private const Double BASE = 16.0;
    18         private const Double EXPONENT_BIAS = 64.0;
    19         private const Double E24 = 16777216.0;
    20         #endregion
    21 
    22         #region 字段
    23         private Byte[] _data;
    24         #endregion
    25 
    26         #region 构造方法
    27         /// <summary>
    28         /// 初始化新的IBM单精度浮点数字
    29         /// </summary>
    30         /// <param name="data">IBM单精度浮点数字字节数组</param>
    31         /// <param name="startIndex">数据起始位置</param>
    32         public IBMSingle(Byte[] data, Int32 startIndex)
    33         {
    34             this._data = new Byte[IBMSingle.LENGTH];
    35             Array.Copy(data, startIndex, this._data, 0, IBMSingle.LENGTH);
    36         }
    37 
    38         /// <summary>
    39         /// 初始化新的IBM单精度浮点数字
    40         /// </summary>
    41         /// <param name="num">系统标准的单精度浮点数字</param>
    42         public IBMSingle(Single num)
    43         {
    44             Int32 s = (num >= 0 ? 0 : 1);
    45 
    46             Double v = Math.Abs(num);
    47             Int32 e = (Int32)(Math.Log(v) / Math.Log(2.0) / 4.0 + 1.0 + IBMSingle.EXPONENT_BIAS);
    48 
    49             Double m = (v / Math.Pow(IBMSingle.BASE, e - IBMSingle.EXPONENT_BIAS));
    50             Int32 f = (Int32)(m * IBMSingle.E24);
    51 
    52             this._data = new Byte[IBMSingle.LENGTH];
    53             this._data[3] = (Byte)(s + e);
    54             this._data[2] = (Byte)((f & 0x00FF0000) >> 16);
    55             this._data[1] = (Byte)((f & 0x0000FF00) >> 8);
    56             this._data[0] = (Byte)(f & 0x000000FF);
    57         }
    58         #endregion
    59 
    60         #region 方法
    61         /// <summary>
    62         /// 获取系统标准的单精度浮点数字
    63         /// </summary>
    64         /// <returns>系统标准的单精度浮点数字</returns>
    65         public Single ToSingle()
    66         {
    67             Byte b1 = this._data[3];
    68             Byte b2 = this._data[2];
    69             Byte b3 = this._data[1];
    70             Byte b4 = this._data[0];
    71 
    72             Double s = (b1 & 0x80) >> 7;
    73             Double e = (b1 & 0x7F);
    74             Double f = (b2 << 16) + (b3 << 8) + b4;
    75             Double m = f / IBMSingle.E24;
    76 
    77             if (e == 0 && f == 0 && s == 0) return 0;
    78 
    79             return (Single)((s == 0 ? 1.0 : -1.0) * m * Math.Pow(IBMSingle.BASE, e - IBMSingle.EXPONENT_BIAS));
    80         }
    81 
    82         /// <summary>
    83         /// 获取IBM单精度浮点数据字节数组
    84         /// </summary>
    85         /// <returns>字节数组</returns>
    86         public Byte[] ToArray()
    87         {
    88             Byte[] data = new Byte[IBMSingle.LENGTH];
    89 
    90             Array.Copy(this._data, data, IBMSingle.LENGTH);
    91 
    92             return data;
    93         }
    94         #endregion
    95     }
    96 }
    View Code

    【三、双精度浮点数的处理】

    双精度浮点数与单精度浮点数类似,只不过会扩大阶码和尾数的范围罢了。对于IEEE754的双精度浮点而言,不仅尾数的位数增加,还会增加阶码的尾数,字节存储如下:

    SEF    S     EEEEEEE EEEE  FFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
    bits   1     2          12 13                                                       64
    bytes  byte1         byte2      byte3    byte4    byte5    byte6    byte7    byte8

    可见,其阶码增加了3位,即最大值是原来翻了3翻,为1024。而为了保证能表示无穷值,所以B为1023。除此之外只需要多读取后边增加的尾数即可,步骤与单精度基本相同。

    而对于VAX和IBM的双精度浮点,更是没有扩大阶码的范围,而只是扩大了尾数的范围,使得只要多读取增加的4位尾数即可,而常数A、B、C更是无需修改。两者字节存储如下:

    VAX双精度浮点:

    SEF    S     EEEEEEEE     FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
    bits   1     2      9     10                                                          64
    bytes  byte2        byte3         byte0    byte1    byte6    byte7    byte4    byte5

    IBM双精度浮点:

    SEF    S     EEEEEEE  FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
    bits   1     2     8  9                                                            64
    bytes  byte1          byte2    byte3    byte4    byte5    byte6    byte7    byte8

    【相关链接】

    1. Transform between IEEE, IBM or VAX floating point number formats and bytes expressions:http://www.codeproject.com/Articles/12363/Transform-between-IEEE-IBM-or-VAX-floating-point-n
    2. VAX F_FLOAT and D_FLOAT to IEEE T_FLOAT and S_FLOAT (double):http://yaadc.blogspot.com/2013/01/vax-ffloat-and-dfloat-to-ieee-tfloat.html
    3. IEEE Arithmetic:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_math.html
    4. Floating-Point:http://andromeda.rutgers.edu/~dupre/231/lecture13.doc
    5. IBM Floating Point Architecture:http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture
    6. VAX floating point to Decimal:http://arstechnica.com/civis/viewtopic.php?f=20&t=171682
  • 相关阅读:
    顺序容器添加,查询,删除元素
    使用fiddler对app做弱网测试
    工作总结
    软件测试面试题_3
    软件测试面试题_2
    软件测试面试题_1
    MySQL的下载及安装
    关于let以及var的区别
    关于获取各种浏览器可见窗口大小的一点点研究
    log4J指定类下面的日志分隔
  • 原文地址:https://www.cnblogs.com/mayswind/p/3365594.html
Copyright © 2020-2023  润新知