我正在构建一个计算器,并希望它自动将每个小数转换为分数。因此,如果用户计算答案为" 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)的任何想法或解决方案将是太棒了!
答案 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”)为您提供了以可读的字符串:“分母/分母”
最后,您可以看到一些有关所有这些工作原理的示例。