JAVA输出x和y和z_java – 当使用双精度时,为什么不(x /(y * z))与(x/y/z)相同?

我看到一些问题,告诉你如何解决这个问题,但不是真正解释发生了什么,除了“浮点舍入误差是坏的,m’kay?所以让我拍一拍。让我首先指出,这个答案中没有什么是Java特有的。舍入误差是数字的任何固定精度表示所固有的问题,因此您可以在C中得到相同的问题。

十进制数据类型中的舍入误差

作为一个简化的例子,假设我们有某种计算机本身使用无符号十进制数据类型,我们称之为float6d。数据类型的长度为6位数:4专用于尾数,2个专用于指数。例如,数字3.142可以表示为

3.142 x 10^0

其将以6位数字存储

503142

前两位数字是指数加50,最后四位是尾数。该数据类型可以表示从0.001×10 ^ -50到9.999×10 ^ 49的任何数字。

其实这不是真的。它不能存储任何数字。如果你想代表3.141592怎么办?还是3.1412034?还是3.141488906?艰难的运气,数据类型不能存储超过四位数的精度,所以编译器必须舍入具有更多数字的任何东西以适应数据类型的约束。如果你写

float6d x = 3.141592;

float6d y = 3.1412034;

float6d z = 3.141488906;

那么编译器将这三个值中的每一个转换为相同的内部表示,即3.142 x 10 ^ 0(记住,存储为503142),以使x == y == z成立。

关键是存在一系列实数,它们都映射到相同的底层数字序列(或实际计算机中的位)。具体地说,满足3.1415 <= x <= 3.1425(假定半偶数舍入)的任何x被转换为表示503142以存储在存储器中。 每当您的程序在内存中存储浮点值时,都会进行舍入。第一次发生的时候是在你的源代码中编写一个常量,就像我以前用x,y和z一样。只要您进行算术运算,就会再次增加超出数据类型可以表示的精度位数。这些效果都被称为roundoff error.有几种不同的方法可以发生:

加法和减法:如果您要添加的其中一个值与另一个值不同,则可以使用额外的精度数字,如果有足够的数字,那么最不重要的值将被丢弃。例如,2.718和121.0都是可以在float6d数据类型中精确表示的值。但是如果您尝试将它们添加在一起:

1.210 x 10^2

+ 0.02718 x 10^2

-------------------

1.23718 x 10^2

其舍入到1.237×10 ^ 2,或123.7,下降两位数的精度。

>乘法:结果中的位数近似为两个操作数中的位数之和。如果您的操作数已经有很多有效数字,这将产生一些舍入误差。例如,121 x 2.718给你

1.210 x 10^2

x 0.02718 x 10^2

-------------------

3.28878 x 10^2

其被舍入到3.289×10 ^ 2或328.9,再次下降两位数的精度。

但是,请注意,如果您的操作数是“不错”的数字,没有多个有效数字,浮点格式就可能完全代表结果,因此您不必处理舍入错误。例如,2.3 x 140给出

1.40 x 10^2

x 0.23 x 10^2

-------------------

3.22 x 10^2

没有四舍五入的问题。

部门:这是事情变得凌乱的地方。除非你划分的数字恰好是基数(在这种情况下,除法只是一个数位移,或二进位的位移),那么除法几乎总是会导致一些量的舍入误差。举个例子,把两个非常简单的数字3和7分开,你得到

3. x 10^0

/ 7. x 10^0

----------------------------

0.428571428571... x 10^0

可以表示为float6d的与该数字最接近的值为4.286 x 10 ^ -1,或0.4286,与确切结果有明显差异。

正如我们将在下一节中看到的,舍入引入的错误随着你所做的每个操作而增长。所以,如果你正在使用“好”的数字,就像你的例子一样,最好尽可能的做分割操作,因为这些操作最有可能在你的程序中引入roundoff错误,而以前没有。

舍入误差分析

一般来说,如果你不能假设你的数字是“好的”,则舍入误差可以是正值还是负数,并且很难根据操作来预测哪个方向。这取决于所涉及的具体值。查看2.718 z的舍入误差的这个图作为z的函数(仍然使用float6d数据类型):

实际上,当您使用使用数据类型完整精度的值时,将舍入误差视为随机错误往往更为容易。看一下这个情节,你可能会猜到错误的大小取决于操作结果的数量级。在这种特殊情况下,当z为10-1的数量级时,2.718z也在10-1的数量级,所以它将是一个形式为0.XXXX的数字。最大舍入误差是最后一位精度的一半;在这种情况下,通过“最后一位精度”我的意思是0.0001,所以舍入误差在-0.00005和0.00005之间变化。在2.718 z跳到下一个数量级的位置,即1 / 2.718 = 0.3679,您可以看到舍入误差也跳一个数量级。

