LoginSignup
3
1

More than 3 years have passed since last update.

Swiftで小数の扱いに疲れたので分数を扱ってみる

Last updated at Posted at 2020-11-23

小数の扱いに悩まされることが多い今日この頃
DoubleやFloatだと正確な計算はできない。

var a = Double(1.1)
var b = Double(1)
print(a - b) // 0.10000000000000009

上の例だとDecimalを使えば解決できます。
足し算引き算などで悩まされることはなくなりました。

let a = Decimal(1.1)
let b = Decimal(1)
print(a - b) // 0.1

しかし割り切れない割り算については

let a = Decimal(2)
let b = Decimal(3)
let c = a / b
print(c) // 0.66666666666666666666666666666666666666
print(c * b) // 1.99999999999999999999999999999999999998

これは自然な結果なのです。小数の桁数は有限なのだから。
cに関しては桁を決めて丸めればいい。

だがc × bに関しては
2 ÷ 3 × 3 = 2 になって欲しい
内部的には正確な値で保持しておきたい。。。
そもそも小数でデータを保持することがそもそもの間違いな気がしてきました。
そこで気付きました
「あ、分数だ」と。

しかしSwfitで分数を扱ってるクラスや構造体を探してみたが見つからないので
作ってみました。

※探し足りない可能性は大いにあるので良い方法があればどなたかご教授いただきたいです。
※2020/12/4 更新
 四則演算子だけでも実用できるように変更

import Foundation

protocol Fractional {
    var toFraction: Fraction { get }
}

struct Fraction {
    var child: Int
    var mother: Int {
        didSet{
            if mother == 0 { fatalError("no mother no child") }
        }
    }

    // ----------------------------------------------------------
    // イニシャライザ
    // ----------------------------------------------------------
    init(_ child: Int, _ mother: Int) {
        let negativeSign = child * mother >= 0 ? 1 : -1
        self.child = abs(child) * negativeSign
        self.mother = abs(mother)
        yakubun()
    }

    // ----------------------------------------------------------
    // 出力
    // ----------------------------------------------------------
    var double: Double {
        return Double(child) / Double(mother)
    }
    var decimal: Decimal {
        return Decimal(child) / Decimal(mother)
    }
    var int: Int {
        var integer = 0
        let negativeSign = child >= 0 ? 1 : -1
        var c = abs(child)
        while c > mother {
            c -= mother
            integer += 1
        }
        return integer * negativeSign
    }
    var under1: Fraction {
        return Fraction((abs(child) % mother) * (child >= 0 ? 1 : -1), mother)
    }

    // ----------------------------------------------------------
    // 足し算 Fractionalとの演算も可
    // ----------------------------------------------------------
    static func + (r0: Fraction, r1: Fraction) -> Fraction {
        let newChild = r0.child * r1.mother + r1.child * r0.mother
        let newMother = r0.mother * r1.mother
        return Fraction(newChild, newMother)
    }
    static func + (r0: Fractional, r1: Fraction) -> Fraction {
        return r0.toFraction + r1
    }
    static func + (r0: Fraction, r1: Fractional) -> Fraction {
        return r0 + r1.toFraction
    }

    // ----------------------------------------------------------
    // 引き算 Fractionalとの演算も可
    // ----------------------------------------------------------
    static func - (r0: Fraction, r1: Fraction) -> Fraction {
        let newChild = r0.child * r1.mother - r1.child * r0.mother
        let newMother = r0.mother * r1.mother
        return Fraction(newChild, newMother)
    }
    static func - (r0: Fractional, r1: Fraction) -> Fraction {
        return r0.toFraction - r1
    }
    static func - (r0: Fraction, r1: Fractional) -> Fraction {
        return r0 - r1.toFraction
    }

    // ----------------------------------------------------------
    // かけ算 Fractionalとの演算も可
    // ----------------------------------------------------------
    static func * (r0: Fraction, r1: Fraction) -> Fraction {
        let newChild = r0.child * r1.child
        let newMother = r0.mother * r1.mother
        return Fraction(newChild, newMother)
    }
    static func * (r0: Fractional, r1: Fraction) -> Fraction {
        return r0.toFraction * r1
    }
    static func * (r0: Fraction, r1: Fractional) -> Fraction {
        return r0 * r1.toFraction
    }

    // ----------------------------------------------------------
    // 割り算 Fractionalとの演算も可
    // ----------------------------------------------------------
    static func / (_ r0: Fraction, _ r1: Fraction) -> Fraction {
        let newChild = r0.child * r1.mother
        let newMother = r0.mother * r1.child
        return Fraction(newChild, newMother)
    }
    static func / (r0: Fractional, r1: Fraction) -> Fraction {
        return r0.toFraction / r1
    }
    static func / (r0: Fraction, r1: Fractional) -> Fraction {
        return r0 / r1.toFraction
    }

    // ----------------------------------------------------------
    // 約分
    // ----------------------------------------------------------
    mutating func yakubun() {
        if child == 0 { return }
        let c = _yucrid(x: abs(child), y: mother)
        child /= c
        mother /= c
    }

    // ----------------------------------------------------------
    // ユークリッドで最大公約数
    // ----------------------------------------------------------
    private func _yucrid(x: Int, y: Int) -> Int {
        if y == 0 {
            return x
        } else {
            return _yucrid(x: y, y: x % y)
        }
    }
}

// ----------------------------------------------------------
// 引数がIntのみのイニシャライザも用意します。
// ----------------------------------------------------------
extension Fraction {
    init(_ child: Int = 1){
        self.init(child, 1)
    }
}

// ----------------------------------------------------------
// Basic Behaviors
// https://developer.apple.com/documentation/swift/swift_standard_library/basic_behaviors
// ----------------------------------------------------------
// Fractionalとの演算も可
extension Fraction: Comparable {
    static func < (r0: Fraction, r1: Fraction) -> Bool {
        return (r0 - r1).child < 0 
    }
    static func < (r0: Fractional, r1: Fraction) -> Bool {
        return (r0.toFraction - r1).child < 0 
    }
    static func < (r0: Fraction, r1: Fractional) -> Bool {
        return (r0 - r1.toFraction).child < 0 
    }
}
// Fractionalとの演算も可
extension Fraction: Equatable {
    static func == (r0: Fraction, r1: Fraction) -> Bool {
        return (r0 - r1).child == 0 
    }
    static func == (r0: Fractional, r1: Fraction) -> Bool {
        return (r0.toFraction - r1).child == 0 
    }
    static func == (r0: Fraction, r1: Fractional) -> Bool {
        return (r0 - r1.toFraction).child == 0 
    }
}

extension Fraction: CustomStringConvertible {   
    var description: String {
        if mother == 1 {
            return String(child)
        }
        return "\(child)/\(mother)"
    }
}

// ----------------------------------------------------------
// 他の数字型とも演算できるようにFractionalに準拠させます。
// ----------------------------------------------------------
// 一旦Intのみ
extension Int: Fractional {
    var toFraction: Fraction {
        return Fraction(self)
    }
}

内部で保持しているのは
分子と分母のInt型だけなので、Realmなどデータベースでの保管も楽かと。

もっと実用できたら他の演算子も増やして行きます!!

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1