雑なメモ書き

気楽にいきます

円周率 マチンの公式 go

ja.wikipedia.org

package main

import "fmt"

func main() {
        var k int
        var p, t, last float64

        p = 0
        k = 1
        t = 16.0 / 5.0

        for {
                last = p
                p += t / float64(k)
                t /= -5.0 * 5.0
                k += 2
                if p == last {
                        break
                }
        }
        k = 1
        t = 4.0 / 239.0
        for {
                last = p
                p -= t / float64(k)
                t /= -239.0 * 239.0
                k += 2
                if p == last {
                        break
                }
        }

        fmt.Println(p)
}
3.141592653589794

丸め誤差で最後の桁はおかしくなってる

追記

AIで丸め誤差を解析させた

Machin の公式によるπ計算 — 丸め誤差の蓄積過程

main.go は Machin の公式

π = 4 · ( 4·arctan(1/5) − arctan(1/239) )

を、arctan のテイラー級数(交代級数)で float64 のまま計算している。

  • フェーズ1: t = 16/5 から始めて 4·arctan(1/5) を求める(main.go 13〜21行目)
  • フェーズ2: t = 4/239 から始めて arctan(1/239) を引く(main.go 22〜32行目)
  • どちらのループも p == last(1つ前の値から変化がなくなった)で打ち切る

各ステップで「次に加える項 t/k」と「その時点の pmath.Pi との差」を記録すると、 どこまでが級数の数学的な収束で、どこから float64 の丸め誤差が支配的になるかが分かる。

ステップごとのデータ

phase step k term (加える項) p diff = p − π
1 1 1 3.2 3.2 5.840734641020706e-2
1 2 3 -4.2666666666666665e-2 3.1573333333333333 1.574067974354021e-2
1 3 5 1.0240000000000002e-3 3.1583573333333335 1.6764679743540345e-2
1 4 7 -2.925714285714286e-5 3.158328076190476 1.673542260068306e-2
1 5 9 9.102222222222223e-7 3.158328986412698 1.6736332822905098e-2
1 6 11 -2.978909090909091e-8 3.1583289566236075 1.6736303033814348e-2
1 7 13 1.008246153846154e-9 3.158328957631854 1.6736304042060723e-2
1 8 15 -3.4952533333333335e-11 3.1583289575969014 1.6736304007108238e-2
1 9 17 1.2336188235294119e-12 3.158328957598135 1.6736304008341918e-2
1 10 19 -4.415056842105264e-14 3.158328957598091 1.6736304008297953e-2
1 11 21 1.5978300952380953e-15 3.158328957598093 1.673630400829973e-2
1 12 23 -5.835553391304349e-17 3.158328957598093 1.673630400829973e-2
2 13 1 -1.6736401673640166e-2 3.141592555924453 -9.76653402595673e-8
2 14 3 9.76663671483352e-8 3.14159265359082 1.0267342531733448e-12
2 15 5 -1.0258892576985893e-12 3.141592653589794 8.881784197001252e-16
2 16 7 1.2828522631138778e-17 3.141592653589794 8.881784197001252e-16

最終値: p = 3.14159265358979400418math.Pi = 3.14159265358979311600、diff = 8.881784e-16

読み方

  • フェーズ1(step 1〜12): 項は 25 倍ずつ縮み、p4·arctan(1/5) ≈ 3.158328957598093 に収束する。 ここでの diff(≈0.0167)は丸め誤差ではなく、まだ arctan(1/239) の補正をしていないことによる数学的なズレ
  • フェーズ2(step 13〜16): 補正項を引くことで diff は急激に縮む(1e-8 → 1e-12 → 1e-16)。
  • 丸め誤差が顔を出す境目: step 15〜16 で「加える項」が p の ULP(float64 の1刻み、p ≈ 3.14 なら約 2.22e-16 × p ≈ 7e-16)を下回る。 そうなると p += term は浮動小数点演算上 p を全く変化させなくなり、p == last が成立してループが終了する。
  • 最終誤差 8.88e-16(≈4 ULP) が、この計算で約16回の加減算を重ねた結果として蓄積した丸め誤差そのもの。 数学的には項をさらに足せばπにもっと近づくはずだが、float64 の精度(約15〜17桁)がそこで頭打ちになり、 それ以上は「級数を計算している」のではなく「丸め誤差の中で足踏みしている」状態になる。

