Swift 中小数到分数的转换

Decimal to Fraction conversion in Swift

我正在构建一个计算器并希望它能自动将每个小数转换为分数。因此,如果用户计算出答案为“0.333333...”的表达式,它将 return“1/3”。对于“0.25”,它将 return“1/4”。使用 GCD,如此处 (Decimal to fraction conversion),我已经想出了如何将任何有理数、终止小数转换为小数,但这不适用于任何重复的小数(如 .333333)。

关于堆栈溢出的所有其他函数都在 Objective-C 中。但是我的 swift 应用程序需要一个功能!所以这个 () 的翻译版本会很好!

关于如何将有理数或repeating/irrational小数转换为分数(即将“0.1764705882...”转换为 3/17)的任何想法或解决方案都会很棒!

如果要将计算结果显示为有理数 那么唯一 100% 正确的解决方案是在所有计算中使用 有理算术 ,即所有中间值都存储为一对整数 (numerator, denominator),所有加法、乘法、除法, 等是使用 rational 的规则完成的 数字。

一旦将结果分配给二进制浮点数Double,信息丢失。例如,

let x : Double = 7/10

x 中存储 0.7 近似值 ,因为该数字不能 精确表示为 Double。来自

print(String(format:"%a", x)) // 0x1.6666666666666p-1

可以看到 x 持有价值

0x16666666666666 * 2^(-53) = 6305039478318694 / 9007199254740992
                           ≈ 0.69999999999999995559107901499373838305

所以 x 的正确表示是有理数 6305039478318694 / 9007199254740992,但这当然不是什么 你期待。你期望的是7/10,但是还有一个问题:

let x : Double = 69999999999999996/100000000000000000

x赋予了完全相同的值,与 0.7 精度为 Double.

那么x应该显示为7/10还是69999999999999996/100000000000000000

如上所述,使用有理算术将是完美的解决方案。 如果那不可行,那么您可以将 Double 转换回 具有给定精度 的有理数 。 (以下内容摘自。)

Continued Fractions 是创建(有限或无限)分数序列的有效方法 hn/kn是给定实数 x 的任意良好近似值, 这是 Swift:

中的可能实现
typealias Rational = (num : Int, den : Int)

func rationalApproximationOf(x0 : Double, withPrecision eps : Double = 1.0E-6) -> Rational {
    var x = x0
    var a = floor(x)
    var (h1, k1, h, k) = (1, 0, Int(a), 1)

    while x - a > eps * Double(k) * Double(k) {
        x = 1.0/(x - a)
        a = floor(x)
        (h1, k1, h, k) = (h, k, h1 + Int(a) * h, k1 + Int(a) * k)
    }
    return (h, k)
}

示例:

rationalApproximationOf(0.333333) // (1, 3)
rationalApproximationOf(0.25)     // (1, 4)
rationalApproximationOf(0.1764705882) // (3, 17)

默认精度为 1.0E-6,但您可以根据需要进行调整:

rationalApproximationOf(0.142857) // (1, 7)
rationalApproximationOf(0.142857, withPrecision: 1.0E-10) // (142857, 1000000)

rationalApproximationOf(M_PI) // (355, 113)
rationalApproximationOf(M_PI, withPrecision: 1.0E-7) // (103993, 33102)
rationalApproximationOf(M_PI, withPrecision: 1.0E-10) // (312689, 99532)

Swift3版本:

typealias Rational = (num : Int, den : Int)

func rationalApproximation(of x0 : Double, withPrecision eps : Double = 1.0E-6) -> Rational {
    var x = x0
    var a = x.rounded(.down)
    var (h1, k1, h, k) = (1, 0, Int(a), 1)

    while x - a > eps * Double(k) * Double(k) {
        x = 1.0/(x - a)
        a = x.rounded(.down)
        (h1, k1, h, k) = (h, k, h1 + Int(a) * h, k1 + Int(a) * k)
    }
    return (h, k)
}

示例:

rationalApproximation(of: 0.333333) // (1, 3)
rationalApproximation(of: 0.142857, withPrecision: 1.0E-10) // (142857, 1000000)

或者——正如@brandonscript 所建议的那样——使用 struct Rational 和初始化程序:

struct Rational {
    let numerator : Int
    let denominator: Int

    init(numerator: Int, denominator: Int) {
        self.numerator = numerator
        self.denominator = denominator
    }

    init(approximating x0: Double, withPrecision eps: Double = 1.0E-6) {
        var x = x0
        var a = x.rounded(.down)
        var (h1, k1, h, k) = (1, 0, Int(a), 1)

        while x - a > eps * Double(k) * Double(k) {
            x = 1.0/(x - a)
            a = x.rounded(.down)
            (h1, k1, h, k) = (h, k, h1 + Int(a) * h, k1 + Int(a) * k)
        }
        self.init(numerator: h, denominator: k)
    }
}

用法示例:

print(Rational(approximating: 0.333333))
// Rational(numerator: 1, denominator: 3)

print(Rational(approximating: .pi, withPrecision: 1.0E-7))
// Rational(numerator: 103993, denominator: 33102)

正如Martin R所说,要进行 (99.99%) 精确计算,唯一的方法就是从头到尾计算所有有理数。

创建此 class 的原因也是我需要进行非常准确的计算,而 swift 提供的类型无法做到这一点。所以我创建了自己的类型。

这是代码,我会在下面解释。

class Rational {

   var alpha = 0
   var beta = 0

