关于乘法的java.lang.Double的奇怪集群

时间:2014-09-02 18:47:05

标签: java clojure floating-point

这些java.lang.Double事情真的很奇怪......这里是[Clojure]代码 - 与Java相同,适用于所有[执行相关]意图和目的:

(doseq [x (range 100)
        :let [x33 (str (+ x 0.33))
              x99 (str (+ x 0.99))
              tx33 (* 100 (Double/parseDouble x33))
              tx99 (* 100 (Double/parseDouble x99))]]
  (do (prn (str x33 "->" tx33))
      (prn (str x99 "->" tx99))))

对于Java人员 - 这遍及java.lang.Long-s的范围,为每个(x33和x99)创建两个java.lang.String-s,分别为它添加0.33和0.99。它将字符串解析为双打,并将结果乘以100,并打印映射。观察结果(最后的结果是无关紧要的):

"0.33->33.0"
"0.99->99.0"
"1.33->133.0"
"1.99->199.0"
"2.33->233.0"
"2.99->299.0"
"3.33->333.0"
"3.99->399.0"
"4.33->433.0"
"4.99->499.0"
"5.33->533.0"
"5.99->599.0"
"6.33->633.0"
"6.99->699.0"
"7.33->733.0"
"7.99->799.0"
"8.33->833.0"
"8.99->899.0"
"9.33->933.0"
"9.99->999.0"
"10.33->1033.0"
"10.99->1099.0"
"11.33->1133.0"
"11.99->1199.0"
"12.33->1233.0"
"12.99->1299.0"
"13.33->1333.0"
"13.99->1399.0"
"14.33->1433.0"
"14.99->1499.0"
"15.33->1533.0"
"15.99->1599.0"
"16.33->1632.9999999999998"
"16.99->1698.9999999999998"
"17.33->1732.9999999999998"
"17.99->1798.9999999999998"
"18.33->1832.9999999999998"
"18.99->1898.9999999999998"
"19.33->1932.9999999999998"
"19.99->1998.9999999999998"
"20.33->2032.9999999999998"
"20.99->2099.0"
"21.33->2133.0"
"21.99->2199.0"
"22.33->2233.0"
"22.99->2299.0"
"23.33->2333.0"
"23.99->2399.0"
"24.33->2433.0"
"24.99->2499.0"
"25.33->2533.0"
"25.99->2599.0"
"26.33->2633.0"
"26.99->2699.0"
"27.33->2733.0"
"27.99->2799.0"
"28.33->2833.0"
"28.99->2899.0"
"29.33->2933.0"
"29.99->2999.0"
"30.33->3033.0"
"30.99->3099.0"
"31.33->3133.0"
"31.99->3199.0"
"32.33->3233.0"
"32.99->3299.0"
"33.33->3333.0"
"33.99->3399.0"
"34.33->3433.0"
"34.99->3499.0"
"35.33->3533.0"
"35.99->3599.0"
"36.33->3633.0"
"36.99->3699.0"
"37.33->3733.0"
"37.99->3799.0"
"38.33->3833.0"
"38.99->3899.0"
"39.33->3933.0"
"39.99->3999.0"
"40.33->4033.0"
"40.99->4099.0"
"41.33->4133.0"
"41.99->4199.0"
"42.33->4233.0"
"42.99->4299.0"
"43.33->4333.0"
"43.99->4399.0"
"44.33->4433.0"
"44.99->4499.0"
"45.33->4533.0"
"45.99->4599.0"
"46.33->4633.0"
"46.99->4699.0"
"47.33->4733.0"
"47.99->4799.0"
"48.33->4833.0"
"48.99->4899.0"
"49.33->4933.0"
"49.99->4999.0"
"50.33->5033.0"
"50.99->5099.0"
"51.33->5133.0"
"51.99->5199.0"
"52.33->5233.0"
"52.99->5299.0"
"53.33->5333.0"
"53.99->5399.0"
"54.33->5433.0"
"54.99->5499.0"
"55.33->5533.0"
"55.99->5599.0"
"56.33->5633.0"
"56.99->5699.0"
"57.33->5733.0"
"57.99->5799.0"
"58.33->5833.0"
"58.99->5899.0"
"59.33->5933.0"
"59.99->5999.0"
"60.33->6033.0"
"60.99->6099.0"
"61.33->6133.0"
"61.99->6199.0"
"62.33->6233.0"
"62.99->6299.0"
"63.33->6333.0"
"63.99->6399.0"
"64.33->6433.0"
"64.99->6498.999999999999"
"65.33->6533.0"
"65.99->6598.999999999999"
"66.33->6633.0"
"66.99->6698.999999999999"
"67.33->6733.0"
"67.99->6798.999999999999"
"68.33->6833.0"
"68.99->6898.999999999999"
"69.33->6933.0"
"69.99->6998.999999999999"
"70.33->7033.0"
"70.99->7098.999999999999"
"71.33->7133.0"
"71.99->7198.999999999999"
"72.33->7233.0"
"72.99->7298.999999999999"
"73.33->7333.0"
"73.99->7398.999999999999"
"74.33->7433.0"
"74.99->7498.999999999999"
"75.33->7533.0"
"75.99->7598.999999999999"
"76.33->7633.0"
"76.99->7698.999999999999"
"77.33->7733.0"
"77.99->7798.999999999999"
"78.33->7833.0"
"78.99->7898.999999999999"
"79.33->7933.0"
"79.99->7998.999999999999"
"80.33->8033.0"
"80.99->8098.999999999999"
"81.33->8133.0"
"81.99->8199.0"
"82.33->8233.0"
"82.99->8299.0"
"83.33->8333.0"
"83.99->8399.0"
"84.33->8433.0"
"84.99->8499.0"
"85.33->8533.0"
"85.99->8599.0"
"86.33->8633.0"
"86.99->8699.0"
"87.33->8733.0"
"87.99->8799.0"
"88.33->8833.0"
"88.99->8899.0"
"89.33->8933.0"
"89.99->8999.0"
"90.33->9033.0"
"90.99->9099.0"
"91.33->9133.0"
"91.99->9199.0"
"92.33->9233.0"
"92.99->9299.0"
"93.33->9333.0"
"93.99->9399.0"
"94.33->9433.0"
"94.99->9499.0"
"95.33->9533.0"
"95.99->9599.0"
"96.33->9633.0"
"96.99->9699.0"
"97.33->9733.0"
"97.99->9799.0"
"98.33->9833.0"
"98.99->9899.0"
"99.33->9933.0"
"99.99->9999.0"
nil