参考: 計測に使ったコード

main.go の各ループに以下の計装を加えて実行した(本体のロジックは変更していない)。

term := t / float64(k)
p += term // または p -= term
// term, p, p-math.Pi を記録

石取りゲーム go

package main

import "fmt"

func main() {
        var n int // 石の数
        var m int // 1回に取れる最大の石の数

        fmt.Print("石の数を入力してください:")
        if _, err := fmt.Scan(&n); err != nil {
                panic("error")
        }
        fmt.Print("1回に取れる最大の石の数を入力してください:")
        if _, err := fmt.Scan(&m); err != nil {
                panic("error")
        }

        if m < 1 || n < 1 {
                panic("error")
        }

        var myTurn bool
        for myTurn = true; n != 0; myTurn = !myTurn {
                var x int
                for x <= 0 || x > n || x > m {
                        if myTurn {
                                x = (n - 1) % (m + 1)
                                if x == 0 {
                                        x = 1
                                }
                                fmt.Printf("私は%d個の石を取ります\n", x)
                        } else {
                                fmt.Print("何個取りますか?")
                                if _, err := fmt.Scan(&x); err != nil {
                                        fmt.Println("入力が正しくありません")
                                        x = 0
                                }
                        }
                }
                n -= x
                fmt.Printf("残りは%d個です\n", n)
        }
        if myTurn {
                fmt.Println("あなたの負けです!!")
        } else {
                fmt.Println("私の負けです!!")
        }
}
石の数を入力してください:3
1回に取れる最大の石の数を入力してください:1
私は1個の石を取ります
残りは2個です
何個取りますか?2
何個取りますか?1
残りは1個です
私は1個の石を取ります
残りは0個です
私の負けです!!

crypt go

package crypto

import (
        "io"
        "math/rand"
)

func New(in io.Reader, out io.Writer, seed int) {
        r := rand.New(rand.NewSource(int64(seed)))

        buf := make([]byte, 4096)
        for {
                n, err := in.Read(buf)
                if n > 0 {
                        for i := 0; i < n; i++ {
                                buf[i] ^= byte(r.Intn(256))
                        }
                        if _, werr := out.Write(buf[:n]); werr != nil {
                                panic(werr)
                        }
                }
                if err == io.EOF {
                        break
                }
                if err != nil {
                        panic(err)
                }
        }
}
package crypto_test

import (
        "bytes"
        "fmt"
        "testing"

        "example.com/crypto"
)

func TestCrypto(t *testing.T) {
        var in bytes.Buffer
        in.WriteString("hello")
        var out bytes.Buffer
        var seed = 12345

        crypto.New(&in, &out, seed)

        fmt.Printf("encrypted: %v\n", out.Bytes())

        var restored bytes.Buffer
        crypto.New(bytes.NewReader(out.Bytes()), &restored, seed)

        fmt.Printf("restored: %s\n", restored.String())
}
encrypted: [35 58 28 48 54]
restored: hello
PASS
ok      example.com/crypto      0.002s

比較的型がつよい言語でモックを嵐のように使ってテスト開発する意味があるのか?

タイトルまんまなんですが、これ非常に短期的な視点の提出側の論理のきがするんですよね。 動的言語最盛期の場合にはこのモックもそこそこ意味があったとは思うんですが、最近の開発言語そもそも 比較的型が強い世界なのでこれを多用しすぎる意味も減ったはず。

何よりも実際の取得部分をまるで動かさずにやるから改修とかしても間違いにまるで気づけないとおもうんですよね。

正直これをやめてちゃんとテスト動く環境になると提出側も改修が死ぬほど楽になると思っている

この辺のなんかいい説明を出来るBlogとか本が欲しい。

追記:

AIに聞いてみたら以下の様な本とBlogを推薦されました。

phauer.com www.kamilgrzybek.com

設定をどう持てば安全なのか

ここのところ、セキュリティ周りのニュースがよく流れてきて、他人事では無いなぁと思いながらみていた。