   init(_ a: Int, _ b: Int) {
       if (a > 0 && b > 0) || (a < 0 && b < 0) {
           simplifier(a,b,"+")
       }
       else {
           simplifier(a,b,"-")
       }
   }

   init(_ double: Double, accuracy: Int = -1) {
       exponent(double, accuracy)
   }

   func exponent(_ double: Double, _ accuracy: Int) {
       //Converts a double to a rational number, in which the denominator is of power of 10.

       var exp = 1
       var double = double

       if accuracy != -1 {
           double = Double(NSString(format: "%.\(accuracy)f" as NSString, double) as String)!
       }

       while (double*Double(exp)).remainder(dividingBy: 1) != 0 {
           exp *= 10
       }

       if double > 0 {
           simplifier(Int(double*Double(exp)), exp, "+")
       }
       else {
           simplifier(Int(double*Double(exp)), exp, "-")
       }

   }

   func gcd(_ alpha: Int, _ beta: Int) -> Int {
       // Calculates 'Greatest Common Divisor'

       var inti: [Int] = []
       var multi = 1
       var a = Swift.min(alpha,beta)
       var b = Swift.max(alpha,beta)

           for idx in 2...a {
               if idx != 1 {
                   while (a%idx == 0 && b%idx == 0) {
                       a = a/idx
                       b = b/idx
                       inti.append(idx)
                   }
               }
           }
       inti.map{ multi *= [=10=] }
       return multi
   }


   func simplifier(_ alpha: Int, _ beta: Int, _ posOrNeg: String) {
       //Simplifies nominator and denominator (alpha and beta) so they are 'prime' to one another.

       let alpha = alpha > 0 ? alpha : -alpha
       let beta = beta > 0 ? beta : -beta

       let greatestCommonDivisor = gcd(alpha,beta)

       self.alpha = posOrNeg == "+" ? alpha/greatestCommonDivisor : -alpha/greatestCommonDivisor
       self.beta = beta/greatestCommonDivisor
   }

}

typealias Rnl = Rational

func *(a: Rational, b: Rational) -> Rational {

   let aa = a.alpha*b.alpha
   let bb = a.beta*b.beta

   return Rational(aa, bb)

}

func /(a: Rational, b: Rational) -> Rational {

   let aa = a.alpha*b.beta
   let bb = a.beta*b.alpha

   return Rational(aa, bb)

}

func +(a: Rational, b: Rational) -> Rational {

   let aa = a.alpha*b.beta + a.beta*b.alpha
   let bb = a.beta*b.beta

   return Rational(aa, bb)

}

func -(a: Rational, b: Rational) -> Rational {

   let aa = a.alpha*b.beta - a.beta*b.alpha
   let bb = a.beta*b.beta

   return Rational(aa, bb)

}

extension Rational {

   func value() -> Double {
       return Double(self.alpha) / Double(self.beta)
   }

}

extension Rational {

   func rnlValue() -> String {

       if self.beta == 1 {
           return "\(self.alpha)"
       }
       else if self.alpha == 0  {
           return "0"
       }
       else {
           return "\(self.alpha) / \(self.beta)"
       }
   }

}

// examples:

let first = Rnl(120,45)
let second = Rnl(36,88)
let third = Rnl(2.33435, accuracy: 2)
let forth = Rnl(2.33435)

print(first.alpha, first.beta, first.value(), first.rnlValue()) // prints  8   3   2.6666666666666665   8 / 3
print((first*second).rnlValue()) // prints  12 / 11
print((first+second).rnlValue()) // prints  203 / 66
print(third.value(), forth.value()) // prints  2.33   2.33435

首先,我们有 class 本身。 class 可以通过两种方式初始化:

在 Rational class 中,alpha ~= nominator & beta ~= denominator

第一种方法是用两个整数初始化class,第一个是分母,第二个是分母。 class 获取这两个整数,然后将它们减少到尽可能少的数字。例如,将 (10,5) 减少到 (2,1) 或者作为另一个例子,将 (144, 60) 减少到 (12,5)。这样,总是存储最简单的数字。 这可以使用 gcd(最大公约数)函数和简化函数,从代码中不难理解。 唯一的问题是 class 面临负数的一些问题,所以它总是保存最终有理数是负数还是正数,如果是负数则使提名人负数。

初始化 class 的第二种方法是使用双精度数和一个名为 'accuracy' 的可选参数。 class 得到双精度数,以及你需要的小数点后多少数字的精度,并将双精度数转换为 nominator/denominator 形式,其中分母是 10 的幂。例如 2.334 将是 2334/1000 或 342.57 将是 34257/100。然后尝试使用在#1 方式中解释的相同方法来简化有理数。

在class定义之后,有一个type-alias 'Rnl',显然你可以随意改变它。

然后有 4 个函数,用于数学的 4 个主要操作:* / + -,我这样定义的,例如您可以轻松地将两个 Rational 类型的数字相乘。

在那之后,有理数类型有 2 个扩展,第一个 ('value') 给你有理数的双精度值,第二个 ('rnlValue') 给你人类可读字符串形式的有理数:"nominator / denominator"

最后,您可以看到所有这些工作原理的一些示例。

这里有点晚了,但我遇到了类似的问题并最终构建了 Swift FractionFormatter。这是可行的,因为您关心的大多数无理数都是粗俗或常见分数集的一部分,并且很容易验证正确的转换。其余部分可能会或可能不会四舍五入,但您非常接近用户可能生成的任何合理分数。它旨在替代 NumberFormatter。