Skip to content

Commit 9fd9c63

Browse files
committed
Introduce new Rational constructor for approximating a Double number.
1 parent dbd3b18 commit 9fd9c63

File tree

5 files changed

+40
-7
lines changed

5 files changed

+40
-7
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 2.3.6 (2020-05-01)
4+
- Fixed serious bug that was leading to a stack overflow if zero was added to a negative `BigInt` number
5+
- Introduced new `Rational` constructor for approximating a `Double` number as a `Rational`
6+
37
## 2.3.5 (2020-04-05)
48
- Port to Swift 5.2
59
- Migrated project to Xcode 11.4

NumberKit.xcodeproj/project.pbxproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@
492492
"@executable_path/../Frameworks",
493493
"@loader_path/Frameworks",
494494
);
495-
MARKETING_VERSION = 2.3.5;
495+
MARKETING_VERSION = 2.3.6;
496496
PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.NumberKit;
497497
PRODUCT_NAME = "$(TARGET_NAME)";
498498
SKIP_INSTALL = YES;
@@ -519,7 +519,7 @@
519519
"@executable_path/../Frameworks",
520520
"@loader_path/Frameworks",
521521
);
522-
MARKETING_VERSION = 2.3.5;
522+
MARKETING_VERSION = 2.3.6;
523523
PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.NumberKit;
524524
PRODUCT_NAME = "$(TARGET_NAME)";
525525
SKIP_INSTALL = YES;
@@ -584,7 +584,7 @@
584584
"@executable_path/Frameworks",
585585
"@loader_path/Frameworks",
586586
);
587-
MARKETING_VERSION = 2.3.5;
587+
MARKETING_VERSION = 2.3.6;
588588
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
589589
MTL_FAST_MATH = YES;
590590
PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.NumberKit;
@@ -623,7 +623,7 @@
623623
"@executable_path/Frameworks",
624624
"@loader_path/Frameworks",
625625
);
626-
MARKETING_VERSION = 2.3.5;
626+
MARKETING_VERSION = 2.3.6;
627627
MTL_FAST_MATH = YES;
628628
PRODUCT_BUNDLE_IDENTIFIER = net.objecthub.NumberKit;
629629
PRODUCT_NAME = NumberKit;

Sources/NumberKit/IntegerNumber.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,15 @@ public protocol IntegerNumber: SignedNumeric,
5757
static func %(lhs: Self, rhs: Self) -> Self
5858

5959
/// Constructs an `IntegerNumber` from an `Int64` value. This constructor might crash if
60+
/// the value cannot be converted to this type.
6061
///
6162
/// - Note: This is a hack right now.
6263
init(_ value: Int64)
6364

65+
/// Constructs an `IntegerNumber` from a `Double` value. This constructor might crash if
66+
/// the value cannot be converted to this type.
67+
init(_ value: Double)
68+
6469
/// Returns the integer as a `Double`.
6570
var doubleValue: Double { get }
6671

Sources/NumberKit/Rational.swift

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,20 @@ public struct Rational<T: IntegerNumber>: RationalNumber, CustomStringConvertibl
141141
public init(_ value: T) {
142142
self.init(numerator: value, denominator: T.one)
143143
}
144-
144+
145+
/// Creates a rational number by rationalizing a `Double` value.
146+
public init(_ value: Double, precision: Double = 1.0e-8) {
147+
var x = value
148+
var a = Foundation.floor(x)
149+
var (h1, k1, h, k) = (T.one, T.zero, T(a), T.one)
150+
while x - a > precision * k.doubleValue * k.doubleValue {
151+
x = 1.0/(x - a)
152+
a = Foundation.floor(x)
153+
(h1, k1, h, k) = (h, k, h1 + T(a) * h, k1 + T(a) * k)
154+
}
155+
self.init(numerator: h, denominator: k)
156+
}
157+
145158
/// Create an instance initialized to `value`.
146159
public init(integerLiteral value: Int64) {
147160
self.init(T(value))
@@ -341,7 +354,7 @@ extension Rational: ExpressibleByStringLiteral {
341354
/// Compute absolute number of `num` and return a tuple consisting of the result and a
342355
/// boolean indicating whether there was an overflow.
343356
private static func absWithOverflow(_ num: T) -> (value: T, overflow: Bool) {
344-
return num < 0 ? T(0).subtractingReportingOverflow(num) : (num, false)
357+
return num < 0 ? T.zero.subtractingReportingOverflow(num) : (num, false)
345358
}
346359

347360
/// Creates a rational number from a numerator and a denominator.
@@ -355,7 +368,7 @@ extension Rational: ExpressibleByStringLiteral {
355368
let (adenom, overflow2) = Rational.absWithOverflow(denominator)
356369
let div = Rational.gcd(anum, adenom)
357370
let (n, overflow3) = anum.dividedReportingOverflow(by: div)
358-
let (numer, overflow4) = negative ? T(0).subtractingReportingOverflow(n) : (n, false)
371+
let (numer, overflow4) = negative ? T.zero.subtractingReportingOverflow(n) : (n, false)
359372
let (denom, overflow5) = adenom.dividedReportingOverflow(by: div)
360373
return (Rational(numerator: numer, denominator: denom),
361374
overflow1 || overflow2 || overflow3 || overflow4 || overflow5)

Tests/NumberKitTests/RationalTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ class RationalTests: XCTestCase {
8787
XCTAssertEqual(r1, Rational(10 * 49, 3 * 31))
8888
}
8989

90+
func testRationalize() {
91+
let r1 = Rational<Int>(1.0/3.0)
92+
XCTAssertEqual(r1, Rational(1, 3))
93+
let r2 = Rational<Int>(1931.0 / 9837491.0, precision: 1.0e-14)
94+
XCTAssertEqual(r2, Rational(1931, 9837491))
95+
let r3 = Rational<Int>(-17.0/3.0)
96+
XCTAssertEqual(r3, -Rational(17, 3))
97+
let r4 = Rational<BigInt>(1931.0 / 9837491.0, precision: 1.0e-14)
98+
XCTAssertEqual(r4, Rational(BigInt(1931), BigInt(9837491)))
99+
}
100+
90101
static let allTests = [
91102
("testConstructors", testConstructors),
92103
("testPlus", testPlus),

0 commit comments

Comments
 (0)