corp.moneyforward.com

当社がソフトウェア開発およびシステム管理に利用している『GitHub』※1の認証情報が漏えいし、これを用いた第三者による不正なアクセスが発生し、『GitHub』※1内の「リポジトリ」※2がコピーされたことが判明しました。

経緯はどうであれ、現代だとバージョン管理システム内に漏れては不味いデータ入れるのはよくないだろうと思われる。 実際にこれに対して何をすべきだったのかと言われると

  1. コードに機密情報を書かない
  2. デプロイ時に環境変数から渡すかSecretManagerでデータを取得する(GoogleCloudの場合

がおそらくすべきことだった。 この2つに関して、1番はいいとして、2番の方が未だに迷う。 管理したい側の意見としては、全部SecretManager管理にしてそこから取得するようにしてね。でいいと思うが CI等の環境変数で設定して何が悪いの言われると困るからAIに聞いてみた結果として、悪いというより、管理境界がCI側に分散するのが問題。 という便利な言葉を貰ったので今後これを使って行きたい。

あと、実際問題。既に情報が埋め込まれている場合にどうするか? という問題に対しては以下のBlogが役に立ちそうなのでメモしておく

engineering.mercari.com

追記:

実際のMFの件は誤ってはいったようで、いかのBlogではそういう物がはいらないにはどうしたらいいかとかのってました

zenn.dev

golangのginでcontextをどう扱うべきだろうか

公式でのContextの扱いの解説

Incoming requests to a server should create a Context, and outgoing calls to servers should accept a Context. The chain of function calls between them must propagate the Context, optionally replacing it with a derived Context created using WithCancel, WithDeadline, WithTimeout, or WithValue. When a Context is canceled, all Contexts derived from it are also canceled.

サーバーへの着信要求はコンテキストを作成する必要があり、サーバーへの発信呼び出しはコンテキストを受け入れる必要があります。それらの間の関数呼び出しのチェーンは、コンテキストを伝播する必要があり、オプションで、WithCancel、WithDeadline、WithTimeout、またはWithValueを使用して作成された派生コンテキストに置き換えます。コンテキストがキャンセルされると、そのコンテキストから派生したすべてのコンテキストもキャンセルされます。

  • 本来引き回すべき物であって
  • 何かしら別の要件があった場合
  • cancel,deadline,timeout,withValueなどで派生コンテキストに置き換えるべきで
  • そのまま引き渡す方がいいに見える

gin.Contextのよくある扱い

  • 公式ではこのような例がある
package main

import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
  • ここから関数を呼び出すときどうすべきなのだろうか
  • 例えば以下の様なメソッドを上記に追加した場合
func myMethod(ctx context.Context) {

}
  • c.Request.Context()を渡すべきなのか
myMethod(c.Request.Context())
  • gin.Contextをそのまま渡すべきなのか

gin.Contexを確認する

  • engine.Newをしているここにある
func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

allocateContext

func (engine *Engine) allocateContext() *Context {
    return &Context{engine: engine, KeysMutex: &sync.RWMutex{}}
}

ServeHTTP

  • これがリクエスト単位で呼び出されている
289 // Conforms to the http.Handler interface.
290 func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
291     c := engine.pool.Get().(*Context)
292     c.writermem.reset(w)
293     c.Request = req
294     c.reset()
295
296     engine.handleHTTPRequest(c)
297
298     engine.pool.Put(c)
299 }
  • このpool.Get()で呼び出されている
  • これの正体は
engine.pool.New = func() interface{} {
    return engine.allocateContext()
}
  • allocateContextなので
  • 毎回このこのコンテキストは割り当てられている
  • なので問題なさそうにみえる
  • 呼び出し単位で扱って良いように見える

rustのcreateのメモ

cargo edit

https://crates.io/crates/cargo-edit

 cargo install cargo-edit
  • こちらが使えるようになる
  • carog.tmolいじらなくて済む
cargo add
cargo rm
cargo upgrade

clippy

  • 所謂linter

https://github.com/rust-lang/rust-clippy

rustup component add clippy

cargo fix

doc.rust-lang.org

  • editionを更新する場合は以下のオプション
cargo fix --edition