1

I think the title says it all but let me go in to it a bit more.

I am storing decimal hours in a DB every 5 minutes while a user uses my app. I am having a bit of trouble displaying the correct time for some of the 5 min blocks in that it shows 4 min and 60 seconds. Every 3rd 5 min block shows this.

So something like this

4:60
10:00
15:00
19:60

Some even have n:59 instead of n:00

Part of the code is:

let hours = Int(floor(decimalHour))
let mins = Int(floor(decimalHour * 60) % 60)
let secs = Int(floor(decimalHour * 3600) % 60)

Any suggestions what I could be doing wrong?

puks1978
  • 3,667
  • 11
  • 44
  • 103
  • `floor(x) % 60` will never give you 60 (or maybe it does for floating points? Try to convert to `int` before doing `%`) Please show more of the code. Also include what the value of `decimalHour` was for these test cases. – Thilo Dec 18 '16 at 09:37
  • If possible, better store your duration as seconds (integer) instead of fractional hours (floating point). – Thilo Dec 18 '16 at 09:40
  • Use `let mins = Int(floor(decimalHour * 60)) % 60` – shallowThought Dec 18 '16 at 15:13

1 Answers1

4

Binary floating point number can not represent all numbers exactly, therefore your code is prone to rounding errors. Example (Swift 2):

let decimalHour = 1.0 + 5.0/60.0

print(decimalHour.debugDescription) // 1.0833333333333333
print(floor(decimalHour * 3600)) // 3899.0

let hours = Int(floor(decimalHour))
let mins = Int(floor(decimalHour * 60) % 60)
let secs = Int(floor(decimalHour * 3600) % 60)

print(hours, mins, secs) // 1 5 59

The actual number stored in decimalHour is slightly less than 1 + 5/60, and therefore the seconds are calculated wrongly.

(Also note that you cannot use % with floating point numbers in Swift 3, compare What does "% is unavailable: Use truncatingRemainder instead" mean?.)

As already said in the comments, a better approach would be to store the duration as an integer (number of seconds).

If that is not possible, round the floating point number to a the number of seconds and then continue with pure integer arithmetic. Example (works with Swift 2+3):

let decimalHour = 1.0 + 5.0/60.0

let totalSeconds = lrint(decimalHour * 3600) // round to seconds
let hours = totalSeconds / 3600
let mins = (totalSeconds % 3600) / 60
let secs = totalSeconds % 60

print(hours, mins, secs) // 1 5 0
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382