IEEE 754 之间的转换

Converting between binary and decimal representations of IEEE 754 floating-point numbers in C++, Java and Python

This post implements a previous post that explains how to convert 32-bit floating point numbers to binary numbers in the IEEE 754 format. What we have is some C++ / Java / Python routines that will allows us to convert a floating point value into it’s equivalent binary counterpart, using the standard IEEE 754 representation consisting of the sign bit, exponent and mantissa (fractional part).

Conversely, there is also a function that converts a string encoding of the IEEE 754 binary number and converts it back to a floating point value. I had been meaning to implement something like this for some time, as there are one or two applications I have in mind for which these conversions would be useful: Genetic Algorithms operating on floating point numbers for example.

Without further ado, please find code snippets and example outputs below:

C++ source code

#include <limits.h>
#include <iostream>
#include <math.h>
#include <bitset>

// Convert the 32-bit binary encoding into hexadecimal
int Binary2Hex( std::string Binary )
{
	std::bitset<32> set(Binary);		
	int hex = set.to_ulong();
	
	return hex;
}

// Convert the 32-bit binary into the decimal
float GetFloat32( std::string Binary )
{
	int HexNumber = Binary2Hex( Binary );

	bool negative  = !!(HexNumber & 0x80000000);
	int  exponent  =   (HexNumber & 0x7f800000) >> 23;	
	int sign = negative ? -1 : 1;

	// Subtract 127 from the exponent
	exponent -= 127;

	// Convert the mantissa into decimal using the
	// last 23 bits
	int power = -1;
	float total = 0.0;
	for ( int i = 0; i < 23; i++ )
	{
		int c = Binary[ i + 9 ] - '0';
		total += (float) c * (float) pow( 2.0, power );
		power--;
	}
	total += 1.0;

	float value = sign * (float) pow( 2.0, exponent ) * total;

	return value;
}

// Get 32-bit IEEE 754 format of the decimal value
std::string GetBinary32( float value )
{
	union
    {
         float input;   // assumes sizeof(float) == sizeof(int)
         int   output;
    }    data;

	data.input = value;

	std::bitset<sizeof(float) * CHAR_BIT>   bits(data.output);

	std::string mystring = bits.to_string<char, 
		                                  std::char_traits<char>,
										  std::allocator<char> >();

	return mystring;
}


int main()
{
	// Convert 19.5 into IEEE 754 binary format..
	std::string str = GetBinary32( (float) 19.5 );
	std::cout << "Binary equivalent of 19.5:" << std::endl;
	std::cout << str << std::endl << std::endl;

	// .. and back again
    float f = GetFloat32( str );
	std::cout << "Decimal equivalent of " << str << ":" << std::endl;
	std::cout << f << std::endl;

	return 0;
}

Giving us the following output:

IEEE754cpp

When float data types are unavailable

As pointed out by ‘johnsmithx’ in the comments below, what if you don’t have floats available, such as when using Meta Trader 4? Here is how to emulate it using doubles and ints, which I’ve posted here using the nice formatting that is as yet unavailable in the comments boxes:

int DoubleToBinary32(double value)
{
  int minus = 0, integer, exponent = 127, fraction = 0, i, result;

  if(value < 0) { minus = 0x80000000; value = -value; }
  integer = floor(value);
  value -= integer;
  for(i = 22; i >= 0; i--)
    {
    value += value;
    fraction += floor(value) * pow(2, i);
    value -= floor(value);
    }
  while((integer != 1) && (exponent > 0) && (exponent < 255))
    {
    if(integer > 1)
      {
      fraction = (integer&1)<<22 + fraction>>1;
      integer = integer>>1;
      exponent++;
      }
    else
      {
      integer = (fraction&0x400000)>>22;
      fraction = (fraction&0x3FFFFF)<<1;
      value += value;
      fraction += floor(value);
      value -= floor(value);
      exponent--;
      }
    }
  result = minus + exponent<<23 + fraction;
  return(result);
}

