C#浮点数的表示和基本运算

1 浮点数的表示

通常,我们可以用下面的格式来表示浮点数

S P M

其中S是符号位,P是阶码,M是尾数

对于IBM-PC而言,单精度浮点数是32位(即4字节)的,双精度浮点数是64位(即8字节)的。两者的S,P,M所占的位数以及表示方法由下表可知

S P M 表示公式 偏移量
1 8 23 (-1)S*2(P-127)*1.M 127
1 11 52 (-1)S*2(P-1023)*1.M 1023

以单精度浮点数为例,可以得到其二进制的表示格式如下

S(第31位) P(30位到23位) M(22位到0位)

其中S是符号位,只有0和1,分别表示正负;P是阶码,通常使用移码表示(移码和补码只有符号位相反,其余都一样。对于正数而言,原码,反码和补码都一样;对于负数而言,补码就是其绝对值的原码全部取反,然后加1.)

为了简单起见,本文都只讨论单精度浮点数,双精度浮点数也是用一样的方式存储和表示的。

2 浮点数的表示约定

单精度浮点数和双精度浮点数都是用IEEE754标准定义的,其中有一些特殊约定。

(1) 当P = 0, M = 0时,表示0。

(2) 当P = 255, M = 0时,表示无穷大,用符号位来确定是正无穷大还是负无穷大。

(3) 当P = 255, M != 0时,表示NaN(Not a Number,不是一个数)。

当我们使用.Net Framework的时候,我们通常会用到下面三个常量

Console.WriteLine(float.MaxValue); // 3.402823E+38
Console.WriteLine(float.MinValue); //-3.402823E+38
Console.WriteLine(float.Epsilon);  // 1.401298E-45
//如果我们把它们转换成双精度类型,它们的值如下
Console.WriteLine(Convert.ToDouble(float.MaxValue)); // 3.40282346638529E+38
Console.WriteLine(Convert.ToDouble(float.MinValue)); //-3.40282346638529E+38
Console.WriteLine(Convert.ToDouble(float.Epsilon));  // 1.40129846432482E-45

那么这些值是如何求出来的呢?

根据上面的约定,我们可以知道阶码P的最大值是11111110(这个值是254,因为255用于特殊的约定,那么对于可以精确表示的数来说,254就是最大的阶码了)。尾数的最大值是11111111111111111111111。

那么这个最大值就是:0 11111110 11111111111111111111111。

也就是 2(254-127) * (1.11111111111111111111111)2 = 2127 * (1+1-2-23) = 3.40282346638529E+38

从上面的双精度表示可以看出,两者是一致的。最小的数自然就是-3.40282346638529E+38。

对于最接近于0的数,根据IEEE754的约定,为了扩大对0值附近数据的表示能力,取阶码P = -126,尾数 M = (0.00000000000000000000001)2 。此时该数的二进制表示为:0 00000000 00000000000000000000001

也就是2-126 * 2-23 = 2-149 = 1.40129846432482E-45。这个数字和上面的Epsilon是一致的。

如果我们要精确表示最接近于0的数字,它应该是 0 00000001 00000000000000000000000

也就是:2-126 * (1+0)  =  1.17549435082229E-38。

3 浮点数的精度问题

浮点数以有限的32bit长度来反映无限的实数集合,因此大多数情况下都是一个近似值。同时,对于浮点数的运算还同时伴有误差扩散现象。特定精度下看似相等的两个浮点数可能并不相等,因为它们的最小有效位数不同。

由于浮点数可能无法精确近似于十进制数,如果使用十进制数,则使用浮点数的数学或比较运算可能不会产生相同的结果。

如果涉及浮点数,值可能不往返。值的往返是指,某个运算将原始浮点数转换为另一种格式,而反向运算又将转换后的格式转换回浮点数,且最终浮点数与原始浮点数相等。由于一个或多个最低有效位可能在转换中丢失或更改,往返可能会失败。

4 将浮点数表示为二进制

4.1 无小数的浮点数转换成二进制表示

首先,我们用一个不带小数的浮点数来说明如何将一个浮点数转换成二进制表示。假设要转换的数据是45678.0f。

在处理这种不带小数的浮点数时,直接将整数部分转化为二进制表示:

1011001001101110.0,这时要加上一位默认的1(这是因为按照浮点数规格化的要求,尾数必须化成 1.M的格式),

