小数の扱いに悩まされることが多い今日この頃
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などデータベースでの保管も楽かと。
もっと実用できたら他の演算子も増やして行きます!!