简体   繁体   English

Swift 中十进制到分数的转换

[英]Decimal to Fraction conversion in Swift

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...", it would return "1/3".因此,如果用户计算出答案为“0.333333...”的表达式,它将返回“1/3”。 For "0.25" it would return "1/4".对于“0.25”,它将返回“1/4”。 Using GCD, as found here ( Decimal to fraction conversion ), I have figured out how to convert any rational, terminating decimal into a decimal, but this does not work on any decimal that repeats (like .333333).使用 GCD,如发现here( 十进制到分数转换),我已经想出了如何将任何有理的终止小数转换为小数,但这不适用于任何重复的小数(如 .333333)。

Every other function for this on stack overflow is in Objective-C.堆栈溢出的所有其他函数都在 Objective-C 中。 But I need a function in my swift app!但是我的 swift 应用程序中需要一个函数! So a translated version of this ( https://stackoverflow.com/a/13430237/5700898 ) would be nice!所以这个( https://stackoverflow.com/a/13430237/5700898 )的翻译版本会很好!

Any ideas or solutions on how to convert a rational or repeating/irrational decimal to a fraction (ie convert "0.1764705882..." to 3/17) would be great!关于如何将有理数或重复/无理数小数转换为分数(即,将“0.1764705882...”转换为 3/17)的任何想法或解决方案都会很棒!

If you want to display the results of calculations as rational numbers then the only 100% correct solution is to use rational arithmetic throughout all calculations, ie all intermediate values are stored as a pair of integers (numerator, denominator) , and all additions, multiplications, divisions, etc are done using the rules for rational numbers.如果您想将计算结果显示为有理数,那么唯一 100% 正确的解决方案是在所有计算中使用有理算术,即所有中间值都存储为一对整数(numerator, denominator) ,以及所有加法、乘法,除法等是使用有理数规则完成的。

As soon as a result is assigned to a binary floating point number such as Double , information is lost.一旦将结果分配给二进制浮点数,例如Double ,信息就会丢失。 For example,例如,

let x : Double = 7/10

stores in x an approximation of 0.7 , because that number cannot be represented exactly as a Double .x存储0.7近似值,因为该数字不能完全表示为Double From

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

one can see that x holds the value可以看到x持有值

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

So a correct representation of x as a rational number would be 6305039478318694 / 9007199254740992 , but that is of course not what you expect.因此,将x正确表示为有理数应该是6305039478318694 / 9007199254740992 ,但这当然不是您所期望的。 What you expect is 7/10 , but there is another problem:您期望的是7/10 ,但还有另一个问题:

let x : Double = 69999999999999996/100000000000000000

assigns exactly the same value to x , it is indistinguishable from 0.7 within the precision of a Double .x分配完全相同的值,在Double的精度范围内与0.7无法区分。

So should x be displayed as 7/10 or as 69999999999999996/100000000000000000 ?那么x应该显示为7/10还是69999999999999996/100000000000000000

As said above, using rational arithmetic would be the perfect solution.如上所述,使用有理算术将是完美的解决方案。 If that is not viable, then you can convert the Double back to a rational number with a given precision .如果这不可行,那么您可以将Double转换回具有给定精度的有理数。 (The following is taken from Algorithm for LCM of doubles in Swift .) (以下内容摘自Swift 中双打 LCM 的算法。)

Continued Fractions are an efficient method to create a (finite or infinite) sequence of fractions h n /k n that are arbitrary good approximations to a given real number x , and here is a possible implementation in Swift:连续分数是创建分数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)
}

Examples:例子:

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

The default precision is 1.0E-6, but you can adjust that to your needs:默认精度为 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 version:斯威夫特 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)
}

Examples:例子:

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

Or – as suggested by @brandonscript – with a struct Rational and an initializer:或者 – 正如@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)
    }
}

Example usage:用法示例:

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

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

So a little late here, but I had a similar problem and ended up building Swift FractionFormatter .所以在这里有点晚了,但我遇到了类似的问题,最终构建了 Swift FractionFormatter This works because most of the irrational numbers you care about are part of the set of vulgar, or common fractions and are easy to validate proper transformation.这是有效的,因为您关心的大多数无理数都是粗俗或常见分数集的一部分,并且很容易验证正确的转换。 The rest may or may not round, but you get very close on any reasonable fraction your user might generate.其余的可能会或可能不会四舍五入,但是您非常接近用户可能生成的任何合理分数。 It is designed to be a drop in replacement for NumberFormatter.它旨在替代 NumberFormatter。

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.正如Martin R所说,获得 (99.99%) 精确计算的唯一方法是用有理数计算一切,从头到尾。

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.创建这个类的原因还在于我需要非常准确的计算,而 swift 提供的类型是不可能的。 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在 Rational 类中,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.第一种方法是使用两个整数初始化类,第一个 with 是分母,第二个是分母。 the class gets those two integers, and then reduces them to the least numbers possible.该类获取这两个整数,然后将它们减少到尽可能少的数字。 eg reduces (10,5) to (2,1) or as another example, reduces (144, 60) to (12,5).例如将 (10,5) 减少到 (2,1) 或者作为另一个例子,将 (144, 60) 减少到 (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.这可以使用 gcd(最大公约数)函数和简化函数来实现,这些函数从代码中不难理解。 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'.初始化类的第二种方法是使用 double 和一个名为“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. eg 2.334 will be 2334/1000 or 342.57 will be 34257/100.该类获取双精度数,以及您需要的小数点后多少个数字的精度,并将双精度数转换为分母/分母形式,其中分母为 10 的幂。例如 2.334 将是 2334/1000 或 342.57将是 34257/100。 then tries to simplify the rational numbers using the same method which was explained in the #1 way.然后尝试使用在 #1 方式中解释的相同方法来简化有理数。

After the class definition, there is type-alias 'Rnl', which you can obviously change it as you wish.在类定义之后,有类型别名 'Rnl',您显然可以根据需要更改它。

Then there are 4 functions, for the 4 main actions of math: * / + -, which i defined so eg you can easily multiply two numbers of type Rational.然后有 4 个函数,用于数学的 4 个主要动作:* / + -,这是我定义的,例如,您可以轻松地将两个 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"之后,Rational 类型有 2 个扩展,第一个 ('value') 为您提供有理数的双倍值,第二个 ('rnlValue') 以人类可读的形式为您提供有理数字符串:“分母/分母”

At last, you can see some examples of how all these work.最后,您可以看到所有这些工作方式的一些示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM