【问题标题】:Scala and Java BigDecimalScala 和 Java BigDecimal
【发布时间】:2010-04-23 09:57:42
【问题描述】:

我想在我的应用程序中为基于数学的模块从 Java 切换到脚本语言。这是由于 mathy Java 的可读性和功能限制。

例如,在 Java 中我有这个:

BigDecimal x = new BigDecimal("1.1");
BigDecimal y = new BigDecimal("1.1");
BigDecimal z = x.multiply(y.exp(new BigDecimal("2"));

如您所见,如果没有 BigDecimal 运算符重载,简单的公式很快就会变得复杂。

使用双打,这看起来不错,但我需要精度。

我希望在 Scala 中我可以做到这一点:

var x = 1.1;
var y = 0.1;
print(x + y);

默认情况下我会得到类似小数的行为,可惜 Scala 默认不使用小数计算。

然后我在 Scala 中执行此操作:

var x = BigDecimal(1.1);
var y = BigDecimal(0.1);
println(x + y);

我仍然得到一个不精确的结果。

是不是我在 Scala 中做得不对?

也许我应该使用 Groovy 来最大化可读性(默认使用小数)?

【问题讨论】:

  • 真的确定使用 BigDecimal 是正确的做法吗?你在做什么样的计算?
  • 我无法验证 Scala 中 BigDecimal 的问题。它至少在 2.8.1 中按预期工作
  • var x:BigDecimal = 11.0 应该是准确的,并且 x = x/10.0 应该是快乐的。

标签: java math scala groovy bigdecimal


【解决方案1】:

我不知道 Scala,但在 Java 中,new BigDecimal(1.1) 使用 double 值初始化 BigDecimal,因此它不完全等于 1.1。在 Java 中,您必须改用 new BigDecimal("1.1")。也许这对 Scala 也有帮助。

【讨论】:

  • 伙计,这太丑了。我想如果您关心小数精度,那么使用 Groovy 是迄今为止最漂亮的选择。它的 BigDecimal 支持还不完全完美,但比任何其他语言都要好。
  • @mcv 在BigDecimal(1.1) 中,表达式 1.1 被解析为双精度数,甚至在 BigDecimal 获取其值之前。所以你需要BigDecimal("1.1") 以便BigDecimal 负责决定“1.1”的含义。这是一个一般性问题(在任何语言的 BigDecimal 或等效语言中),AFAICT 您只能使用不会自动将数字文字作为浮点值读取的语言来解决(我一直认为这会节省很多混乱),但我我不知道有任何语言以这种方式做事。
  • @ShreevatsaR 这正是 Groovy 所做的。它具有原生 BigDecimal 支持,并立即将 1.1 解释为 BigDecimal。这可能会让习惯使用原生浮点数的人感到困惑,但在业务逻辑中,这通常是您真正想要的。
【解决方案2】:

将您的 Scala 代码更改为:

var x = BigDecimal("1.1");   // note the double quotes
var y = BigDecimal("0.1");
println(x + y);

它会像在 Java 中一样工作。

【讨论】:

  • 这里发生了什么?为什么new 既不需要也不接受?
  • 请注意,没有newBigDecimal("1.0")BigDecimal.apply("1.0") 的简写——它调用object BigDecimalapply 方法,class BigDecimal 的伴随对象。 apply 方法是工厂方法。
  • 它不适用于new(至少在 Scala 2.10.0 中不行),因为显然BigDecimal 没有采用String 的公共构造函数。这可能是 Scala 版本的BigDecimal 中的一个错误。请注意,如果您执行new java.math.BigDecimal("1.0"),它会起作用。
  • 那是一个不同的类别。我在哪里可以了解无构造函数版本的工作原理?
  • 查看对象scala.math.BigDecimal的API文档。 BigDecimal(...)BigDecimal.apply(...) 含义相同的符号是 Scala 编译器魔法。
【解决方案3】:

Scala 在这方面绝对与 Java 相同。

根据 Joachim 的回答,写 val x = BigDecimal(1.1)

相当于写

val d : Double = 1.1
val x = BigDecimal(d)

当然,问题在于 Double d 已经存在舍入错误,因此您使用错误数据初始化 x。

改用接受字符串的构造函数,一切都会好的。

根据您的示例,您最好使用vals 而不是vars,并且您也可以安全地在 Scala 中不使用分号。

【讨论】:

  • 我无法在 Scala 中验证 BigDecimal 的问题。它在 2.8.1 中按预期工作
  • 不等价。在第二种情况下,您使用新变量 d 污染命名空间。
【解决方案4】:

您可以在内部将值存储为整数/字符串(无精度)并使用scale(这是来自 Scala REPL 的脚本):

scala> val Scale = 2
Scale: Int = 2

scala> val x = BigDecimal(110, Scale)
x: scala.math.BigDecimal = 1.10

scala> val y = BigDecimal(303, Scale)
y: scala.math.BigDecimal = 3.03

scala> (x+y, (x+y).scale)
res0: (scala.math.BigDecimal, Int) = (4.13,2)

scala> (x*2, (x*2).scale)
res1: (scala.math.BigDecimal, Int) = (2.20,2)

或者如果你想解析一个字符串,你可以控制舍入:

scala> val z = BigDecimal("8.937").setScale(Scale, BigDecimal.RoundingMode.FLOOR)      
z: scala.math.BigDecimal = 8.93

scala> val z = BigDecimal("8.937").setScale(Scale, BigDecimal.RoundingMode.CEILING)
z: scala.math.BigDecimal = 8.94

【讨论】:

  • 为什么你使用val x = new BigDecimal(new JBigD(JBigI.valueOf(110), Scale))而不是val x = BigDecimal(110, Scale)
  • 感谢您的指出!我不记得这种方法在 Scala 2.7.x 中是否可用,但它绝对是在 Scala 2.8 中使用的方法。我会修复答案。
【解决方案5】:
scala> implicit def str2tbd(str: String) = new {
     |     def toBD = BigDecimal(str)
     | }
str2tbd: (str: String)java.lang.Object{def toBD: scala.math.BigDecimal}

scala> var x = "1.1".toBD
x: scala.math.BigDecimal = 1.1

scala> var y = "0.1".toBD
y: scala.math.BigDecimal = 0.1

scala> x + y
res0: scala.math.BigDecimal = 1.2

scala> implicit def str2bd(str: String) = BigDecimal(str)
str2bd: (str: String)scala.math.BigDecimal

scala> x + y + "1.2345566"
res1: scala.math.BigDecimal = 2.4345566

scala>

【讨论】:

  • 这实际上并没有回答这个问题,它只是演示了一种使用字符串构造函数的不同方法,正如不同答案中已经涵盖的那样!
【解决方案6】:

我知道这个问题已经过时并且已经得到解答,但是如果您对不同的语言持开放态度(就像 OP 似乎那样),另一种选择是使用 Clojure。 Clojure 有,IMO,BigDecimal 数学的一些最简单的语法(注意尾随 Ms - 表示 BigDecimal):

user=> (def x 1.1M)
#'user/x
user=> (def y 1.1M)
#'user/y
user=> (def z (* x (.pow y 2)))
#'user/z
user=> z
1.331M
user=> (type z)
java.math.BigDecimal

我喜欢 Clojure 的数学,因为它在许多情况下默认为精度,例如它使用Ratio

user=> (/ 60 14)
30/7
user=> (type (/ 60 14))
clojure.lang.Ratio

【讨论】:

  • 肯定比提供的 Scala 版本更好 - 如果有人喜欢波兰符号。
  • @ziggystar 是的,你必须愿意接受前缀/波兰语表示法,这对许多人来说可能被视为一个大障碍:)
  • 只是想指出,由于 Clojure 的延展性很强,它可以在宏中使用中缀表示法。 This post 展示了几年前 Incanter 库中的一个示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-10
  • 1970-01-01
  • 1970-01-01
  • 2020-07-28
  • 2019-09-20
  • 2014-02-26
  • 1970-01-01
相关资源
最近更新 更多