From 5cb5befba67ac9def750e1be56f3b41aa37d6c6e Mon Sep 17 00:00:00 2001 From: khanhkhanhlele Date: Tue, 11 Nov 2025 10:07:52 +0700 Subject: [PATCH 1/2] Add algorithm SinFunction --- Algorithms.Tests/Numeric/SinTests.cs | 115 +++++++++++++++++++++++++++ Algorithms/Numeric/Sin.cs | 66 +++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 Algorithms.Tests/Numeric/SinTests.cs create mode 100644 Algorithms/Numeric/Sin.cs diff --git a/Algorithms.Tests/Numeric/SinTests.cs b/Algorithms.Tests/Numeric/SinTests.cs new file mode 100644 index 00000000..39827670 --- /dev/null +++ b/Algorithms.Tests/Numeric/SinTests.cs @@ -0,0 +1,115 @@ +using System; +using Algorithms.Numeric; +using NUnit.Framework; + +namespace Algorithms.Tests.Numeric; + +/// +/// Tests for the Sin class, which implements sine calculation via Maclaurin series. +/// +public static class SinTests +{ + // A tolerance level for comparing the series approximation with the built-in Math.Sin. + private const double Tolerance = 1e-6; + + /// + /// Tests the sine calculation for various common trigonometric angles + /// using the default number of terms (now 15). + /// + /// The angle in radians. + [TestCase(0.0)] + [TestCase(Math.PI / 6)] // sin(30 deg) = 0.5 + [TestCase(Math.PI / 2)] // sin(90 deg) = 1.0 + [TestCase(Math.PI)] // sin(180 deg) = 0.0 + [TestCase(3 * Math.PI / 2)] // sin(270 deg) = -1.0 + [TestCase(2 * Math.PI)] // sin(360 deg) = 0.0 + [TestCase(1.5)] // Arbitrary value + public static void GetsSineValueWithDefaultTerms(double x) + { + // Arrange + var expected = Math.Sin(x); + + // Act + var result = Sin.Calculate(x); + + // Assert + // Check if the result is close to the expected value within the defined tolerance. + Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); + } + + /// + /// Tests if the angle reduction logic works correctly by passing a large angle. + /// The angle (20*PI + PI/2) should be equivalent to PI/2, where sin(x) = 1. + /// + [Test] + public static void GetsSineValueWithLargeAngleReduction() + { + // Arrange: Angle significantly larger than 2*PI + double largeAngle = 20 * Math.PI + (Math.PI / 2); // Should reduce to PI/2 + + // Act + var result = Sin.Calculate(largeAngle); + + // Assert + // Expected value is 1.0 (sin(PI/2)) + Assert.That(result, Is.EqualTo(1.0).Within(Tolerance)); + } + + /// + /// Tests the sine calculation for negative angles. + /// + /// The negative angle in radians. + [TestCase(-Math.PI / 2)] // sin(-90 deg) = -1.0 + [TestCase(-Math.PI / 4)] // sin(-45 deg) = -0.707... + public static void GetsSineValueForNegativeAngle(double x) + { + // Arrange + var expected = Math.Sin(x); + + // Act + var result = Sin.Calculate(x); + + // Assert + Assert.That(result, Is.EqualTo(expected).Within(Tolerance)); + } + + /// + /// Tests the precision when only a small number of terms is used (e.g., 1 term). + /// sin(x) approx x for small x. + /// + [Test] + public static void GetsSineValueWithMinimalTerms() + { + // Arrange + double x = 0.1; // A small angle where the first term dominates + int terms = 1; // Only the first term (x) is calculated + + // Act + var result = Sin.Calculate(x, terms); + + // Assert + // Result should be approximately Math.Sin(0.1). + // The error of the one-term approximation (x) is roughly x^3/3! (approx 1.66e-4 for x=0.1), + // so we use a looser tolerance (2e-4) to ensure the test passes reliably. + Assert.That(result, Is.EqualTo(Math.Sin(x)).Within(2e-4)); // Tolerance increased to 2e-4 + } + + /// + /// Tests that calculating the sine with a non-positive number of terms throws an ArgumentException. + /// + /// The invalid number of terms. + [TestCase(0)] + [TestCase(-1)] + [TestCase(-10)] + public static void ThrowsExceptionForInvalidTerms(int numTerms) + { + // Arrange + double x = 1.0; + + // Act + void Act() => Sin.Calculate(x, numTerms); + + // Assert + _ = Assert.Throws(Act); + } +} \ No newline at end of file diff --git a/Algorithms/Numeric/Sin.cs b/Algorithms/Numeric/Sin.cs new file mode 100644 index 00000000..5c0b80a4 --- /dev/null +++ b/Algorithms/Numeric/Sin.cs @@ -0,0 +1,66 @@ +using System; +using System.Numerics; + +namespace Algorithms.Numeric; + +/// +/// Calculates the sine function (sin(x)) using the Maclaurin series expansion (Taylor series centered at 0). +/// sin(x) = x - x^3/3! + x^5/5! - x^7/7! + ... +/// +public static class Sin +{ + /// + /// Calculates the sine of an angle x using a finite number of terms from the Maclaurin series. + /// Note: For high precision, use Math.Sin(). This method is for demonstrating the series expansion. + /// + /// The angle in radians. + /// The number of terms (odd powers) to include in the series. Default is 20 for required precision. + /// The approximate value of sin(x). + public static double Calculate(double x, int terms = 20) // Increased default from 15 to 20 for higher precision + { + if (terms <= 0) + { + throw new ArgumentException("The number of terms must be a positive integer."); + } + + // Best practice for series convergence: reduce the angle to the range [-2*PI, 2*PI]. + // The series converges for all x, but convergence is faster near 0. + x %= 2 * Math.PI; + + double result = 0.0; + double xSquared = x * x; + + // currentPower: Stores x^k + double currentPower = x; // Starts at x^1 + + // currentFactorial: Stores k! + double currentFactorial = 1.0; // Starts at 1! + + int sign = 1; // Starts positive + + // k is the exponent/factorial index (1, 3, 5, 7, ...) + for (int i = 0, k = 1; i < terms; i++, k += 2) + { + if (k > 1) + { + // Iteratively update the components for the next term: + // Current exponent k is (k-2) + 2. + // x^k = x^(k-2) * x^2 + currentPower *= xSquared; + + // k! = (k-2)! * (k-1) * k + // Example: 5! = 3! * 4 * 5 + currentFactorial *= (k - 1) * k; + } + + // Calculate the term: (sign) * x^k / k! + double term = currentPower / currentFactorial; + + result += sign * term; + + sign *= -1; // Alternate the sign for the next term + } + + return result; + } +} From 6164b17ef3ed41e2a88fbed3a4a1ac9db3ca7f00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?= <55955273+khanhkhanhlele@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:12:22 +0700 Subject: [PATCH 2/2] Update Algorithms.Tests/Numeric/SinTests.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Algorithms.Tests/Numeric/SinTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Algorithms.Tests/Numeric/SinTests.cs b/Algorithms.Tests/Numeric/SinTests.cs index 39827670..cfb44fea 100644 --- a/Algorithms.Tests/Numeric/SinTests.cs +++ b/Algorithms.Tests/Numeric/SinTests.cs @@ -14,7 +14,7 @@ public static class SinTests /// /// Tests the sine calculation for various common trigonometric angles - /// using the default number of terms (now 15). + /// using the default number of terms (now 20). /// /// The angle in radians. [TestCase(0.0)]