为什么c#不能计算数学函数的精确值

为什么c#不能做任何精确的操作。

Math.Pow(Math.Sqrt(2.0),2) == 2.0000000000000004 

我知道双打是如何工作的,我知道舍入误差来自哪里,我知道它几乎是正确的值,而且我知道你不能在有限的双精度中存储无限数。 但是为什么没有一种方法可以让c#完全计算出来,而我的计算器可以做到。

编辑

这不是我的计算器,我只是举个例子:

http://www.wolframalpha.com/input/?i=Sqrt%282.000000000000000000000000000000000000000000000000000000000000000000000000000000001%29%5E2

干杯

机会是你的计算器不能完全做到 – 但它可能存储的信息多于它显示的信息,因此平方后的错误最终会超出显示的范围。 要么是这种情况,要么在这种情况下它的错误就会被取消 – 但这与以正当的方式完全正确地解决它并不相同。

另一种选择是计算器记住导致先前结果的操作,并应用代数来取消操作……但这似乎不太可能。 .NET 肯定不会尝试这样做 – 它将计算中间值(两个根),然后将其平方。

如果您认为自己可以做得更好,我建议您尝试将两个平方根写入(比方说)50个小数位,然后将其完全平方。 看看你是否正好2 …

你的计算器没有精确计算它,只是圆角误差太小而没有显示。

我相信大多数计算器使用二进制编码的小数,这相当于C#的十进制类型(因此完全准确)。 也就是说,每个字节包含数字的两位数字,数学通过对数完成。

是什么让你认为你的计算器可以做到这一点? 它几乎可以肯定显示的数字少于它计算的数字,如果打印出的只有五个小数位的2.0000000000000004 (例如),你就会得到’正确’的结果。

我想你可能会发现它不能。 当我做2平方根然后自己乘以时,得到1.999999998

2平方根是像PI这样恼人的无理数之一,因此不能用正常的IEEE754双精度或甚至十进制类型表示。 要准确地表示它,您需要一个能够进行符号数学的系统,其中值存储为“两个平方根”,以便后续计算可以提供正确的结果。

计算器对数字进行舍入的方式因模型而异。 我的TI Voyage 200做代数来简化方程式(除其他外),但大多数计算器只会在结果上应用圆函数后显示计算的实际值的一部分。 例如,您可能会发现平方根为2,计算器将存储(假设)54位小数,但只显示12位舍入小数。 因此,当做2的平方根时,那么得到2的结果的幂将返回相同的值,因为结果是舍入的。 在任何情况下,除非计算器可以保持无限小数,否则您将始终从复杂操作获得最佳近似结果。

顺便说一句,尝试用二进制表示10.0 ,你会发现你不能均匀地表示它,你最终会得到(例如) 10.00000000000..01

您的计算器具有识别和操纵非理性输入值的方法。

例如:如果您没有明确告诉它(如ti89 / 92),2 ^(1/2)可能不会计算到计算器中的数字。

此外,计算器具有可用于操纵它们的逻辑,例如x ^(1/2)* y ^(1/2)=(x * y)^ 1/2,然后可以对其进行清洗,冲洗,重复该方法处理不合理的价值观。

如果你给c#一些方法来做到这一点,我想它也可以。 毕竟,像mathematica这样的代数求解器并不神奇。

之前已经提到过,但我认为你所寻找的是一个计算机代数系统。 这些示例包括Maxima和Mathematica,它们的设计仅用于为数学计算提供精确值,而CPU未涵盖这些值。

像C#这样的语言中的数学例程是为数值计算而设计的:如果你作为一个程序进行计算,你可能已经将它简化了,或者你只需​​要一个数值结果。

2.00000000000000042.均以单精度表示为10. . 在您的情况下,对C#使用单精度应该给出确切的答案

对于您的其他示例,Wolfram Alpha可能使用比机器精度更高的精度进行计算。 这会增加很大的性能损失。 例如,在Mathematica中,精度更高会使计算速度慢300倍

 k = 1000000; vec1 = RandomReal[1, k]; vec2 = SetPrecision[vec1, 20]; AbsoluteTiming[vec1^2;] AbsoluteTiming[vec2^2;] 

在我的机器上,这是0.01秒对比3秒

您可以通过在Java中执行类似以下操作所引入的单精度和双精度来查看结果的差异

 public class Bits { public static void main(String[] args) { double a1=2.0; float a2=(float)2.0; double b1=Math.pow(Math.sqrt(a1),2); float b2=(float)Math.pow(Math.sqrt(a2),2); System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(a1))); System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(a2))); System.out.println(Long.toBinaryString(Double.doubleToRawLongBits(b1))); System.out.println(Integer.toBinaryString(Float.floatToRawIntBits(b2))); } } 

您可以看到单精度结果是精确的,而双精度是一位偏差