Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Introduction to safe programming with numeric l...

Introduction to safe programming with numeric library / 数値ライブラリで始める安全なプログラミング

Calculating charges is a universal concern in application, accuracy of numeric type is critical. This session introduce practice of typelevel/spire to solve complex calculations where formula, rounding, serialization are applied.

料金計算はアプリケーションにおける普遍的な関心事であり数値型の精度の考慮が重要です。私達のチームでは複雑な料金計算において段階的に計算式・端数処理・シリアライズを適用するケースに対して、typelevel/spireを導入し課題の解決にトライしています。本セッションでは実例を元に数値をより安全に扱う方法を紹介し、最小限のテストでバグの少ないプログラミングを明日から実践できるようにします。

Kushiro Taichi

June 08, 2024
Tweet

More Decks by Kushiro Taichi

Other Decks in Programming

Transcript

  1. Introduction to safe programming with numeric library ScalaMatsuri 2024 @boykush

    数値ライブラリで始める安全なプログラミング
  2. Our Products Scalebase is a SaaS for recurring service providers.

    We automate billings based on complex pricing models and recurring charge contracts. 継続型サービス事業者向けのSaaSです。複雑なプライシング や継続課金契約に基づく請求を自動化します。
  3. Calculating charges is a universal concern in applications; accuracy of

    the numeric type is critical. 料金計算はアプリケーションにおける普遍的な関心事であ り、数値型の精度の考慮が非常に重要です。
  4. General approach Calculating charges are generally done using decimal floating

    point types such as scala.math.BigDecimal. To avoid binary floating point errors. scala> val double: Double = 1.0 - 0.9 val double: Double = 0.09999999999999998 scala> val decimal: BigDecimal = BigDecimal(1.0) - BigDecimal(0.9) val decimal: BigDecimal = 0.1 料金計算では一般に BigDecimal 型のような10進浮動小数点 型が用いられます。
  5. The critical decimal floating point issue The most critical issue

    is the error in repeating decimal. In some cases, the final integer billing price contains errors. scala> val step1 = BigDecimal(8) / BigDecimal(30) val step1: BigDecimal = 0.2666666666666666666666666666666667 scala> val step2 = step1 * 3000 val step2: BigDecimal = 800.0000000000000000000000000000001 scala> val rounded = step2.setScale(0, BigDecimal.RoundingMode.UP) val rounded: BigDecimal = 801 最も重大な課題は循環小数における誤差です。最終的な整数 の請求金額に誤差が生じるケースがあります。
  6. Risk of scattered roundings Especially when calculating charges across multiple

    contexts, it’s very difficult to round at many locations while keeping accuracy in mind. 特に複数の文脈に跨がって料金計算をする場合、精度を保ち ながら多くの箇所で端数処理を行うのはとても大変です。
  7. The Spire library provides numeric types to be generic, fast

    and precise efficient syntax for writing numeric code Spireライブラリは汎用的で高速かつ高精度な数値と、効率 的な数値コードシンタックスを提供します。
  8. The Rational type Represents rational numbers as a ratio between

    two Longs. Convertible to BigDecimal type as an approximation. scala> import spire.math.Rational scala> val rational = Rational(1, 3) val rational: spire.math.Rational = 1/3 scala> val substracted = rational - Rational(1, 2) val substracted: spire.math.Rational = -1/6 scala> val appr = rational.toBigDecimal(4, java.math.RoundingMode.DOWN) val rounded: BigDecimal = 0.3333 Rational型は分数として2つの整数を保持し、有理数を表現 します。近似値としてBigDecimal型に変換可能です。
  9. Exact repeating decimals The issue of repeating decimals is solved

    with the Rational number type. scala> val step1 = Rational(8, 30) val step1: spire.math.Rational = 4/15 scala> val step2 = step1 * Rational(3000) val step2: spire.math.Rational = 800 scala> val rounded = step2.toBigDecimal(0, java.math.RoundingMode.UP) val rounded: BigDecimal = 800 循環小数の課題はRational型によって解決できます。
  10. Localization of accuracy concerns When calculating charges across multiple contexts,

    the concern for accuracy is eliminated except at the conversion to the final calculation result. 複数の文脈に跨って料金計算をする場合、精度への関心を最 終的な計算結果への変換に閉じることができます。
  11. The Real type Represents real numbers including irrational numbers. We

    can apply to precision to obtain a Rational. scala> import spire.math.Real scala> val rational = Real(Rational(1, 3)) val rational: spire.math.Real = 1/3 scala> val irrational = Real(2).sqrt val irrational: spire.math.Real = 1.414213562373095048801688724209698078 scala> (rational + irrational).toRational(10) val res0: spire.math.Rational = 895/512 Real型は無理数を含む実数を表現します。精度を適用して有 理数を得ることができます。
  12. Closest approximation to the desired accuracy Irrationals of the Real

    type are implemented internally via functions from precision to approximation. Unlike Float and Double, the approximation is not stored for each operation. def +(y: Real): Real = (x, y) match { case (Exact(nx), Exact(ny)) => Exact(nx + ny) case (Exact(Rational.zero), _) => y case (_, Exact(Rational.zero)) => x case _ => Real(p => roundUp(Rational(x(p + 2) + y(p + 2), 4))) } Real型の無理数は精度を受け取り近似値を返す関数で実装さ れています。求める精度に最も近い近似値を得ます。
  13. The Polynomial type Represents polynomial with a single variable (written.

    x). We can substitute to the variable x to obtain a Rational. scala> import spire.math.Polynomial scala> val perUnit = Polynomial.linear(Rational(100)) val perUnit: spire.math.Polynomial[spire.math.Rational] = (100x) scala> val discount = Polynomial.constant(Rational(500)) val discount: spire.math.Polynomial[spire.math.Rational] = (500) scala> val pricing = perUnit - discount val pricing: spire.math.Polynomial[spire.math.Rational] = (100x - 500) scala> val price = pricing.apply(Rational(20)) val price: spire.math.Rational = 1500 Polynomial型は一変数多項式を表現します。変数xに代入し て有理数を得ることができます。
  14. Serialization in Applications Delegate lifecycle storage to a data store

    Inter-service communication in servers Inter-layer communication with clients, etc. アプリケーションではデータストア、サービス間通信、レイ ヤ間通信等でシリアライズが行われます。
  15. Serialization for data storage When internally serializing the Rational (or

    Polynomial) type in data storage, interconvertibility with string types is possible. scala> val rational = Rational(1, 2) val rational: spire.math.Rational = 1/2 scala> val serialized = rational.toString() val serialized: String = 1/2 scala> val deserialized = Rational(serialized) val deserialized: spire.math.Rational = 1/2 データストアでRational型を内部的にシリアライズをする場 合、文字列型との相互変換が可能です。
  16. Serialization for internal communication For internal communication of the Rational

    type between services or layers, there are several options. Communicate between supporting string conversions Prepare the following definitions in a interface define language syntax = "proto3"; message Rational{ long numerator = 1; long denominator = 2; } サービスまたはレイヤ間でRational型を内部的にシリアライ ズをするにはいくつかの選択肢があります。
  17. Built-in type classes For example, the Rational type provides a

    type class implementation of a Ring. The spire.algebra.Ring is an alias for cats algebra. scala> import spire.algebra.Ring scala> val items = List(Rational(500), Rational(1000), Rational(1500)) val items: List[spire.math.Rational] = List(500, 1000, 1500) scala> given cats.Monoid[Rational] = Ring[Rational].additive lazy val given_Monoid_Rational: cats.kernel.Monoid[spire.math.Rational] scala> val subtotals = cats.Foldable[List].fold(items) val subtotals: spire.math.Rational = 3000 Ring(環)のような型クラス実装が標準で提供されます。 spire.algebra.Ring は cats algebra のエイリアスです。
  18. Conclusion Rational number types can easily be used when numerical

    precision is required. The Spire library localizes the concern in accuracy as in lazy evaluation. You will be able to take advantage of convenient string conversions and syntax. 有理数型は簡単に正確な数値を扱えます。Spireライブラリ は精度の関心を遅延評価のように局所化します。