double Binary32ToDouble(int value)
{
  int minus = -1, exponent;
  double fraction, result;

  if(value&0x80000000 == 0) minus = 1;
  exponent = ((value&0x7F800000)>>23) - 127;
  fraction = value&0x7FFFFF + 0x800000;
  fraction = fraction / 0x800000;
  result = minus * fraction * pow(2, exponent);
  return(result);
}

Java source code

Of course, with Java this is all a lot simpler, since the conversions are already pretty much done for us:

public class main {
		
	// Convert the 32-bit binary into the decimal  
	private static float GetFloat32( String Binary )  
	{  
		int intBits = Integer.parseInt(Binary, 2);
		float myFloat = Float.intBitsToFloat(intBits);
	    return myFloat;  
	} 
	
	// Get 32-bit IEEE 754 format of the decimal value  
	private static String GetBinary32( float value )  
	{  
		int intBits = Float.floatToIntBits(value); 
		String binary = Integer.toBinaryString(intBits);
		return binary;
	}  	

	/**
	 * @param args
	 */
	public static void main(String[] args) 
	{
		// Convert 19.5 into IEEE 754 binary format..  
	    String str = GetBinary32( (float) 19.5 );  
	    System.out.println( "Binary equivalent of 19.5:" );  
	    System.out.println( str );  
	  
	    // .. and back again  
	    float f = GetFloat32( str );  
	    System.out.println( "Decimal equivalent of " + str + ":");  
	    System.out.println( f );  		
	}
}

Giving the following output:

Binary equivalent of 19.5:
1000001100111000000000000000000
Decimal equivalent of 1000001100111000000000000000000:
19.5

Python source code

Remember that in Python floats are represented by IEEE 754 floating-point format which are 64 bits long – not 32 bits.

import struct

getBin = lambda x: x > 0 and str(bin(x))[2:] or "-" + str(bin(x))[3:]

def floatToBinary64(value):
    val = struct.unpack('Q', struct.pack('d', value))[0]
    return getBin(val)

def binaryToFloat(value):
    hx = hex(int(value, 2))   
    return struct.unpack("d", struct.pack("q", int(hx, 16)))[0]

# floats are represented by IEEE 754 floating-point format which are 
# 64 bits long (not 32 bits)

# float to binary
binstr = floatToBinary64(19.5)
print('Binary equivalent of 19.5:')
print(binstr + '\n')

# binary to float
fl = binaryToFloat(binstr)
print('Decimal equivalent of ' + binstr)
print(fl)

This gives the following console output:

Binary equivalent of 19.5:
100000000110011100000000000000000000000000000000000000000000000

Decimal equivalent of 100000000110011100000000000000000000000000000000000000000000000
19.5

For more number system conversions in C++, see this post.


IEEE-754 标准是 IEEE 邀请 William Kahan 教授作为顾问帮忙设计的处理器浮点数标准,目前几乎所有计算机都支持这一标准,它大大提高了科学应用程序尤其是针对浮点数的程序在不同机器上的可移植性。

首先我们应该明白,计算机存储数字是以 bit 位为基本元素存储的二进制数字,举个例子,在一个 8 位处理器上,存储器存储整数 7 的方式如下

76543210
00000111

目前广泛使用的 32 位处理器和 64 位处理器都是这样存储整数的,当然其中还涉及到 大端小端 问题,为了便于讨论,后面将一律采取大端法。

而二进制小数的书写方式也可以从二进制整数中类推而来,对于一个二进制小数 abc.def,它的值定义为

abc.de = a * 2^(2) + b * 2^(1) + c * 2^(0) + d * 2^(-1) + e * 2^(-2)

这里可以看出,二进制小数不能准确表达十进制中的小数,特别是对于不是2的幂次的小数,是无法通过有限个二进制位精确表示的,所以只能采取近似的方式表达。

同时,由于存储器上的每一个 bit 位都只有 0 和 1 两个值,计算机对小数点的存储和识别就成为了一个难题,而 IEEE-754 标准则采取了巧妙的方式解决了这个问题。

IEEE-754 标准

浮点数表达式

IEEE-754 标准将任意一个浮点数(包含小数部分的数字)通过下面的公式表达

V = (-1)^s * M * 2^E
  • 符号-s 决定正负
  • 尾数-M 一个二进制小数
  • 阶码-E 对浮点数加权