正如您所看到的,有两个这样的双打集群,其行为与其他集群不同。对我来说是一个谜......上帝知道还有多少通往无限的道路。任何人都可以对此有所了解吗?谢谢!我知道并非所有有理数都可以用浮点表示法表示,但我认为这是其他的东西..

2 个答案:

答案 0 :(得分:2)

IEEE 754二进制浮点数应被视为按binades组织,间隔范围从2的幂到下一个,其中所有数字都以相同的最小间隔(ULP)分隔。 / p>

"15.99->1599.0"
"16.33->1632.9999999999998"
...
"20.33->2032.9999999999998"
"20.99->2099.0"

当原点值在16-32 binade中并且结果在1024-2048 binade中时,上述簇对应于比通常更不准确的计算。

"63.99->6399.0"
"64.33->6433.0"
"64.99->6498.999999999999"
"65.33->6533.0"
"65.99->6598.999999999999"
"66.33->6633.0"
"66.99->6698.999999999999"
...
"80.33->8033.0"
"80.99->8098.999999999999"
"81.33->8133.0"
"81.99->8199.0"

出于某种原因,还有另一个聚类,其中一个计算中的两个比平时更不准确,原点值在64-128个binade中,结果在4096-8192 binade中。

通过查看二进制级别的计算,您可以更精确地观察这两个集群中每个集群发生的情况。输入值和输出值分别位于同一个二进制位置的值的计算看起来非常相似(这就是它们要么全部准确,要么同时都是不准确的。)

下面的C99程序使得计算在第一个集群中显得规则(只要你可以使用Java的打印功能和the %a format for hexadecimal,这个程序应该很容易转换为Clojure。 ):

#include <stdio.h>

int main(int argc, char** argv) 
{  
  printf("%a -> %.13a (exact result %a)\n", 15.33, 15.33 * 100.0, 1533.0);
  printf("%a -> %.13a (exact result %a)\n", 16.33, 16.33 * 100.0, 1633.0);
  printf("%a -> %.13a (exact result %a)\n", 17.33, 17.33 * 100.0, 1733.0);
  printf("%a -> %.13a (exact result %a)\n", 18.33, 18.33 * 100.0, 1833.0);
  printf("...\n");
  printf("%a -> %.13a (exact result %a)\n", 20.33, 20.33 * 100.0, 2033.0);
  printf("%a -> %.13a (exact result %a)\n", 21.33, 21.33 * 100.0, 2133.0);
}

输出结果为:

0x1.ea8f5c28f5c29p+3 -> 0x1.7f40000000000p+10 (exact result 0x1.7f4p+10)
0x1.0547ae147ae14p+4 -> 0x1.983ffffffffffp+10 (exact result 0x1.984p+10)
0x1.1547ae147ae14p+4 -> 0x1.b13ffffffffffp+10 (exact result 0x1.b14p+10)
0x1.2547ae147ae14p+4 -> 0x1.ca3ffffffffffp+10 (exact result 0x1.ca4p+10)
...
0x1.4547ae147ae14p+4 -> 0x1.fc3ffffffffffp+10 (exact result 0x1.fc4p+10)
0x1.5547ae147ae14p+4 -> 0x1.0aa0000000000p+11 (exact result 0x1.0aap+11)

上面,数字的十六进制表示中的p+4指数表示该数字位于16-32 binade中。如您所见,此binade中的所有X.33数字都具有相似的表示形式。对它们的计算会产生类似的精确结果,但是当结果低于2048时,它必须以不同于精确结果高于2048的方式进行舍入。

将以…ae147ae14结尾的数字乘以100会产生以…fffffffd…11111111101结尾的精确结果。

  • 当要乘以100的数字为16.33时,需要切断最后的两个二进制数字以进行舍入。以二进制…11111111101结尾的计算的确切结果更接近于先前可表示的数字而不是下一个可表示的数字,因此乘法的结果四舍五入到以…111111111结尾的二进制数,或十六进制…fff(或十进制…99998)。

  • 当乘以100的数字是21.33时,因为这次乘法的结果落在下一个binade中,所以需要减去最后的三个二进制数字四舍五入。被切断的最后三个二进制数字是101。它们代表ULP的一半以上,因此最接近的可表示值是高于精确值的可表示值。该值由FPU作为乘法结果选择,以…00000结尾,恰好是2133.0的浮点表示。

答案 1 :(得分:-1)

您遇到了浮点数的常见问题。如果您需要精确的结果,则应使用BigDecimal。