big.Rat.RatString()だけじゃない!Goでの有理数文字列化の選択肢

2025-06-01

RatString() メソッドは、この big.Rat 型の値を、人間が読みやすい文字列形式で表現するために使われます。具体的には、分子と分母をスラッシュ / で区切った文字列を返します。

例えば、ある big.Rat 型の変数が 43という値を保持している場合、その変数に対して RatString() メソッドを呼び出すと、文字列 "3/4" が返されます。

もし、big.Rat 型の値が整数、例えば 5 であれば、RatString()"5/1" という文字列を返します。これは、整数 5 が 15と表現できる有理数であるためです。



  1. big.Rat 型の変数が初期化されていない (nil ポインタ)

    • エラー
      panic: runtime error: invalid memory address or nil pointer dereference
    • 説明
      big.Rat 型の変数を new(big.Rat)new(big.Int).SetInt64(numerator).Quo(new(big.Int).SetInt64(denominator)) などで適切に初期化する前に RatString() を呼び出すと、nil ポインタ参照のエラーが発生します。
    • 解決策
      big.Rat 型の変数を使用する前に、必ず初期化を行ってください。
    package main
    
    import (
        "fmt"
        "math/big"
    )
    
    func main() {
        var r *big.Rat // 初期化されていない
    
        // fmt.Println(r.RatString()) // これはエラーを引き起こす
    
        r = new(big.Rat).SetFrac64(3, 4) // 正しい初期化
        fmt.Println(r.RatString())
    }
    
  2. 期待される文字列形式と異なる場合

    • 状況
      RatString() は常に "分子/分母" の形式で文字列を返します。例えば、整数値は "整数/1" となります。
    • トラブルシューティング
      もし、整数部分だけを表示したい、あるいは浮動小数点数のように表示したい場合は、RatString() の結果を自分で加工する必要があります。big.Rat 型には、浮動小数点数への変換 (Float64()) や整数部分の取得 (Num()) などの他のメソッドも用意されています。
    package main
    
    import (
        "fmt"
        "math/big"
    )
    
    func main() {
        r := big.NewRat(15, 3)
        ratStr := r.RatString()
        fmt.Println("RatString:", ratStr) // Output: RatString: 15/3
    
        floatVal, _ := r.Float64()
        fmt.Println("Float64:", floatVal)   // Output: Float64: 5
    
        integerPart := r.Num()
        fmt.Println("Integer Part:", integerPart) // Output: Integer Part: 5
    }
    
  3. 大きな分子や分母

    • 状況
      big.Rat は任意の精度を持つため、非常に大きな分子や分母を持つ可能性があります。RatString() はそれらをそのまま文字列として出力します。
    • トラブルシューティング
      出力する文字列が非常に長くなる可能性があることに注意してください。必要に応じて、表示を制限するなどの処理を検討してください。
  4. 他の型との連携

    • 状況
      RatString()string 型の値を返します。これを数値として扱いたい場合は、適切な型変換が必要です。
    • トラブルシューティング
      文字列を big.Rat 型に戻すには、SetString() メソッドを使用します。他の数値型に変換する場合は、Float64()Int64() などのメソッドを利用できますが、精度が失われる可能性があることに注意してください。
    package main
    
    import (
        "fmt"
        "math/big"
    )
    
    func main() {
        r := big.NewRat(7, 2)
        strVal := r.RatString()
        fmt.Println("String Value:", strVal) // Output: String Value: 7/2
    
        newRat := new(big.Rat)
        _, ok := newRat.SetString(strVal)
        if ok {
            fmt.Println("Back to Rat:", newRat.RatString()) // Output: Back to Rat: 7/2
        }
    }
    


例1: 基本的な RatString() の使い方

この例では、big.Rat 型の変数をいくつか作成し、それぞれの値に対して RatString() メソッドを呼び出して文字列として表示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	// 整数から big.Rat を作成
	r1 := big.NewRat(5, 1)
	fmt.Printf("r1 (%s)\n", r1.RatString()) // Output: r1 (5/1)

	// 分数から big.Rat を作成
	r2 := big.NewRat(3, 4)
	fmt.Printf("r2 (%s)\n", r2.RatString()) // Output: r2 (3/4)

	// 負の数を含む分数
	r3 := big.NewRat(-7, 2)
	fmt.Printf("r3 (%s)\n", r3.RatString()) // Output: r3 (-7/2)

	r4 := big.NewRat(10, -3) // 分母が負の数でも分子に符号が移る
	fmt.Printf("r4 (%s)\n", r4.RatString()) // Output: r4 (-10/3)

	// ゼロ
	r5 := big.NewRat(0, 5)
	fmt.Printf("r5 (%s)\n", r5.RatString()) // Output: r5 (0/1)
}