存储一个浮点数

在处理器中,一个内存单元被划分成下面三部分用来存放一个浮点数

对于单精度浮点格式(32 位)

3130 - 2322 - 0
sexpfrac

对于双精度浮点格式(64 位)

6362 - 5251 - 0
sexpfrac

其中包含三个字段:

  • 1 个单独的 s 符号位
  • k 位阶码字段,与 E 相关
  • n 位小数字段,与尾数 M 相关

以下我们以单精度浮点数为例进行讨论。单精度浮点数定义了 1 位符号位,8 位阶码字段,23 位小数字段。

根据阶码字段的不同,我们可以将浮点数分为 4 类:

1. 规格化浮点数
3130 - 2322 - 0
s!= 0 & != 255frac
2. 非规格化浮点数
3130 - 2322 - 0
s00000000frac
3. 无穷大
3130 - 2322 - 0
s11111111000……000
4. NaN(Not a Number)
3130 - 2322 - 0
s11111111!= 0

规格化浮点数

对于规格化浮点数,阶码值 E = e - Bias , 其中 e 就是阶码段表示的数字,而 Bias = 2^k-1^ - 1 ,在单精度中是127,双精度中是1023。因此单精度中阶码值 E 的范围是 [-126, +127],双精度中阶码值 E 的范围是 [-1022, +1023]。

对于小数字段 frac,形式为 f~n-1~ f~n-2~ f~n-3~ …… f~2~ f~1~ f~0~ ,它所表示的二进制值是 f = 0.f~n-1~ f~n-2~ f~n-3~ …… f~2~ f~1~ f~0~ 。而尾数 M = f + 1 。

可以看出,其实这里我们将浮点数首位默认为 1,所以没有显式地存储在存储器中。之所以能够这样做,是因为我们可以通过调整阶码 E 的值,使得二进制小数部分落在 1 和 2 之间,从而可以获得一个额外的精度位。

举个例子,对于一个浮点数 0.10111,原本我们需要存储 10111 五位小数位,我们可以表示为

1.0111 * 2^(-1)

这样我们其实只需要存储 0111 四位就可以,因为我们默认了浮点数的首位为 1。

非规格化浮点数

对于非规格化符段数,定义阶码值 E = 1 - Bias ,定义尾数 M = f = 0.f~n-1~ f~n-2~ f~n-3~ …… f~2~ f~1~ f~0~ 。

特殊值

剩下两种特殊值,无穷大和 NaN 的表示比较简单,这里就不说明了。

表示范围

首先要明白有限位的浮点数在数轴上的分布是稀疏的,至于具体的分布情况,可以参考这个问题 计算机中的浮点数在数轴上分布均匀吗?

而标准可以表示的范围,在单精度下,是 [-2 ^ (127), 2 ^ (127)],双精度下是 [-2 ^ (1023), 2 ^ (1023)]。

同时我们也可以知道,规范化浮点数表示的是绝对值大于 2 ^ (-126) 或 2 ^ (-1022) 的数字,非规范化浮点数表示的则是小于 2 ^ (-126) 或 2 ^ (-1022) 的值。

标准实现

三类转换过程

对于一个给定的十进制数字 input,根据 IEEE-754 标准,我们又可以分成三种情况,为了方便讨论,我们定义 input > 0,在单精度下表示。

1. input > 1

显而易见,应该用规范化浮点数来表示。

这里我们首先确定 input 的二进制形式的整数位数,从而确定阶码应该为多少,然后计算出阶码存储值和小数段。

举个例子,假设 input = 8.25,那么其二进制形式为 1000.01,要转换为 1.000001 * 2 ^ (3) 的形式,故阶码值为 3,阶码存储值为 e = 3 + 127 = 130,转换为二进制形式为 1000010,小数部分则为 00001

2. input < 1 && input > 2 ^ (-126)

这里依然要用规范化浮点数来表示。

首先定位 input 的二进制形式中,小数部分第一个 1 出现的位置,从而可以确定阶码值。

举个例子,假设 input = 0.25,那么它的二进制形式为 0.01,所以要表示成 1.0 * 2 ^ (-2) 的形式,故阶码值为 -2,阶码存储值为 e = -2 + 127 = 125 ,转换为二进制形式为 01111101,小数部分全为零。

