C#: decrementing a clock using modulus math
Trying to emulate the rollover of a 24 hour clock by hand (with math vs. using the timespan classes). The incrementing part was easy to figure out how to roll over from 23:00 to 0:00 and from, but getting it to go the other way is turning out to be really confusing. Here's what I have so far:
static void IncrementMinute(int min, int incr)
{
int newMin = min + incr,
hourIncrement = newMin / 60;
//increment or decrement the hour
if((double)newMin % 60 < 0 && (double)newMin % 60 > -1)
hourIncrement = -1;
Console.WriteLine("Hour increment is {0}: ", hourIncrement);
}
The problem that im finding is when going backwards, if the the modulus of is between numbers, it will not decrement correctly. Example: it is 12:00 and you subtract 61 minutes, we know the time would be 10:59 as the hour should roll back 1 hour for going from 12:00 to 11:59, then back again for going from 11:00 to 10:59. Unfortunately the way im 开发者_开发百科calculating it: newMin % 60 in this case, only grabs the first hour rollback, but since the second rollback is technically -1.0166 as a remainder, and since mod only returns a whole number, its rounding off. Im sure im missing some basic math here, but could someone help me out?
EDIT: I've written this a number of ways long and short. Some are closer than others, but I know this is simpler than it seems. I know this one seems kinda "wtf was he doing", but you should be able to see basically what Im trying to do. Incrementing a clock and having it rollover from 23:59 to 0:00 is easy. Going backwards has proven to be not so easy.
OK, here's the incrementMinute with the rollover. Simple. But try to go backwards. Doesn't work.
static void IncrementMinute(int min, int incr)
{
int newMin = min + incr,
hourIncrement = newMin / 60;
min = newMin % 60;
Console.WriteLine("The new minute is {0} and the hour has incremented by {1}", min, hourIncrement);
}
I'd go for something a bit simpler
public class Clock
{
public const int HourPerDay = 24;
public const int MinutesPerHour = 60;
public const int MinutesPerDay = MinutesPerHour * HourPerDay;
private int totalMinutes;
public int Minute
{
get { return this.totalMinutes % MinutesPerHour; }
}
public int Hour
{
get { return this.totalMinutes / MinutesPerHour; }
}
public void AddMinutes(int minutes)
{
this.totalMinutes += minutes;
this.totalMinutes %= MinutesPerDay;
if (this.totalMinutes < 0)
this.totalMinutes += MinutesPerDay;
}
public void AddHours(int hours)
{
this.AddMinutes(hours * MinutesPerHour);
}
public override string ToString()
{
return string.Format("{0:00}:{1:00}", this.Hour, this.Minute);
}
}
Sample usage :
new Clock().AddMinutes(-1); // 23:59
new Clock().AddMinutes(-61); // 22:59
new Clock().AddMinutes(-1441); // 23:59
new Clock().AddMinutes(1); // 00:01
new Clock().AddMinutes(61); // 01:01
new Clock().AddMinutes(1441); // 00:01
You might try calculating both minute and hour increments first, then handling cases where the new minutes crosses an hour boundary, something like this:
int hourIncrement = incr / 60;
int minIncrement = incr % 60;
int newMin = min + minIncrement;
if (newMin < 0)
{
newMin += 60;
hourIncrement--;
}
else if (newMin > 60)
{
newMin -= 60;
hourIncrement++;
}
Edit
I like @Ben Voigts answer, but was wondering if there would be any difference in performance. I ran the console application below to time them both, and was a little surprised by the results.
- 40 ms for the code above
- 2876 ms for Ben's answer
This was done in a release build. Can anyone else run this and confirm? Am I making any mistakes in the way I time them?
using System;
using System.Diagnostics;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
int max = 100000000;
sw.Start();
for (int i = 0; i < max; i++)
IncrementMinute1(0, -61);
sw.Stop();
Console.WriteLine("IncrementMinute1: {0} ms", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
for (int i = 0; i < max; i++)
IncrementMinute2(0, -61);
sw.Stop();
Console.WriteLine("IncrementMinute2: {0} ms", sw.ElapsedMilliseconds);
Console.ReadLine();
}
static void IncrementMinute1(int min, int incr)
{
int hourIncrement = incr / 60;
int minIncrement = incr % 60;
int newMin = min + minIncrement;
if (newMin < 0)
{
newMin += 60;
hourIncrement--;
}
else if (newMin > 60)
{
newMin -= 60;
hourIncrement++;
}
}
static void IncrementMinute2(int min, int incr)
{
min += incr;
int hourIncrement = (int)Math.Floor(min / 60.0);
min -= hourIncrement * 60;
}
}
}
Modular mathematics is only defined for the integers. If you are attempting to mix modular arithmetic with real numbers you will not succeed. You need to figure out a different mathematical approach.
Try
int newMin = min + incr,
hourIncrement = (int)Math.Floor(newMin / 60.0);
min -= hourIncrement * 60;
The essential problem was that you want hourIncrement
to round down, but integer division rounds toward zero. They're the same with positive numbers, but not for negative...
EDIT (getting rid of useless extra variable):
min += incr;
int hourIncrement = (int)Math.Floor(min / 60.0);
min -= hourIncrement * 60;
EDIT2 (avoid floating-point arithmetic):
min += incr;
int hourIncrement = min / 60;
min -= hourIncrement * 60;
if (min < 0) { min += 60; --hourIncrement; }
Why to complicate things
public System.Timers.Timer timer = new System.Timers.Timer(1000);
public DateTime d;
public void init()
{
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
d = new DateTime(2011, 11, 11, 23, 59, 50);
d=d.AddHours(1);
Console.Writeline(d);
d=d.AddHours(-2);
Console.Writeline(d);
timer.Enabled = true;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)(() =>
{
MoveClockHands();
d=d.AddSeconds(1);
Console.WriteLine(d);
}));
}
void MoveClockHands() //12 hours clock
(
s=d.Second * 6;
m=d.Minute * 6;
h=0.5 * ((d.Hour % 12) * 60 + d.Minute)
}
精彩评论