この例では、big.NewRat(a, b) 関数を使って分子 a と分母 b から新しい big.Rat 型の値を生成しています。そして、それぞれの big.Rat 変数に対して RatString() を呼び出し、その結果を fmt.Printf で表示しています。出力を見ると、RatString() が "分子/分母" の形式で文字列を返していることがわかります。特に、分母が 1 の場合は整数として、負の符号は分子に付いていることが確認できます。また、ゼロの場合は "0/1" と表示されます。

例2: SetString() で文字列から big.Rat を作成し、RatString() で表示

この例では、文字列で表現された有理数を SetString() メソッドを使って big.Rat 型に変換し、その後 RatString() で文字列として表示します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	strRat1 := "7/3"
	r1 := new(big.Rat)
	_, ok := r1.SetString(strRat1)
	if ok {
		fmt.Printf("r1 (%s) from string '%s'\n", r1.RatString(), strRat1) // Output: r1 (7/3) from string '7/3'
	} else {
		fmt.Println("Failed to parse string:", strRat1)
	}

	strRat2 := "-5/2"
	r2 := new(big.Rat)
	_, ok = r2.SetString(strRat2)
	if ok {
		fmt.Printf("r2 (%s) from string '%s'\n", r2.RatString(), strRat2) // Output: r2 (-5/2) from string '-5/2'
	} else {
		fmt.Println("Failed to parse string:", strRat2)
	}

	strRat3 := "10" // 整数を表す文字列
	r3 := new(big.Rat)
	_, ok = r3.SetString(strRat3)
	if ok {
		fmt.Printf("r3 (%s) from string '%s'\n", r3.RatString(), strRat3) // Output: r3 (10/1) from string '10'
	} else {
		fmt.Println("Failed to parse string:", strRat3)
	}

	strRat4 := "1.5" // 浮動小数点数のような形式 (SetString はこの形式を直接は扱えない)
	r4 := new(big.Rat)
	_, ok = r4.SetString(strRat4)
	if ok {
		fmt.Printf("r4 (%s) from string '%s'\n", r4.RatString(), strRat4)
	} else {
		fmt.Println("Failed to parse string:", strRat4) // Output: Failed to parse string: 1.5
	}
}

この例では、SetString() メソッドに "分子/分母" 形式の文字列や、整数を表す文字列を渡して big.Rat 型の値を生成しています。SetString() は成功すると big.Rattrue を、失敗すると nilfalse を返します。注目すべきは、SetString() は "1.5" のような浮動小数点数の形式を直接は扱えないことです。このような文字列を big.Rat に変換するには、別途処理が必要になります。

例3: RatString() と他のメソッドの組み合わせ (Float64() との比較)

この例では、RatString() で得られた文字列と、Float64() メソッドで得られた浮動小数点数の値を比較します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	r := big.NewRat(3, 5)
	ratStr := r.RatString()
	floatVal, _ := r.Float64()

	fmt.Printf("big.Rat: %s\n", ratStr)    // Output: big.Rat: 3/5
	fmt.Printf("float64: %f\n", floatVal) // Output: float64: 0.600000

	r2 := big.NewRat(1, 3)
	ratStr2 := r2.RatString()
	floatVal2, _ := r2.Float64()

	fmt.Printf("big.Rat: %s\n", ratStr2)     // Output: big.Rat: 1/3
	fmt.Printf("float64: %f\n", floatVal2)  // Output: float64: 0.333333

	// 非常に大きな数
	r3 := big.NewRat(1234567890123456789, 9876543210987654321)
	ratStr3 := r3.RatString()
	floatVal3, _ := r3.Float64()

	fmt.Printf("big.Rat: %s\n", ratStr3)     // Output: big.Rat: 1234567890123456789/9876543210987654321
	fmt.Printf("float64: %f\n", floatVal3)  // Output: float64: 0.124999
}