3. input < 2 ^ (-126)

此时我们需要用非规范化浮点数表示,由于阶码值固定为 -126,所以其实只要想办法把 input 转换为 f * 2 ^ (-126) 的形式,再把 f 转换为二进制就可以了。

验证转换过程

想知道我们的实现是否正确,最简单的方式就是逆向转换过程,通过二进制浮点数字符串,看看能否计算出我们的输入值。

Java代码实现(基于单精度浮点格式)

    private static final String intervalChar = " "; //二进制浮点字符串中的间隔符,便于查看和处理
    private static long E = 0;//指数值
    private static final long bias = 127;//指数偏移值
    private static long e = 0;//十进制指数存储值 = 指数值 + 指数便宜值

    public static void main(String[] args) {
        String binaryFloatPointArray = "";//二进制浮点字符串

        //不同的输入值
        String input = "0.000000000000000000000000000000000000002";
        /*String input = "0.987654321";*/
        /*String input = "8.0";*/

        String[] temp = input.split("\\.");//用小数点分割输入值
        long integerPart = Long.parseLong(temp[0]);
        double decimalPart = Double.parseDouble("0." + temp[1]);
        String binaryIntegerString = Long.toBinaryString(integerPart);//十进制整数转换为二进制整数字符串
        String binaryDecimalString = doubleToBinaryString(decimalPart);//十进制小数转换为二进制小数字符串
        binaryFloatPointArray = getFloatPointArray(binaryIntegerString, binaryDecimalString);//获得二进制浮点字符串
        System.out.println("Float Point Array: " + binaryFloatPointArray);

        System.out.println("Original Input: " + getOriginalInput(binaryFloatPointArray));//逆向获得二进制浮点字符串对应的十进制小数值
    }


    /**
     * 输入:二进制整数字符串,二进制小数字符串
     * 输出:IEEE 754标准的二进制浮点数字符串
    **/
    private static String getFloatPointArray(String binaryIntegerString, String binaryDecimalString){
        String result = "";
        if (!binaryIntegerString.equals("0")){ //输入值 > 1
            E = binaryIntegerString.length() - 1;//获得小数点前移的位数
            e = E + bias;//十进制指数存储值
            result = "0" + intervalChar
                    + autoCompleteBinaryExponentArray(Long.toBinaryString(e)) +
                    intervalChar
                    + autoCompleteBinaryDecimalArray(binaryIntegerString.substring(1, binaryIntegerString.length()) + binaryDecimalString);
        } else {
            if (binaryDecimalString.indexOf("1") >= (126 - 1)){ //输入值 <= 2^(-125)
                result = "0"
                        + intervalChar
                        + "00000000"
                        + intervalChar
                        + autoCompleteBinaryDecimalArray(binaryDecimalString.substring(126, binaryDecimalString.length()));
            } else { //输入值介于 2^(-125) 与 1 之间
                E = binaryDecimalString.indexOf("1") + 1;
                e = 0 - E + bias;
                result = "0"
                        + intervalChar
                        + autoCompleteBinaryExponentArray(Long.toBinaryString(e))
                        + intervalChar
                        + autoCompleteBinaryDecimalArray(binaryDecimalString.substring((int) E, binaryDecimalString.length()));
            }
        }
        return result;
    }


    /**
     * 输入:二进制浮点数字符串
     * 输出:double类型的十进制小数值
     ***/
    private static double getOriginalInput(String floatPointArray){
        String[] results = floatPointArray.split(intervalChar);
        double originInput = 0.0;
        if (results[1].equals("00000000")){ //非规格化值
            originInput = binaryStringToDouble(results[2]) * Math.pow(2, -126);
        } else if (!results[1].equals("11111111")){ //规格化值
            originInput = (binaryStringToDouble(results[2]) + 1) * Math.pow(2, Integer.valueOf(results[1], 2) - bias);
        }
        return originInput;
    }

    private static String doubleToBinaryString(double input){
        String result = "";
        int temp = 0;
        for (int i = 0; i < 150; i++){
            temp = (int) (input * 2);
            input = input * 2 - (double)temp;
            result += temp;
        }
        return result;
    }

    private static double binaryStringToDouble(String input){
        double output = 0;
        for (int i = 0; i < input.length(); i++){
            output += (Double.parseDouble(String.valueOf(input.charAt(i)))) /(Math.pow(2, i + 1));
        }
        return output;
    }

    private static String autoCompleteBinaryExponentArray(String input){
        String temp = "00000000";//8 zeros
        if (input.length() > 8){
            System.out.println("Overflow Error in Exponent Part");
        }
        return temp.substring(0, 8 - input.length()) + input;
    }

    private static String autoCompleteBinaryDecimalArray(String input){
        String temp = "00000000000000000000000";//23 zeros
        if (input.length() > 23){
            return input.substring(0, 23);
        } else {
            return input + temp.substring(0, 23 - input.length());
        }
    }

