浮点数的输入以及浮点数运算

写在前面

上一次我们讲解了IEEE的标准,还记得多少?

之前我提到过,有很多小数是二进制浮点数无法表示的,因此就难免会遇到舍入的问题.这一点其实在我们平时的计算当中会经常出现,就比如我们之前提到过的0.3,就无法使用浮点小数来准确表示.

我使用C#写了一个程序,打印出0.3的二进制表示,是这样的一个数字:0 01111101 00110011001100110011010.不信没关系,用我昨天说的那个公式计算一下啊.这个二进制数大概是多少,它的阶码在偏置之后的值为-2,它的尾数位在加1之后为1+1/8+1/16+1/128+1/256=1.19921875.后面还有有效位,不过我们只是大概的计算一下,不用很精确,结果大概是0.29多.(我说这个玩意就是想证明一件事,十进制的小数无法用二进制来准确表示).

如何把带小数的十进制转换成二进制数?

让大哥教你们一招好办法,怎么简单的转换,以及怎么判别能否用给定的位数精确表示一个10进制小数。

以0.635为例,这个数能用二进制小数表示么,能的话需要多少位呢。

因为0.635*256=162.56,注意小数部分不为0。所以8位二进制小数无法精确表示。再看0.635*65536=41615.36。小数部分仍不为0,所以16也不能精确表示。

到这里相信大家也看出点什么了,看一个十进制小数能否用给定的n位二进制小数表示,就用那个十进制乘以2的n次方,看小数部分是否为零,是的话就能用n位精确表示,否的话就不能。

其实楼上的52.63可以转换的,8位表示小数部分,把0.63*256也就是2的8次方得到161.23,23不要,把161化成二进制表示为10100001,再连上52的,就成了1010010.10100001,这里用8位表示小数是表示不尽的,因为0.63*256=161.23,小数部分不为零。

浮点数的舍入

在我们平时使用的十进制当中,我们一般会对一个无理数或者有位数限制的有理数进行舍入时,大部分时候会采取四舍五入的办法,这算是一种比较符合我们期望的方式.

不过针对浮点数来说,我们的舍入方式会更丰富一些.一共有四种方式,分别是向偶数舍入,向零舍入,向上舍入,向下舍入.

这四种方式不难理解,其中向偶数舍入就是向最靠近的偶数舍入,比如将1.5舍入为2,将0.1舍入为0.而向零舍入则是向靠近零的值舍入,比如将1.5舍入为1,将0.1舍入为0.对于向上摄入来说,则是往大了(也就是正无穷大)摄入的意思,比如将1.5舍入为2,将-1.5舍入为-1.而向下舍入则与向上舍入相反,是向较小的值(也就是负无穷大)舍入的意思.

这里需要所明一点,除了向偶数舍入除外,,其他三种方式都会有明确的边界.这里的含义是指这三种方式舍入后的值x与舍入之前的值x’会有一个明确的大小关系,比如对于向上舍入来说,则一定有x<=x’.对于向下舍入来说,一定有|x|>=|x’|.

对于向偶数舍入来讲,它最大的作用是在统计时使用.向偶数舍入可以让我们在统计时,将舍入产生的误差平均,从而尽可能的抵消.而其他三种方式在这一方面都是有缺陷的,向上和向下舍入很明显会造成指的偏大或者偏小.对于向零舍入来说,如果全是正数的时候会造成结果偏小,全是负数会造成结果偏大.

通常情况下我们采取的舍入规则是在原来的值是舍入值的中间值时,采取向偶数舍入,在二进制中,偶数我们认为是末尾为0的数.而倘若不是这种情况的话,则一般会有选择性的使用向上和向下舍入,但总会向最接近的值舍入.其实这正是IEEE才去的默认的舍入方式,因为这种舍入方式总是企图向最近的值舍入.

比如对于10.10011这个值来讲,当舍入到个位数时,会采取向上舍入,因此此时的值为11.当舍入到小数点后1位时,会采取向下舍入,因此此时的值为10.1.当舍入到这个小数点后4位时,由于此时的10.10011舍入值的中间值,因此采取想偶数舍入,此时舍入后的值为10.1010.

