Skip to content

Commit 011986c

Browse files
committed
Fix overflow issues in the approximate function and crashes in the rounding functions related to rational big integers.
1 parent 67343b1 commit 011986c

File tree

2 files changed

+82
-23
lines changed

2 files changed

+82
-23
lines changed

Sources/LispKit/Compiler/Scanner.swift

+16-5
Original file line numberDiff line numberDiff line change
@@ -533,13 +533,24 @@ public final class Scanner {
533533
case .float:
534534
let max = Double(Int64.max)
535535
if self.token.floatVal > -max && self.token.floatVal < max {
536-
self.token.kind = .rat
537-
self.token.ratVal = MathLibrary.approximate(self.token.floatVal)
538-
} else {
536+
if let rat = MathLibrary.approximate(self.token.floatVal) {
537+
self.token.kind = .rat
538+
self.token.ratVal = rat
539+
self.token.floatVal = 0.0
540+
} else if let rat = MathLibrary.approximateBigRat(self.token.floatVal) {
541+
self.token.kind = .bigrat
542+
self.token.bigRatVal = rat
543+
self.token.floatVal = 0.0
544+
} else {
545+
self.token.floatVal = .infinity
546+
}
547+
} else if let rat = MathLibrary.approximateBigRat(self.token.floatVal) {
539548
self.token.kind = .bigrat
540-
self.token.bigRatVal = MathLibrary.approximateBigRat(self.token.floatVal)
549+
self.token.bigRatVal = rat
550+
self.token.floatVal = 0.0
551+
} else {
552+
self.token.floatVal = .infinity
541553
}
542-
self.token.floatVal = 0.0
543554
case .complex:
544555
self.signal(LexicalError.exactComplexNumbersUnsupported)
545556
default:

Sources/LispKit/Primitives/MathLibrary.swift

+66-18
Original file line numberDiff line numberDiff line change
@@ -500,35 +500,45 @@ public final class MathLibrary: NativeLibrary {
500500
}
501501
}
502502

