Swift中的十进制到分数转换

时间:2016-03-09 15:01:45

标签: ios swift swift2 calculator fractions

我正在构建一个计算器,并希望它自动将每个小数转换为分数。因此,如果用户计算答案为" 0.333333 ..."的表达式,则返回" 1/3"。对于" 0.25"它将返回" 1/4"。使用GCD,如此处所示(Decimal to fraction conversion),我已经想出如何将任何有理的终止十进制转换为十进制,但这对任何重复的小数都不起作用(如.333333)。

堆栈溢出的每个其他函数都在Objective-C中。但我需要一个快速应用程序中的功能!所以这个(https://stackoverflow.com/a/13430237/5700898)的翻译版本会很好!

如何将理性或重复/无理小数转换为分数(即转换" 0.1764705882 ......"至3/17)的任何想法或解决方案将是太棒了!

3 个答案:

答案 0 :(得分:30)

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

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

let x : Double = 7/10

存储x {em}近似 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转换回 具有给定精度的有理数 。 (以下摘自Algorithm for LCM of doubles in Swift。)

Continued Fractions 是一种有效的方法来创建(有限或无限)分数 h n / k n ,它们是对给定实数的任意良好近似数字 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)

Swift 3 版本:

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)

答案 1 :(得分:1)

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

答案 2 :(得分:0)

正如Martin R所说,进行精确计算(99.99%)的唯一方法是从开始到结束都使用有理数进行计算。

创建此类的背后原因还在于,我需要进行非常精确的计算,而对于迅速提供的类型,这是不可能的。所以我创建了自己的类型。

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

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 *= $0 }
       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

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

在Rational类中,alpha〜=分子和beta〜=分母

第一种方法是使用两个整数初始化类,其中with的第一个是分母,第二个是分母。该类将获得这两个整数,然后将它们减少到可能的最小数目。例如将(10,5)减少为(2,1),或作为另一个示例,将(144,60)减少为(12,5)。这样,总是存储最简单的数字。 使用gcd(最大公约数)函数和simplifier函数可以做到这一点,这在代码中并不难理解。 唯一的事情是该类面临一些带有负数的问题,因此它总是保存最终的有理数是负数还是正数,如果它是负数,则使提名者为负数。

初始化类的第二种方法是使用双精度,并使用一个称为“ accuracy”的可选参数。该类将获取双精度值,以及所需小数点后多少数字的精度,并将该双精度值转换为分母/分母形式,其中分母的幂为10。例如2.334将为2334/1000或342.57将为34257/100。然后尝试使用与#1方式相同的方法简化有理数。

在类定义之后,有类型别名“ Rnl”,您可以根据需要显然地对其进行更改。

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

此后,有2个对Rational类型的扩展,其中第一个(“值”)为您提供了Rational数的双精度值,第二个(“ rnlValue”)为您提供了以可读的字符串:“分母/分母”

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