那么可以表示成:11011001001101110.0。

然后将小数点向左移,一直移到离最高位只有1位,也就是 1.1011001001101110,一共移动了16位,我们知道,左移位表示乘法,右移位表示除法。所以原数就等于这样:1.1011001001101110 * ( 216 )。现在尾数和指数都出来了。因为最高位的1是根据标准加上去的,只是为了满足规格化的要求,这时候需要把这个1去掉。尾数的二进制就变成了:1011001001101110。

最后在尾数的后面补0,一直到补够23位,就是:10110010011011100000000。

再回来看指数,根据前面的定义,P-127=16,那么P = 143,表示成二进制就是:10001111。

45678.0f这个数是正的,所以符号位是0,那么我们按照前面讲的格式把它拼起来,就是:0 10001111 10110010011011100000000。

这就是45678.0f这个数的二进制表示,如果我们要得到16进制的表示,非常简单,我们只需要把这个二进制串4个一组,转换成16进制数就可以了。但是要注意的是x86架构的CPU都是Little Endian的(也就是低位字节在前,高位字节在后),所以在实际内存中该数字是按上面二进制串的倒序存储的。要知道CPU是不是little endian的也很容易。

BitConverter.IsLittleEndian;

4.2 含小数的浮点数表示为二进制

对于含小数的浮点数,会有精度的问题,下面举例说明。假设要转换的小数为123.456f。

对于这种带小数的就需要把整数部和小数部分开处理。对于整数部分的处理不再赘述,直接化成二进制为:100100011。小数部份的处理比较麻烦一些,我们知道,使用二进制表示只有0和1,那么对于小数就只能用下面的方式来表示:

a1*2-1+a2*2-2+a3*2-3+......+an*2-n

其中a1等数可以是0或者1,从理论上将,使用这种表示方法可以表示一个有限的小数。但是尾数只能有23位,那么就必然会带来精度的问题。

在很多情况下,我们只能近似地表示小数。来看0.456这个十进制纯小数,该如何表示成二进制呢?一般说来,我们可以通过乘以2的方法来表示。

首先,把这个数字乘以2,小于1,所以第一位为0,然后再乘以2,大于1,所以第二位为1,将这个数字减去1,再乘以2,这样循环下去,直到这个数字等于0为止。

在很多情况下,我们得到的二进制数字都大于23位,多于23位的就要舍去。舍入原则是0舍1入。通过这样的办法,我们可以得到二进制表示:1111011.01110100101111001。

现在开始向左移小数点,一共移了6位,这时候尾数为:1.11101101110100101111001,阶码为6加上127得131,二进制表示为:10000101,那么总的二进制表示为:

0  10000101  11101101110100101111001

表示成十六进制是:42  F6  E9  79

由于CPU是Little Endian的,所以在内存中表示为:79  E9  F6  42。

4.3 将纯小数表示成二进制

对于纯小数转化为二进制来说,必须先进行规格化。例如0.0456,我们需要把它规格化,变为1.xxxx * (2n )的形式,要求得纯小数X对应的n可用下面的公式:
n = int( 1 + log 2X )

0.0456我们可以表示为1.4592乘以以2为底的-5次方的幂,即1.4592 * ( 2-5 )。转化为这样形式后,再按照上面处理小数的方法处理,得到二进制表示

1. 01110101100011100010001

去掉第一个1,得到尾数

01110101100011100010001

阶码为:-5 + 127 = 122,二进制表示为

0  01111010  01110101100011100010001

最后转换成十六进制
11 C7 3A 3D

5 浮点数的数学运算

5.1 浮点数的加减法

设两个浮点数 X=Mx*2Ex ,Y=My*2Ey

实现X±Y要用如下5步完成:

(1)对阶操作:小阶向大阶看齐
(2)进行尾数加减运算
(3)规格化处理:尾数进行运算的结果必须变成规格化的浮点数,对于双符号位(就是使用00表示正数,11表示负数,01表示上溢出,10表示下溢出)的补码尾数来说,就必须是
001×××…×× 或110×××…××的形式
若不符合上述形式要进行左规或右规处理。
(4)舍入操作:在执行对阶或右规操作时常用“0”舍“1”入法将右移出去的尾数数值进行舍入,以确保精度。
(5)判结果的正确性:即检查阶码是否溢出

若阶码下溢(移码表示是00…0),要置结果为机器0;
若阶码上溢(超过了阶码表示的最大值)置溢出标志。

