【www.gdgbn.com--安卓教程】

比较浮点数字

所有这些可以得出一个推论,你应该非常,非常少的去直接比较浮点数间是否相等。通常比较大于或者小于会好些,但是当你对相等感兴趣时你应该总是考虑是否你实际上想要的接近相等:一个数字总是与另外一个相同。做这个的一个简单的方式是用一个数减去另外一个数,使用math.abs来找到绝对值的不同,然后检查是否这个误差是否低到可以忍受的级别。

也有一些情况是病理的,这些是由于jit优化导致。查看下面的代码:

view source print? word">using system; word">class test {     static float f;     static void main(string[] args)     {         f = sum (0.1f, 0.2f);         float g = sum (0.1f, 0.2f);         console.writeline (f==g); view source print? //g = g + 1; view source print?     }     static float sum (float f1, float f2)     {         return f1+f2;     } } view source print? 它应该总是打印true, 对不?错,很不幸。当在debug模式下运行时,jit不能像正常那样做一些 view source print? 优化处理,它将打印true.当正常运行时jit可以将sum 的结果存储的比一个float可以实际表示 view source print? 的数更加精确 - 它可以使用默认x86 80位表示,例如,对sum 本身,返回值和本地变量。 view source print? 查看ecma cli 规范,第一部分, 12.1.3 章节来获得更多细节。取消上面的注释,让jit的 view source print? 行为稍微谨慎一些 - 结果将会是true - 尽管在当前的实现可以让结果是true,但是不应该被 view source print? 信赖.(在上面语句中将g强制转换成float也可以有同样的效果,尽管它看起来像一个 view source print? 空操作(no-op).) view source print? 这是另外的避免对浮点数做相等比较的原因,尽管你非常确定结果应该是一样的。 view source print? (译者注: .net 平台的运行结果总是true.)

.net 是如何格式化浮点数的?

在.net中没有查看一个浮点数的精确十进制值的内建方式,尽管你可以通过一些工作来完成。(查看这篇文章的末尾的一些可以实现这个功能的代码。)默认情况下,.net将一个double类型数格式化成15个十进制位置,将一个float类型数格式化成7个十进制位置。(在一些情况将使用科学计数法;查看msdn标准数字格式字符串页来获得更多内容。)如果你使用往返模式规范(“r”),它会将数字格式化成最短格式,当截取(成同样类型)时,将会变成初始数字。如果你以字符串存储浮点数字而且精确的值对你来说很重要,你应该定义使用往返模式规范,否则你非常可能丢失数据。

一个浮点数在内存中看起来究竟是什么样子的?

正如上面所说的,一个浮点数基本有一个符号位,一个指数和一个尾数。所有这些都是整数,它们三个的联合精确的确定数字的表示形式。有很多浮点数类别: 规范数,低于正常数,无穷数和非数字(nan, not a number).大多数数字是规范化的,意味着二进制尾数位的第一位是1,也意味着你实际上不需要存储它。例如,二进制数1.01101可以仅用.01101表示 - 开始的1是假设的,如果是0将会使用一个不同的指数。那个技术只有当数字在可以选择适合的指数范围时才可以工作。不在那个范围中的数字(非常,非常小的数字)被称为非正常数字,并假设没有开始位。”不是一个数字”(nan, not a number)是像指0/0的结果之类的,等等。nan有很多不同的类别,也有一些老的行为。非正常数字有时候也称作非规范数。

符号位,指数和尾数在比特级别的表示方法都是一个无符号整数,存储的值按顺序先是符号位,然后是指数位,最后是尾数。”真实的”指数是有偏移值的 - 例如,一个double型数,指数是1023偏移,所以当你回来计算出实际值时,一个存储指数值为1026的值就变成3。下面的表显示了符号位,指数和尾数的每种组合的意思,使用double作为一个例子。相同的原则也适用于float,仅有一些不同值(比如偏移值不同)。注意这里给出的指数值是指存储的指数,在偏移值应用之前。(那就是为什么偏移值显示在”值”列。)

符号位(s, 1位)

存储的指数(e, 11位)

尾数(m, 52位)

数字类型

任意 非零 任意 正常 (-1)s x 1.m (二进制) x 2e-1023 0 0 0 0 +0 1 0 0 0 +0 0 2047 0 无穷大 正无穷大 1 2047 0 无穷大 负无穷大 0 2047 非零 非数字 n/a

可以工作的例子

考虑下面的64位二进制数:

0100000001000111001101101101001001001000010101110011000100100011

作为一个double型数,可以被拆分成:

  符号位: 0

  指数位: 10000000100 二进制=1028 十进制

  尾数位: 0111001101101101001001001000010101110011000100100011

这是因此一个正常数的值

(-1)0 x 10111001101101101001001001000010101110011000100100011 (binary) x 21028-1023

也可以更简单的表示为

1.0111001101101101001001001000010101110011000100100011 (binary) x 25

或者

101110.01101101101001001001000010101110011000100100011

在十进制,这是46.42829231507700882275457843206822872161865234375,但是.net 将会默认显示46.428292315077 或者使用”往返”格式规范表示为46.428292315077009.

示例代码

doubleconverter.cs : 这是一个相当简单的允许你将一个double类型数转换成它的以字符串表示的精确十进制数的类。注意尽管无穷大十进制数不总是有无穷大二进制表示方法,所有的无穷大二进制数有一个无穷大十进制表示(因为2是10的因数,本质上)。这个类使用起来超级简单 = 仅仅调用doubleconverter.toexactstring(value)然后value的精确字符串表示形式就会返回。

nans

nans 是奇兽。有两种类型的nans - 信号和安静(signalling and quiet, 译意可能不准确)或者简短表示为snan和qnan。在位模式概念中,一个安静的nan有高位尾数, 而一个信号nan将它清除了。安静nans用来标记精确操作是未定义的,而信号nans用来定义其他的(操作是非法的,而不是仅有一个不确定输出)。

大多数人想知道的最奇怪的事情时nans不等于它们自己。例如,double.nan==double.nan 结果是false.相反,你需要使用double.nans来检查是否一个值不是一个数字。幸运的是,大多数人不可能遇到nans除了在这篇文章里。

结论

只要你知道发生了什么并且不期望你在你的程序中输入的十进制数就是十进制数值,并且不期望设计二进制浮点数的计算必须生成精确结果,那么二进制浮点算术是很好的。尽管两个数字都被你正在使用的类型精确表示,涉及这两个数的操作结果将不会必须精确表示。这个可以很简单的通过除法操作(例如1/10 不是精确表示的,但1 和10都是精确表示的)看出来但是它可以在任何操作中发生 - 尽管看起来不可能发生的如加法和减法操作。

如果你特别需要精确十进制数字,考虑使用decimal类型来代替 - 但是这样做要考虑到付出性能的代价。(一个非常快设计的测试显示doubles类型数的乘法比decimals类型的乘法快40倍;不要为这个情况花费额外的注意,但是要将在当前硬件环境里二进制浮点运算比十进制浮点运算快很多作为一个提示看待。)

本文来源:http://www.gdgbn.com/shoujikaifa/28542/