1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
// MARK: Rounding
extension Duration {
func rounded(increment: Duration, rule: FloatingPointRoundingRule = .toNearestOrEven) -> Duration {
rounded(rule, toMultipleOf: increment).value
}
func rounded(_ rule: FloatingPointRoundingRule = .toNearestOrEven, toMultipleOf increment: Duration) -> (value: Duration, roundsToEven: Bool) {
let increment = abs(increment)
let (truncated, truncatedCount) = roundedTowardZero(toMultipleOf: increment)
let diffToTruncated = abs(abs(truncated) - abs(self))
guard diffToTruncated != .zero else {
return (self, truncatedCount % 2 == .zero)
}
let ceiled = truncated + (self < .zero ? .zero - increment : increment)
let diffToCeiled = abs(abs(ceiled) - abs(self))
let rounded: Duration
switch rule {
case .up:
rounded = Swift.max(truncated, ceiled)
case .down:
rounded = Swift.min(truncated, ceiled)
case .towardZero:
rounded = truncated
case .awayFromZero:
rounded = ceiled
case .toNearestOrAwayFromZero:
if diffToTruncated < diffToCeiled {
rounded = truncated
} else {
rounded = ceiled
}
case .toNearestOrEven:
if diffToTruncated < diffToCeiled || diffToTruncated == diffToCeiled && truncatedCount % 2 == .zero {
rounded = truncated
} else {
rounded = ceiled
}
@unknown default:
fatalError()
}
return (rounded, (truncatedCount % 2 == .zero) == (rounded == truncated))
}
fileprivate static func % (_ lhs: Duration, _ rhs: Int64) -> Duration {
lhs - ((lhs / rhs) * rhs)
}
fileprivate static func / (_ lhs: Duration, _ rhs: Int64) -> Duration {
// Unfortunately, division between a Duration and an
// Int64 is not implemented on 32 bit systems. We thus
// repeatedly apply a floating point division until
// the remainder is small enough to get a precise result.
// This should take no more than three iterations because
// Double has 52 fraction bits and Duration is 128 bits.
#if arch(i386) || arch(arm) || arch(arm64_32) || arch(wasm32)
let absSelf = abs(lhs)
let absDivLower = abs(rhs)
let absDiv = Duration(secondsComponent: 0, attosecondsComponent: absDivLower)
var count = Duration.zero
var remainder = abs(lhs)
while abs(remainder) >= absDiv {
count += .seconds(1e-18 * (remainder / absDiv))
remainder = absSelf - (count * absDivLower)
}
if remainder < .zero {
count -= .init(secondsComponent: 0, attosecondsComponent: 1)
}
return (lhs < .zero) != (rhs < .zero) ? .zero - count : count
#else
return lhs / (rhs as any BinaryInteger)
#endif
}
private func roundedTowardZero(toMultipleOf divisor: Duration) -> (duration: Duration, count: Duration) {
let absSelf = abs(self)
let (s, _) = absSelf.components
let absDiv = abs(divisor)
let (ds, dattos) = absDiv.components
let absCount: Duration
let absValue: Duration
if ds == 0 {
absCount = absSelf / dattos
absValue = absCount * dattos
} else if dattos == 0 {
absCount = .init(secondsComponent: 0, attosecondsComponent: s / ds)
absValue = .init(secondsComponent: ds * (s / ds), attosecondsComponent: 0)
} else if absSelf < absDiv {
absCount = .zero
absValue = .zero
} else {
// When reaching this branch, we know that absDiv is at least
// one second, and that absSelf is even bigger.
// This also means, that our result (theoretically) fits into
// Int64, because wost case, we divide Int64.max seconds by
// 1 second and 1 attosecond.
// We first use the floating point based division provided by
// the standard library to get an approximate count. Since
// double cannot represent Int64.max at integer precision, but
// rounds up to a higher number, we use UInt64, which can fit
// even this rounded up number.
let count = UInt64(absSelf / absDiv)
// However, since Double only uses 52 bits to store the fraction,
// our remainder can be (absolutely) bigger than absDiv. To get a
// precise result, we do another floating point based division on
// the remainder. Since the remainder is at most 2^(64-52) = 4096
// big and absDiv is greater than 1, we know that the resulting
// Double will have integer precision.
let remainder = absSelf - (absDiv * count)
let remainderCount = Int64(remainder / absDiv)
absCount = .init(secondsComponent: 0, attosecondsComponent: 1) * count
+ .init(secondsComponent: 0, attosecondsComponent: 1) * remainderCount
absValue = absDiv * count
+ absDiv * remainderCount
}
if (self < Self.zero) != (divisor < Self.zero) {
return (.zero - absValue, .zero - absCount)
} else {
return (absValue, absCount)
}
}
}
// MARK: Utility
func abs(_ duration: Duration) -> Duration {
duration < .zero ? Duration.zero - duration : duration
}
|