この例では、RatString() が正確な有理数の表現を文字列で提供するのに対し、Float64() は浮動小数点数として近似値を返すことがわかります。特に、31のように有限の小数で表現できない有理数の場合、Float64() は近似値になります。また、非常に大きな分子や分母を持つ場合でも、RatString() はその正確な比率を文字列で表現できますが、Float64() は精度限界により情報が失われる可能性があります。



FloatString(prec int) メソッド

このメソッドは、big.Rat 型の値を指定した精度 (prec) の浮動小数点数の文字列として返します。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	r := big.NewRat(1, 3)
	floatStr := r.FloatString(10) // 小数点以下 10 桁の精度で文字列化
	fmt.Printf("FloatString (prec=10): %s\n", floatStr) // Output: FloatString (prec=10): 0.3333333333

	r2 := big.NewRat(5, 2)
	floatStr2 := r2.FloatString(3) // 小数点以下 3 桁の精度で文字列化
	fmt.Printf("FloatString (prec=3): %s\n", floatStr2)  // Output: FloatString (prec=3): 2.500
}

FloatString() を使うと、有理数を浮動小数点数のように表現できます。prec 引数で小数点以下の桁数を指定することで、出力の精度を制御できます。ただし、有理数が無限小数になる場合は、指定した桁数で丸められます。

分子と分母を個別に取得して文字列を組み立てる

big.Rat 型は、分子 (Num()) と分母 (Den()) を big.Int 型としてそれぞれ取得できます。これらの big.Int 型の値を String() メソッドで文字列に変換し、自分で "分子/分母" 以外の形式で組み立てることができます。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	r := big.NewRat(7, 5)
	num := r.Num()
	den := r.Den()

	numeratorStr := num.String()
	denominatorStr := den.String()

	customStr := fmt.Sprintf("分子: %s, 分母: %s", numeratorStr, denominatorStr)
	fmt.Println(customStr) // Output: 分子: 7, 分母: 5

	// 例えば、分数形式でなくても良い場合
	anotherCustomStr := fmt.Sprintf("%s over %s", numeratorStr, denominatorStr)
	fmt.Println(anotherCustomStr) // Output: 7 over 5
}

この方法の利点は、出力形式を完全に制御できることです。分子と分母を個別に処理したり、他の情報と組み合わせて表示したりする場合に便利です。big.Int 型の String() メソッドは、整数の値を文字列として返します。

Float64() メソッドと fmt.Sprintf() などの書式付き出力

big.Rat 型の値を Float64() メソッドで float64 型に変換し、その後 fmt.Sprintf() などの書式付き出力関数を使って文字列化する方法です。ただし、float64 型は精度に限界があるため、非常に大きな数や循環小数などを正確に表現できない可能性があることに注意が必要です。

package main

import (
	"fmt"
	"math/big"
)

func main() {
	r := big.NewRat(1, 7)
	floatVal, _ := r.Float64()
	formattedStr := fmt.Sprintf("%.8f", floatVal) // 小数点以下 8 桁でフォーマット
	fmt.Printf("Float64 formatted: %s\n", formattedStr) // Output: Float64 formatted: 0.14285714

	r2 := big.NewRat(123456789012345, 1000000000000)
	floatVal2, _ := r2.Float64()
	formattedStr2 := fmt.Sprintf("%.2f", floatVal2)
	fmt.Printf("Float64 formatted (large): %s\n", formattedStr2) // Output: Float64 formatted (large): 123.46
}

この方法は、浮動小数点数としての表現が必要な場合に簡便ですが、元の有理数の精度が完全に保持されるわけではありません。

big.Rat 型の値を直接 JSON などのデータ形式にエンコードしようとすると、標準のエンコーダでは対応していない場合があります。そのような場合は、RatString() や上記の方法で文字列に変換してからエンコードする必要があります。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type MyData struct {
	RatioStr string `json:"ratio"`
}

func main() {
	r := big.NewRat(22, 7)
	data := MyData{
		RatioStr: r.RatString(),
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSON marshal error:", err)
		return
	}
	fmt.Println(string(jsonData)) // Output: {"ratio":"22/7"}
}

このように、big.Rat 型の値を他のデータ形式で扱いたい場合は、一旦文字列に変換することが一般的です。