Swift 中小数到分数的转换
Decimal to Fraction conversion in Swift
我正在构建一个计算器并希望它能自动将每个小数转换为分数。因此,如果用户计算出答案为“0.333333...”的表达式,它将 return“1/3”。对于“0.25”,它将 return“1/4”。使用 GCD,如此处 (Decimal to fraction conversion),我已经想出了如何将任何有理数、终止小数转换为小数,但这不适用于任何重复的小数(如 .333333)。
关于堆栈溢出的所有其他函数都在 Objective-C 中。但是我的 swift 应用程序需要一个功能!所以这个 () 的翻译版本会很好!
关于如何将有理数或repeating/irrational小数转换为分数(即将“0.1764705882...”转换为 3/17)的任何想法或解决方案都会很棒!
如果要将计算结果显示为有理数
那么唯一 100% 正确的解决方案是在所有计算中使用 有理算术 ,即所有中间值都存储为一对整数 (numerator, denominator)
,所有加法、乘法、除法, 等是使用 rational 的规则完成的
数字。
一旦将结果分配给二进制浮点数
如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.7
精度为 Double
.
那么x
应该显示为7/10
还是69999999999999996/100000000000000000
?
如上所述,使用有理算术将是完美的解决方案。
如果那不可行,那么您可以将 Double
转换回
具有给定精度 的有理数 。
(以下内容摘自。)
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)
Swift3版本:
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)
正如Martin R所说,要进行 (99.99%) 精确计算,唯一的方法就是从头到尾计算所有有理数。
创建此 class 的原因也是我需要进行非常准确的计算,而 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 *= [=10=] }
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
首先,我们有 class 本身。 class 可以通过两种方式初始化:
在 Rational class 中,alpha ~= nominator & beta ~= denominator
第一种方法是用两个整数初始化class,第一个是分母,第二个是分母。 class 获取这两个整数,然后将它们减少到尽可能少的数字。例如,将 (10,5) 减少到 (2,1) 或者作为另一个例子,将 (144, 60) 减少到 (12,5)。这样,总是存储最简单的数字。
这可以使用 gcd(最大公约数)函数和简化函数,从代码中不难理解。
唯一的问题是 class 面临负数的一些问题,所以它总是保存最终有理数是负数还是正数,如果是负数则使提名人负数。
初始化 class 的第二种方法是使用双精度数和一个名为 'accuracy' 的可选参数。 class 得到双精度数,以及你需要的小数点后多少数字的精度,并将双精度数转换为 nominator/denominator 形式,其中分母是 10 的幂。例如 2.334 将是 2334/1000 或 342.57 将是 34257/100。然后尝试使用在#1 方式中解释的相同方法来简化有理数。
在class定义之后,有一个type-alias 'Rnl',显然你可以随意改变它。
然后有 4 个函数,用于数学的 4 个主要操作:* / + -,我这样定义的,例如您可以轻松地将两个 Rational 类型的数字相乘。
在那之后,有理数类型有 2 个扩展,第一个 ('value') 给你有理数的双精度值,第二个 ('rnlValue') 给你人类可读字符串形式的有理数:"nominator / denominator"
最后,您可以看到所有这些工作原理的一些示例。
这里有点晚了,但我遇到了类似的问题并最终构建了 Swift FractionFormatter。这是可行的,因为您关心的大多数无理数都是粗俗或常见分数集的一部分,并且很容易验证正确的转换。其余部分可能会或可能不会四舍五入,但您非常接近用户可能生成的任何合理分数。它旨在替代 NumberFormatter。
我正在构建一个计算器并希望它能自动将每个小数转换为分数。因此,如果用户计算出答案为“0.333333...”的表达式,它将 return“1/3”。对于“0.25”,它将 return“1/4”。使用 GCD,如此处 (Decimal to fraction conversion),我已经想出了如何将任何有理数、终止小数转换为小数,但这不适用于任何重复的小数(如 .333333)。
关于堆栈溢出的所有其他函数都在 Objective-C 中。但是我的 swift 应用程序需要一个功能!所以这个 () 的翻译版本会很好!
关于如何将有理数或repeating/irrational小数转换为分数(即将“0.1764705882...”转换为 3/17)的任何想法或解决方案都会很棒!
如果要将计算结果显示为有理数
那么唯一 100% 正确的解决方案是在所有计算中使用 有理算术 ,即所有中间值都存储为一对整数 (numerator, denominator)
,所有加法、乘法、除法, 等是使用 rational 的规则完成的
数字。
一旦将结果分配给二进制浮点数
如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.7
精度为 Double
.
那么x
应该显示为7/10
还是69999999999999996/100000000000000000
?
如上所述,使用有理算术将是完美的解决方案。
如果那不可行,那么您可以将 Double
转换回
具有给定精度 的有理数 。
(以下内容摘自
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)
Swift3版本:
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)
正如Martin R所说,要进行 (99.99%) 精确计算,唯一的方法就是从头到尾计算所有有理数。
创建此 class 的原因也是我需要进行非常准确的计算,而 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 *= [=10=] }
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
首先,我们有 class 本身。 class 可以通过两种方式初始化:
在 Rational class 中,alpha ~= nominator & beta ~= denominator
第一种方法是用两个整数初始化class,第一个是分母,第二个是分母。 class 获取这两个整数,然后将它们减少到尽可能少的数字。例如,将 (10,5) 减少到 (2,1) 或者作为另一个例子,将 (144, 60) 减少到 (12,5)。这样,总是存储最简单的数字。 这可以使用 gcd(最大公约数)函数和简化函数,从代码中不难理解。 唯一的问题是 class 面临负数的一些问题,所以它总是保存最终有理数是负数还是正数,如果是负数则使提名人负数。
初始化 class 的第二种方法是使用双精度数和一个名为 'accuracy' 的可选参数。 class 得到双精度数,以及你需要的小数点后多少数字的精度,并将双精度数转换为 nominator/denominator 形式,其中分母是 10 的幂。例如 2.334 将是 2334/1000 或 342.57 将是 34257/100。然后尝试使用在#1 方式中解释的相同方法来简化有理数。
在class定义之后,有一个type-alias 'Rnl',显然你可以随意改变它。
然后有 4 个函数,用于数学的 4 个主要操作:* / + -,我这样定义的,例如您可以轻松地将两个 Rational 类型的数字相乘。
在那之后,有理数类型有 2 个扩展,第一个 ('value') 给你有理数的双精度值,第二个 ('rnlValue') 给你人类可读字符串形式的有理数:"nominator / denominator"
最后,您可以看到所有这些工作原理的一些示例。
这里有点晚了,但我遇到了类似的问题并最终构建了 Swift FractionFormatter。这是可行的,因为您关心的大多数无理数都是粗俗或常见分数集的一部分,并且很容易验证正确的转换。其余部分可能会或可能不会四舍五入,但您非常接近用户可能生成的任何合理分数。它旨在替代 NumberFormatter。