您可以使用知名的techniques of error analysis来分析一定程度的随机(或不可预测)错误如何影响您的结果。具体来说,对于乘法或除法,您的结果中的“平均”相对误差可以通过在正交中的每个操作数中加上相对误差来近似,即对它们进行平方,加上它们,并取平方根。使用我们的float6d数据类型,相对误差在0.0005之间(对于值为0.101)和0.00005(对于0.995的值)。

让我们将0.0001作为x和y值的相对误差的粗略平均值。然后给出x * y或x / y中的相对误差

sqrt(0.0001^2 + 0.0001^2) = 0.0001414

这是每个单独值的相对误差大于sqrt(2)的因子。

在组合操作时,您可以多次应用此公式,每次浮点运算一次。例如,对于z /(x * y),x * y中的相对误差平均为0.0001414(在该十进制示例中),然后z /(x * y)中的相对误差为

sqrt(0.0001^2 + 0.0001414^2) = 0.0001732

请注意,平均相对误差随着每个操作而增长,特别是作为乘法和除法次数的平方根。

类似地,对于z / x * y,z / x中的平均相对误差为0.0001414,z / x * y的相对误差为

sqrt(0.0001414^2 + 0.0001^2) = 0.0001732

所以,在这种情况下也是如此。这意味着对于任意值,平均来说,两个表达式引入大致相同的错误。 (在理论上,就是这样,我看到这些操作在实践中表现得非常不同,但这是另一个故事。)

血腥细节

您可能对您在问题中提出的具体计算感到好奇,而不仅仅是平均值。为了进行分析,我们来看一下二进制算术的真实世界。大多数系统和语言中的浮点数用IEEE standard 754表示。对于64位数字,format规定了尾数专用的52位,指数为11位,符号为1位。换句话说,当在基础2中写入时,浮点数是表单的值

1.1100000000000000000000000000000000000000000000000000 x 2^00000000010

52 bits 11 bits

前导1未明确存储,构成第53位。此外,您应该注意,存储以表示指数的11位实际上是真正的指数加上1023.例如,该特定值为7,即1.75×22。尾数为1.75,二进制为1.11,指数是1023 2 = 1025在二进制,或10000000001,所以存储在内存中的内容是

01000000000111100000000000000000000000000000000000000000000000000

^ ^

exponent mantissa

但这并不重要。

你的例子也涉及到450,

1.1100001000000000000000000000000000000000000000000000 x 2^00000001000

和60,

1.1110000000000000000000000000000000000000000000000000 x 2^00000000101

您可以使用this converter或互联网上的其他许多其他人玩这些值。

当您计算第一个表达式450 /(7 * 60)时,处理器首先进行乘法,获得420或

1.1010010000000000000000000000000000000000000000000000 x 2^00000001000

然后它将450除以420.这产生15/14,这是

1.0001001001001001001001001001001001001001001001001001001001001001001001...

Inexact results must be rounded to the representable value nearest to the infinitely precise result; if the two nearest representable values are equally near, the one with its least significant bit zero is chosen. This is the IEEE 754 standard’s default rounding mode known as round to nearest.

64位IEEE 754格式的最接近的值可以是15/14

1.0001001001001001001001001001001001001001001001001001 x 2^00000000000

大约是小数点的1.0714285714285714。 (更准确地说,这是唯一指定这个特定二进制表示的最小精度的十进制值。)

另一方面,如果先计算450/7,则结果为64.2857142857 …或以二进制计算,

1000000.01001001001001001001001001001001001001001001001001001001001001001...

最接近的代表值是

1.0000000100100100100100100100100100100100100100100101 x 2^00000000110

这是64.28571428571429180465 …注意由于舍入误差导致的二进制尾数(与精确值相比较)的最后一位的变化。把它分成60,给你

1.000100100100100100100100100100100100100100100100100110011001100110011...

看看结局:图案是不同的!在其他情况下,重复001,而不是001。最接近的可表示值是

1.0001001001001001001001001001001001001001001001001010 x 2^00000000000

它与最后两位的其他操作顺序不同:它们是10而不是01。小数等效值为1.0714285714285716。

如果您查看确切的二进制值,则会导致此差异的特定舍入:

1.0001001001001001001001001001001001001001001001001001001001001001001001...

1.0001001001001001001001001001001001001001001001001001100110011001100110...

^ last bit of mantissa

在这种情况下,原来的结果,数字上是15/14,恰好是精确值的最准确的表示。这是一个例子,说明如何离开部门,直到最终使你受益。但是,只要您使用的值不使用数据类型的完整精度,此规则才会保留。一旦你开始使用不精确的(四舍五入)值,你不再通过首先进行乘法来保护自己免受进一步的舍入误差。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值