提问人:Saurabh 提问时间:11/8/2019 最后编辑:Saurabh 更新时间:9/21/2022 访问量:1957
scala.math.BigDecimal : 1.2 和 1.20 相等
scala.math.BigDecimal : 1.2 and 1.20 are equal
问:
如何在将 Double 或 String 转换为 scala.math.BigDecimal 时保持精度和尾随零?
用例 - 在 JSON 消息中,属性的类型为 String,值为“1.20”。但是在 Scala 中读取此属性并将其转换为 BigDecimal 时,我丢失了精度并将其转换为 1.2
答:
我通常不做数字,但是:
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
对于 ,与 完全相同,因此无法将它们转换为不同的 s。因为,您不会失去精度;你可以看到,因为而不是!但是 on 恰好被定义,因此数值相等的 s 是相等的,即使它们是可区分的。Double
1.20
1.2
BigDecimal
String
res3: scala.math.BigDecimal = 1.20
... = 1.2
equals
scala.math.BigDecimal
BigDecimal
如果你想避免这种情况,你可以使用 s for whichjava.math.BigDecimal
与 compareTo 不同,此方法认为两个 BigDecimal 对象仅在值和小数位数相等时才相等(因此,使用此方法进行比较时,2.0 不等于 2.00)。
就您的情况而言,将是错误的。res2.underlying == res3.underlying
当然,它的文档也指出
注意:如果将 BigDecimal 对象用作 SortedMap 中的键或 SortedSet 中的元素,则应小心,因为 BigDecimal 的自然顺序与 equals 不一致。有关详细信息,请参阅 Comparable、SortedMap 或 SortedSet。
这可能是 Scala 设计者决定采取不同行为的部分原因。
@Saurabh 真是个好问题!分享用例至关重要!
我想我的答案可以以最安全、最有效的方式解决它......简而言之,它是:
使用 jsoniter-scala 精确解析 BigDecimal
值。
任何数值类型的 JSON 字符串的编码/解码都可以按编解码器或按类字段定义。请看下面的代码:
将依赖项添加到:build.sbt
libraryDependencies ++= Seq(
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-core" % "2.17.4",
"com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.17.4" % Provided // required only in compile-time
)
定义数据结构,派生根结构的编解码器,解析响应正文并将其序列化回来:
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(true) // 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")))
将结果打印到控制台并进行验证:
println(response)
println(new String(json, "UTF-8"))
Response(1000,1.20)
{"amount":1000,"price":"1.20"}
为什么所提出的方法是安全的?
井。。。解析 JSON 是一个雷区,尤其是当您将在此之后获得精确的值时。大多数 Scala 的 JSON 解析器都使用 Java 的构造函数来表示字符串,这具有复杂性(其中尾数中的数字数),并且不会将结果舍入到安全选项(默认情况下,该值用于 Scala 的构造函数和操作)。BigDecimal
O(n^2)
n
MathContext
MathContext.DECIMAL128
BigDecimal
它为接受不受信任输入的系统引入了低带宽 DoS/DoW 攻击下的漏洞。下面是一个简单的示例,如何在 Scala REPL 中使用类路径中最流行的 Scala JSON 解析器的最新版本重现它:
...
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...
对于当代 1Gbit 网络,收到 10 毫秒的 1M 位数字的恶意消息可能会在单个内核上产生 29 秒的 100% CPU 负载。超过 256 个内核可以在全带宽速率下有效地进行 DoS ed。最后一个表达式演示了如果 Scala 2.12.8 使用了后续 OR 操作,如何使用带有 13 字节数字的消息将 CPU 内核烧录 ~1.5 分钟。+
-
而且,jsoniter-scala 负责处理 Scala 2.11.x、2.12.x、2.13.x 和 3.x 的所有这些情况。
为什么它是最有效的?
下面是图表,在解析 128 个小(最多 34 位尾数)值和中等(128 位尾数)值的数组期间,不同 JVM 上 Scala 的 JSON 解析器的吞吐量(每秒操作数,因此越大越好):BigDecimal
jsoniter-scala 中 BigDecimal
的解析例程:
对最多 36 位的小数字使用具有紧凑表示的值
BigDecimal
对 37 到 284 位的中型数字使用更高效的热回路
切换到递归算法,该算法对于超过 285 位的值具有复杂性
O(n^1.5)
此外,jsoniter-scala 将 JSON 直接从 UTF-8 字节解析和序列化到您的数据结构并返回,并且速度非常快,无需使用运行时反射、中间 AST、字符串或哈希映射,只需最少的分配和复制。请在此处查看 GeoJSON、Google Maps API、OpenRTB 和 Twitter API 针对不同数据类型的 115 个基准测试的结果以及真实消息示例。
评论
BigDecimal