"% 不可用:请使用 truncatingRemainder" 是什么意思?

127

当我使用扩展代码时,出现以下错误,根据互联网搜索,我不确定他们是要求只使用不同的运算符还是修改表达式中的值。

错误:%不可用:请改用truncatingRemainder

扩展代码:

extension CMTime {
    var durationText:String {
        let totalSeconds = CMTimeGetSeconds(self)
        let hours:Int = Int(totalSeconds / 3600)
        let minutes:Int = Int(totalSeconds % 3600 / 60)
        let seconds:Int = Int(totalSeconds % 60)

        if hours > 0 {
            return String(format: "%i:%02i:%02i", hours, minutes, seconds)
        } else {
            return String(format: "%02i:%02i", minutes, seconds)
        }
    }
}

设置分钟和秒数变量时发生错误。


1
我认为CMTimeGetSeconds返回浮点数。 - zombie
3
这意味着 % 运算符不可用,您应该考虑使用类似于 truncatingRemainder 方法的东西代替。 - matt
1
你不能在 Float64 上使用模运算,只能在 Int 上使用;因此,let minutes:Int = Int(totalSeconds) % 3600 / 60; let seconds:Int = Int(totalSeconds) % 60 是正确的方式。 - holex
@holex。你错了。你只能在符合“BinaryInteger”类型的操作数上使用模运算符,而不仅仅是“Int”类型。 - Peter Schorn
@holex,我可以向您保证,Int从来不是您可以在其上使用运算符的唯一类型。 - Peter Schorn
显示剩余3条评论
5个回答

197

CMTimeGetSeconds()返回浮点数(Float64,也称为Double)。在Swift 2中,您可以计算浮点除法的余数如下:

let rem = 2.5 % 1.1
print(rem) // 0.3

在Swift 3中,可以使用以下方法来实现

let rem = 2.5.truncatingRemainder(dividingBy: 1.1)
print(rem) // 0.3

应用于您的代码:

let totalSeconds = CMTimeGetSeconds(self)
let hours = Int(totalSeconds / 3600)
let minutes = Int((totalSeconds.truncatingRemainder(dividingBy: 3600)) / 60)
let seconds = Int(totalSeconds.truncatingRemainder(dividingBy: 60))

但在这种情况下,最好一开始就将时长转换为整数:

let totalSeconds = Int(CMTimeGetSeconds(self)) // Truncate to integer
// Or:
let totalSeconds = lrint(CMTimeGetSeconds(self)) // Round to nearest integer

接下来的几行代码可以简化为

let hours = totalSeconds / 3600
let minutes = (totalSeconds % 3600) / 60
let seconds = totalSeconds % 60

27
% 取模运算符仅适用于整数类型。对于浮点数类型,您需要更具体地说明您想要的IEEE 754除法/余数行为,因此您必须调用一个方法:要么是 remainder ,要么是 truncatingRemainder。(如果你正在进行浮点数运算,你实际上需要关心这个问题和其他很多问题,否则你可能会得到意外/错误的结果。)
如果您实际上想要执行整数取模运算,则需要在使用%之前将CMTimeGetSeconds的返回值转换为整数。(请注意,如果您这样做,您将丢失小数秒...这可能很重要,具体取决于您在何处使用CMTime。例如,您想要分钟:秒:帧吗?)
根据您想要在UI中呈现CMTime值的方式,最好提取秒值并将其传递给NSDateFormatterNSDateComponentsFormatter,以便获得适当的本地化支持。

10

在Swift 3中恢复简单的模数语法:

实际上,这种语法是在苹果官方的Swift邮件列表这里被建议的,但由于某种原因他们选择了一种不太优雅的语法。

infix operator %%/*<--infix operator is required for custom infix char combos*/
/**
 * Brings back simple modulo syntax (was removed in swift 3)
 * Calculates the remainder of expression1 divided by expression2
 * The sign of the modulo result matches the sign of the dividend (the first number). For example, -4 % 3 and -4 % -3 both evaluate to -1
 * EXAMPLE: 
 * print(12 %% 5)    // 2
 * print(4.3 %% 2.1) // 0.0999999999999996
 * print(4 %% 4)     // 0
 * NOTE: The first print returns 2, rather than 12/5 or 2.4, because the modulo (%) operator returns only the remainder. The second trace returns 0.0999999999999996 instead of the expected 0.1 because of the limitations of floating-point accuracy in binary computing.
 * NOTE: Int's can still use single %
 * NOTE: there is also .remainder which supports returning negatives as oppose to truncatingRemainder (aka the old %) which returns only positive.
 */
public func %% (left:CGFloat, right:CGFloat) -> CGFloat {
    return left.truncatingRemainder(dividingBy: right)
}

这个简单的 Swift 3 迁移技巧是完整的 Swift 3 迁移指南的一部分,其中包括许多见解(35k loc / 8 天迁移)http://eon.codes/blog/2017/01/12/swift-3-migration/


1
这份A很好,提供了有趣的信息,并试图回答该问题。 - Jakub Truhlář
3
@Jakub Truhlář ...谢谢。在我看来,这是我最好的Swift 3迁移修复方式。我不敢相信人们会down-vote它。模数是一个非常重要的概念,几乎每一本算法书中都有讲到。使其冗长无意义,因为代码中的算术应尽可能地简洁。当您能够看到完整的图片而不是理解单个变量的含义时,我们的认知能力可以更好地理解算术。在商业逻辑中,全面而清晰的变量命名很重要,但在算术中则相反。 - Sentry.co
2
@GitSync 取模是一个重要的概念,但它仅适用于整数。你应该理解这个区别。 - Sulthan
5
取模运算只适用于整数,您所说的是余数。十进制数有两种类型的余数。这就是为什么Swift决定将此操作明确化。在双精度浮点数上计算整数余数(截断余数)并不常见。 - Sulthan
1
@GitSync 有 remainder(dividingBy:)truncatingRemainder(dividingBy:)。你可能想要阅读两者的文档。此外,参考同样关于 C++ 的问题 https://dev59.com/lW025IYBdhLWcg3wST2Q - Sulthan
显示剩余3条评论

3

除非您认为这样可以使代码更加安全,否则无需为浮点数创建单独的模运算符。您可以重载 % 运算符来接受浮点数,如下所示:

func %<N: BinaryFloatingPoint>(lhs: N, rhs: N) -> N {
    lhs.truncatingRemainder(dividingBy: rhs)
}

使用方法

let a: Float80 = 10
let b: Float80 = 3
print(a % b)

现在您可以使用 % 对相同类型的两个浮点数进行运算。

2
我发现以下代码在Swift 3中可行:
    let minutes = Int(floor(totalSeconds / 60))
    let seconds = Int(totalSeconds) % 60

其中totalSeconds是一个TimeIntervalDouble)。


3
将“floor”和“round”混用并不是一个好主意,例如对于 totalSeconds = 59.8,您的代码计算出0分钟和0秒。 - Martin R
是的,你说得对。实际上,round 根本不需要。 - benwiggy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接