这周,我终于抽出时间来与Sonar一起分析我们的代码库。 特别是让我意识到了很多有关浮点运算的问题。
有趣的Java浮点运算法则
那些在学术背景下学习过Java的人可能还记得关于FP算术的一些东西。 然后,如果您从未使用过它们,您可能会忘记它们。 这是一个有趣的非常简单的示例,事实证明是:
doublea=5.8d;
doubleb=5.6d;
doublesub=a-b;
assertThat(sub).isEqualTo(0.2d);
与常识相反,此代码段引发一个AssertionError
: sub
不等于0.2
而是0.20000000000000018
。
BigDecimal作为拐杖
当然,没有一种值得称呼的语言能使这种说法站得住脚。 BigDecimal是Java的答案:
BigDecimal类提供了用于算术,缩放操作,舍入,比较,哈希和格式转换的操作。
让我们使用BigDecimal
更新以上代码段:
BigDecimala=newBigDecimal(5.8d);
BigDecimalb=newBigDecimal(5.6d);
BigDecimalsub=a.subtract(b);
assertThat(sub).isEqualTo(newBigDecimal(0.2d));
然后再次运行测试......糟糕,它仍然失败:
java.lang.AssertionError: Expecting: <0.20000000000000017763568394002504646778106689453125> to be equal to: <0.200000000000000011102230246251565404236316680908203125> but was not.
使用构造函数不会改变任何东西,而必须使用静态valueOf()
方法。
BigDecimala=BigDecimal.valueOf(5.8d);
BigDecimalb=BigDecimal.valueOf(5.6d);
BigDecimalsub=a.subtract(b);
assertThat(sub).isEqualTo(BigDecimal.valueOf(0.2d));
终于奏效了,但是作为很多仪式的代价...
Kotlin救援
仅仅将代码移植到Kotlin只会稍微提高可读性:
vala=BigDecimal.valueOf(5.8)
valb=BigDecimal.valueOf(5.6)
valsub=a.subtract(b)
assertThat(sub).isEqualTo(BigDecimal.valueOf(0.2))
请注意,在Kotlin中,默认情况下浮点数是双精度的。
为了使API更加流畅,从而使代码更具可读性,可以应用两个有价值的Kotlin功能。
第一个是扩展方法(我已经在以前的文章中展示了它们在改进SLF4J日志记录中的用法。让我们在这里使用它轻松地从Double
创建BigDecimal
对象:
funDouble.toBigDecimal():BigDecimal=BigDecimal.valueOf(this)
vala=5.8.toBigDecimal()// Now a is a BigDecimal
第二个功能-与方法扩展一起,是运算符重载。 Kotlin位于Java(无法进行运算符重载)和Scala(可以重载每个运算符)之间(我想知道为什么现在还没有表情符号库):仅某些运算符可以重载,包括算术运算符- +
, -
, *
和/
。
可以很容易地覆盖它们,如下所示:
operatorfunBigDecimal.plus(a:BigDecimal)=this.add(a)
operatorfunBigDecimal.minus(a:BigDecimal)=this.subtract(a)
operatorfunBigDecimal.times(a:BigDecimal)=this.multiply(a)
operatorfunBigDecimal.div(a:BigDecimal)=this.divide(a)
valsub=a-b
注意,这已经在Kotlin的stdlib中得到了解决 。
原始代码段现在可以这样写:
vala=5.8.toBigDecimal()
valb=5.6.toBigDecimal()
assertThat(a-b).isEqualTo(0.2.toBigDecimal())
断言线可能可以进一步改善。 为了使isEqualTo()
方法接受Double
参数,可能的解决方案还包括AssertJ自定义断言或扩展方法。
结论
使用Kotlin扩展方法,可以使任何复杂的API或库更易于阅读。 你在等什么?
翻译自: https://blog.frankel.ch/fixing-floating-point-arithmetics-with-kotlin/