现在用一个具体的例子来说明上面的5个步骤

例题:假定X=0 .0110011*211,Y=0.1101101*2-10(此处的数均为二进制), 计算X+Y;

首先,我们要把这两个数变成2进制表示,对于浮点数来说,阶码通常用移码表示,而尾数通常用补码表示。

要注意的是-10的移码是00110
        [X]浮: 0        1 010  1100110
        [Y]浮: 0        0 110  1101101
                   符号位 阶码   尾数

(1)求阶差:│ΔE│=|1010-0110|=0100

(2)对阶:Y的阶码小,Y的尾数右移4位
        [Y]浮变为 0 1 010 0000110 1101暂时保存

(3)尾数相加,采用双符号位的补码运算
     00 1100110
   +00 0000110
     00 1101100

(4)规格化:满足规格化要求

(5)舍入处理,采用0舍1入法处理

故最终运算结果的浮点数格式为: 0 1 010 1101101

即X+Y=+0. 1101101*210

5.2 浮点数的乘除法

(1)阶码运算:阶码求和(乘法)或阶码求差(除法)
    即  [Ex+Ey]移= [Ex]移+ [Ey]补
          [Ex-Ey]移= [Ex]移+  [-Ey]补
(2)浮点数的尾数处理:浮点数中尾数乘除法运算结果要进行舍入处理

例题:X=0 .0110011*211,Y=0.1101101*2-10  求X*Y

解:[X]浮: 0 1 010 1100110
        [Y]浮: 0 0 110 1101101

(1)阶码相加
[Ex+Ey]移=[Ex]移+[Ey]补=1 010+1 110=1 000
1 000为移码表示的0

(2)原码尾数相乘的结果为:
0 10101101101110

(3)规格化处理:已满足规格化要求,不需左规,尾数不变,阶码不变。

(4)舍入处理:按舍入规则,加1进行修正

所以 X※Y= 0.1010111*20

/******************************************************************************************
*【Author】:flyingbread
*【Date】:2007年3月2日
*【Notice】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
*4、本文参考了网络上的若干资料,不一一列举,但是一并致谢。
******************************************************************************************/

(0)

