Decimal to Fraction conversion in Swift

后端 未结 2 912
醉酒成梦
醉酒成梦 2020-11-30 09:14

I am building a calculator and want it to automatically convert every decimal into a fraction. So if the user calculates an expression for which the answer is \"0.333333...\

2条回答
  •  南方客
    南方客 (楼主)
    2020-11-30 09:47

    As Martin R said, the Only way to have (99.99%)exact calculations, is to calculate everything with rational numbers, from beginning to the end.

    the reason behind the creation of this class was also the fact that i needed to have very accurate calculations, and that was not possible with the swift-provided types. so i created my own type.

    here is the code, i'll explain it below.

    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
    
    

    First of all, we have the class itself. the class can be initialised in two ways:

    in the Rational class, alpha ~= nominator & beta ~= denominator

    The First way is initialising the class using two integers, first of with is the nominator, and the second one is the denominator. the class gets those two integers, and then reduces them to the least numbers possible. e.g reduces (10,5) to (2,1) or as another example, reduces (144, 60) to (12,5). this way, always the simplest numbers are stored. this is possible using the gcd (greatest common divisor) function and simplifier function, which are not hard to understand from the code. the only thing is that the class faces some issues with negative numbers, so it always saves whether the final rational number is negative or positive, and if its negative it makes the nominator negative.

    The Second way to initialise the class, is with a double, and with an optional parameter called 'accuracy'. the class gets the double, and also the accuracy of how much numbers after decimal point you need, and converts the double to a nominator/denominator form, in which the denominator is of power of 10. e.g 2.334 will be 2334/1000 or 342.57 will be 34257/100. then tries to simplify the rational numbers using the same method which was explained in the #1 way.

    After the class definition, there is type-alias 'Rnl', which you can obviously change it as you wish.

    Then there are 4 functions, for the 4 main actions of math: * / + -, which i defined so e.g. you can easily multiply two numbers of type Rational.

    After that, there are 2 extensions to Rational type, first of which ('value') gives you the double value of a Rational number, the second one ('rnlValue') gives you the the Rational number in form of a human-readable string: "nominator / denominator"

    At last, you can see some examples of how all these work.

提交回复
热议问题