程序当中的浮点数舍入

之前讲解了一堆舍入的方式,最终我们得出一个结论,就是IEEE标准默认的舍入方式,是企图向最近的值舍入.

之前我们已经详细的解释了IEEE标准中默认的舍入方式.但是估计还有人不是很明白,没关系,咱们看看具体的案例.

在看案例之前,暂满需要先了解一些中间值的概念.中间值就是指的,比如1.1(二进制)这个数字,假设要舍入到个位,那么他就是一个中间值,因为它处于1(二进制)和10(二进制)的中间,在这个时候将会采用向偶数舍入的方式.

public class Main{

public static void main(String[] args){

System.out.println("舍入前:         10.10011111111111111111101");

System.out.print("舍入后:");

printFloatBinaryString(2.62499964237213134765625f);

System.out.println();

System.out.println("舍入前:         10.10011111111111111111111");

System.out.print("舍入后:");

printFloatBinaryString(2.62499988079071044921875f);

System.out.println();

System.out.println("舍入前:         10.10011111111111111111101011");

System.out.print("舍入后:");

printFloatBinaryString(2.62499968707561492919921875f);

System.out.println();

System.out.println("舍入前:         10.10011111111111111111100011");

System.out.print("舍入后:");

printFloatBinaryString(2.62499956786632537841796875f);

System.out.println();

System.out.println("舍入前:        -10.10011111111111111111101");

System.out.print("舍入后:");

printFloatBinaryString(-2.62499964237213134765625f);

System.out.println();

System.out.println("舍入前:        -10.10011111111111111111111");

System.out.print("舍入后:");

printFloatBinaryString(-2.62499988079071044921875f);

System.out.println();

System.out.println("舍入前:        -10.10011111111111111111101011");

System.out.print("舍入后:");

printFloatBinaryString(-2.62499968707561492919921875f);

System.out.println();

System.out.println("舍入前:        -10.10011111111111111111100011");

System.out.print("舍入后:");

printFloatBinaryString(-2.62499956786632537841796875f);

System.out.println();

}

public static void printFloatBinaryString(Float f){

char[] binaryChars = getBinaryChars(f);

for (int i = 0; i < binaryChars.length; i++) {

System.out.print(binaryChars[i]);

if (i == 0 || i == 8) {

System.out.print(" ");

}

}

System.out.println();

}

public static char[] getBinaryChars(Float f){

char[] result = new char[32];

char[] binaryChars = Integer.toBinaryString(Float.floatToIntBits(f)).toCharArray();

if (binaryChars.length < result.length) {

System.arraycopy(binaryChars, 0, result, result.length - binaryChars.length, binaryChars.length);

for (int i = 0; i < result.length - binaryChars.length; i++) {

result[i] = ‘0‘;

}

}else {

result = binaryChars;

}

return result;

}

}

代码从别人那里copy的,JAVA这么火,你不会不了解吧..反正我是不了解.

分析一下:上面一共有8次舍入,前四次是整数,后四次是负数.可以看出来对于负数来说,舍入后的位表示是一样的.只是最高位的符号位不同而已,因此这里就不再分析下面四个复数的舍入方式了,主要来看前四次舍入.

第一次和第二次对于末尾01和11的舍入,由于是中间只,因此全部采取向偶数舍入的方式,保证最低位为0.第三次由于比中间值大,,而数值又是整数,因此采取向上摄入的方式,第四次则比中间值小,数值同样也是整数,因此采取向下舍入的方式.

因为本屌最近在搞C#,使用了C#的代码演示了一遍,采用的是同样的舍入方式.

浮点数运算

在IEEE标准中,指定了关于浮点数的运算规则,就是我们将两个浮点数运算后的精确结果的舍入值,作为我们最终的运算结果.正是因为有了这一个特殊点,造成了浮点数运算中,很多运算不满足我们平时熟知的一些运算特性.

比如加法的结合律:a+b+c=a+(b+c),这是很简单的加法特性,但是浮点数不满足这一点,案例如下:

public static void main(String[] args){        System.out.println(1f + 10000000000f - 10000000000f);        System.out.println(1f + (10000000000f - 10000000000f));    }

