Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions api/CourseRegistration.Application/Services/IMathService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CourseRegistration.Application.Services;

/// <summary>
/// Interface for mathematical utility operations.
/// </summary>
public interface IMathService
{
/// <summary>
/// Calculates the factorial of a non-negative integer.
/// </summary>
/// <param name="n">A non-negative integer whose factorial is to be computed.</param>
/// <returns>The factorial of <paramref name="n"/> as a <see cref="long"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="n"/> is negative.</exception>
/// <exception cref="OverflowException">Thrown when the result exceeds <see cref="long.MaxValue"/>.</exception>
long CalculateFactorial(int n);
}
34 changes: 34 additions & 0 deletions api/CourseRegistration.Application/Services/MathService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
namespace CourseRegistration.Application.Services;

/// <summary>
/// Provides mathematical utility operations such as factorial calculation.
/// </summary>
public class MathService : IMathService
{
// 20! is the largest factorial that fits in a long (Int64)
private const int MaxFactorialInput = 20;

/// <summary>
/// Calculates the factorial of a non-negative integer iteratively.
/// </summary>
/// <param name="n">A non-negative integer (0 – 20) whose factorial is to be computed.</param>
/// <returns>The factorial of <paramref name="n"/>.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="n"/> is negative.</exception>
/// <exception cref="OverflowException">Thrown when <paramref name="n"/> exceeds 20 and the result would overflow <see cref="long"/>.</exception>
public long CalculateFactorial(int n)
{
if (n < 0)
throw new ArgumentOutOfRangeException(nameof(n), "Factorial is not defined for negative numbers.");

if (n > MaxFactorialInput)
throw new OverflowException($"Input {n} is too large; factorial exceeds the range of a 64-bit integer (max supported: {MaxFactorialInput}).");

long result = 1;
for (int i = 2; i <= n; i++)
{
result *= i;
}

return result;
}
}
75 changes: 75 additions & 0 deletions api/CourseRegistration.Tests/Services/MathServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Xunit;
using CourseRegistration.Application.Services;

namespace CourseRegistration.Tests.Services;

/// <summary>
/// Unit tests for <see cref="MathService"/> factorial calculation.
/// </summary>
public class MathServiceTests
{
private readonly MathService _mathService = new();

// ── Base cases ─────────────────────────────────────────────────────────────

[Fact]
public void CalculateFactorial_Zero_ReturnsOne()
{
// Arrange / Act
var result = _mathService.CalculateFactorial(0);

// Assert
Assert.Equal(1L, result);
}

[Fact]
public void CalculateFactorial_One_ReturnsOne()
{
// Arrange / Act
var result = _mathService.CalculateFactorial(1);

// Assert
Assert.Equal(1L, result);
}

// ── Typical positive values ────────────────────────────────────────────────

[Theory]
[InlineData(2, 2L)]
[InlineData(3, 6L)]
[InlineData(4, 24L)]
[InlineData(5, 120L)]
[InlineData(10, 3628800L)]
[InlineData(15, 1307674368000L)]
[InlineData(20, 2432902008176640000L)]
public void CalculateFactorial_PositiveInput_ReturnsCorrectResult(int n, long expected)
{
// Arrange / Act
var result = _mathService.CalculateFactorial(n);

// Assert
Assert.Equal(expected, result);
}

// ── Error paths ────────────────────────────────────────────────────────────

[Theory]
[InlineData(-1)]
[InlineData(-100)]
[InlineData(int.MinValue)]
public void CalculateFactorial_NegativeInputs_ThrowsArgumentOutOfRangeException(int n)
{
// Arrange / Act / Assert
Assert.Throws<ArgumentOutOfRangeException>(() => _mathService.CalculateFactorial(n));
}

[Theory]
[InlineData(21)]
[InlineData(100)]
[InlineData(int.MaxValue)]
public void CalculateFactorial_LargeInputs_ThrowsOverflowException(int n)
{
// Arrange / Act / Assert
Assert.Throws<OverflowException>(() => _mathService.CalculateFactorial(n));
}
}
Loading