如果要将计算结果显示为有理数
那么唯一 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.7 在 Double 的精度范围内。
那么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)