当我执行以下程序时,我开始了解准确性问题:
public static void main(String args[])
{
double table[][] = new double[5][4];
int i, j;
for(i = 0, j = 0; i <= 90; i+= 15)
{
if(i == 15 || i == 75)
continue;
table[j][0] = i;
double theta = StrictMath.toRadians((double)i);
table[j][1] = StrictMath.sin(theta);
table[j][2] = StrictMath.cos(theta);
table[j++][3] = StrictMath.tan(theta);
}
System.out.println("angle#sin#cos#tan");
for(i = 0; i < table.length; i++){
for(j = 0; j < table[i].length; j++)
System.out.print(table[i][j] + "\t");
System.out.println();
}
}
输出是:
angle#sin#cos#tan
0.0 0.0 1.0 0.0
30.0 0.49999999999999994 0.8660254037844387 0.5773502691896257
45.0 0.7071067811865475 0.7071067811865476 0.9999999999999999
60.0 0.8660254037844386 0.5000000000000001 1.7320508075688767
90.0 1.0 6.123233995736766E-17 1.633123935319537E16
(请原谅无组织的输出)。 我注意到了几件事:
0.5
存储为0.49999999999999994
。1.0
存储为0.9999999999999999
。infinity
或undefined
存储为1.633123935319537E16
(这是一个非常大的数字)。当然,我很难看到输出(甚至在解密输出后)。
所以我看过this帖子,最好的答案告诉我:
这些accuracy problems归因于internal representation浮动&gt;点数,你可以做的就是避免它。
顺便说一下,在运行时打印这些值通常仍会导致正确的结果,至少使用现代C ++编译器。对于大多数操作而言,这不是一个问题。
于2008年10月7日7:42回答
康拉德鲁道夫
所以,我的问题是:
我应该完成结果吗?在这种情况下,我如何存储infinity
,即Double.POSITIVE_INFINITY
?
答案 0 :(得分:4)
你必须对浮点数采取一些zen *方法:而不是消除错误,学会忍受它。
在实践中,这通常意味着做:
String.format
指定要显示的精确度(它将为您进行适当的舍入)==
)。相反,请寻找足够小的delta:Math.abs(myValue - expectedValue) <= someSmallError
编辑:对于无穷大,同样的原则适用,但有一个调整:你必须选择一些数字“足够大”来视为无限。这也是因为你必须学会接受而不是解决不精确的价值观。在像tan(90度)这样的情况下,double不能以无限精度存储π/ 2,所以你的输入是非常接近但不完全是90度的东西 - 因此,结果非常接近大,但不是无限。你可以问“当你把最接近的两倍传递给π/ 2时,他们为什么不返回Double.POSITIVE_INFINITY
,”但这可能会导致含糊不清:如果你真的想要那个数字的棕褐色,而不是90度?或者,如果(由于先前的浮点错误)你有一些稍微远离π/ 2的东西而不是最接近的可能值,但是对于你的需要它仍然是π/ 2? JDK不是为你做出任意决定,而是以面值处理你接近但不完全π/ 2的数字,从而给你一个很大但不是无穷大的结果。
对于某些操作,尤其是与金钱有关的操作,您可以使用BigDecimal
来消除浮点错误:您可以真正表示像0.1这样的值(而不是真正接近0.1的值,这是最好的浮子或双人都可以)。但这速度要慢得多,并且对sin / cos之类的东西没有帮助(至少对于内置库而言)。
*这可能实际上不是禅宗,但在口语意义上
答案 1 :(得分:3)
您必须使用BigDecimal
代替double
。很遗憾,StrictMath
不支持BigDecimal
,因此您必须使用其他库或您自己的sin
/ cos
/ tan
实现。
答案 2 :(得分:2)
这是使用任何语言的浮点数所固有的。实际上,使用具有固定最大精度的任何表示都是固有的。
有几种解决方案。一种是使用扩展精度数学包 - BigDecimal
通常建议用于Java。 BigDecimal
可以处理更多的精度数字,而且 - 因为它是十进制表示而不是二进制补码表示 - 倾向于以对于习惯在基础工作的人类不太令人惊讶的方式进行四舍五入10.(这并不一定使它们更正正确,请注意。二进制不能完全代表1/3,但也不能十进制。)
还有扩展精度二进制补码浮点表示。 Java直接支持float和double(通常也由硬件支持),但是可以编写支持更多精度数字的版本。
当然,任何扩展精度软件包都会降低您的计算速度。所以你不应该求助于它们,除非你真的需要它们。
另一种可能使用定点二进制而不是浮点。例如,大多数财务计算的标准解决方案只是根据最小货币单位计算 - 美国便士 - 整数,仅转换为显示格式(例如美元和美分) / O。这也是Java中使用时间的方法 - 内部时钟报告整数毫秒(或纳秒,如果使用纳米级调用),这提供了足够的精度和足够实用的值范围目的。同样,这意味着舍入倾向于以符合人类期望的方式发生......再次,这不是关于准确性而是关于不会让用户感到惊讶。而这些表示,因为它们以整数或长整数形式处理,允许快速计算 - 实际上比浮点更快。
还有其他解决方案涉及计算有理数或其他变化,试图在计算成本和精度之间进行折衷。
但我也要问......你真的需要比漂浮给你更精确吗?我知道这种结果令人惊讶,但在很多情况下,让它发生是完全可以接受的,当你向用户显示结果时,可能会转向不那么令人惊讶的分数数字。在许多情况下,浮点数或双精度对于实际使用来说都很好。这就是硬件支持它们的原因,这也是他们使用该语言的原因。