这一段代码会依次输出0.0和1.0,正是因为舍入而造成了这一误差.在第一条输出语句中,计算1f+1000000000f时,会将1这个有效数值舍入掉,而导致最终结果为0.0.而在第二个输出语句中10000000000f-10000000000f将先得到结果0.0,因此最终的结果为1.0,因此最终结果为1.0.

(JAVA代码在这里就证明了浮点数运算不满足结合律这一特性,我用了C#代码反而两个输出的结果一样呢?证明不了浮点数不满足结合律呢?我狠狠的给了自己一个大嘴巴子....)

有问题得解决啊,于是乎我开始了下面的扯犊子..

在使用JAVA的时候,

public class Test

{

public static void main(String[] args)

{

double x = 0.01;

double y = 0.09;

System.out.println(x + y);

}

}

为什么输出结果是0.09999999999999999而不是0.1啊?

奇怪的是当x,y改为float后,结果就等于0.1了,

更奇怪的是,如果把x,y分别改为float的0.01和0.04,在相加,结果居然是0.049999997,

这种浮点运算不精确的背后原理到底是什么呢?

可是我在使用C#的时候:

class Test

{

static void Main()

{

double x = 0.01;

double y = 0.09;

Console.WriteLine(x + y);

Console.ReadKey();

}

}

结果是0.1,为啥呢?

第一点,你要明确java里面32位的float对0.1在内存当中的表示是不精确的,不是你理解的那么100%精确,抛弃这个概念,产生这个误差的原因简单理解在32位的float的表达能力有限和计算机的二进制缘故吧,往下说也很复杂。

大体说下,float的0.1二进制形式是001111011 10011001100110011001101,根据符号位换算为10进制表达的值精确应该是这样计算 110011001100110011001101乘以2的负27次方,实际值是0.100000001490116119384765625

这样就产生了实际误差

这个误差对我们生活小打小闹没啥影响,但是对科学计算和银行这样的应用或者领域是致命的,因此要用Java银行以及科学计算会用java.math.BigDecimal提高精度,否则后果极其严重。

看一段C#代码:

float Fvar = 1f;            double Dvar = (double)Fvar;            Console.WriteLine("Float Var={0};Double Var={1}", Fvar, Dvar);             float Flt = 0.9f;            double Dbl = (double)Flt;            Console.WriteLine("Float Var={0};Double Var={1}", Flt, Dbl);

即在C#中,当float转换为double时,若float数值不带小数,可得出正确值;但包含小数时,会得到一个近似的“超级”小数。

1、这是由于小数精度引起的,小数转换二进制是乘2取整的,但是对于那些永远也乘不完的数,比如你这个0.9,那么它会一直取下去知道位数满了。所以肯定是不准确的。

2、float转double需要补位,这样就有误差了。举个例子,float的精度可能为0.001,而double的精度可能为0.00001这样转换后可能会有0.001 - 0.00001的误差。

所以应该使用decimal

看代码:

float Fvar = 1f;             double Dvar = (double)Fvar;             Console.WriteLine("Float Var={0};Double Var={1}", Fvar, Dvar);             float Flt = 0.9f;             double Dbl = (double)Flt;             decimal Dec = (decimal)Flt;             Console.WriteLine("Float Var={0};Double Var={1};Decimal Var={2}", Flt, Dbl,Dec);             Console.ReadLine();

其实相应的,浮点数的乘法也不满足结合律,就是说:a*b*c!=a*(b*c),同时也不满足分配律,即a*(b+c)!=a*b+a*c.浮点数失去了运算数的很多方面的特性,因此也导致很多优化手段无法进行,比如我们试图优化这样一段代码(以JAVA为例,因为JAVA的实现具有代表性).

/*   优化前       */

float x = a + b + c;

float y = b + c + d;

/*   优化后       */

float t = b + c;

float x = a + t;

float y = t + d;

对于优化前的代码来讲,进行了4次浮点运算,而优化后则是3次。然而这种优化是编译器无法进行的,因为可能会引入误差,比如就像前面的小例子中的结果0和1一样。编译器在此时一般是不敢进行优化的,试想一下,如果是银行系统的汇款或者收款等功能,如果编译器进行优化的话,很可能一不小心就把别人的钱给优化掉了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-02 13:17:00

浮点数的输入以及浮点数运算的相关文章

输入一个浮点数,并输出该数的整数部分和小数部分

package javaapplication29; import java.util.Scanner;import java.util.StringTokenizer; /** * * @author qingzhu */public class JavaApplication29 { /** * @param args the command line arguments */ public static void main(String[] args) { String[] mess={"

浮点数(有限浮点数、无限循环浮点数)的精确表达

在计算机中,是否decimal或者float或者double来存储小数是不能得到精确值得.如果你希望能得到精确的计算结果,最好是用分数形式来表示小数.有限小数或者无限循环小数都可以转化为分数. 例如: 0.9=9/10 0.333(3)=1/3 给定一个小数,它的形式为0.34.0.30.0.33(33)......形如这些的小数把它们转化成分数形式(最简分数). 首先我们先定义一个分数类和小数类 1 /// <summary> 2 /// 分数类 3 /// </summary>

浮点数的输入/输出

#include <stdio.h> #include <math.h> #define PI 3.1415927 int main() { double r,vol; while (scanf ("%lf",&r)!=EOF) { vol=4.0/3*PI*r*r*r;//There is 4.0,isn't 4!! printf ("%.3f\n",vol); } return 0; }这里必须是4.0而不是4,如果是4的话将不能

字符串 到浮点数 整数 又到浮点数

sprintf  比较好用 float  2  char 用sprintf() 就可以了: 例如,float,double 到 char #include<stdio.h> #include<stdlib.h> void main() { float f= 1234.5; double d= 789.8765; char sf[20],sd[20]; sprintf(sf,"%f",f); // float 到 char sprintf(sd,"%lf

C语言中整数上溢,浮点数上溢,浮点数下溢问题

1.整数上溢的问题:在我的系统中int最大值为2147483647 ,如果出现整数上溢,运行结果为: 如果是无符号整数,出现整数上溢,运行结果为: 2.如果是浮点型,例如系统的最大值是3.4e38 ,如果*100,就会发生浮点数的上溢.本机运行结果为: 如果出现下溢,本机运行结果为: 出现NaN

程序员必知之浮点数运算原理详解

导读:浮点数运算是一个非常有技术含量的话题,不太容易掌握.许多程序员都不清楚使用==操作符比较float/double类型的话到底出现什么问题. 许多人使用float/double进行货币计算时经常会犯错.这篇文章是这一系列中的精华,所有的软件开发人员都应该读一下. 随着你经验的增长,你肯定 想去深入了解一些常见的东西的细节,浮点数运算就是其中之一. 1. 什么是浮点数? 在计算机系统的发展过程中,曾经提出过多种方法表达实数. [1]典型的比如相对于浮点数的定点数(Fixed Point Num

【转载】程序员必知之浮点数运算原理详解

https://blog.csdn.net/tercel_zhang/article/details/52537726 导读:浮点数运算是一个非常有技术含量的话题,不太容易掌握.许多程序员都不清楚使用==操作符比较float/double类型的话到底出现什么问题. 许多人使用float/double进行货币计算时经常会犯错.这篇文章是这一系列中的精华,所有的软件开发人员都应该读一下. 随着你经验的增长,你肯定 想去深入了解一些常见的东西的细节,浮点数运算就是其中之一. 1. 什么是浮点数? 在计

Linux上整数和浮点数的运算

一:shell中对整数和浮点数的运算 常用的运算符号 加法+    减法 -     乘法*     除法/     求余% +=        -=        *=       /=        %= 1.整数的运算 (1).使用expr命令(注意:要求操作数和操作数之间用空格隔开,否则只会打印字符串) expr 1 + 1 expr 2 - 1 expr 2 \* 2 expr 2 / 1 expr 1 % 6 例如执行:#! /bin/bash num=$(expr 1 + 1) e

Python3基础 int,float,str 输入浮点数,整数,字符串

镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.------------------------------------------ code: myFloat=float(input('请输入一个浮点数:')) myInt=int(input('请输入一个整数:')) myStr=str(input('请输入一个字符串:')) result: ============= RESTART: C:/Users/Admini