127

I get the following error when using code for an extension, I'm not sure if they're asking to just use a different operator or modify the values in the expression based on an internet search.

Error: % is unavailable: Use truncatingRemainder instead

Extension code:

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)
        }
    }
}

The error(s) occur when setting the minutes and seconds variables.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
Laurence Wingo
  • 3,912
  • 7
  • 33
  • 61
  • 1
    i think CMTimeGetSeconds returns float – zombie Nov 08 '16 at 19:58
  • 3
    It means that the `%` operator is unavailable and you should consider using something like the `truncatingRemainder` method instead. – matt Nov 08 '16 at 20:08
  • 1
    you cannot use modulo on `Float64` but on `Int` only; therefore: `let minutes:Int = Int(totalSeconds) % 3600 / 60; let seconds:Int = Int(totalSeconds) % 60` is the correct way. – holex Jan 10 '17 at 15:57
  • @holex. You are wrong. You can only use the modulo operator on operands with types that conform to `BinaryInteger`, not just `Int`. – Peter Schorn Aug 27 '20 at 12:26
  • @PeterSchorn, thanks for correcting a 3 years old comment – that protocol was not available at the time at all. – holex Aug 27 '20 at 14:42
  • @holex Still, I can guarantee you that Int was never the only type that you can use the operator on. – Peter Schorn Aug 28 '20 at 19:47
  • @PeterSchorn, don't need to guarantee something you can't prove – that protocol (among many other [numeric protocols](https://developer.apple.com/documentation/swift/swift_standard_library/numbers_and_basic_values/numeric_protocols)) introduced in September 2017, literally one WWDC, one major Swift version (Swift 4.0 as it was not part of Swift 3.1), and about 9 months later – just saying for the sake of having the facts right. ;) – holex Aug 29 '20 at 20:31
  • @holex I now understand that the `BinaryInteger` protocol was not available at the time, but do you seriously believe that the `%` operator only worked for `Int` but not for other integer types, such as `UInt32` and `Int16`? What's so special about `Int`? Surely you can't be that stubborn. I tried to download Xcode 8.3 to prove it to you, but it's not compatible with macOS catalina. – Peter Schorn Aug 31 '20 at 07:36

5 Answers5

196

CMTimeGetSeconds() returns a floating point number (Float64 aka Double). In Swift 2 you could compute the remainder of a floating point division as

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

In Swift 3 this is done with

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

Applied to your code:

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

However, in this particular case it is easier to convert the duration to an integer in the first place:

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

Then the next lines simplify to

let hours = totalSeconds / 3600
let minutes = (totalSeconds % 3600) / 60
let seconds = totalSeconds % 60
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
27

The % modulus operator is defined only for integer types. For floating-point types, you need to be more specific about the kind of IEEE 754 division/remainder behavior you want, so you have to call a method: either remainder or truncatingRemainder. (If you're doing floating-point math you actually need to care about this, and lots of other stuff, or you can get unexpected / bad results.)

If you actually intend to do integer modulus, you need to convert the return value of CMTimeGetSeconds to an integer before using %. (Note that if you do, you'll lop off the fractional seconds... depending on where you're using CMTime that may be important. Do you want minutes:seconds:frames, for example?)

Depending on how you want to present CMTime values in your UI, it might be better to extract the seconds value and pass it to NSDateFormatter or NSDateComponentsFormatter so you get appropriate locale support.

rickster
  • 124,678
  • 26
  • 272
  • 326
10

Bring back the simple modulo syntax in swift 3:

This syntax was actually suggested on Apples official swift mailing list here but for some reason they opted for a less elegant syntax.

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)
}

This simple swift 3 migration tip is part of a more comprehensive swift 3 migration guide with many insights (35k loc / 8-days of migration) http://eon.codes/blog/2017/01/12/swift-3-migration/

Sentry.co
  • 5,355
  • 43
  • 38
  • 1
    This A is fine, provides interesting information and also tries to answer the Q. – Jakub Truhlář Mar 30 '17 at 20:06
  • 3
    @Jakub Truhlář ...Man, Thx. IMO This was my best swift 3 migration fix. Can't believe people down-voted it. Modulo is such an important concept and is thought in every code book that has arithmetic. Making it verbose makes no sense since Arithmetic in code should be written as compactly as possible. As our cognitive ability to comprehend arithmetic increases when you can see the complete picture as oppose to understanding what the individual variables mean. IMO Comprehensive variable naming is important in business logic but not arithmetic, quite the opposite. – Sentry.co Mar 31 '17 at 14:41
  • 2
    @GitSync Modulo is an important concept but it exists only for integers. You should understand the difference. – Sulthan Jul 20 '17 at 11:24
  • @Sulthan What do you mean? It exists for all Numeric types in swift to my knowledge. This Post points out that adding verbosity isn't a good idea when doing arithmetic. – Sentry.co Jul 21 '17 at 15:28
  • 5
    @GitSync The modulo operation exists only for integers. You are talking about remainder. Decimal values have two types of remainders. That's why Swift decided to make the operation explicit. It's not very common to calculate an integer remainder (*truncating remainder*) on double values. – Sulthan Jul 21 '17 at 15:35
  • @Sulthan Good point, I will make a note about Int and modulo in my examples. I use modulo with CGFloat when doing Trig like Normalising an angle for instance. Question: What do you mean by: Decimal values have two types of remainders? – Sentry.co Jul 21 '17 at 17:31
  • 1
    @GitSync There is `remainder(dividingBy:)` and `truncatingRemainder(dividingBy:)`.You might want to read the docs for both. Also, refer to the same question for C++ https://stackoverflow.com/questions/6102948/why-does-modulus-division-only-work-with-integers – Sulthan Jul 21 '17 at 17:54
  • @Sulthan Seems remainder supports returning negatives as oppose to truncatingRemainder (aka the old %) which returns only positive. I'll make a note about this too. Thanks for your help. – Sentry.co Jul 22 '17 at 09:11
3

There's no need to create a separate modulo operator for floating point numbers, unless you think it makes the code safer. You can overload the % operator to accept floating point numbers like so:

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

Usage

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

You can now use % with any two floating point numbers of the same tye.

Peter Schorn
  • 916
  • 3
  • 10
  • 20
2

I found that the following works in Swift 3:

    let minutes = Int(floor(totalSeconds / 60))
    let seconds = Int(totalSeconds) % 60

where totalSeconds is a TimeInterval (Double).

benwiggy
  • 1,440
  • 17
  • 35