503-
static func approximateNumber(_ x: Double, tolerance: Double = 1.0e-16) -> Expr {
504-
let max = Double(Int64.max)
505-
if x > -max && x < max {
506-
return .makeNumber(MathLibrary.approximate(x))
503+
static func approximateNumber(_ x: Double, tolerance: Double = 1.0e-15) -> Expr {
504+
if let rat = MathLibrary.approximate(x, tolerance: tolerance) {
505+
return .makeNumber(rat)
506+
} else if let rat = MathLibrary.approximateBigRat(x, tolerance: tolerance) {
507+
return .makeNumber(rat)
507508
} else {
508-
return .makeNumber(MathLibrary.approximateBigRat(x))
509+
return .false
509510
}
510511
}
511512

512-
static func approximate(_ x: Double, tolerance: Double = 1.0e-16) -> Rational<Int64> {
513+
static func approximate(_ x: Double, tolerance: Double = 1.0e-15) -> Rational<Int64>? {
513514
let mx = x * tolerance
514515
var y = x
515516
var (n1, d1) = (Int64(1), Int64(0))
516517
var (n2, d2) = (Int64(0), Int64(1))
517518
repeat {
518-
let fy = Int64(Foundation.floor(y))
519-
(n1, n2) = (fy * n1 + n2, n1)
520-
(d1, d2) = (fy * d1 + d2, d1)
521-
y = 1.0 / (y - Foundation.floor(y))
519+
guard y.isFinite else {
520+
return nil
521+
}
522+
if let fy = Int64(exactly: Foundation.floor(y)) {
523+
(n1, n2) = (fy * n1 + n2, n1)
524+
(d1, d2) = (fy * d1 + d2, d1)
525+
y = 1.0 / (y - Foundation.floor(y))
526+
} else {
527+
return nil
528+
}
522529
} while abs(x - Double(n1) / Double(d1)) > mx
523530
return Rational(n1, d1)
524531
}
525532

526-
static func approximateBigRat(_ x: Double, tolerance: Double = 1.0e-16) -> Rational<BigInt> {
533+
static func approximateBigRat(_ x: Double, tolerance: Double = 1.0e-15) -> Rational<BigInt>? {
527534
let mx = x * tolerance
528535
var y = x
529536
var (n1, d1) = (BigInt(1), BigInt(0))
530537
var (n2, d2) = (BigInt(0), BigInt(1))
531538
repeat {
539+
guard y.isFinite else {
540+
return nil
541+
}
532542
let fy = BigInt(Foundation.floor(y))
533543
(n1, n2) = (fy * n1 + n2, n1)
534544
(d1, d2) = (fy * d1 + d2, d1)
@@ -547,7 +557,14 @@ public final class MathLibrary: NativeLibrary {
547557
case .rational(.fixnum(let n), .fixnum(let d)):
548558
return .makeNumber(Int64(Foundation.floor(Double(n) / Double(d))))
549559
case .rational(.bignum(let n), .bignum(let d)):
550-
return .makeNumber(Int64(Foundation.floor(n.doubleValue / d.doubleValue)))
560+
let (quotient, remainder) = n.divided(by: d)
561+
if remainder.isZero {
562+
return .makeNumber(quotient)
563+
} else if quotient.isNegative {
564+
return .makeNumber(quotient.minus(1))
565+
} else {
566+
return .makeNumber(quotient)
567+
}
551568
case .flonum(let num):
552569
return .makeNumber(Foundation.floor(num))
553570
default:
@@ -562,7 +579,12 @@ public final class MathLibrary: NativeLibrary {
562579
case .rational(.fixnum(let n), .fixnum(let d)):
563580
return .makeNumber(Int64(Foundation.ceil(Double(n) / Double(d))))
564581
case .rational(.bignum(let n), .bignum(let d)):
565-
return .makeNumber(Int64(Foundation.ceil(n.doubleValue / d.doubleValue)))
582+
let (quotient, remainder) = n.divided(by: d)
583+
if remainder.isZero || quotient.isNegative {
584+
return .makeNumber(quotient)
585+
} else {
586+
return .makeNumber(quotient.plus(1))
587+
}
566588
case .flonum(let num):
567589
return .makeNumber(ceil(num))
568590
default:
@@ -577,7 +599,7 @@ public final class MathLibrary: NativeLibrary {
577599
case .rational(.fixnum(let n), .fixnum(let d)):
578600
return .makeNumber(Int64(Foundation.trunc(Double(n) / Double(d))))
579601
case .rational(.bignum(let n), .bignum(let d)):
580-
return .makeNumber(Int64(Foundation.trunc(n.doubleValue / d.doubleValue)))
602+
return .makeNumber(n.divided(by: d).quotient)
581603
case .flonum(let num):
582604
return .makeNumber(trunc(num))
583605
default:
@@ -587,14 +609,40 @@ public final class MathLibrary: NativeLibrary {
587609

588610
private func round(_ expr: Expr) throws -> Expr {
589611
switch expr {
590-
case .fixnum(_), .bignum(_):
612+
case .fixnum(_),
613+
.bignum(_):
591614
return expr
592615
case .rational(.fixnum(let n), .fixnum(let d)):
593-
return .makeNumber(Int64(Foundation.round(Double(n) / Double(d))))
616+
return .makeNumber(Int64((Double(n) / Double(d)).rounded(.toNearestOrEven)))
594617
case .rational(.bignum(let n), .bignum(let d)):
595-
return .makeNumber(Int64(Foundation.round(n.doubleValue / d.doubleValue)))
618+
let (quotient, remainder) = n.divided(by: d)
619+
if remainder.isZero {
620+
return .makeNumber(quotient)
621+
} else {
622+
let doubleQuotient = remainder.abs.times(2)
623+
let denom = d.abs
624+
if doubleQuotient == denom {
625+
if quotient.isOdd {
626+
if quotient.isNegative {
627+
return .makeNumber(quotient.minus(1))
628+
} else {
629+
return .makeNumber(quotient.plus(1))
630+
}
631+
} else {
632+
return .makeNumber(quotient)
633+
}
634+
} else if doubleQuotient > denom {
635+
if quotient.isNegative {
636+
return .makeNumber(quotient.minus(1))
637+
} else {
638+
return .makeNumber(quotient.plus(1))
639+
}
640+
} else {
641+
return .makeNumber(quotient)
642+
}
643+
}
596644
case .flonum(let num):
597-
return .makeNumber(Foundation.round(num))
645+
return .makeNumber(num.rounded(.toNearestOrEven))
598646
default:
599647
throw RuntimeError.type(expr, expected: [.realType])
600648
}

0 commit comments

Comments
 (0)