我们遇到的问题是,我们用另一个大十字来划分一个大十字,并试图通过取除数的倒数并乘以红利来验证它。
例如,基本数学会告诉我们:a / b = a * (1 / b)
更具体地说,以下是我们要做的事情:6.0075 / 0.89 = 6.0075 * (1 / 0.89) = 6.75
使用BigDecimal增加精度,我们可以得到第一部分:
scala> BigDecimal(6.0075) / BigDecimal(0.89)
res1: scala.math.BigDecimal = 6.75
但不是第二个:
scala> BigDecimal(6.0075) * (BigDecimal(1.00) / BigDecimal(0.89))
res2: scala.math.BigDecimal = 6.749999999999999999999999999999999
进一步深入研究,我们发现BigDecimal(1.00) / BigDecimal(0.89)
是一个不精确的结果,Scala的BigDecimal包装器围绕Java的math.BigDecimal指定了一个35位小数的精度,RoundingMode为HALF_EVEN。不幸的是,1.00 / 0.89
具有无限多个小数位,因此直接使用java的BigDecimal也无济于事。
还有其他人遇到过这个问题吗?除了尝试重写表达式1.00/0.89
之外,Scala或Java中是否有办法处理此问题?
谢谢!
答案 0 :(得分:3)
不幸的是,没有100%准确的方法来表示具有无限十进制扩展的数字,如1.00/0.89
(或1/3
),具有浮点数。这是有限机器试图模拟无限属性的限制。
但是,如果您只关心一定数量的数字(如果使用货币,则为2),那么您可以使用Java的MathContext
和setScale
方法:
scala> import java.math.{RoundingMode => RM, MathContext => MC}
import java.math.{RoundingMode=>RM, MathContext=>MC}
scala> val mc = new MC(100, RM.HALF_UP)
mc: java.math.MathContext = precision=100 roundingMode=HALF_UP
scala> val a = BigDecimal("6.0075")
a: scala.math.BigDecimal = 6.0075
scala> val b = BigDecimal("1")
b: scala.math.BigDecimal = 1
scala> val c = BigDecimal("0.89")
c: scala.math.BigDecimal = 0.89
scala> val d = (a * (b(mc) / c)).setScale(2)
d: scala.math.BigDecimal = 6.75
请注意,我使用的是双打的字符串版本,因为它们是更精确的表示(因为即使0.89
也不能完美地用浮点数表示)。
修改强>:
受到@jwvh关于Rational Numbers的评论的启发,我把这个基本的Rational
课程放在了一起:
scala> :pa
// Entering paste mode (ctrl-D to finish)
object Rational
{
def apply(n: BigInt, d: BigInt): Rational =
{
val neg_mod = if (d < BigInt(0)) BigInt(-1) else BigInt(1)
val (n_mod, d_mod) = (neg_mod * n, neg_mod * d)
val gcd_val = gcd(n_mod, d_mod)
new Rational(n_mod / gcd_val, d_mod / gcd_val)
}
def gcd(a: BigInt, b: BigInt): BigInt = if (b == BigInt(0)) a else gcd(b, a % b)
}
class Rational(val n: BigInt, val d: BigInt)
{
override def toString: String = if (n == BigInt(0)) "0" else if (d == BigInt(1)) s"$n" else s"$n/$d"
def toDouble: Double = n.toDouble / d.toDouble
def *(that: Rational): Rational = Rational(n * that.n, d * that.d)
def /(that: Rational): Rational = Rational(n * that.d, d * that.n)
def +(that: Rational): Rational = Rational(n * that.d + that.n * d, d * that.d)
def -(that: Rational): Rational = this + (-that)
def unary_- = Rational(-n, d)
}
// Exiting paste mode, now interpreting.
defined object Rational
defined class Rational
scala> val a = Rational(60075, 10000)
a: Rational = 2403/400
scala> val b = Rational(1, 1)
b: Rational = 1
scala> val c = Rational(89, 100)
c: Rational = 89/100
scala> a * (b / c)
res0: Rational = 27/4
scala> (a * (b / c)).toDouble
res1: Double = 6.75
答案 1 :(得分:3)
无法回归无限回归的有限表示会产生舍入误差。 (另见:Is floating point math broken?)
我看到两种可能的解决方案,它们都不是微不足道的。
String
表示的RegEx模式匹配来完成)并进行相应调整。答案 2 :(得分:-1)
您想验证a / b
是否等于a * (1 / b)
问题:(1 / b)
的值可能不准确。
我的解决方法:
让u = 0.00000000000000000000000000000000001
(你说它可以工作到35位小数)
然后验证a / b
至少a * ((1 / b) - u)
,最多a * ((1 / b) + u)
如果a
为负数,那么您需要交换这些比较的符号。
这不是一个完美的解决方法,但我希望它能做到。