Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public static void Call()
Check(".Call $x.ToString()", Expression.Call(x, typeof(int).GetMethod("ToString", Type.EmptyTypes)));
Check(".Call $s.Substring($x)", Expression.Call(s, typeof(string).GetMethod("Substring", new[] { typeof(int) }), x));
Check(".Call $s.Substring(\\r\\n $x,\\r\\n $y)", Expression.Call(s, typeof(string).GetMethod("Substring", new[] { typeof(int), typeof(int) }), x, y));
Check(".Call System.TimeSpan.FromSeconds($d)", Expression.Call(null, typeof(TimeSpan).GetMethod("FromSeconds", new[] { typeof(int) }), d));
Check(".Call System.TimeSpan.FromSeconds($d)", Expression.Call(null, typeof(TimeSpan).GetMethod("FromSeconds", new[] { typeof(double) }), d));
}

[Fact]
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/Math.cs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@ public static long BigMul(long a, long b, out long low)
return (long)high - ((a >> 63) & b) - ((b >> 63) & a);
}

/// <summary>Produces the full product of two 64-bit numbers.</summary>
/// <param name="a">The first number to multiply.</param>
/// <param name="b">The second number to multiply.</param>
/// <returns>The full product of the specified numbers.</returns>
internal static Int128 BigMul(long a, long b)
{
long high = Math.BigMul(a, b, out long low);
return new Int128((ulong)high, (ulong)low);
}

