Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

About me Taichi Kushiro / SWE Alp, Inc. boykush boykush315 自己紹介

Slide 3

Slide 3 text

Our Products Scalebase is a SaaS for recurring service providers. We automate billings based on complex pricing models and recurring charge contracts. 継続型サービス事業者向けのSaaSです。複雑なプライシング や継続課金契約に基づく請求を自動化します。

Slide 4

Slide 4 text

Now, to the point… 本題に入ります。

Slide 5

Slide 5 text

… about the accuracy of numbers when calculating charges. 料金計算における数値の精度についてです。

Slide 6

Slide 6 text

Calculating charges is a universal concern in applications; accuracy of the numeric type is critical. 料金計算はアプリケーションにおける普遍的な関心事であ り、数値型の精度の考慮が非常に重要です。

Slide 7

Slide 7 text

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進浮動小数点 型が用いられます。

Slide 8

Slide 8 text

However, decimal floating point types also has its issues. しかし10進浮動小数点型にも課題は残ります。

Slide 9

Slide 9 text

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 最も重大な課題は循環小数における誤差です。最終的な整数 の請求金額に誤差が生じるケースがあります。

Slide 10

Slide 10 text

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. 特に複数の文脈に跨がって料金計算をする場合、精度を保ち ながら多くの箇所で端数処理を行うのはとても大変です。

Slide 11

Slide 11 text

How to solve these issues? 課題を解決するには?

Slide 12

Slide 12 text

The numerical library typelevel/spire is a solution 数値ライブラリ typelevel/spire によって解決します。

Slide 13

Slide 13 text

The Spire library provides numeric types to be generic, fast and precise efficient syntax for writing numeric code Spireライブラリは汎用的で高速かつ高精度な数値と、効率 的な数値コードシンタックスを提供します。

Slide 14

Slide 14 text

Let’s look at an exact rational number type 正確な有理数型を紹介します。

Slide 15

Slide 15 text

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型に変換可能です。

Slide 16

Slide 16 text

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型によって解決できます。

Slide 17

Slide 17 text

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. 複数の文脈に跨って料金計算をする場合、精度への関心を最 終的な計算結果への変換に閉じることができます。

Slide 18

Slide 18 text

Safe programming with minimal testing!! テストを最小限に安全なプログラミングができる

Slide 19

Slide 19 text

Other else types? その他の型は?

Slide 20

Slide 20 text

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型は無理数を含む実数を表現します。精度を適用して有 理数を得ることができます。

Slide 21

Slide 21 text

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型の無理数は精度を受け取り近似値を返す関数で実装さ れています。求める精度に最も近い近似値を得ます。

Slide 22

Slide 22 text

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に代入し て有理数を得ることができます。

Slide 23

Slide 23 text

Others include Algebraic, Natural, Interval, Complex, etc. 他にはAlgebraic, Natural, Interval, Complex型などがあ ります。

Slide 24

Slide 24 text

Serialization of the Rational type Rational型のシリアライズ

Slide 25

Slide 25 text

Serialization in Applications Delegate lifecycle storage to a data store Inter-service communication in servers Inter-layer communication with clients, etc. アプリケーションではデータストア、サービス間通信、レイ ヤ間通信等でシリアライズが行われます。

Slide 26

Slide 26 text

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型を内部的にシリアライズをする場 合、文字列型との相互変換が可能です。

Slide 27

Slide 27 text

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型を内部的にシリアライ ズをするにはいくつかの選択肢があります。

Slide 28

Slide 28 text

Efficient syntax for writing numeric code 効率的な数値コード記述

Slide 29

Slide 29 text

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 のエイリアスです。

Slide 30

Slide 30 text

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ライブラリ は精度の関心を遅延評価のように局所化します。

Slide 31

Slide 31 text

Safe programming life! 安全なプログラミングライフを!