【问题标题】:Decimal to Fraction conversion in SwiftSwift 中的小数到分数转换
【发布时间】:2016-06-24 00:54:53
【问题描述】:

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

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

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

【问题讨论】:

    标签: ios swift swift2 calculator fractions


    【解决方案1】:

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

    一旦将结果分配给二进制浮点数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.7Double 的精度范围内。

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

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

    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)
    

    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)
    

    【讨论】:

    • 这在 Swift 3 中是什么样子的? x0 实际上是什么?在 Swift 3 中,它是一个 unresolved identifier
    • @AlexeiS.:感谢编辑,我忽略了那部分!
    • @MartinR 这个人真的应该为此付钱给你。要求只翻译一个函数...
    • @Saren Inden:感谢您的编辑建议。但是,我建议您将其放在自己的答案中。
    • 你可以用一个结构来让它更光滑一点,例如struct Rational { var numerator: Int var denominator: Int }
    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

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

      创建这个类的原因也是我需要非常准确的计算,而这对于 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 *= $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 ~= nominator & beta ~= denominator

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

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

      在类定义之后,有类型别名'Rnl',你显然可以随意改变它。

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

      之后,Rational 类型有 2 个扩展,第一个 ('value') 为您提供 Rational 数的 double 值,第二个 ('rnlValue') 以 a 的形式为您提供 Rational 数人类可读的字符串:“nominator / denominator”

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-03
        • 1970-01-01
        • 1970-01-01
        • 2021-01-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多