浮点algorithm没有产生确切的结果
我需要在Java中执行一些浮点运算,如下面的代码所示:
public class TestMain { private static Map<Integer, Double> ccc = new HashMap<Integer, Double>() { { put(1, 0.01); put(2, 0.02); put(3, 0.05); put(4, 0.1); put(6, 0.2); put(10, 0.5); put(20, 1.0); put(30, 2.0); put(50, 5.0); put(100, 10.0); } }; Double increment(Double i, boolean up) { Double inc = null; while (inc == null) { inc = ccc.get(i.intValue()); if (up) --i; else ++i; } return inc; } public static void main(String[] args) { TestMain tt = new TestMain(); for (double i = 1; i < 1000; i += tt.increment(i, true)) { System.out.print(i + ","); } } }
这是为了模拟必发微调控件输出的值的范围。
Java中的浮点algorithm似乎引入了一些意想不到的错误。 例如,我得到了2.180000000000001而不是2.18。 浮点数的用途是什么?你不能相信在它们上执行的算术结果? 我怎样才能解决这个问题?
如果你需要确切的十进制值,你应该使用java.math.BigDecimal
。 然后阅读“每个计算机科学家应该知道什么是浮点运算” ,了解为什么你会得到这些结果。
(我有一个以.NET为中心的文章 ,你可能会发现它更容易阅读,而且确实比较短。为了理解这个问题,Java和.NET之间的区别大多不相关。
浮点数使用二进制分数而不是小数部分。 也就是说,你习惯于由十分之一的数字,百分之一,千分之一等组成的十进制小数d1 / 10 + d2 / 100 + d3 / 1000 …但是浮点数是二进制的,所以他们有半位数,四分位数,八位数等。d1 / 2 + d2 / 4 + d3 / 8 …
许多小数不能完全表示在任何有限数量的二进制数字。 例如,1/2是没有问题的:十进制是.5,二进制是.1。 3/4是十进制.75,二进制.11。 但1/10是十进制的干净的.1,但是二进制是.0001100110011 …与“0011”永远重复。 由于电脑只能储存有限的数字,所以在某些时候必须将其切断,所以答案并不准确。 当我们将输出转换回十进制时,我们会看到一个奇怪的数字。
正如Jon Skeet所说,如果您需要精确的小数部分,请使用BigDecimal。 如果性能是一个问题,你可以滚动你自己的小数部分。 比如,如果你知道你总是只要3位小数,而且数字不会超过一百万,那么你可以简单地用int的小数点后三位,当你做算术和写输出格式函数将小数点插入到正确的位置。 但99%的时间performance并不是一个足够大的问题值得麻烦。
浮点数是不精确的,特别是因为它们以二进制分数(1/2,1 / 4,1 / 8,1 / 16,1 / 32,…)而不是小数部分(1/10,1 / 100,1/1000,…)。 只需定义你感觉“足够接近”,并使用类似Math.abs(ab) < 0.000001
。
在哲学上,我想知道:今天的大多数计算机CPU都内置了对整数运算和浮点运算的支持,但不支持十进制算术。 为什么不? 由于这个四舍五入的问题,我还没有写一个可以使用浮点数的应用程序。 您当然不能使用它们来赚钱:没有人想要在“$ 42.3200003”的销售收据上打印价格。 没有一个会计师会接受“我们可能会在这里和那里一分钱,因为我们正在使用二进制分数,并有四舍五入错误”。
对于测量来说,漂浮物是很好的,比如距离或者温度,在那里没有“确切的答案”这样的东西,而且无论如何你都必须在仪器的精确度上进行精确的测量。 我想对于在化学实验室对计算机进行编程的人来说,常常使用浮游物。 但是对于我们这个商业世界的人来说,他们几乎没用。
早在我在大型机上进行编程的那些古老时代,IBM 360系列CPU就内置了对十进制算术运算的支持。 它们存储了每个字节都有两位十进制数字的string,也就是说,前四位的值是从0到9,其次是四位,CPU有算术function来操纵它们。 为什么英特尔不能这样做? 然后Java可以添加一个“十进制”数据types,我们不需要额外的垃圾。
当然,我并不是说要取消花车。 只需添加小数。
噢,正如伟大的社会运动一样,我不认为这会在街上引起大量的兴奋或骚动。
通过使用格式化输出,可以使程序的输出看起来更像您所期望的。
http://java.sun.com/javase/6/docs/api/java/util/Formatter.html
显然,底层浮点运算仍然是相同的,但至less输出将更具可读性。
例如,将结果舍入到小数点后两位:
System.out.print(String.format(".2f", i) + ",");
你可以写一些代码来在你的机器上计算Epsilon。 我相信std :: C ++定义它,它的定义在其他方面取决于你在用什么。
private static float calcEpsilonFloat() { float epsi = 1.0f; while ((float) (1.0 + (epsi / 2.0)) != 1.0) { epsi /= 2.0f; } return epsi; }
我唯一一次担心Epsilon的时候,是在比较阈值信号的时候。 即使这样,我也不确定我真的需要担心,以我的经验来看,如果你关心Epsilon, 你可能还有其他的考虑要先处理。
顺便说一句,你可以尝试使用这个函数来确保(不要太多的小数位数),你的号码将被重新格式化,只保留你需要的小数。
n
是有很多小数的数字(例如Math.PI
), numberOfDecimals
是您需要的最大小数位数(例如3.14的2或3.151的3)。
理论上,对numberOfDecmals
给一个负值,它也会中断数字的低位整数。 例如把n=1588.22
和numberOfDecimals=-2
,函数将返回1500.0
。
让我知道,如果这是错的。