代码里有一些工具类方法,具体包括 double 值与二进制字符串的相互转换、输出字符串的自动补全等。

主要的转换过程就是 getFloatPointArray() 和 getOriginalInput(),前者获得十进制输入值的二进制浮点数字符串,后者获得二进制浮点字符串对应的原十进制输入数字。

接下来看看运行情况

  • input = "8.125"

输出如下

Float Point Array: 0 10000010 00000100000000000000000
Original Input: 8.125
  • input = "0.987654321"

输出如下

Float Point Array: 0 01111110 11111001101011011101001
Original Input: 0.9876542687416077
  • input = "0.000000000000000000000000000000000000003" (小数点后 38 个 0)

输出如下

Float Point Array: 0 00000000 01000001010101011000111
Original Input: 2.9999992446175354E-39

补充

IEEE 754 浮点表示标准有许多有趣的特性,比如前面提到的在数轴上的分布,还有对于 0 的表示、浮点数的偶数舍入、浮点数运算等等,可以对比计算机对于整数的表示方法。

浮点数数据格式有许多种,IEEE754标准的浮点数与IBM格式浮点数用的比较多,它们数据结构与表示的范围等存在较大的差异,关于这连个不同格式数据间的转换有许多不同的实现方法与手段,有一些相关资料如下:

 

1.32 bit IEEE floating format

分三部分:符号位(sign)占1bit,指数部分(exp)占8bits,尾数部分(x)占23bits.

所表示的十进制数值result = (-1)^sign * ( 1 + x / ( 2^23 )   )   * 2 ^ ( exp -127 )

2. 32 bit IBM floating format

分三部分: 符号位(sign)占1 bit, 指数部分(exp )占7bits, 尾数部分(mant)占24 bits.

所得数值result = (-1)^sign * ( mant / (2^24) ) * 16 ^ ( exp - 64 )

3.   十进制浮点数转换为32 bit IEEE floating format

void Form32BitFloat::num2ieee( float dec)

{

      int sign,e;

     uint x;

     // 符号位:负数取1,其他取0。:

     sign =   dec<0?1:0;

        

     //    abs(dec) :

     dec= dec* pow(-1, sign);   

    float d1;        // integer part of float :

     d1=(float)(int )dec;      

     double d2;       // other part of float :

     d2=(double )(dec - d1);

     // gain e : 指数

     int   e0=0;

     int d1d = d1;

     if ( dec >0   ) //非0值才有必要计算

     {

if (d1d >= 1)// d1 will shift right :

{

      while ( d1d>1)

      {

   d1d=(int)d1d/2;

   e0++;

      }

}   

else //   d2 will shift left :

{

      d2d=d2;

      while ((int)d2d!=1)

      {

   d2d*=2;

   e0--;            

      }

}

     }

     e= e0 + 127;

   

     // gain x :

     float x0;

     x0 = dec * pow(2,-e0) - 1 ;

     x = x0 * pow(2, 23 ) ;

   

     if (dec==0)      {x=0 ; e = 0 ;}     //0值特殊对待

   

     // merge sign,e,x:

    ulong result ;

     if ( sign==0)

result = sign*pow(2,31) + e*pow(2,23) + x ;//正值以原码形式存放

     else

result =   (~ e) *pow(2,23) + (~x )    +1 ;// 负值以补码形式存放

}

