fred-ye / summary

my blog
43 stars 9 forks source link

[Java] 浮点型数据精度丢失问题 #20

Open fred-ye opened 10 years ago

fred-ye commented 10 years ago

最近在项目中碰到这么一个问题: 有这么一段代码:

 int num = (int) (Float.parse(str) * 100).

想像一下,如果str是"0.53",最终的输出结果会是多少? 53,非也,运行一看52. 原因很简单,当我们在利用浮点型数据进行运算时,很容易造成精度丢失。 再看一下这两行代码:

  System.out.println(0.53f);
  System.out.println(0.53f * 100);

第一行中,由于0.53f没有参与运算,最终打印出来的结果仍是0.53, 第二行是将0.53f和整型的100进行运算,最终输出的结果是52.999996

如果说有代码像这个样子:

float f = 0.01f;
double d = f;
Log.i(TAG, "d:" + d);

大家可能会猜到,输出的d值可能不是0.01, 实际上通常是0.009999xxxx,这是一个类型转换导致的。但上面的两个例子可能会隐蔽一些。在写代码的时候,对于这种问题一定要提高警惕。

浮点型数据运算过程中精度丢失这个问题并不仅仅存在于java语言中,在其它的语言中也是广泛存在的。浮点十进制值通常没有完全相同的二进制表示形式。 这是 CPU 所采用的浮点数据表示形式的副作用。 为此,可能会经历一些精度丢失,并且一些浮点运算可能会产生意外的结果。

在我们的项目中,货币的计算,精确到小数点后面两位。向服务器端提交数据的时候,提交的是美分,将金额转为整型再提交上去,因此会有一个乘以100的操作,写一段程序统计了一下两位小数乘以100后的结果。 代码如下:

    public class TestFloat {
        public static void main(String args[]) {
            float i = 0.01f;
            int j = 0;
            while (i < 1.0f) {            
                j = j + 1;
                i = j / 100.0f;
                System.out.println(j + "   " +  i + "   " + i*100);
            }
        }
    }

输出结果如下:

    1   0.01   1.0
    2   0.02   2.0
    3   0.03   3.0
    4   0.04   4.0
    5   0.05   5.0
    6   0.06   6.0
    7   0.07   7.0
    8   0.08   8.0
    9   0.09   9.0
    10   0.1   10.0
    11   0.11   11.0
    12   0.12   12.0
    13   0.13   13.0
    14   0.14   14.0
    15   0.15   15.000001
    16   0.16   16.0
    17   0.17   17.0
    18   0.18   18.0
    19   0.19   19.0
    20   0.2   20.0
    21   0.21   21.0
    22   0.22   22.0
    23   0.23   23.0
    24   0.24   24.0
    25   0.25   25.0
    26   0.26   26.0
    27   0.27   27.000002
    28   0.28   28.0
    29   0.29   29.0
    30   0.3   30.000002
    31   0.31   31.0
    32   0.32   32.0
    33   0.33   33.0
    34   0.34   34.0
    35   0.35   35.0
    36   0.36   36.0
    37   0.37   37.0
    38   0.38   38.0
    39   0.39   39.0
    40   0.4   40.0
    41   0.41   41.0
    42   0.42   42.0
    43   0.43   43.0
    44   0.44   44.0
    45   0.45   45.0
    46   0.46   46.0
    47   0.47   47.0
    48   0.48   48.0
    49   0.49   49.0
    50   0.5   50.0
    51   0.51   51.0
    52   0.52   52.0
    53   0.53   52.999996
    54   0.54   54.000004
    55   0.55   55.0
    56   0.56   56.0
    57   0.57   57.0
    58   0.58   58.0
    59   0.59   58.999996
    60   0.6   60.000004
    61   0.61   61.0
    62   0.62   62.0
    63   0.63   63.0
    64   0.64   64.0
    65   0.65   65.0
    66   0.66   66.0
    67   0.67   67.0
    68   0.68   68.0
    69   0.69   69.0
    70   0.7   70.0
    71   0.71   71.0
    72   0.72   72.0
    73   0.73   73.0
    74   0.74   74.0
    75   0.75   75.0
    76   0.76   76.0
    77   0.77   77.0
    78   0.78   78.0
    79   0.79   79.0
    80   0.8   80.0
    81   0.81   81.0
    82   0.82   82.0
    83   0.83   83.0
    84   0.84   84.0
    85   0.85   85.0
    86   0.86   86.0
    87   0.87   87.0
    88   0.88   88.0
    89   0.89   89.0
    90   0.9   90.0
    91   0.91   91.0
    92   0.92   92.0
    93   0.93   93.0
    94   0.94   94.0
    95   0.95   95.0
    96   0.96   96.0
    97   0.97   97.0
    98   0.98   98.0
    99   0.99   99.0
    100   1.0   100.0

请注意,对于0.530.95这两个数字,在乘以100之后,得到分别是52.999996, 58.999996,这是float类型,如果在代码中采用强转,采用代码int num = (int) (Float.parse(str) * 100) 将float转为int,那问题就大了。52.999996将变为52, 58.999996将变为58.

在Java中如果涉及到精确的数据计算,尤其是在货币,金融行业,Java是推荐使用String 和BigDecimal配合完成数据的计算。(http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html). BigDecimal中提供各种计算方法,使用非常方便。对于两个数的相加,代码如下:

 public static float add (String str1, String str2) {
        BigDecimal number1 = new BigDecimal(str1);
        BigDecimal number2 = new BigDecimal(str1);
        return number1.add(number2).floatValue();
    }