public static double BitDecrement(double x)
{
ulong bits = BitConverter.DoubleToUInt64Bits(x);
Expand Down
189 changes: 189 additions & 0 deletions src/libraries/System.Private.CoreLib/src/System/TimeSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,195 @@ public TimeSpan Duration()

public override int GetHashCode() => _ticks.GetHashCode();

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TimeSpan FromUnits(long units, long ticksPerUnit, long minUnits, long maxUnits)
{
System.Diagnostics.Debug.Assert(minUnits < 0);
System.Diagnostics.Debug.Assert(maxUnits > 0);

if (units > maxUnits || units < minUnits)
{
ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong();
}
return TimeSpan.FromTicks(units * ticksPerUnit);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// days.
/// </summary>
/// <param name="days">Number of days.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of days.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromDays(int days) => FromUnits(days, TicksPerDay, MinDays, MaxDays);

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// days, hours, minutes, seconds, milliseconds, and microseconds.
/// </summary>
/// <param name="days">Number of days.</param>
/// <param name="hours">Number of hours.</param>
/// <param name="minutes">Number of minutes.</param>
/// <param name="seconds">Number of seconds.</param>
/// <param name="milliseconds">Number of milliseconds.</param>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of days, hours, minutes, seconds, milliseconds, and microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromDays(int days, int hours = 0, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0)
{
Int128 totalMicroseconds = Math.BigMul(days, MicrosecondsPerDay)
+ Math.BigMul(hours, MicrosecondsPerHour)
+ Math.BigMul(minutes, MicrosecondsPerMinute)
+ Math.BigMul(seconds, MicrosecondsPerSecond)
+ Math.BigMul(milliseconds, MicrosecondsPerMillisecond)
+ microseconds;

return FromMicroseconds(totalMicroseconds);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// hours.
/// </summary>
/// <param name="hours">Number of hours.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of hours.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromHours(int hours) => FromUnits(hours, TicksPerHour, MinHours, MaxHours);

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// hours, minutes, seconds, milliseconds, and microseconds.
/// </summary>
/// <param name="hours">Number of hours.</param>
/// <param name="minutes">Number of minutes.</param>
/// <param name="seconds">Number of seconds.</param>
/// <param name="milliseconds">Number of milliseconds.</param>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of hours, minutes, seconds, milliseconds, and microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromHours(int hours, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same general note for these ones. We have a few lines of code to replicate that would allow completely skipping more expensive parts of the FromDays algorithm, the savings increase the lower the unit of time we have is.

It's a bit more code, but given these are in part there for perf and accuracy, I personally think that's fine. It might be different if we could constant fold Int128 arithmetic, but that's not something the JIT is likely to get in the .NET 9 timeframe.

{
Int128 totalMicroseconds = Math.BigMul(hours, MicrosecondsPerHour)
+ Math.BigMul(minutes, MicrosecondsPerMinute)
+ Math.BigMul(seconds, MicrosecondsPerSecond)
+ Math.BigMul(milliseconds, MicrosecondsPerMillisecond)
+ microseconds;

return FromMicroseconds(totalMicroseconds);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// minutes.
/// </summary>
/// <param name="minutes">Number of minutes.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of minutes.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromMinutes(long minutes) => FromUnits(minutes, TicksPerMinute, MinMinutes, MaxMinutes);

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// minutes, seconds, milliseconds, and microseconds.
/// </summary>
/// <param name="minutes">Number of minutes.</param>
/// <param name="seconds">Number of seconds.</param>
/// <param name="milliseconds">Number of milliseconds.</param>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of minutes, seconds, milliseconds, and microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromMinutes(long minutes, long seconds = 0, long milliseconds = 0, long microseconds = 0)
{
Int128 totalMicroseconds = Math.BigMul(minutes, MicrosecondsPerMinute)
+ Math.BigMul(seconds, MicrosecondsPerSecond)
+ Math.BigMul(milliseconds, MicrosecondsPerMillisecond)
+ microseconds;

return FromMicroseconds(totalMicroseconds);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// seconds.
/// </summary>
/// <param name="seconds">Number of seconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of seconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromSeconds(long seconds) => FromUnits(seconds, TicksPerSecond, MinSeconds, MaxSeconds);

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// seconds, milliseconds, and microseconds.
/// </summary>
/// <param name="seconds">Number of seconds.</param>
/// <param name="milliseconds">Number of milliseconds.</param>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of seconds, milliseconds, and microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromSeconds(long seconds, long milliseconds = 0, long microseconds = 0)
{
Int128 totalMicroseconds = Math.BigMul(seconds, MicrosecondsPerSecond)
+ Math.BigMul(milliseconds, MicrosecondsPerMillisecond)
+ microseconds;

return FromMicroseconds(totalMicroseconds);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// milliseconds, and microseconds.
/// </summary>
/// <param name="milliseconds">Number of milliseconds.</param>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of milliseconds, and microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromMilliseconds(long milliseconds, long microseconds = 0)
{
Int128 totalMicroseconds = Math.BigMul(milliseconds, MicrosecondsPerMillisecond)
+ microseconds;

return FromMicroseconds(totalMicroseconds);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TimeSpan FromMicroseconds(Int128 microseconds)
{
if ((microseconds > MaxMicroseconds) || (microseconds < MinMicroseconds))
{
ThrowHelper.ThrowArgumentOutOfRange_TimeSpanTooLong();
}
long ticks = (long)microseconds * TicksPerMicrosecond;
return TimeSpan.FromTicks(ticks);
}

/// <summary>
/// Initializes a new instance of the <see cref="TimeSpan"/> structure to a specified number of
/// microseconds.
/// </summary>
/// <param name="microseconds">Number of microseconds.</param>
/// <returns>Returns a <see cref="TimeSpan"/> that represents a specified number of microseconds.</returns>
/// <exception cref="ArgumentOutOfRangeException">
/// The parameters specify a <see cref="TimeSpan"/> value less than <see cref="MinValue"/> or greater than <see cref="MaxValue"/>
/// </exception>
public static TimeSpan FromMicroseconds(long microseconds) => FromUnits(microseconds, TicksPerMicrosecond, MinMicroseconds, MaxMicroseconds);

public static TimeSpan FromHours(double value) => Interval(value, TicksPerHour);

private static TimeSpan Interval(double value, double scale)
Expand Down
10 changes: 10 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5613,6 +5613,16 @@ protected TimeProvider() { }
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? value) { throw null; }
public bool Equals(System.TimeSpan obj) { throw null; }
public static bool Equals(System.TimeSpan t1, System.TimeSpan t2) { throw null; }
public static System.TimeSpan FromDays(int days) { throw null; }
public static System.TimeSpan FromDays(int days, int hours = 0, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0) { throw null; }
public static System.TimeSpan FromHours(int hours) { throw null; }
public static System.TimeSpan FromHours(int hours, long minutes = 0, long seconds = 0, long milliseconds = 0, long microseconds = 0) { throw null; }
public static System.TimeSpan FromMinutes(long minutes) { throw null; }
public static System.TimeSpan FromMinutes(long minutes, long seconds = 0, long milliseconds = 0, long microseconds = 0) { throw null; }
public static System.TimeSpan FromSeconds(long seconds) { throw null; }
public static System.TimeSpan FromSeconds(long seconds, long milliseconds = 0, long microseconds = 0) { throw null; }
public static System.TimeSpan FromMilliseconds(long milliseconds, long microseconds = 0) { throw null; }
public static System.TimeSpan FromMicroseconds(long microseconds) { throw null; }
public static System.TimeSpan FromDays(double value) { throw null; }
public static System.TimeSpan FromHours(double value) { throw null; }
public static System.TimeSpan FromMicroseconds(double value) { throw null; }
Expand Down
Loading