scala.math.BigDecimal:1.2和1.20相等

在将Double或String转换为scala.math.BigDecimal时,如何保持精度和尾随零?

用例-在JSON消息中,属性的类型为String,且值为“ 1.20”。但是在Scala中读取此属性并将其转换为BigDecimal时,我失去了精度,并将其转换为1.2

scala.math.BigDecimal:1.2和1.20相等

yumuyang111 回答:scala.math.BigDecimal:1.2和1.20相等

@Saurabh真是个好问题!共享用例至关重要!

我认为我的答案可以用最安全,最有效的方式来解决...简而言之,它是:

使用jsoniter-scala来精确解析BigDecimal值。

可以通过每个编解码器或每个类字段定义对任何数字类型的JSON字符串进行编码/从中解码。请参见下面的代码:

1)将依赖项添加到您的build.sbt

libraryDependencies ++= Seq(
  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core"   % "2.0.1","com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.0.1" % Provided // required only in compile-time
)

2)定义数据结构,为根结构派生一个编解码器,解析响应主体并将其序列化回去:

import com.github.plokhotnyuk.jsoniter_scala.core._
import com.github.plokhotnyuk.jsoniter_scala.macros._

case class Response(
  amount: BigDecimal,@stringified price: BigDecimal)

implicit val codec: JsonValueCodec[Response] = JsonCodecMaker.make {
  CodecMakerConfig
    .withIsStringified(false) // switch it on to stringify all numeric and boolean values in this codec
    .withBigDecimalPrecision(34) // set a precision to round up to decimal128 format: java.math.MathContext.DECIMAL128.getPrecision
    .withBigDecimalScaleLimit(6178) // limit scale to fit the decimal128 format: BigDecimal("0." + "0" * 33 + "1e-6143",java.math.MathContext.DECIMAL128).scale + 1
    .withBigDecimalDigitsLimit(308) // limit a number of mantissa digits to be parsed before rounding with the specified precision
}

val response = readFromArray("""{"amount":1000,"price":"1.20"}""".getBytes("UTF-8"))
val json = writeToArray(Response(amount = BigDecimal(1000),price = BigDecimal("1.20")))

3)将结果打印到控制台并进行验证:

println(response)
println(new String(json,"UTF-8"))

Response(1000,1.20)
{"amount":1000,"price":"1.20"}   

为什么建议的方法安全?

好吧... Parsing of JSON is a minefield,尤其是在此之后要获得精确的BigDecimal值时。大多数用于Scala的JSON解析器都是使用Java的字符串表示形式的构造函数来实现的,该构造函数具有O(n^2)复杂度(其中n是尾数中的数字)并且不会将结果四舍五入到{{1 }}(默认情况下,MathContext值用于Scala的MathContext.DECIMAL128构造函数和操作中的值)。

它为接受不受信任的输入的系统引入了低带宽DoS / DoW攻击下的漏洞。下面是一个简单的示例,如何在Scala REPL中使用类路径中针对Scala的最受欢迎的JSON解析器的最新版本进行复制:

BigDecimal

对于当代的1Gbit网络,在10ms内收到1M数字的恶意消息可能会在单个内核上产生29秒的100%CPU负载。在全带宽速率下,可以有效地对256个以上的内核进行DoS处理。最后一个表达式演示了如何在Scala 2.12.8中使用后续的... Starting scala interpreter... Welcome to Scala 2.12.8 (OpenJDK 64-Bit Server VM,Java 1.8.0_222). Type in expressions for evaluation. Or try :help. scala> def timed[A](f: => A): A = { val t = System.currentTimeMillis; val r = f; println(s"Elapsed time (ms): ${System.currentTimeMillis - t}"); r } timed: [A](f: => A)A scala> timed(io.circe.parser.decode[BigDecimal]("9" * 1000000)) Elapsed time (ms): 29192 res0: Either[io.circe.Error,BigDecimal] = Right(999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999... scala> timed(io.circe.parser.decode[BigDecimal]("1e-100000000").right.get + 1) Elapsed time (ms): 87185 res1: scala.math.BigDecimal = 1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000... +操作时,使用带有13字节数字的消息将CPU内核刻录约1.5分钟。

而且,jsoniter-scala会处理Scala 2.11.x,2.12.x和2.13.x的所有这些情况。

为什么效率最高?

以下是在解析128个小(最多34位尾数)值和一个中号(带有一个小数)的数组时,不同JVM上Scala的JSON解析器的吞吐量(每秒的操作数,越大越好)的图表。 -的128位尾数):

enter image description here

enter image description here

The parsing routine for BigDecimal(在jsoniter-scala中):

  • 使用BigDecimal值进行紧凑表示,以表示最多36位的小数字

  • 对具有37到284位数字的中号使用更有效的热循环

  • 切换到递归算法,该算法对于具有超过285位数字的值具有BigDecimal复杂度

此外,jsoniter-scala直接将JSON从UTF-8字节解析并序列化到您的数据结构,然后再返回,并疯狂地快速执行,而无需使用运行时反射,中间AST,字符串或哈希映射,只需最少的分配和复制。请查看here的针对不同数据类型的115个基准测试的结果以及GeoJSON,Google Maps API,OpenRTB和Twitter API的真实消息示例。

,

对于Double1.201.2完全相同,因此不能将它们转换为不同的BigDecimal。对于String,您不会失去精度;您会看到这是因为res3: scala.math.BigDecimal = 1.20而不是... = 1.2!但是equals上的scala.math.BigDecimal恰好被定义为即使它们是可区分的,数值上相等的BigDecimal也相等。

如果要避免这种情况,可以使用java.math.BigDecimal

  

Unlike compareTo,this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method).

对于您而言,res2.underlying == res3.underlying将为假。

当然,其文档中也注明

  

注意:如果将BigDecimal对象用作SortedMap的键或SortedSet中的元素,则应格外小心,因为BigDecimal的自然顺序与不一致。有关更多信息,请参见Comparable,SortedMap或SortedSet。

这可能是Scala设计师决定不同行为的原因的一部分。

,

我通常不做数字,但是:

scala> import java.math.MathContext
import java.math.MathContext

scala> val mc = new MathContext(2)
mc: java.math.MathContext = precision=2 roundingMode=HALF_UP

scala> BigDecimal("1.20",mc)
res0: scala.math.BigDecimal = 1.2

scala> BigDecimal("1.2345",mc)
res1: scala.math.BigDecimal = 1.2

scala> val mc = new MathContext(3)
mc: java.math.MathContext = precision=3 roundingMode=HALF_UP

scala> BigDecimal("1.2345",mc)
res2: scala.math.BigDecimal = 1.23

scala> BigDecimal("1.20",mc)
res3: scala.math.BigDecimal = 1.20

编辑:还有https://github.com/scala/scala/pull/6884

scala> res3 + BigDecimal("0.003")
res4: scala.math.BigDecimal = 1.20

scala> BigDecimal("1.2345",new MathContext(5)) + BigDecimal("0.003")
res5: scala.math.BigDecimal = 1.2375
本文链接:https://www.f2er.com/3141281.html

大家都在问