4. 32 bit IEEE floating format 转换为十进制浮点数

void Form32BitFloat::pushButtonIeee2Decimal_clicked()

{

    ulong ieee;

     ieee=lineEditIEEEFloatInt->text().toULong();

     int sign; //符号

     sign =( ieee& 0x80000000 ) *pow(2,-31);

     if (sign ==1)// for value < 0 :

ieee =~( ieee&0x7fffffff ) ;//负数则为补码

     int e;//指数

     e=(   ieee & 0x7f800000 ) * pow(2,-23) - 127 - sign   ;

     uint x ; //尾数

     x = ieee & 0x007fffff   -sign   ; // - sign : for value < 0

     float x0 ;

     x0 = x* pow(2,-23);

     float result;

     if ( x0 ==0 && e + 127 ==0 ) //0值特殊对待

            result = 0;      

    else

             result = pow(-1,sign)*(1+x0)*pow(2,e);

}

5. 十进制数转 32 bit IBM floating format

void Form32BitFloat::num2ibm(float input)

{

      long sign;//符号

     sign =   ( input<0?1:0 ) ;

      long exp;//指数

      float input1 ; // attention : cannot use   long input1;

     input   = input * pow(-1, sign);// abs(input)

     exp=0;

     input1 = input;

     if (input >0 )    // 非0值才计算

     {

        if( (int)input>0)

       {

           exp++;

           while   ((int) input1/16 > 0)

          {

             exp++;

             input1= input1/16;

           }

      }

     else

       {

               while ( (int)input1*16 ==0)

              {

                    exp--;

                    input1=input1*16;

              }

             exp++;// attention :    ibm fmant   :     0.mant   not 1.mant !

      }

    }

     long e;

     e = (   exp + 64 ) ;

   

      double     fm = input * pow(16,-exp);

   

     long fmant=(long) (   fm * pow(2,24) ) ;//尾数

     ulong result ;

     result = ( sign<<31) | (   e <<24   )    |   fmant ;     

}

6. IBM 转十进制数

void Form32BitFloat::pushButtonIbm2decimal_clicked()

{   

     ulong DataUint32;      

     DataUint32 = lineEditIBMFloatInt->text().toULong();

         // gain sign from first bit

     double   sign = (double )( DataUint32 >>31) ;     

     // gain exponent from first byte, last 7 bits

     double   exp0 = (double) ( (   DataUint32 &0x7f000000 )   >>24) ;

        // remove bias from exponent  

     double   exp =(double   )(exp0   - 64 )   ;  

     // gain mantissa from last 3 bytes

     double frac = ( double )( DataUint32 &0x00ffffff   ) ;

     double fmant = frac/ (pow(2,24) ) ;

     float   result = ( 1-2*sign)*( pow( 16 ,exp) ) *fmant;

}

7. IEEE to IBM

void Form32BitFloat::ieee2ibm(ulong fconv)

{

     int endian;

     endian=checkBoxBigEndian->isChecked();  

     ulong result ;

     ulong fmant;

     ulong   fff ;  

     fff=0;

     long sign;

     long t0,t ;

     long   exp;   long mant ;

     if (fconv)

     {

          sign = ( 0x80000000 & fconv ) >> 31;

          fmant = (0x007fffff & fconv ) | 0x00800000 ;

          t0 = 0x7f800000 & fconv ;

           t = (long )(t0 >> 23 ) - 126;

          while ( t & 0x3 ) { ++t; fmant >>=1;}

         exp =   t>>2;

           fff = ( 0x80000000 & fconv ) | ((( t>>2) + 64 ) << 24 ) | fmant ;

     }

     if ( endian==0)

           fff=(fff<<24) | ((fff>>24)&0xff) |

                 (( fff&0xff00)<<8) | ((fff&0xff0000)>>8);   

     result = fff;

}

8. 特别说明

以上各算法经过正负整数、小数、0的测试后,发现除IEEE2IBM算法外其他算法皆测试正确。

9. 遗留问题

IEEE2IBM算法对正数可以正确转换,但负数却转换错误!有待进一步分析



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值