相关推荐

  • 浅谈c# 浮点数计算

    给大家看个计算题,看看大家的算术能力. 0.1 +0.1 +0.1 - 0.3 等于几? 大家可能会说这么简单的问题,是不是看不起我?肯定等于0啊. 如果大家直接算的是没有问题的,但是如果用计算机呢? 见证奇迹的时刻到了,看代码: void Main() { var f = 0.1 +0.1 +0.1 -0.3; Console.WriteLine("f=={0}",f); } 运行结果: 这是因为计算机的精度的问题,在计算机的内部存储和运算的精度缺失等问题,我解释的可能不太明白,不过

  • C#实现将浮点数表示的货币数量以汉字大写形式输出的方法

    本文实例讲述了C#实现将浮点数表示的货币数量以汉字大写形式输出的方法.分享给大家供大家参考.具体如下: 1.函数代码 注:本段代码中能转化的最大的数为(1亿亿-0.01),如果要转化更大的浮点数,则需要做适当修改. /// <summary> /// 将字符型变量转化为大写汉语数字 /// </summary> /// <param name="ch">字符 '0'~'9'</param> /// <returns></

  • C#浮点数的表示和基本运算

    1 浮点数的表示 通常,我们可以用下面的格式来表示浮点数 S P M 其中S是符号位,P是阶码,M是尾数 对于IBM-PC而言,单精度浮点数是32位(即4字节)的,双精度浮点数是64位(即8字节)的.两者的S,P,M所占的位数以及表示方法由下表可知 S P M 表示公式 偏移量 1 8 23 (-1)S*2(P-127)*1.M 127 1 11 52 (-1)S*2(P-1023)*1.M 1023 以单精度浮点数为例,可以得到其二进制的表示格式如下 S(第31位) P(30位到23位) M(

  • Java中BigDecimal的基本运算(详解)

    BigDecimal一共有4个够造方法,让来看看其中比较常用的两种用法: 第一种:BigDecimal(double val) Translates a double into a BigDecimal. 第二种:BigDecimal(String val) Translates the String repre sentation of a BigDecimal into a BigDecimal. 使用BigDecimal要用String来够造,要做一个加法运算,需要先将两个浮点数转为Str

  • Java中浮点数精度问题的解决方法

    问题描述 在项目中用Java做浮点数计算时,发现对于4.015*100这样的计算,结果不是预料中的401.5,而是401.49999999999994.如此长的位数,对于显示来说很不友好. 问题原因:浮点数表示 查阅相关资料,发现原因是:计算机中的浮点数并不能完全精确表示.例如,对于一个double型的38414.4来说,计算机是这样存储它的: 转成二进制:1001011000001110.0110011001100110011001100110011001100 转成科 学计数法:1.0010

  • Python入门教程1. 基本运算【四则运算、变量、math模块等】 原创

    在熟悉了Python的基本安装与环境配置之后,我们来看看Python的基本运算操作. 1. 基本运算 >>>6 # 这里的'#'是注释符号,不参与运算 6 >>>666666666666666 #整数类型,原样输出 666666666666666 >>>3.14 #浮点数类型 3.14 >>>id(6) #id()函数用于查看内存地址 1409471616 >>>help(id) #help()函数可用于查看函数文档

  • C/C++的浮点数在内存中的存储方式分析及实例

    C/C++的浮点数在内存中的存储方式分析 任何数据在内存中都是以二进制的形式存储的,例如一个short型数据1156,其二进制表示形式为00000100 10000100.则在Intel CPU架构的系统中,存放方式为  10000100(低地址单元) 00000100(高地址单元),因为Intel CPU的架构是小端模式.但是对于浮点数在内存是如何存储的?目前所有的C/C++编译器都是采用IEEE所制定的标准浮点格式,即二进制科学表示法. 在二进制科学表示法中,S=M*2^N 主要由三部分构成

  • perl简单变量 整型 浮点数 字符串

    基本上,简单变量就是一个数据单元,这个单元可以是数字或字符串.一.整型 1.整型   PERL最常用的简单变量,由于其与其它语言基本相同,不再赘述.   例:   $x = 12345;   if (1217 + 116 == 1333) {   # statement block goes here   }  整型的限制:   PERL实际上把整数存在你的计算机中的浮点寄存器中,所以实际上被当作浮点数看待.在多数计算机中,浮点寄存器可以存贮约16位数字,长于此的被丢弃.整数实为浮点数的特例.2

  • python使用正则搜索字符串或文件中的浮点数代码实例

    用python和numpy处理数据次数比较多,写了几个小函数,可以方便地读写数据: # -*- coding: utf-8 -*- #---------------------------------------------------------------------- # FileName:gettxtdata.py #功能:读取字符串和文件中的数值数据(浮点数) #主要提供类似matlab中的dlmread和dlmwrite函数 #同时提供loadtxtdata和savetxtdata函

  • Python中的浮点数原理与运算分析

    本文实例讲述了Python中的浮点数原理与运算.分享给大家供大家参考,具体如下: 先看一个违反直觉的例子: >>> s = 0. >>> for i in range(10): s += .1 >>> s 0.9999999999999999 # 错误被累加 再看一个更为普遍,直接影响判断逻辑的例子: >>> from math import sqrt >>> a = sqrt(2) >>> a*a

  • 深入理解JavaScript中的浮点数

    js只有一种数值型数据类型,不管是整数还是浮点数,js都把归为数字. typeof 17;   // "number" typeof 98.6; // "number" typeof –2.1; // "number" js中的所有数字都是双精度浮点数.是由IEEE754标准制定的64位编码数字(这个是什么东东,不知道,回头查一下吧) 那么js是如何表达整数的,双精度浮点数可以完美地表示高达53位精度的整数(没有什么概念,没处理过多大的数据,没用

  • javascript基于牛顿迭代法实现求浮点数的平方根【递归原理】

    本文实例讲述了javascript基于牛顿迭代法实现求浮点数的平方根.分享给大家供大家参考,具体如下: 今天在网上看到一则利用牛顿迭代法求浮点数的平方根的方法,发现很好,比一些语言自带的sqrt方法运行要快,在这里备份一下,以待后用,这里稍微做了些改动. 首先是牛顿迭代法原理: 比如我们要求a的平方根,首先随便猜一个近似值x,然后不断令x等于x和a/x的平均数,迭代几次后x的值就已经相当精确了. 如我们要求的数学假设为 a=7, var x=a; ( 7  + 7/7 ) / 2 = 3.642

随机推荐