├── .gitignore ├── Decimal2D.Tests ├── Decimal2D.Tests.csproj └── Point2DTests.cs ├── Decimal2D ├── Arc2D.cs ├── Circle2D.cs ├── Decimal2D.csproj ├── LineSeg2D.cs ├── Point2D.cs ├── RightTriangle.cs ├── RightTriangleAbstract.cs ├── Transform2D.cs └── Vector2D.cs ├── DecimalEx.Tests ├── DecimalEx.Tests.csproj ├── DecimalExTests │ ├── AGMeanTests.cs │ ├── AverageTests.cs │ ├── CeilingTests.cs │ ├── ExpTests.cs │ ├── FactorialTests.cs │ ├── FloorTests.cs │ ├── GCFTests.cs │ ├── GetDecimalPlacesTests.cs │ ├── LogTests.cs │ ├── PowTests.cs │ ├── RemainderTests.cs │ ├── SolveQuadraticTests.cs │ └── SqrtTests.cs ├── DecimalExTrigTests │ ├── ACosTests.cs │ ├── ASinTests.cs │ ├── ATan2Tests.cs │ ├── ATanTests.cs │ ├── CosTests.cs │ ├── NormalizeAngleTests.cs │ └── SinTests.cs ├── ExtensionsTest │ ├── InRangeTests.cs │ └── RoundFromZeroTests.cs ├── Helper.cs ├── MatrixTests.cs └── TransformationMatrixBaseTests.cs ├── DecimalEx ├── DecimalEx.cs ├── DecimalEx.csproj ├── DecimalExConstants.cs ├── DecimalExTrig.cs ├── Extensions.cs ├── Helper.cs ├── ITransformable.cs ├── Matrix.cs └── TransformationMatrixBase.cs ├── DecimalMath.sln ├── LICENSE ├── README.md └── packaging ├── Build Decimal2D Package.bat └── Build DecimalEx Package.bat /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUnit 46 | *.VisualState.xml 47 | TestResult.xml 48 | nunit-*.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | -------------------------------------------------------------------------------- /Decimal2D.Tests/Decimal2D.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | Debug;Release;DebugWithMessages 6 | AnyCPU 7 | 8 | 9 | 10 | true 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Decimal2D.Tests/Point2DTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace Decimal2DTests 6 | { 7 | public class Point2DTests 8 | { 9 | [TestCase(1, 3)] 10 | public void TestConstructor(int x, int y) 11 | { 12 | var p = new Point2D(x, y); 13 | Assert.AreEqual(p.X, x); 14 | Assert.AreEqual(p.Y, y); 15 | } 16 | 17 | [TestCase(0, 3, true)] 18 | [TestCase(1, 0, true)] 19 | [TestCase(1, 3, false)] 20 | public void TestOnAnAxis(int x, int y, bool isOnAnAxis) 21 | { 22 | var p = new Point2D(x, y); 23 | Assert.That(p.OnAnAxis, Is.EqualTo(isOnAnAxis)); 24 | } 25 | 26 | [TestCase(1, 3, 1)] 27 | [TestCase(-1, 3, 2)] 28 | [TestCase(-1, -3, 3)] 29 | [TestCase(1, -3, 4)] 30 | public void TestQuadrant(int x, int y, int quadrant) 31 | { 32 | var p = new Point2D(x, y); 33 | Assert.That(p.Quadrant, Is.EqualTo(quadrant)); 34 | } 35 | [TestCase(0, 3)] 36 | [TestCase(1, 0)] 37 | public void TestQuadrantOnAxis(int x, int y) 38 | { 39 | var p = new Point2D(x, y); 40 | Assert.Throws(() => {var q = p.Quadrant;}); 41 | } 42 | 43 | [TestCase("0", "3", "0", "3", "0")] 44 | [TestCase("0", "0", "0", "1", "1")] 45 | [TestCase("0", "0", "1", "0", "1")] 46 | public void TestDistanceTo(string x1, string y1, string x2, string y2, string distance) 47 | { 48 | var p1 = new Point2D(Convert.ToDecimal(x1), Convert.ToDecimal(y1)); 49 | var p2 = new Point2D(Convert.ToDecimal(x2), Convert.ToDecimal(y2)); 50 | Assert.AreEqual(p1.DistanceTo(p2), Convert.ToDecimal(distance)); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Decimal2D/Arc2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace DecimalMath 5 | { 6 | [DebuggerDisplay("Center: (X = {Circle.X} Y = {Circle.Y}) Radius: {Circle.Radius} Angle: {StartAngle} to {EndAngle}")] 7 | public struct Arc2D: ITransformable 8 | { 9 | 10 | public Circle2D Circle; 11 | private decimal _startAngle; 12 | 13 | private decimal _endAngle; 14 | /// 15 | /// Creates and arc with the given circle and start/end angle. 16 | /// 17 | /// The circle that the arc lies on. 18 | /// Start angle. 19 | /// End angle. 20 | public Arc2D(Circle2D c, decimal startAngle, decimal endAngle) 21 | { 22 | this.Circle = c; 23 | _startAngle = startAngle; 24 | _endAngle = endAngle; 25 | } 26 | /// 27 | /// Creates an arc centered at (0,0) with the given radius and start/end angle. 28 | /// 29 | /// Radius of the arc. 30 | /// Start angle. 31 | /// End angle. 32 | public Arc2D(decimal radius, decimal startAngle, decimal endAngle) 33 | { 34 | this.Circle = new Circle2D(0, 0, radius); 35 | _startAngle = startAngle; 36 | _endAngle = endAngle; 37 | } 38 | /// 39 | /// Creates an arc centered on the given point with the specified radius and start/end angle. 40 | /// 41 | /// Center of the arc. 42 | /// Radius of the arc. 43 | /// Start angle. 44 | /// End angle. 45 | public Arc2D(Point2D center, decimal radius, decimal startAngle, decimal endAngle) 46 | { 47 | this.Circle = new Circle2D(center, radius); 48 | _startAngle = startAngle; 49 | _endAngle = endAngle; 50 | } 51 | /// 52 | /// Creates an arc on the given circle with the start angle projected through 53 | /// the start point and the end angle projected through the end point. 54 | /// 55 | /// Circle that the arc lies on. 56 | /// Point that start angle is projected through. 57 | /// Point that end angle is projected through. 58 | 59 | public Arc2D(Circle2D c, Point2D startPoint, Point2D endPoint) 60 | { 61 | this.Circle = c; 62 | _startAngle = c.AngleThroughPoint(startPoint); 63 | _endAngle = c.AngleThroughPoint(endPoint); 64 | 65 | } 66 | /// 67 | /// Creates an arc with the given center, start, and end points. Performs a check 68 | /// at the given precision to make sure that both the start and end points are 69 | /// the same distance from the center, throwing an exception if they're not. Note 70 | /// that because radius is generated from start point, the resultant arc's start 71 | /// point will be more accurate than the end point. 72 | /// 73 | /// Center of the arc. 74 | /// Start point of the arc. 75 | /// Point that end angle is projected through. 76 | /// Precision at which to check the distance of the endpoints to the center. 77 | /// A negative value will perform an exact check. 78 | 79 | public Arc2D(Point2D center, Point2D startPoint, Point2D endPoint, int decimals = 15) 80 | : this(center, center.DistanceTo(startPoint), 0, 0) 81 | { 82 | 83 | 84 | if ((decimals < 0 && center.DistanceTo(endPoint) != Radius) || (decimals >= 0 && center.DistanceTo(endPoint).RoundFromZero(decimals) != Radius.RoundFromZero(decimals))) 85 | { 86 | throw new Exception("Can't create arc from center and endpoints because endpoints have different distances from the center!"); 87 | 88 | } 89 | 90 | _startAngle = Circle.AngleThroughPoint(startPoint); 91 | _endAngle = Circle.AngleThroughPoint(endPoint); 92 | 93 | } 94 | /// 95 | /// Creates an arc based on another arc, but with a different radius. 96 | /// 97 | /// An arc from which to take the center and start/end angles. 98 | /// A radius for the new arc. 99 | public Arc2D(Arc2D a, decimal radius) 100 | { 101 | Circle = new Circle2D(a.Center, radius); 102 | _startAngle = a._startAngle; 103 | _endAngle = a._endAngle; 104 | } 105 | 106 | /// 107 | /// Creates an arc using three points, two of which are endpoints and 108 | /// one of which is designated as on the arc but not an endpoint. 109 | /// 110 | /// One endpoint of the arc. 111 | /// One endpoint of the arc. 112 | /// A point on the arc which is not an endpoint. 113 | /// 114 | public static Arc2D FromPointsOnArc(Point2D endPointA, Point2D endPointB, Point2D pointOnArc) 115 | { 116 | 117 | Arc2D a = default(Arc2D); 118 | 119 | a.Circle = new Circle2D(endPointA, pointOnArc, endPointB); 120 | 121 | decimal angleA = 0m; 122 | decimal angleOnArc = 0m; 123 | decimal angleB = 0m; 124 | 125 | angleA = a.Circle.AngleThroughPoint(endPointA); 126 | angleOnArc = a.Circle.AngleThroughPoint(pointOnArc); 127 | angleB = a.Circle.AngleThroughPoint(endPointB); 128 | 129 | if (angleA == angleB) 130 | throw new Exception("End points are too close together or equal! Can't create arc."); 131 | if (angleOnArc == angleA || angleOnArc == angleB) 132 | throw new Exception("Point on arc is too close or equal to one of the end points! Can't create arc."); 133 | 134 | // We want angle B to be greater than angle A so that 135 | // if the angle on the arc is between them then A is 136 | // the start otherwise B is for certain the start. 137 | if (angleB < angleA) 138 | { 139 | Helper.Swap(ref angleA, ref angleB); 140 | Helper.Swap(ref endPointA, ref endPointB); 141 | } 142 | 143 | if (angleA < angleOnArc && angleOnArc < angleB) 144 | { 145 | a._startAngle = angleA; 146 | a._endAngle = angleB; 147 | } 148 | else 149 | { 150 | a._startAngle = angleB; 151 | a._endAngle = angleA; 152 | } 153 | 154 | return a; 155 | 156 | } 157 | 158 | public static Arc2D operator +(Arc2D arc, Vector2D vector) 159 | { 160 | return new Arc2D(arc.Circle + vector, arc._startAngle, arc._endAngle); 161 | } 162 | 163 | public static bool operator ==(Arc2D objA, Arc2D objB) 164 | { 165 | return objA.Circle == objB.Circle && objA._startAngle == objB._startAngle && objA._endAngle == objB._endAngle; 166 | } 167 | public static bool operator !=(Arc2D objA, Arc2D objB) 168 | { 169 | return objA.Circle != objB.Circle || objA._startAngle != objB._startAngle || objA._endAngle != objB._endAngle; 170 | } 171 | 172 | /// 173 | /// Gets or sets the center of the arc as an XY point. 174 | /// 175 | public Point2D Center 176 | { 177 | [DebuggerStepThrough()] 178 | get { return Circle.Center; } 179 | [DebuggerStepThrough()] 180 | set { Circle.Center = value; } 181 | } 182 | /// Radius of the arc. 183 | public decimal Radius 184 | { 185 | [DebuggerStepThrough()] 186 | get { return Circle.Radius; } 187 | [DebuggerStepThrough()] 188 | set { Circle.Radius = value; } 189 | } 190 | /// 191 | /// The start angle of the arc in degrees. Arc starts at this angle 192 | /// and extends counter-clockwise toward the end angle. 193 | /// Normalized to be >= 0 and < 360. 194 | /// 195 | public decimal StartAngle 196 | { 197 | [DebuggerStepThrough()] 198 | get { return _startAngle; } 199 | [DebuggerStepThrough()] 200 | set { _startAngle = DecimalEx.NormalizeAngleDeg(value); } 201 | } 202 | /// 203 | /// The end angle of the arc in degrees. Arc starts at the start 204 | /// angle and extends counter-clockwise toward this angle. 205 | /// Normalized to be >= 0 and < 360. 206 | /// 207 | public decimal EndAngle 208 | { 209 | [DebuggerStepThrough()] 210 | get { return _endAngle; } 211 | [DebuggerStepThrough()] 212 | set { _endAngle = DecimalEx.NormalizeAngleDeg(value); } 213 | } 214 | /// 215 | /// The angle in degrees that bisects the arc. 216 | /// 217 | public decimal BisectingAngle 218 | { 219 | 220 | get 221 | { 222 | if (_startAngle <= _endAngle) 223 | { 224 | return (_startAngle + _endAngle) / 2m; 225 | } 226 | else 227 | { 228 | return DecimalEx.NormalizeAngleDeg((_startAngle + (_endAngle + 360m)) / 2m); 229 | } 230 | 231 | } 232 | } 233 | /// 234 | /// Determines whether or not the given angle lies on the arc. 235 | /// 236 | /// The angle in question. 237 | public bool IsAngleOnArc(decimal angle) 238 | { 239 | 240 | angle = DecimalEx.NormalizeAngleDeg(angle); 241 | 242 | if (_startAngle <= _endAngle) 243 | { 244 | return (_startAngle <= angle) && (angle <= _endAngle); 245 | } 246 | else 247 | { 248 | return (angle > _startAngle) || (angle < _endAngle); 249 | } 250 | 251 | } 252 | 253 | /// 254 | /// Returns the sagitta (the distance between the highest point of 255 | /// the arc and the center of the chord) for a circle with the 256 | /// supplied radius the chord length. 257 | /// 258 | public decimal Sagitta 259 | { 260 | 261 | get 262 | { 263 | System.Diagnostics.Debugger.Break(); 264 | // and test 265 | 266 | decimal ret = Circle2D.GetSagitta(Circle.Radius, GetChord().Length); 267 | 268 | if (CentralAngle > 180) 269 | ret = 2 * Circle.Radius - ret; 270 | 271 | return ret; 272 | 273 | } 274 | } 275 | 276 | /// 277 | /// Gets the central angle of the arc, i.e. the total angle covered by the arc. 278 | /// 279 | /// http://www.mathopenref.com/circlecentral.html 280 | public decimal CentralAngle 281 | { 282 | get { return _endAngle - _startAngle; } 283 | } 284 | /// 285 | /// Gets the length of the arc. 286 | /// 287 | /// http://www.mathopenref.com/arclength.html 288 | public decimal ArcLength 289 | { 290 | get { return Circle.Radius * ((2m * DecimalEx.Pi * CentralAngle) / 360m); } 291 | } 292 | 293 | /// 294 | /// Gets or sets the point on the circle at the start angle. Note 295 | /// that when setting the point, the angle projected from the center 296 | /// of the arc through the specified point will be used if the point 297 | /// is not on the circle. 298 | /// 299 | public Point2D StartPt 300 | { 301 | get { return Circle.PointAt(_startAngle); } 302 | set { _startAngle = Circle.AngleThroughPoint(value); } 303 | } 304 | /// 305 | /// Gets or sets the point on the circle at the ending angle. Note 306 | /// that when setting the point, the angle projected from the center 307 | /// of the arc through the specified point will be used if the point 308 | /// is not on the circle. 309 | /// 310 | public Point2D EndPt 311 | { 312 | get { return Circle.PointAt(_endAngle); } 313 | set { _endAngle = Circle.AngleThroughPoint(value); } 314 | } 315 | /// 316 | /// Gets the middle point on the arc, i.e. the point on the circle 317 | /// at the bisecting angle. 318 | /// 319 | public Point2D MidPt 320 | { 321 | get { return Circle.PointAt(BisectingAngle); } 322 | } 323 | 324 | /// 325 | /// Gets a tangent line at the start angle. 326 | /// See . 327 | /// 328 | public LineSeg2D TangentAtStart(decimal length, bool clockwise) 329 | { 330 | return Circle.TangentAt(_startAngle, length, clockwise); 331 | } 332 | /// 333 | /// Gets a tangent line at the end angle. 334 | /// See . 335 | /// 336 | public LineSeg2D TangentAtEnd(decimal length, bool clockwise) 337 | { 338 | return Circle.TangentAt(_endAngle, length, clockwise); 339 | } 340 | 341 | /// 342 | /// Gets the chord where the first point is the start point of the arc and 343 | /// the second point is the end point of the arc. 344 | /// 345 | public LineSeg2D GetChord() 346 | { 347 | return new LineSeg2D(StartPt, EndPt); 348 | } 349 | 350 | public Arc2D GrowRadius(decimal amount) 351 | { 352 | 353 | return new Arc2D(Circle.Grow(amount), _startAngle, _endAngle); 354 | 355 | } 356 | public Arc2D ShrinkRadius(decimal amount) 357 | { 358 | 359 | return new Arc2D(Circle.Shrink(amount), _startAngle, _endAngle); 360 | 361 | } 362 | 363 | /// 364 | /// Attempts to transform this arc by applying the transform to the 365 | /// center, start, and end points. If the resulting points do not 366 | /// form an arc, for example if they are skewed, then an exception 367 | /// is thrown. 368 | /// 369 | public Arc2D Transform(Transform2D matrix) 370 | { 371 | var centPt = matrix.Transform(Center); 372 | var startPt = matrix.Transform(StartPt); 373 | var midPt = matrix.Transform(MidPt); 374 | var endPt = matrix.Transform(EndPt); 375 | 376 | var newA = new Arc2D(centPt, startPt, endPt); 377 | 378 | // Detect translations that would alter the shape so it's no longer an arc 379 | if (centPt.DistanceTo(midPt).RoundFromZero(15) != newA.Radius.RoundFromZero(15)) 380 | { 381 | throw new Exception("Can't transform arc. Distance from transformed midpoint to center does not equal the radius of the arc!"); 382 | } 383 | 384 | // Check if the start and end angles were flipped in the transform 385 | if (!newA.IsAngleOnArc(newA.Circle.AngleThroughPoint(midPt))) 386 | { 387 | var tmp = newA.StartAngle; 388 | newA.StartAngle = newA.EndAngle; 389 | newA.EndAngle = tmp; 390 | } 391 | 392 | return newA; 393 | } 394 | 395 | public static Arc2D FromLinesCircle(LineSeg2D line1, LineSeg2D line2, Circle2D c, int decimals = -1) 396 | { 397 | 398 | Point2D[] intersects1 = null; 399 | Point2D[] intersects2 = null; 400 | 401 | intersects1 = c.GetIntersect(line1, decimals); 402 | intersects2 = c.GetIntersect(line2, decimals); 403 | 404 | throw new NotImplementedException(); 405 | } 406 | 407 | public string ToAutoCADCmd(bool linefeedTerminate = true) 408 | { 409 | 410 | return string.Format("_arc {0},{1} c {2},{3} {4},{5}{6}", StartPt.X, StartPt.Y, Circle.Center.X, Circle.Center.Y, EndPt.X, EndPt.Y, (linefeedTerminate ? "\r\n" : " ")); 411 | 412 | } 413 | 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /Decimal2D/Decimal2D.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Portable math support for Decimal-based geometric and trigonometric calculations. 6 | Nathan P Jones 7 | DecimalMath.Decimal2D 8 | Copyright 2015-2019 - Nathan P Jones 9 | LICENSE 10 | https://github.com/nathanpjones/DecimalMath 11 | c# decimal math geometry trigonometry 12 | 1.0.1.0 13 | 1.0.1.0 14 | $(AssemblyVersion) 15 | 16 | false 17 | https://github.com/nathanpjones/DecimalMath.git 18 | git 19 | Debug;Release;DebugWithMessages 20 | AnyCPU 21 | 22 | 23 | 24 | true 25 | false 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | True 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Decimal2D/Point2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace DecimalMath 5 | { 6 | [DebuggerDisplay("X = {X} Y = {Y}")] 7 | public struct Point2D: ITransformable 8 | { 9 | public readonly decimal X; 10 | public readonly decimal Y; 11 | 12 | public static readonly Point2D Origin = new Point2D(0, 0); 13 | 14 | public Point2D(decimal x, decimal y) 15 | { 16 | X = x; 17 | Y = y; 18 | } 19 | 20 | /// 21 | /// Gets whether or not a point is on one or both axes. 22 | /// 23 | public bool OnAnAxis 24 | { 25 | get { return (X == 0) || (Y == 0); } 26 | } 27 | /// 28 | /// Gets which quadrant the point is in as long as it's actually 29 | /// in a quadrant. 30 | /// 31 | /// See http://mathworld.wolfram.com/Quadrant.html 32 | /// The point is on one of the axes or is the origin. 33 | public int Quadrant 34 | { 35 | get 36 | { 37 | if (OnAnAxis) 38 | { 39 | throw new Exception("Point lies on an axis or axes and therefore is not in one of the quadrants!"); 40 | } 41 | if (Y > 0) 42 | { 43 | return X < 0 ? 2 : 1; 44 | } 45 | else 46 | { 47 | return X < 0 ? 3 : 4; 48 | } 49 | } 50 | } 51 | 52 | public decimal DistanceTo(Point2D pt) 53 | { 54 | 55 | return DistanceTo(pt.X, pt.Y); 56 | 57 | } 58 | public decimal DistanceTo(decimal x, decimal y) 59 | { 60 | var xDist = this.X - x; 61 | var yDist = this.Y - y; 62 | 63 | return DecimalEx.Sqrt(xDist * xDist + yDist * yDist); 64 | } 65 | public decimal DistanceTo(Circle2D c) 66 | { 67 | return c.DistanceTo(this); 68 | } 69 | public decimal DistanceTo(LineSeg2D l, bool treatLineSegmentAsLine = false) 70 | { 71 | return l.DistanceTo(this, treatLineSegmentAsLine); 72 | } 73 | public Vector2D GetVectorTo(Point2D other) 74 | { 75 | return new Vector2D(this, other); 76 | } 77 | 78 | public static bool PointsAreColinear(decimal x1, decimal y1, decimal x2, decimal y2, decimal x3, decimal y3) 79 | { 80 | var l1 = new LineSeg2D(x1, y1, x2, y2); 81 | var l2 = new LineSeg2D(x2, y2, x3, y3); 82 | var intersect = l1.GetIntersect(l2, true); 83 | 84 | // Points won't have an intersection if they are parallel. If they 85 | // are parallel and share a point, then they are colinear. 86 | return (!intersect.HasValue); 87 | } 88 | public static bool PointsAreColinear(Point2D pt1, Point2D pt2, Point2D pt3) 89 | { 90 | return PointsAreColinear(pt1.X, pt1.Y, pt2.X, pt2.Y, pt3.X, pt3.Y); 91 | } 92 | 93 | /// 94 | /// Offsets a point by the adding the components of a vector. 95 | /// 96 | /// A 2D point. 97 | /// A 2D vector. 98 | public static Point2D operator +(Point2D pt, Vector2D v) 99 | { 100 | return new Point2D(pt.X + v.X, pt.Y + v.Y); 101 | } 102 | /// 103 | /// Offsets a point by the subtracting the components of a vector. 104 | /// 105 | /// A 2D point. 106 | /// A 2D vector. 107 | public static Point2D operator -(Point2D pt, Vector2D v) 108 | { 109 | return new Point2D(pt.X - v.X, pt.Y - v.Y); 110 | } 111 | public static bool operator !=(Point2D ptA, Point2D ptB) 112 | { 113 | return (ptA.X != ptB.X) || (ptA.Y != ptB.Y); 114 | } 115 | public static bool operator ==(Point2D ptA, Point2D ptB) 116 | { 117 | return (ptA.X == ptB.X) && (ptA.Y == ptB.Y); 118 | } 119 | 120 | /// 121 | /// Creates a new XYPoint with the X and Y components rounded to the given 122 | /// decimal places. Rounding used is always AwayFromZero. 123 | /// 124 | /// The number of significant decimal places (precision) in the return value. 125 | public Point2D RoundTo(int decimals) 126 | { 127 | return new Point2D(X.RoundFromZero(decimals), Y.RoundFromZero(decimals)); 128 | } 129 | 130 | /// 131 | /// Transforms this point using a transformation matrix. 132 | /// 133 | /// The transformation matrix. 134 | public Point2D Transform(Transform2D matrix) 135 | { 136 | var m = new[] 137 | { 138 | X, 139 | Y, 140 | 1 141 | }; 142 | 143 | var transformed = matrix.Transform(m); 144 | 145 | return new Point2D(transformed[0], transformed[1]); 146 | } 147 | 148 | /// 149 | /// Compares this point against another object. 150 | /// 151 | /// The object to compare against. 152 | public override bool Equals(object obj) 153 | { 154 | if (ReferenceEquals(null, obj)) return false; 155 | return obj is Point2D && Equals((Point2D)obj); 156 | } 157 | 158 | /// 159 | /// Compares this point against another. 160 | /// 161 | /// A 2D point. 162 | public bool Equals(Point2D other) 163 | { 164 | return X == other.X && Y == other.Y; 165 | } 166 | 167 | public override int GetHashCode() 168 | { 169 | unchecked { return Helper.HashStart.HashValue(X).HashValue(Y); } 170 | } 171 | 172 | /// 173 | /// Compares this point against another to the given number of decimal places. Rounds away from 174 | /// zero to the given number of decimals. 175 | /// 176 | /// A 2D point. 177 | /// The number of significant decimal places (precision) in the return value. 178 | public bool Equals(Point2D other, int decimals) 179 | { 180 | return RoundTo(decimals) == other.RoundTo(decimals); 181 | } 182 | 183 | /// 184 | /// Compares a list of points and returns the one with the minimum Y value. 185 | /// If more than one point has the same minimum Y value, then the first 186 | /// match is returned. 187 | /// 188 | /// Points to compare. 189 | public static Point2D MinY(params Point2D[] points) 190 | { 191 | if (points == null) throw new ArgumentException("Null array when expecting list of points!"); 192 | if (points.Length == 0) throw new ArgumentException("List of points is empty!"); 193 | 194 | var lowest = 0; 195 | for (var i = 1; i < points.Length; i++) 196 | { 197 | if (points[i].Y < points[lowest].Y) lowest = i; 198 | } 199 | 200 | return points[lowest]; 201 | } 202 | /// 203 | /// Compares a list of points and returns the one with the maximum Y value. 204 | /// If more than one point has the same maximum Y value, then the first 205 | /// match is returned. 206 | /// 207 | /// Points to compare. 208 | public static Point2D MaxY(params Point2D[] points) 209 | { 210 | if (points == null) throw new ArgumentException("Null array when expecting list of points!"); 211 | if (points.Length == 0) throw new ArgumentException("List of points is empty!"); 212 | 213 | var highest = 0; 214 | for (var i = 1; i < points.Length; i++) 215 | { 216 | if (points[i].Y > points[highest].Y) highest = i; 217 | } 218 | 219 | return points[highest]; 220 | } 221 | 222 | /// 223 | /// Compares a list of points and returns the one with the minimum X value. 224 | /// If more than one point has the same minimum X value, then the first 225 | /// match is returned. 226 | /// 227 | /// Points to compare. 228 | public static Point2D MinX(params Point2D[] points) 229 | { 230 | if (points == null) throw new ArgumentException("Null array when expecting list of points!"); 231 | if (points.Length == 0) throw new ArgumentException("List of points is empty!"); 232 | 233 | var lowest = 0; 234 | for (var i = 1; i < points.Length; i++) 235 | { 236 | if (points[i].X < points[lowest].X) lowest = i; 237 | } 238 | 239 | return points[lowest]; 240 | } 241 | /// 242 | /// Compares a list of points and returns the one with the maximum X value. 243 | /// If more than one point has the same maximum X value, then the first 244 | /// match is returned. 245 | /// 246 | /// Points to compare. 247 | public static Point2D MaxX(params Point2D[] points) 248 | { 249 | if (points == null) throw new ArgumentException("Null array when expecting list of points!"); 250 | if (points.Length == 0) throw new ArgumentException("List of points is empty!"); 251 | 252 | var highest = 0; 253 | for (var i = 1; i < points.Length; i++) 254 | { 255 | if (points[i].X > points[highest].X) highest = i; 256 | } 257 | 258 | return points[highest]; 259 | } 260 | 261 | public override string ToString() 262 | { 263 | return X + "," + Y; 264 | } 265 | } 266 | 267 | } 268 | -------------------------------------------------------------------------------- /Decimal2D/RightTriangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DecimalMath 4 | { 5 | public class RightTriangle 6 | { 7 | 8 | private RightTriangle() 9 | { 10 | // No using instance... 11 | } 12 | 13 | /// 14 | /// Gets the hypotenuse from the two other sides. 15 | /// 16 | /// Length of one side. 17 | /// Length of other side. 18 | public static decimal GetHypFromSides(decimal sideA, decimal sideB) 19 | { 20 | // a^2 + b^2 = c^2 21 | // c = sqrt(a^2 + b^2) 22 | return DecimalEx.Sqrt(sideA * sideA + sideB * sideB); 23 | } 24 | /// 25 | /// Gets hypotenuse from a known side and the angle adjacent to that side. 26 | /// 27 | /// Length of the known side. 28 | /// The angle adjacent to the known side in degrees. 29 | public static decimal GetHypFromSideAdjAngle(decimal side, decimal angleAdjacentToSide) 30 | { 31 | // cos(a) = s / h 32 | // h * cos(a) = s 33 | // h = s / cos(a) 34 | return side / DecimalEx.Cos(DecimalEx.ToRad(angleAdjacentToSide)); 35 | } 36 | /// 37 | /// Gets hypotenuse from a known side and the angle opposite to it. 38 | /// 39 | /// Length of the known side. 40 | /// Angle opposite to the known side in degrees. 41 | public static decimal GetHypFromSideOppAngle(decimal side, decimal angleOppositeToSide) 42 | { 43 | // sin(a) = s / h 44 | // h = s / sin(a) 45 | return side / DecimalEx.Sin(DecimalEx.ToRad(angleOppositeToSide)); 46 | } 47 | 48 | /// 49 | /// Gets a side from a known side and the hypotenuse. 50 | /// 51 | /// Length of the known side. 52 | /// Length of the hypotenuse. 53 | public static decimal GetSideFromSideHyp(decimal side, decimal hypotenuse) 54 | { 55 | // a^2 + b^2 = c^2 56 | // a^2 = c^2 - b^2 57 | // a = sqrt(c^2 - b^2) 58 | decimal sideSquared = hypotenuse * hypotenuse - side * side; 59 | if (sideSquared < 0) 60 | { 61 | throw new Exception("Error finding side from other side and hypotenuse! Side and hypotenuse swapped or invalid right triangle."); 62 | } 63 | return DecimalEx.Sqrt(sideSquared); 64 | } 65 | /// 66 | /// Gets a side from the adjacent angle and the length of the opposite side. 67 | /// 68 | /// Angle adjacent to the side to calculate in degrees. 69 | /// Length of the opposite side. 70 | public static decimal GetSideFromAdjAngleOppSide(decimal adjacentAngle, decimal oppositeSide) 71 | { 72 | // tan(adjacentAngle) = oppositeSide / x 73 | // x = oppositeSide / tan(adjacentAngle) 74 | return oppositeSide / Convert.ToDecimal(DecimalEx.Tan(DecimalEx.ToRad(adjacentAngle))); 75 | } 76 | /// 77 | /// Gets a side from the opposite angle and the length of the opposite side. 78 | /// 79 | /// Angle opposite the side to calculate in degrees. 80 | /// Length of the opposite side. 81 | public static decimal GetSideFromOppAngleOppSide(decimal oppositeAngle, decimal oppositeSide) 82 | { 83 | // tan(oppositeAngle) = x / oppositeSide 84 | // x = oppositeSide * tan(oppositeAngle) 85 | return oppositeSide * Convert.ToDecimal(DecimalEx.Tan(DecimalEx.ToRad(oppositeAngle))); 86 | } 87 | /// 88 | /// Gets a side from the adjacent angle and the length of the hypotenuse. 89 | /// 90 | /// Angle adjacent to the side to calculate in degrees. 91 | /// Length of the hypotenuse. 92 | public static decimal GetSideFromAdjAngleHyp(decimal adjacentAngle, decimal hypotenuse) 93 | { 94 | // cos(adjacentAngle) = x / hypotenuse 95 | // x = hypotenuse * cos(adjacentAngle) 96 | return hypotenuse * Convert.ToDecimal(DecimalEx.Cos(DecimalEx.ToRad(adjacentAngle))); 97 | } 98 | /// 99 | /// Gets a side from the opposite angle and the length of the hypotenuse. 100 | /// 101 | /// Angle opposite to the side to calculate in degrees. 102 | /// Length of the hypotenuse. 103 | public static decimal GetSideFromOppAngleHyp(decimal oppositeAngle, decimal hypotenuse) 104 | { 105 | // sin(oppositeAngle) = x / hypotenuse 106 | // x = hypotenuse * sin(oppositeAngle) 107 | return hypotenuse * Convert.ToDecimal(DecimalEx.Sin(DecimalEx.ToRad(oppositeAngle))); 108 | } 109 | 110 | /// 111 | /// Gets angle in degrees from a known angle (other than the 90 degree angle). 112 | /// 113 | /// Known angle in degrees (not the 90 degree angle). 114 | public static decimal GetAngleFromOtherAngle(decimal otherAngle) 115 | { 116 | return 90m - otherAngle; 117 | } 118 | /// 119 | /// Gets angle in degrees from the two known sides. 120 | /// 121 | /// Length of the known side opposite the angle. 122 | /// Length of the known side adjacent to the angle. 123 | public static decimal GetAngleFromSides(decimal oppositeSide, decimal adjacentSide) 124 | { 125 | // tan(a) = opposideSide / adjacentSide 126 | // a = atan(opposideSide / adjacentSide) 127 | return DecimalEx.ToDeg(DecimalEx.ATan(oppositeSide / adjacentSide)); 128 | } 129 | /// 130 | /// Gets angle in degrees from the opposite side and the hypotenuse. 131 | /// 132 | /// Length of the known side opposite the angle. 133 | /// Length of the hypotenuse. 134 | public static decimal GetAngleFromOppSideHyp(decimal oppositeSide, decimal hypotenuse) 135 | { 136 | // sin(a) = oppositeSide / hypotenuse 137 | // a = asin(oppositeSide / hypotenuse) 138 | return DecimalEx.ToDeg(DecimalEx.ASin(oppositeSide / hypotenuse)); 139 | } 140 | /// 141 | /// Gets angle in degrees from the adjacent side and the hypotenuse. 142 | /// 143 | /// Length of the known side adjacent to the angle. 144 | /// Length of the hypotenuse. 145 | public static decimal GetAngleFromAdjSideHyp(decimal adjacentSide, decimal hypotenuse) 146 | { 147 | // cos(a) = adjacentSide / hypotenuse 148 | // a = acos(adjacentSide / hypotenuse) 149 | return DecimalEx.ToDeg(DecimalEx.ACos(adjacentSide / hypotenuse)); 150 | } 151 | 152 | /// 153 | /// Gets a side from information for a similar triangle. 154 | /// 155 | /// Length of a side in similar triangle. Should correspond to the side to calculate. 156 | /// Length of the hypotenusue in the similar triangle. 157 | /// Length of the hypotenuse in the target triangle. 158 | public static decimal GetSideFromSimilarSideHyp(decimal similarSide, decimal similarHyp, decimal hypotenuse) 159 | { 160 | 161 | return (hypotenuse / similarHyp) * similarSide; 162 | 163 | } 164 | /// 165 | /// Gets the hypotenuse from information for a similar triangle. 166 | /// 167 | /// Length of a side in similar triangle. 168 | /// Length of the hypotenusue in the similar triangle. 169 | /// Length of the side that corresponds to . 170 | public static decimal GetHypFromSimilarSideHyp(decimal similarSide, decimal similarHyp, decimal correspondingSide) 171 | { 172 | 173 | return similarHyp * (correspondingSide / similarSide); 174 | 175 | } 176 | /// 177 | /// Gets a side from similar sides of another right triangle and the corresponding side to one of them. 178 | /// 179 | /// Length of a side in similar triangle. Corresponds to . 180 | /// Length of a side in similar triangle. Corresponds to side to calculate. 181 | /// Length of a side in target triangle. Corresponds to 182 | public static decimal GetSideFromSimilarSides(decimal similarSideA, decimal similarSideB, decimal sideA) 183 | { 184 | 185 | return similarSideB * (sideA / similarSideA); 186 | 187 | } 188 | 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /Decimal2D/RightTriangleAbstract.cs: -------------------------------------------------------------------------------- 1 | namespace DecimalMath 2 | { 3 | /// 4 | /// Represents a right triangle without specifying the location of any of its points. 5 | /// 6 | /// 7 | /// See http://mathworld.wolfram.com/RightTriangle.html 8 | /// 9 | public struct RightTriangleAbstract 10 | { 11 | /// 12 | /// Gets the angle adjacent to side A. Angle is in degrees. 13 | /// 14 | public decimal AngleA { get; private set; } 15 | /// 16 | /// Gets the angle adjacent to side B. Angle is in degrees. 17 | /// 18 | public decimal AngleB { get; private set; } 19 | /// 20 | /// Gets the length of the hypotenuse. 21 | /// 22 | public decimal Hypotenuse { get; private set; } 23 | /// 24 | /// Gets the length of side A. 25 | /// 26 | public decimal LengthA { get; private set; } 27 | /// 28 | /// Gets the length of side B. 29 | /// 30 | public decimal LengthB { get; private set; } 31 | 32 | /// 33 | /// Creates a right triangle from its two sides. 34 | /// 35 | /// Length of side A. 36 | /// Length of side B. 37 | public static RightTriangleAbstract FromTwoSides(decimal lengthA, decimal lengthB) 38 | { 39 | var t = new RightTriangleAbstract 40 | { 41 | LengthA = lengthA, 42 | LengthB = lengthB, 43 | Hypotenuse = RightTriangle.GetHypFromSides(lengthA, lengthB), 44 | AngleA = RightTriangle.GetAngleFromSides(lengthB, lengthA), 45 | AngleB = RightTriangle.GetAngleFromSides(lengthA, lengthB) 46 | }; 47 | return t; 48 | } 49 | 50 | /// 51 | /// Creates a right triangle from one side and the hypotenuse. 52 | /// This side is treated as side A. 53 | /// 54 | /// Length of side A. 55 | /// Length of the hypotenuse. 56 | public static RightTriangleAbstract FromSideAHypotenuse(decimal lengthA, decimal hypotenuse) 57 | { 58 | var t = new RightTriangleAbstract 59 | { 60 | LengthA = lengthA, 61 | Hypotenuse = hypotenuse, 62 | LengthB = RightTriangle.GetSideFromSideHyp(lengthA, hypotenuse), 63 | AngleA = RightTriangle.GetAngleFromAdjSideHyp(lengthA, hypotenuse), 64 | AngleB = RightTriangle.GetAngleFromOppSideHyp(lengthA, hypotenuse) 65 | }; 66 | return t; 67 | } 68 | 69 | /// 70 | /// Creates a right triangle from one side and its adjacent angle in degrees. 71 | /// This side and angle are treated as side/angle A. 72 | /// 73 | /// Length of side A. 74 | /// Angle adjacent to side A in degrees. 75 | public static RightTriangleAbstract FromSideAAngleA(decimal lengthA, decimal angleA) 76 | { 77 | var t = new RightTriangleAbstract 78 | { 79 | LengthA = lengthA, 80 | AngleA = angleA, 81 | LengthB = RightTriangle.GetSideFromOppAngleOppSide(angleA, lengthA), 82 | AngleB = RightTriangle.GetAngleFromOtherAngle(angleA), 83 | Hypotenuse = RightTriangle.GetHypFromSideAdjAngle(lengthA, angleA) 84 | }; 85 | return t; 86 | } 87 | 88 | /// 89 | /// Creates a right triangle from one side and the hypotenuse. 90 | /// This side is treated as side B. 91 | /// 92 | /// Length of side B. 93 | /// Length of the hypotenuse. 94 | public static RightTriangleAbstract FromSideBHypotenuse(decimal lengthA, decimal hypotenuse) 95 | { 96 | return FromSideAHypotenuse(lengthA, hypotenuse).SwapSides(); 97 | } 98 | 99 | /// 100 | /// Creates a right triangle from one side and its adjacent angle in degrees. 101 | /// This side and angle are treated as side/angle A. 102 | /// 103 | /// Length of side B. 104 | /// Angle adjacent to side B in degrees. 105 | public static RightTriangleAbstract FromSideBAngleB(decimal lengthB, decimal angleB) 106 | { 107 | return FromSideAAngleA(lengthB, angleB).SwapSides(); 108 | } 109 | 110 | /// 111 | /// Swaps which sides / angles are considered A and B. 112 | /// 113 | public RightTriangleAbstract SwapSides() 114 | { 115 | var t = new RightTriangleAbstract 116 | { 117 | LengthA = LengthB, 118 | AngleA = AngleB, 119 | LengthB = LengthA, 120 | AngleB = AngleA, 121 | Hypotenuse = Hypotenuse, 122 | }; 123 | return t; 124 | } 125 | } 126 | } -------------------------------------------------------------------------------- /Decimal2D/Transform2D.cs: -------------------------------------------------------------------------------- 1 | namespace DecimalMath 2 | { 3 | /// 4 | /// Provides storage and transformation functions on a 3 x 3 matrix of 5 | /// an affine transformation. 6 | /// 7 | /// 8 | /// Internal matrix organization is addressed by row and then column. 9 | /// In other words, matrix(0, 2) is first row and the last column. 10 | /// Points are represented as column vectors and consequently transformations 11 | /// are applied by left-multiplying the transformation matrix (i.e. 12 | /// transform * existing matrix = new matrix). NOTE: GDI+ uses row vectors 13 | /// and right-multiplies. This other format was chosen because it is more 14 | /// often used in mathematics and computer science texts (and Wikipedia). 15 | /// 16 | public class Transform2D : TransformationMatrixBase 17 | { 18 | 19 | // Orientation (translation transformation shown) 20 | // 21 | // [ 1 0 tx ][ x ] 22 | // [ 0 1 ty ][ y ] 23 | // [ 0 0 1 ][ 1 ] 24 | // 25 | // Transformations are applied by left-multiplying transformation matrix. 26 | 27 | 28 | public Transform2D() 29 | : base(3) 30 | { } 31 | public Transform2D(decimal[,] values) 32 | : base(3, values) 33 | { } 34 | 35 | #region " Actual Transformations " 36 | 37 | /// 38 | /// Scales around the origin. Returns a new matrix with the result. 39 | /// 40 | /// Scale factor in X. 41 | /// Scale factor in Y. 42 | public Transform2D Scale(decimal scaleX, decimal scaleY) 43 | { 44 | 45 | var r = new Transform2D(); 46 | 47 | // 0 1 2 48 | // 0 Sx 0 0 49 | // 1 0 Sy 0 50 | // 2 0 0 1 51 | 52 | r[0, 0] = scaleX; 53 | r[1, 1] = scaleY; 54 | r[2, 2] = 1; 55 | 56 | return r.Multiply(this); 57 | 58 | } 59 | /// 60 | /// Scale around a given point. Returns a new matrix with the result. 61 | /// 62 | /// The X coordinate of the point at which to scale from. 63 | /// The Y coordinate of the point at which to scale from. 64 | /// Scale factor in X. 65 | /// Scale factor in Y. 66 | public Transform2D ScaleAt(decimal x, decimal y, decimal scaleX, decimal scaleY) 67 | { 68 | 69 | // Translate so (x,y) is now at origin, perform scaling, and then 70 | // translate so (x,y) is back at its original location 71 | return Translate(-x, -y).Scale(scaleX, scaleY).Translate(x, y); 72 | 73 | } 74 | /// 75 | /// Rotates about the origin. Returns a new matrix with the result. 76 | /// 77 | /// The degrees to rotate. 78 | /// If False, then + degrees rotates counter clockwise. 79 | /// If True, then + degrees rotates clockwise. Of course, if the sign of the degrees 80 | /// is -, then the rotation will be opposite whatever the + direction is. 81 | public Transform2D Rotate(decimal degrees, bool clockwise = false) 82 | { 83 | var r = new Transform2D(); 84 | 85 | var theta = DecimalEx.ToRad(degrees); 86 | if (clockwise) theta *= -1; 87 | 88 | // 0 1 2 89 | // 0 cos -sin 0 90 | // 1 sin cos 0 91 | // 2 0 0 1 92 | 93 | r[0, 0] = DecimalEx.Cos(theta); 94 | r[0, 1] = -DecimalEx.Sin(theta); 95 | r[1, 0] = DecimalEx.Sin(theta); 96 | r[1, 1] = DecimalEx.Cos(theta); 97 | 98 | return r.Multiply(this); 99 | } 100 | /// 101 | /// Rotates about a given point. Returns a new matrix with the result. 102 | /// 103 | /// The X coordinate of the point at which to rotate. 104 | /// The Y coordinate of the point at which to rotate. 105 | /// The degrees to rotate. 106 | /// If False, then + degrees rotates counter clockwise. 107 | /// If True, then + degrees rotates clockwise. Of course, if the sign of the degrees 108 | /// is -, then the rotation will be opposite whatever the + direction is. 109 | public Transform2D RotateAt(decimal x, decimal y, decimal degrees, bool clockwise = false) 110 | { 111 | 112 | // Translate so (x,y) is now at origin, perform rotation, and then 113 | // translate so (x,y) is back at its original location. 114 | return Translate(-x, -y).Rotate(degrees, clockwise).Translate(x, y); 115 | 116 | } 117 | /// 118 | /// Rotates about a given point. Returns a new matrix with the result. 119 | /// 120 | /// The point at which to rotate. 121 | /// The degrees to rotate. 122 | /// If False, then + degrees rotates counter clockwise. 123 | /// If True, then + degrees rotates clockwise. Of course, if the sign of the degrees 124 | /// is -, then the rotation will be opposite whatever the + direction is. 125 | public Transform2D RotateAt(Point2D pt, decimal degrees, bool clockwise = false) 126 | { 127 | 128 | return RotateAt(pt.X, pt.Y, degrees, clockwise); 129 | 130 | } 131 | /// 132 | /// Mirrors across line through line segment. Returns a new matrix with the result. 133 | /// 134 | /// The across which to mirror. 135 | public Transform2D Mirror(LineSeg2D l) 136 | { 137 | 138 | // See here: http://planetmath.org/encyclopedia/DerivationOf2DReflectionMatrix.html 139 | 140 | 141 | // Mirror around origin 142 | // 143 | // 0 1 2 144 | // 0 x^2 - y^2 2xy 0 145 | // 1 2xy y^2 - x^2 0 146 | // 2 0 0 1 147 | 148 | var v = l.GetVectorP1toP2().Normalize(); 149 | var mirror = new Transform2D( 150 | new[,] 151 | { 152 | { DecimalEx.Pow(v.X, 2) - DecimalEx.Pow(v.Y, 2), 2 * v.X * v.Y, 0 }, 153 | { 2 * v.X * v.Y, DecimalEx.Pow(v.Y, 2) - DecimalEx.Pow(v.X, 2), 0 }, 154 | { 0, 0, 1 } 155 | }); 156 | 157 | // Translate to origin because mirroring around a vector is relative to the origin. 158 | var r = Translate(-l.Pt1.X, -l.Pt1.Y); 159 | 160 | // Left multiply this transformation matrix 161 | r = mirror.Multiply(r); 162 | 163 | // Translate back where we came from 164 | r = r.Translate(l.Pt1.X, l.Pt1.Y); 165 | 166 | return r; 167 | } 168 | /// 169 | /// Mirrors across line that passes through the given points. Returns a new matrix with the result. 170 | /// 171 | /// A point. 172 | /// A point. 173 | public Transform2D Mirror(Point2D pt1, Point2D pt2) 174 | { 175 | 176 | return Mirror(new LineSeg2D(pt1, pt2)); 177 | 178 | } 179 | 180 | /// 181 | /// Translates by the given amounts in X and Y. For example, translating 182 | /// a point from (0,0) by X = 1, Y = 2 will yield (1,2). Returns a new matrix with the result. 183 | /// 184 | /// The X distance to translate. 185 | /// The Y distance to translate. 186 | public Transform2D Translate(decimal x, decimal y) 187 | { 188 | var t = new Transform2D(); 189 | 190 | // 0 1 2 191 | // 0 1 0 x 192 | // 1 0 1 y 193 | // 2 0 0 1 194 | 195 | t[0, 2] = x; 196 | t[1, 2] = y; 197 | 198 | return t.Multiply(this); 199 | } 200 | 201 | #endregion 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Decimal2D/Vector2D.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace DecimalMath 5 | { 6 | /// 7 | /// Represents a 2D vector. 8 | /// 9 | /// 10 | /// Useful links: 11 | /// http://emweb.unl.edu/math/mathweb/vectors/vectors.html 12 | /// http://www.mathrec.org/vector.html 13 | /// 14 | [DebuggerDisplay("X = {X} Y = {Y}")] 15 | public struct Vector2D: ITransformable 16 | { 17 | public decimal X; 18 | public decimal Y; 19 | 20 | /// 21 | /// Creates a vector with the supplied X and Y components. 22 | /// 23 | /// The X component. 24 | /// The Y component. 25 | public Vector2D(decimal x, decimal y) 26 | { 27 | this.X = x; 28 | this.Y = y; 29 | } 30 | /// 31 | /// Creates a new vector that starts from the starting point and points to 32 | /// the end point with a length of the distance between the two points. 33 | /// 34 | /// The starting point. 35 | /// The ending point. 36 | public Vector2D(Point2D startPt, Point2D endPt) 37 | { 38 | this.X = endPt.X - startPt.X; 39 | this.Y = endPt.Y - startPt.Y; 40 | } 41 | /// 42 | /// Creates a new vector that starts from the starting point and points to 43 | /// the end point with the given magnitude. 44 | /// 45 | /// The starting point. 46 | /// The ending point. 47 | /// A magnitude. 48 | public Vector2D(Point2D startPt, Point2D endPt, decimal magnitude) 49 | { 50 | this.X = endPt.X - startPt.X; 51 | this.Y = endPt.Y - startPt.Y; 52 | this.Magnitude = magnitude; 53 | } 54 | /// 55 | /// Creates a new vector that starts from the starting point and points to 56 | /// the end point with a length of the distance between the two points. 57 | /// 58 | /// The starting point X component. 59 | /// The starting point Y component. 60 | /// The ending point X component. 61 | /// The ending point Y component. 62 | public Vector2D(decimal startX, decimal startY, decimal endX, decimal endY) 63 | { 64 | this.X = endX - startX; 65 | this.Y = endY - startY; 66 | } 67 | /// 68 | /// Creates a new vector with the same direction as the given vector 69 | /// but with the supplied magnitude. 70 | /// 71 | /// A vector. 72 | /// A magnitude. 73 | /// 74 | public Vector2D(Vector2D v, decimal magnitude) 75 | { 76 | this.X = v.X; 77 | this.Y = v.Y; 78 | this.Magnitude = magnitude; 79 | } 80 | /// 81 | /// Creates a new vector starting at the origin and terminating at this point. 82 | /// 83 | /// End point of vector starting at origin. 84 | public Vector2D(Point2D endPt) 85 | { 86 | this.X = endPt.X; 87 | this.Y = endPt.Y; 88 | } 89 | 90 | /// 91 | /// Creates a new XYPoint with the X and Y components rounded to the given 92 | /// decimal places. Rounding used is always AwayFromZero. 93 | /// 94 | /// The number of significant decimal places (precision) in the return value. 95 | public Vector2D RoundTo(int decimals) 96 | { 97 | return new Vector2D(X.RoundFromZero(decimals), Y.RoundFromZero(decimals)); 98 | } 99 | 100 | public override bool Equals(object obj) 101 | { 102 | if (!(obj is Vector2D)) 103 | { 104 | throw new Exception("Can't compare " + this.GetType().Name + " to an object of a different type!"); 105 | } 106 | return this == (Vector2D)obj; 107 | } 108 | /// 109 | /// Compares this point against another to the given number of decimal places. 110 | /// 111 | /// A 2D vector. 112 | /// The number of significant decimal places (precision) in the return value. 113 | public bool Equals(Vector2D other, int decimals) 114 | { 115 | return this.RoundTo(decimals) == other.RoundTo(decimals); 116 | } 117 | 118 | public static bool operator ==(Vector2D objA, Vector2D objB) 119 | { 120 | return objA.X == objB.X && objA.Y == objB.Y; 121 | } 122 | public static bool operator !=(Vector2D objA, Vector2D objB) 123 | { 124 | return objA.X != objB.X || objA.Y != objB.Y; 125 | } 126 | 127 | /// 128 | /// Gets whether or not this is a unit vector, i.e. has a magnitude of 1. 129 | /// 130 | /// Optional precision at which to make the comparison. 131 | public bool IsUnitVector(int decimals = -1) 132 | { 133 | // TODO: Change name to GetIsUnitVector 134 | if (decimals >= 0) 135 | { 136 | return (Magnitude.RoundFromZero(decimals) == 1m); 137 | } 138 | else 139 | { 140 | return (Magnitude == 1m); 141 | } 142 | } 143 | 144 | /// 145 | /// Gets whether or not this is the null (or zero or empty) vector, in other 146 | /// words all its components are equal to zero. 147 | /// 148 | /// 149 | /// Could be "X = 0 AndAlso Y = 0" except when you actually make the calculation 150 | /// to get the magnitude, you sometimes get 0 even when one of the numbers is 151 | /// a very small non-zero value. Since this routine is called to avoid divide-by- 152 | /// zero errors when dividing by magnitude, this routine should look at magnitude 153 | /// instead. 154 | /// 155 | public bool IsNull 156 | { 157 | 158 | 159 | get { return Magnitude == 0; } 160 | } 161 | /// 162 | /// Gets or sets the magnitude or length of the vector. 163 | /// 164 | public decimal Magnitude 165 | { 166 | 167 | 168 | get { return Point2D.Origin.DistanceTo(X, Y); } 169 | 170 | set 171 | { 172 | if (this.IsNull && value != 0) 173 | { 174 | throw new Exception("Can't set magnitude of null vector to non-zero value since it has no defined direction!"); 175 | } 176 | 177 | // This seems to yield better precision than using an 178 | // intermediary value for "value / Magnitude". 179 | decimal origMagnitude = Magnitude; 180 | X = X * value / origMagnitude; 181 | Y = Y * value / origMagnitude; 182 | 183 | } 184 | } 185 | 186 | /// 187 | /// Converts this vector to its unit vector, i.e. same direction but 188 | /// with a magnitude of 1. 189 | /// 190 | public Vector2D Normalize() 191 | { 192 | 193 | if (this.IsNull) 194 | { 195 | throw new Exception("Can't normalize a null vector!"); 196 | } 197 | 198 | return this / Magnitude; 199 | 200 | } 201 | 202 | /// 203 | /// Adds the two vectors. Is commutative. 204 | /// 205 | /// A 2D vector. 206 | /// A 2D vector. 207 | public static Vector2D operator +(Vector2D v1, Vector2D v2) 208 | { 209 | 210 | return new Vector2D(v1.X + v2.X, v1.Y + v2.Y); 211 | 212 | } 213 | /// 214 | /// Subtracts one vector from another. v1 - v2 = v1 + (-v2) 215 | /// 216 | /// A 2D vector. 217 | /// A 2D vector. 218 | public static Vector2D operator -(Vector2D v1, Vector2D v2) 219 | { 220 | 221 | return new Vector2D(v1.X - v2.X, v1.Y - v2.Y); 222 | 223 | } 224 | /// 225 | /// Reverses the vector by multiplying its elements by -1. 226 | /// 227 | /// A 2D vector. 228 | public static Vector2D operator -(Vector2D v) 229 | { 230 | 231 | return new Vector2D(-v.X, -v.Y); 232 | 233 | } 234 | /// 235 | /// Multiplies a vector by a scalar value. 236 | /// 237 | /// The vector to multiply. 238 | /// The scalar value. 239 | public static Vector2D operator *(Vector2D v, decimal scale) 240 | { 241 | return new Vector2D(v.X * scale, v.Y * scale); 242 | } 243 | /// 244 | /// Multiplies a vector by a scalar value. 245 | /// 246 | /// The scalar value. 247 | /// The vector to multiply. 248 | public static Vector2D operator *(decimal scale, Vector2D v) 249 | { 250 | return new Vector2D(v.X * scale, v.Y * scale); 251 | } 252 | /// 253 | /// Divides each element of a vector by a scalar value. 254 | /// 255 | /// A 2D vector. 256 | /// The scalar value. 257 | public static Vector2D operator /(Vector2D v, decimal scale) 258 | { 259 | return new Vector2D(v.X / scale, v.Y / scale); 260 | } 261 | /// 262 | /// Returns the dot product of this vector and the other vector. 263 | /// (Is commutative.) 264 | /// 265 | /// The other vector to get the dot product with. 266 | public decimal Dot(Vector2D other) 267 | { 268 | 269 | return this.X * other.X + this.Y * other.Y; 270 | 271 | } 272 | /// 273 | /// Projects this vector onto another. 274 | /// 275 | /// 276 | /// See http://mathworld.wolfram.com/Projection.html 277 | public Vector2D ProjectOnto(Vector2D other) 278 | { 279 | 280 | decimal otherMag = 0m; 281 | decimal multiplier = 0m; 282 | 283 | if (other.IsNull) 284 | { 285 | throw new Exception("Can't project onto a null vector!"); 286 | } 287 | 288 | otherMag = other.Magnitude; 289 | multiplier = this.Dot(other) / otherMag; 290 | 291 | return multiplier * (other / otherMag); 292 | 293 | } 294 | /// 295 | /// Gets the angle in degrees between this vector and another. 296 | /// 297 | /// The other vector to get the angle to. 298 | /// See http://en.wikipedia.org/wiki/Dot_product 299 | public decimal AngleTo(Vector2D other) 300 | { 301 | 302 | if (this.IsNull || other.IsNull) 303 | { 304 | throw new Exception("Can't find angle when one or both vectors are null vectors!"); 305 | } 306 | 307 | // Cos(theta) = DotProduct(v1,v2) / (length(v1) * length(v2)) 308 | // aka theta = acos(v.normalize.dot(other.normalize)), however, the equation 309 | // used gives us better precision 310 | return DecimalEx.ToDeg(DecimalEx.ACos(this.Dot(other) / (this.Magnitude * other.Magnitude))); 311 | 312 | } 313 | /// 314 | /// Gets the angle of the vector in degrees from the X+ axis. Will return result in the range 315 | /// -180 to +180. Will return 0 for null vector. 316 | /// 317 | public decimal Angle() 318 | { 319 | 320 | return DecimalEx.ToDeg(DecimalEx.ATan2(Y, X)); 321 | 322 | } 323 | 324 | /// 325 | /// Gets a vector of the same magnitude pointing in the opposite direction. 326 | /// 327 | public Vector2D GetReversed() 328 | { 329 | 330 | return new Vector2D(-X, -Y); 331 | 332 | } 333 | /// 334 | /// Gets one of the two vectors perpendicular to this vector. New vector will 335 | /// be 90 degrees clockwise from original vector. 336 | /// 337 | public Vector2D GetPerpendicular() 338 | { 339 | 340 | return new Vector2D(Y, -X); 341 | 342 | } 343 | 344 | /// 345 | /// Transforms this vector, treating it as a line segment starting at the origin and 346 | /// ending at the (X,Y) of the vector elements. Returns a new vector translated back 347 | /// to the origin. 348 | /// 349 | public Vector2D Transform(Transform2D matrix) 350 | { 351 | // Translate as line segment 352 | var basePt = matrix.Transform(Point2D.Origin); 353 | var endPt = matrix.Transform(new Point2D(X, Y)); 354 | 355 | // Constructor will reset base point to origin 356 | return new Vector2D(basePt, endPt); 357 | } 358 | 359 | public override string ToString() 360 | { 361 | 362 | return X + "," + Y; 363 | 364 | } 365 | 366 | /// 367 | /// The null (or zero or empty) vector. 368 | /// 369 | public static readonly Vector2D Null = new Vector2D(0, 0); 370 | /// The X unit vector. 371 | public static readonly Vector2D XUnit = new Vector2D(1, 0); 372 | /// The Y unit vector. 373 | 374 | public static readonly Vector2D YUnit = new Vector2D(0, 1); 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalEx.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.2 5 | Debug;Release;DebugWithMessages 6 | AnyCPU 7 | 8 | 9 | 10 | true 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/AGMeanTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | 8 | public class AGMeanTests 9 | { 10 | public const decimal Tolerance = 1m; 11 | 12 | public static decimal[][] TestCases = 13 | { 14 | new[] {24m, 6m, 13.4581714817256154207668131569m, Tolerance}, 15 | new[] {3m, 6m, 4.3703730931407206075592971498m, Tolerance}, 16 | new[] {-3m, -6m, -4.3703730931407206075592971498m, Tolerance}, 17 | new[] {256636754m, 372843828m, 312029591.54689409232001318882m, Tolerance}, 18 | new[] {DecimalEx.SmallestNonZeroDec, 372843828m, 6842214.3831477590161m, Tolerance}, // full result would be 6842214.3831477590161357941832 but for loss of precision 19 | }; 20 | 21 | [TestCaseSource("TestCases")] 22 | public void Test(decimal x, decimal y, decimal expected, decimal tolerance) 23 | { 24 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 25 | Assert.That(DecimalEx.AGMean(x, y), Is.EqualTo(expected).Within(tolerance)); 26 | } 27 | 28 | public static decimal[][] SpecialCases = 29 | { 30 | new[] {0m, 6m, 0m, 0m}, 31 | new[] {6m, 0m, 0m, 0m}, 32 | }; 33 | 34 | [TestCaseSource("SpecialCases")] 35 | public void TestSpecialCases(decimal x, decimal y, decimal expected, decimal tolerance) 36 | { 37 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 38 | Assert.That(DecimalEx.AGMean(x, y), Is.EqualTo(expected).Within(tolerance)); 39 | } 40 | 41 | [Test] 42 | public void RejectMixedSign() 43 | { 44 | Assert.Throws(() => DecimalEx.AGMean(-3, 6)); 45 | Assert.Throws(() => DecimalEx.AGMean(3, -6)); 46 | } 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/AverageTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTests 5 | { 6 | class AverageTests 7 | { 8 | [Test] 9 | public void GenericTest() 10 | { 11 | Assert.That(DecimalEx.Average(5, 10, 34, 8), Is.EqualTo(14.25m)); 12 | } 13 | 14 | [Test] 15 | public void OverflowTest() 16 | { 17 | const decimal halfMax = decimal.MaxValue / 2m; 18 | Assert.That(DecimalEx.Average(halfMax, halfMax, halfMax), Is.EqualTo(halfMax).Within(1m)); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/CeilingTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using DecimalMath; 4 | using NUnit.Framework; 5 | 6 | namespace DecimalExTests.DecimalExTests 7 | { 8 | 9 | public class CeilingTests 10 | { 11 | public static IEnumerable TestCases 12 | { 13 | get 14 | { 15 | yield return new TestCaseData(1.999m, 0).Returns(2m); 16 | yield return new TestCaseData(2.2360679774997896964091736687m, 3).Returns(2.237m); 17 | yield return new TestCaseData(2.2360679774997896964091736687m, 27).Returns(2.236067977499789696409173669m); 18 | yield return new TestCaseData(-2.2360679774997896964091736687m, 27).Returns(-2.236067977499789696409173668m); 19 | yield return new TestCaseData(2.2360679774997896964091736687m, 28).Returns(2.2360679774997896964091736687m); 20 | yield return new TestCaseData(-2.2360679774997896964091736687m, 28).Returns(-2.2360679774997896964091736687m); 21 | } 22 | } 23 | 24 | [TestCaseSource("TestCases")] 25 | public decimal Test(decimal value, int places) 26 | { 27 | return DecimalEx.Ceiling(value, places); 28 | } 29 | 30 | [Test] 31 | public void TestArgumentBounds() 32 | { 33 | Assert.Throws(() => DecimalEx.Ceiling(10m, -1)); 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/ExpTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTests 5 | { 6 | 7 | public class ExpTests 8 | { 9 | public const decimal Tolerance = 5m; 10 | 11 | public static decimal[][] TestCases = 12 | { 13 | new[] {3m, 20.085536923187667740928529654582m, Tolerance}, 14 | new[] {2m, 7.389056098930650227230427460575m, Tolerance}, 15 | new[] {1.5m, 4.4816890703380648226020554601193m, Tolerance}, 16 | new[] {1m, 2.7182818284590452353602874713527m, 0m}, 17 | new[] {0m, 1m, 0m}, 18 | new[] {-42m, 0.0000000000000000005749522264m, Tolerance}, 19 | new[] {-66m, 0m, 0m}, // This is not actually 0, but so close that it resolves to 0 20 | }; 21 | 22 | [TestCaseSource("TestCases")] 23 | public void Test(decimal d, decimal expected, decimal tolerance) 24 | { 25 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 26 | Assert.That(DecimalEx.Exp(d), Is.EqualTo(expected).Within(tolerance)); 27 | } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/FactorialTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | 8 | public class FactorialTests 9 | { 10 | public const decimal Tolerance = 5m; 11 | 12 | public static decimal[][] TestCases = 13 | { 14 | new[] {0m, 1m}, 15 | new[] {1m, 1m}, 16 | new[] {2m, 2m}, 17 | new[] {7m, 5040m}, 18 | new[] {13m, 6227020800m}, 19 | }; 20 | 21 | [TestCaseSource("TestCases")] 22 | public void Test(decimal n, decimal expected) 23 | { 24 | Assert.That(DecimalEx.Factorial(n), Is.EqualTo(expected)); 25 | } 26 | 27 | [Test] 28 | public void RejectNegative() 29 | { 30 | Assert.Throws(() => DecimalEx.Factorial(-1m)); 31 | } 32 | 33 | [Test] 34 | public void RejectFractional() 35 | { 36 | Assert.Throws(() => DecimalEx.Factorial(0.5m)); 37 | } 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/FloorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using DecimalMath; 4 | using NUnit.Framework; 5 | 6 | namespace DecimalExTests.DecimalExTests 7 | { 8 | 9 | public class FloorTests 10 | { 11 | public static IEnumerable TestCases 12 | { 13 | get 14 | { 15 | yield return new TestCaseData(1.999m, 0).Returns(1m); 16 | yield return new TestCaseData(2.2360679774997896964091736687m, 3).Returns(2.236m); 17 | yield return new TestCaseData(2.2360679774997896964091736687m, 27).Returns(2.236067977499789696409173668m); 18 | yield return new TestCaseData(-2.2360679774997896964091736687m, 27).Returns(-2.236067977499789696409173669m); 19 | yield return new TestCaseData(2.2360679774997896964091736687m, 28).Returns(2.2360679774997896964091736687m); 20 | yield return new TestCaseData(-2.2360679774997896964091736687m, 28).Returns(-2.2360679774997896964091736687m); 21 | } 22 | } 23 | 24 | [TestCaseSource("TestCases")] 25 | public decimal Test(decimal value, int places) 26 | { 27 | return DecimalEx.Floor(value, places); 28 | } 29 | 30 | [Test] 31 | public void TestArgumentBounds() 32 | { 33 | Assert.Throws(() => DecimalEx.Floor(10m, -1)); 34 | } 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/GCFTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | 8 | public class GCFTests 9 | { 10 | // For verification, can use: https://www.omnicalculator.com/math/gcf 11 | public static IEnumerable TestCases 12 | { 13 | get 14 | { 15 | yield return new TestCaseData(1.2m, 0.42m).Returns(.06m); 16 | yield return new TestCaseData(1071m, 462m).Returns(21m); 17 | yield return new TestCaseData(decimal.MaxValue / 1000m, .28m).Returns(.035m); 18 | yield return new TestCaseData(0.0000000000000000000000823543m, 0.0000000000019626617431640625m).Returns(0.0000000000000000000000000343m); 19 | } 20 | } 21 | 22 | [TestCaseSource(nameof(TestCases))] 23 | public decimal Test(decimal a, decimal b) 24 | { 25 | return DecimalEx.GCF(a, b); 26 | } 27 | 28 | public static IEnumerable TestMultipleCases 29 | { 30 | get 31 | { 32 | yield return new TestCaseData(20m, 50m, new[]{120m}).Returns(10m); 33 | yield return new TestCaseData(20m, 50m, new[]{120m, 35m}).Returns(5m); 34 | yield return new TestCaseData(20m, 50m, new[]{120m, 35m, 49m}).Returns(1m); 35 | } 36 | } 37 | 38 | [TestCaseSource("TestMultipleCases")] 39 | public decimal TestMultiple(decimal a, decimal b, params decimal[] values) 40 | { 41 | return DecimalEx.GCF(a, b, values); 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/GetDecimalPlacesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | class GetDecimalPlacesTests 8 | { 9 | public static IEnumerable TestCases 10 | { 11 | get 12 | { 13 | // Test "Normal" Values 14 | yield return new TestCaseData(55.9016994374m).Returns(10); 15 | yield return new TestCaseData(-55.9016994374m).Returns(10); 16 | yield return new TestCaseData(42m).Returns(0); 17 | yield return new TestCaseData(0m).Returns(0); 18 | 19 | // Test Values With Trailing Zeros 20 | yield return new TestCaseData(0.0100m).Returns(2); 21 | yield return new TestCaseData(100.0000m).Returns(0); 22 | yield return new TestCaseData(0.0000m).Returns(0); 23 | yield return new TestCaseData(decimal.Negate(0.0000m)).Returns(0); 24 | } 25 | } 26 | 27 | [TestCaseSource("TestCases")] 28 | public int ExcludeTrailingZeros(decimal dec) 29 | { 30 | return DecimalEx.GetDecimalPlaces(dec, false); 31 | } 32 | 33 | [Test] 34 | public void CountTrailingZeros() 35 | { 36 | var x = DecimalEx.SmallestNonZeroDec; 37 | for (int i = 28; i >= 0; i--) 38 | { 39 | Assert.That(DecimalEx.GetDecimalPlaces(x, false), Is.EqualTo(i)); 40 | x *= 10; 41 | } 42 | 43 | x = -DecimalEx.SmallestNonZeroDec; 44 | for (int i = 28; i >= 0; i--) 45 | { 46 | Assert.That(DecimalEx.GetDecimalPlaces(x, false), Is.EqualTo(i)); 47 | x *= 10; 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/LogTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | 8 | public class LogTests 9 | { 10 | public const decimal Tolerance = 5m; 11 | 12 | #region Log Tests 13 | 14 | public static decimal[][] LogNaturalTestCases = 15 | { 16 | new[] { 15000m, 9.6158054800843471180499789342018m, Tolerance }, 17 | new[] { .15m, -1.89711998488588130203997833922m, Tolerance }, 18 | new[] { 15m, 2.7080502011022100659960045701487m, Tolerance }, 19 | new[] { 1m, 0m, 0m }, 20 | }; 21 | 22 | [TestCaseSource(nameof(LogNaturalTestCases))] 23 | public void LogNaturalTest(decimal value, decimal expected, decimal tolerance) 24 | { 25 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 26 | Assert.That(DecimalEx.Log(value), Is.EqualTo(expected).Within(tolerance)); 27 | } 28 | 29 | [Test] 30 | public void LogNaturalRejectNegativeValue() 31 | { 32 | Assert.Throws(() => DecimalEx.Log(-1)); 33 | } 34 | [Test] 35 | public void LogNaturalRejectZeroValue() 36 | { 37 | Assert.Throws(() => DecimalEx.Log(0)); 38 | } 39 | 40 | #endregion 41 | 42 | #region Log With Base Tests 43 | 44 | public static decimal[][] LogBaseTestCases = 45 | { 46 | new[] { 15000m, 10m, 4.1760912590556812420812890085306m, Tolerance * 2 }, 47 | new[] { .15m, 938m, -0.27720474871548463595021495017423m, Tolerance * 2 }, 48 | new[] { 15m, 2m, 3.9068905956085185293240583734372m, Tolerance * 2 }, 49 | new[] { 1m, 100m, 0m, 0m }, 50 | new[] { 1m, 1m, 0m, 0m }, 51 | }; 52 | 53 | [TestCaseSource(nameof(LogBaseTestCases))] 54 | public void LogBaseTest(decimal value, decimal newBase, decimal expected, decimal tolerance) 55 | { 56 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 57 | Assert.That(DecimalEx.Log(value, newBase), Is.EqualTo(expected).Within(tolerance)); 58 | } 59 | 60 | [Test] 61 | public void LogBaseRejectBaseOf1() 62 | { 63 | Assert.Throws(() => DecimalEx.Log(1234, 1)); 64 | } 65 | [Test] 66 | public void LogBaseRejectNegativeValue() 67 | { 68 | Assert.Throws(() => DecimalEx.Log(-1, 10)); 69 | Assert.Throws(() => DecimalEx.Log(10, -1)); 70 | } 71 | [Test] 72 | public void LogBaseRejectZeroValue() 73 | { 74 | Assert.Throws(() => DecimalEx.Log(0, 10)); 75 | Assert.Throws(() => DecimalEx.Log(10, 0)); 76 | } 77 | 78 | #endregion 79 | 80 | #region Log 10 Tests 81 | 82 | public static decimal[][] Log10TestCases = 83 | { 84 | new[] { 15000m, 4.1760912590556812420812890085306m, Tolerance }, 85 | new[] { .15m, -0.82390874094431875791871099146938m, Tolerance }, 86 | new[] { 15m, 1.1760912590556812420812890085306m, Tolerance }, 87 | new[] { 10000000000000000000000000000m, 28m, 0m }, 88 | new[] { 1000000000000000000000000000.0m, 27m, 0m }, 89 | new[] { 100000000000000000000000000.00m, 26m, 0m }, 90 | new[] { 10000000000000000000000000.000m, 25m, 0m }, 91 | new[] { 1000000000000000000000000.0000m, 24m, 0m }, 92 | new[] { 100000000000000000000000.00000m, 23m, 0m }, 93 | new[] { 10000000000000000000000.000000m, 22m, 0m }, 94 | new[] { 1000000000000000000000.0000000m, 21m, 0m }, 95 | new[] { 100000000000000000000.00000000m, 20m, 0m }, 96 | new[] { 10000000000000000000.000000000m, 19m, 0m }, 97 | new[] { 1000000000000000000.0000000000m, 18m, 0m }, 98 | new[] { 100000000000000000.00000000000m, 17m, 0m }, 99 | new[] { 10000000000000000.000000000000m, 16m, 0m }, 100 | new[] { 1000000000000000.0000000000000m, 15m, 0m }, 101 | new[] { 100000000000000.00000000000000m, 14m, 0m }, 102 | new[] { 10000000000000.000000000000000m, 13m, 0m }, 103 | new[] { 1000000000000.0000000000000000m, 12m, 0m }, 104 | new[] { 100000000000.00000000000000000m, 11m, 0m }, 105 | new[] { 10000000000.000000000000000000m, 10m, 0m }, 106 | new[] { 1000000000.0000000000000000000m, 9m, 0m }, 107 | new[] { 100000000.00000000000000000000m, 8m, 0m }, 108 | new[] { 10000000.000000000000000000000m, 7m, 0m }, 109 | new[] { 1000000.0000000000000000000000m, 6m, 0m }, 110 | new[] { 100000.00000000000000000000000m, 5m, 0m }, 111 | new[] { 10000.000000000000000000000000m, 4m, 0m }, 112 | new[] { 1000.0000000000000000000000000m, 3m, 0m }, 113 | new[] { 100.00000000000000000000000000m, 2m, 0m }, 114 | new[] { 10.000000000000000000000000000m, 1m, 0m }, 115 | new[] { 1.0000000000000000000000000000m, 0m, 0m }, 116 | new[] { 0.1m, -1m, 0m }, 117 | new[] { 0.01m, -2m, 0m }, 118 | new[] { 0.001m, -3m, 0m }, 119 | new[] { 0.0001m, -4m, 0m }, 120 | new[] { 0.00001m, -5m, 0m }, 121 | new[] { 0.000001m, -6m, 0m }, 122 | new[] { 0.0000001m, -7m, 0m }, 123 | new[] { 0.00000001m, -8m, 0m }, 124 | new[] { 0.000000001m, -9m, 0m }, 125 | new[] { 0.0000000001m, -10m, 0m }, 126 | new[] { 0.00000000001m, -11m, 0m }, 127 | new[] { 0.000000000001m, -12m, 0m }, 128 | new[] { 0.0000000000001m, -13m, 0m }, 129 | new[] { 0.00000000000001m, -14m, 0m }, 130 | new[] { 0.000000000000001m, -15m, 0m }, 131 | new[] { 0.0000000000000001m, -16m, 0m }, 132 | new[] { 0.00000000000000001m, -17m, 0m }, 133 | new[] { 0.000000000000000001m, -18m, 0m }, 134 | new[] { 0.0000000000000000001m, -19m, 0m }, 135 | new[] { 0.00000000000000000001m, -20m, 0m }, 136 | new[] { 0.000000000000000000001m, -21m, 0m }, 137 | new[] { 0.0000000000000000000001m, -22m, 0m }, 138 | new[] { 0.00000000000000000000001m, -23m, 0m }, 139 | new[] { 0.000000000000000000000001m, -24m, 0m }, 140 | new[] { 0.0000000000000000000000001m, -25m, 0m }, 141 | new[] { 0.00000000000000000000000001m, -26m, 0m }, 142 | new[] { 0.000000000000000000000000001m, -27m, 0m }, 143 | new[] { 0.0000000000000000000000000001m, -28m, 0m }, 144 | }; 145 | 146 | [TestCaseSource(nameof(Log10TestCases))] 147 | public void Log10Test(decimal value, decimal expected, decimal tolerance) 148 | { 149 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 150 | Assert.That(DecimalEx.Log10(value), Is.EqualTo(expected).Within(tolerance)); 151 | } 152 | 153 | [Test] 154 | public void Log10RejectNegativeValue() 155 | { 156 | Assert.Throws(() => DecimalEx.Log(-1)); 157 | } 158 | [Test] 159 | public void Log10RejectZeroValue() 160 | { 161 | Assert.Throws(() => DecimalEx.Log(0)); 162 | } 163 | 164 | #endregion 165 | 166 | #region Log 2 Tests 167 | 168 | public static decimal[][] Log2TestCases = 169 | { 170 | new[] { 15000m, 13.872674880270605572935016661905m, Tolerance }, 171 | new[] { .15m, -2.7369655941662061664165804855416m, Tolerance }, 172 | new[] { 15m, 3.9068905956085185293240583734372m, Tolerance }, 173 | new[] { 1m, 0m, 0m }, 174 | }; 175 | 176 | [TestCaseSource(nameof(Log2TestCases))] 177 | public void Log2Test(decimal value, decimal expected, decimal tolerance) 178 | { 179 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 180 | Assert.That(DecimalEx.Log2(value), Is.EqualTo(expected).Within(tolerance)); 181 | } 182 | 183 | [Test] 184 | public void Log2RejectNegativeValue() 185 | { 186 | Assert.Throws(() => DecimalEx.Log(-1)); 187 | } 188 | [Test] 189 | public void Log2RejectZeroValue() 190 | { 191 | Assert.Throws(() => DecimalEx.Log(0)); 192 | } 193 | 194 | #endregion 195 | } 196 | 197 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/PowTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using DecimalMath; 4 | using NUnit.Framework; 5 | 6 | namespace DecimalExTests.DecimalExTests 7 | { 8 | 9 | public class PowTests 10 | { 11 | public const decimal FractionalTolerance = 5m; 12 | 13 | public static decimal[][] TestCases = 14 | { 15 | new[] {1m, 0m, 1m, 0m}, 16 | new[] {2m, 8m, 256m, 0m}, 17 | new[] {3m, 5m, 243m, 0m}, 18 | new[] {5m, 40m, 9094947017729282379150390625m, 0m}, 19 | new[] {99m, 1m, 99m, 0m}, 20 | 21 | // Fractional powers are going to be off a bit 22 | new[] {5m, .5m, 2.2360679774997896964091736687m, FractionalTolerance}, 23 | new[] {5m, 2.5m, 55.901699437494742410229341718m, FractionalTolerance}, 24 | new[] {5m, 10.5m, 21836601.342771383753995836609m, FractionalTolerance}, 25 | new[] {5m, 15.5m, 68239379196.160574231236989402m, FractionalTolerance}, 26 | new[] {5m, 20.5m, 213248059988001.79447261559188m, FractionalTolerance}, 27 | new[] {5m, 40.5m, 20336919783401660392056998432m, FractionalTolerance}, 28 | }; 29 | 30 | [TestCaseSource("TestCases")] 31 | public void Test(decimal x, decimal y, decimal expected, decimal tolerance) 32 | { 33 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 34 | Debug.WriteLine("Pow({0}, {1}) = {2} within {3} (is {4})", x, y, DecimalEx.Pow(x, y), tolerance, DecimalEx.Pow(x, y) - expected); 35 | Assert.That(DecimalEx.Pow(x, y), Is.EqualTo(expected).Within(tolerance)); 36 | } 37 | 38 | [Test] 39 | public void NegativeExponentOfZero() 40 | { 41 | Assert.Throws(() => DecimalEx.Pow(0, -1)); 42 | } 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/RemainderTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTests 5 | { 6 | /// 7 | /// Tests for . 8 | /// 9 | public class RemainderTests 10 | { 11 | public static decimal[][] TestCases = 12 | { 13 | new[] { decimal.MaxValue, 1m + DecimalEx.SmallestNonZeroDec, 0.0771837485735662406456049673m, 0m }, 14 | new[] { decimal.MaxValue, 1.5m, 0m, 0m }, 15 | new[] { 12m, 2.5m, 2m, 0m }, 16 | new[] { 12m, 4m, 0m, 0m }, 17 | new[] { 1700000000000000000000000000m, DecimalEx.TwoPi, 4.5962074945229569987647604598m, 0m }, 18 | new[] { -1700000000000000000000000000m, DecimalEx.TwoPi, -4.5962074945229569987647604598m, 0m }, 19 | new[] { -DecimalEx.TwoPi * 2m, DecimalEx.TwoPi, -6.2831853071795864769252867664m, 0m }, 20 | new[] { -DecimalEx.TwoPi * 2m, -DecimalEx.TwoPi, -6.2831853071795864769252867664m, 0m }, 21 | new[] { DecimalEx.TwoPi * 2m, DecimalEx.TwoPi, 6.2831853071795864769252867664m, 0m }, 22 | new[] { DecimalEx.TwoPi * 2m, -DecimalEx.TwoPi, 6.2831853071795864769252867664m, 0m }, 23 | }; 24 | 25 | [TestCaseSource("TestCases")] 26 | public void Test(decimal d1, decimal d2, decimal expected, decimal tolerance) 27 | { 28 | Assert.That(DecimalEx.Remainder(d1, d2), Is.EqualTo(expected).Within(tolerance)); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/SolveQuadraticTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using DecimalMath; 3 | using NUnit.Framework; 4 | 5 | namespace DecimalExTests.DecimalExTests 6 | { 7 | 8 | public class SolveQuadraticTests 9 | { 10 | public static IEnumerable TestCases 11 | { 12 | get 13 | { 14 | yield return new TestCaseData(1m, 4m, 1m).Returns(new[] { -0.2679491924311227064725536585m, -3.7320508075688772935274463415m }); 15 | yield return new TestCaseData(.001m, .004m, .001m).Returns(new[] { -0.2679491924311227064725536585m, -3.7320508075688772935274463415m }); 16 | yield return new TestCaseData(4m, 78m, 3m).Returns(new[] { -0.0385377002224840022315433965m, -19.461462299777515997768456604m }); 17 | yield return new TestCaseData(2m, 3m, 4m).Returns(new decimal[] {}); 18 | yield return new TestCaseData(0m, 0m, 4m).Returns(new decimal[] {}); 19 | yield return new TestCaseData(0m, 2m, 4m).Returns(new [] { -2m }); 20 | yield return new TestCaseData(1m, 2m, 1m).Returns(new [] { -1m }); 21 | yield return new TestCaseData(-0.0635m, 0.0002m, 0.000456m) 22 | .Returns(new[] { -0.0831812135522464037086398875m, 0.0863308198514590021338367379m }); 23 | yield return new TestCaseData(0.0000000000063525m, -0.000000000000021m, -0.000000045625m) 24 | .Returns(new[] { 84.74958343011745328203598717m, -84.74627764499348633988722686m }); 25 | yield return new TestCaseData(0.0000000000063525m, -121m, -0.000000045625m) 26 | .Returns(new[] { 19047619047619.047619047996113m, -.00000000037706611570247933884m }); 27 | yield return new TestCaseData(314286000m, 314159000m, 195313m) 28 | .Returns(new[] { -0.0006220882633818043324833699m, -0.9989738211948823245183085839m }); 29 | yield return new TestCaseData(DecimalEx.SmallestNonZeroDec, .002m, DecimalEx.SmallestNonZeroDec) 30 | .Returns(new[] { -.00000000000000000000000005m, -20000000000000000000000000m }); 31 | } 32 | } 33 | 34 | [TestCaseSource("TestCases")] 35 | public decimal[] Test(decimal a, decimal b, decimal c) 36 | { 37 | return DecimalEx.SolveQuadratic(a, b, c); 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTests/SqrtTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using DecimalMath; 6 | using NUnit.Framework; 7 | 8 | namespace DecimalExTests.DecimalExTests 9 | { 10 | /// 11 | /// Tests for . 12 | /// 13 | public class SqrtTests 14 | { 15 | public static decimal[][] TestCases = 16 | { 17 | new[] { DecimalEx.SmallestNonZeroDec, 0m }, 18 | new[] { 0m, 0m }, 19 | new[] { 1m, 1m }, 20 | new[] { 9m, 3m }, 21 | new[] { 1777m, 42.15447781671598408129937593716m }, 22 | new[] { 982451653m, 31344.084816756095550750189105763m }, 23 | new[] { 775351687m, 27845.137582709121891734259812835m }, 24 | new[] { 8137454422m, 90207.840135988180082281179096568m }, 25 | new[] { 4294967296m, 65536m }, 26 | new[] { 2199023255551m, 1482910.4003785933391041923650903m }, 27 | new[] { 2199023255552m, 1482910.4003789305138922795556769m }, 28 | new[] { 2199023255553m, 1482910.4003792676886803666695989m }, 29 | new[] { decimal.MaxValue, 281474976710655.99999999999999822m }, 30 | }; 31 | 32 | [TestCaseSource(nameof(TestCases))] 33 | public void Test(decimal s, decimal expected) 34 | { 35 | Assert.That(DecimalEx.Sqrt(s), Is.EqualTo(expected).Within(0m)); 36 | } 37 | 38 | [Test] 39 | public void RejectNegativeValue() 40 | { 41 | Assert.Throws(() => DecimalEx.Sqrt(-1)); 42 | } 43 | 44 | /// 45 | /// This test is just to exercise the algorithm so ignore for automatic tests. 46 | /// 47 | [Test] 48 | [Ignore("Exercise algorithm.")] 49 | public void TestRange() 50 | { 51 | var reset = new AutoResetEvent(false); 52 | var step = 1m; 53 | var i = 0m; 54 | while (true) 55 | { 56 | Task.Factory.StartNew(() => 57 | { 58 | Debug.WriteLine("Sqrt({0})={1}", i, DecimalEx.Sqrt(i)); 59 | reset.Set(); 60 | }); 61 | Assert.IsTrue(reset.WaitOne(30000)); 62 | 63 | step *= 1.01m; 64 | try { i += step; } 65 | catch (OverflowException) 66 | { 67 | if (i == Decimal.MaxValue) break; 68 | i = decimal.MaxValue; 69 | } 70 | } 71 | } 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/ACosTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class ACosTests 8 | { 9 | public const decimal Tolerance = 1m; 10 | 11 | public static decimal[][] SpecialCases = 12 | { 13 | new[] { -1m, DecimalEx.Pi, 0m }, 14 | new[] { 0m, DecimalEx.PiHalf, 0m }, 15 | new[] { 1m, 0m, 0m }, 16 | }; 17 | 18 | [TestCaseSource("SpecialCases")] 19 | public void TestSpecialCases(decimal d, decimal expected, decimal tolerance) 20 | { 21 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 22 | Assert.That(DecimalEx.ACos(d), Is.EqualTo(expected).Within(tolerance)); 23 | } 24 | 25 | public static decimal[][] TestCases = 26 | { 27 | new[] { -1m + DecimalEx.SmallestNonZeroDec, 3.1415926535897790963270196523m, 1m }, 28 | new[] { -0.00272843m, 1.5735247601801302970459854516m, 18m }, 29 | new[] { 0.00063728m, 1.5701590467517606365472099600m, 14m }, 30 | new[] { 0.5m, 1.0471975511965977461542144611m, 1m }, 31 | new[] { 0.63728m, 0.8798328036755322132581581473m, 5m }, 32 | new[] { 1m - DecimalEx.SmallestNonZeroDec, 0.0000000000000141421356237310m, 0m }, 33 | }; 34 | 35 | [TestCaseSource("TestCases")] 36 | public void Test(decimal d, decimal expected, decimal tolerance) 37 | { 38 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 39 | Assert.That(DecimalEx.ACos(d), Is.EqualTo(expected).Within(tolerance)); 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/ASinTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class ASinTests 8 | { 9 | public static decimal[][] SpecialCases = 10 | { 11 | new[] { -1m, -DecimalEx.PiHalf, 0m }, 12 | new[] { 0m, 0m, 0m }, 13 | new[] { 1m, DecimalEx.PiHalf, 0m }, 14 | }; 15 | 16 | [TestCaseSource("SpecialCases")] 17 | public void TestSpecialCases(decimal d, decimal expected, decimal tolerance) 18 | { 19 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 20 | Assert.That(DecimalEx.ASin(d), Is.EqualTo(expected).Within(tolerance)); 21 | } 22 | 23 | public static decimal[][] TestCases = 24 | { 25 | new[] { -1m + DecimalEx.SmallestNonZeroDec, -1.5707963267948824770956979607m, 11m }, 26 | new[] { -0.00272843m, -0.0027284333852336778146637600m, 1m }, 27 | new[] { 0.00063728m, 0.0006372800431359826841117316m, 2m }, 28 | new[] { 0.5m, 0.5235987755982988730771072305m, 1m }, 29 | new[] { 0.63728m, 0.6909635231193644059731635443m, 3m }, 30 | new[] { 1m - DecimalEx.SmallestNonZeroDec, 1.5707963267948824770956979607m, 11m }, 31 | }; 32 | 33 | [TestCaseSource("TestCases")] 34 | public void Test(decimal d, decimal expected, decimal tolerance) 35 | { 36 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 37 | Assert.That(DecimalEx.ASin(d), Is.EqualTo(expected).Within(tolerance)); 38 | } 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/ATan2Tests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class ATan2Tests 8 | { 9 | public const decimal Tolerance = 5m; 10 | 11 | public static decimal[][] SpecialCases = 12 | { 13 | new[] { 0m, 0m, 0m, 0m }, 14 | new[] { 1m, 0m, DecimalEx.PiHalf, 0m }, 15 | new[] { -1m, 0m, -DecimalEx.PiHalf, 0m }, 16 | new[] { 0m, 1m, 0, 0m }, 17 | new[] { 0m, -1m, DecimalEx.Pi, 0m }, 18 | }; 19 | 20 | [TestCaseSource("SpecialCases")] 21 | public void TestSpecialCases(decimal y, decimal x, decimal expected, decimal tolerance) 22 | { 23 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 24 | Assert.That(DecimalEx.ATan2(y, x), Is.EqualTo(expected).Within(tolerance)); 25 | } 26 | 27 | public static decimal[][] TestCases = 28 | { 29 | new[] { 1m, 1m, DecimalEx.PiQuarter, Tolerance }, 30 | new[] { 1m, -1m, DecimalEx.PiHalf + DecimalEx.PiQuarter, Tolerance }, 31 | new[] { -1m, -1m, -(DecimalEx.PiHalf + DecimalEx.PiQuarter), Tolerance }, 32 | new[] { -1m, 1m, -DecimalEx.PiQuarter, Tolerance }, 33 | 34 | new[] { 2m, .5m, 1.3258176636680324650592392104285m, Tolerance }, 35 | new[] { 2m, -.5m, 1.815774989921760773403404172851m, Tolerance }, 36 | new[] { -2m, -.5m, -1.815774989921760773403404172851m, Tolerance }, 37 | new[] { -2m, .5m, -1.3258176636680324650592392104285m, Tolerance }, 38 | }; 39 | 40 | [TestCaseSource("TestCases")] 41 | public void Test(decimal y, decimal x, decimal expected, decimal tolerance) 42 | { 43 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 44 | Assert.That(DecimalEx.ATan2(y, x), Is.EqualTo(expected).Within(tolerance)); 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/ATanTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class ATanTests 8 | { 9 | public const decimal Tolerance = 5m; 10 | 11 | public static decimal[][] SpecialCases = 12 | { 13 | new[] { -1m, -DecimalEx.PiQuarter, 0m }, 14 | new[] { 0m, 0m, 0m }, 15 | new[] { 1m, DecimalEx.PiQuarter, 0m }, 16 | }; 17 | 18 | [TestCaseSource("SpecialCases")] 19 | public void TestSpecialCases(decimal d, decimal expected, decimal tolerance) 20 | { 21 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 22 | Assert.That(DecimalEx.ATan(d), Is.EqualTo(expected).Within(tolerance)); 23 | } 24 | 25 | public static decimal[][] TestCases = 26 | { 27 | new[] { 0.5m, 0.46364760900080611621425623146121m, Tolerance }, 28 | new[] { 0.625m, 0.55859931534356243597150821640166m, Tolerance }, 29 | new[] { 1.5m, 0.98279372324732906798571061101467m, Tolerance }, 30 | new[] { 15877m, 1.5707333426040118384856405100905m, Tolerance }, 31 | new[] { -37m, -1.5437758776076318304431463582812m, Tolerance }, 32 | new[] { 0.0000000000000007777777777777m, .0000000000000007777777777777m, Tolerance }, 33 | new[] { 1700000000000000000000000000m, 1.5707963267948966192313216910515m, Tolerance }, 34 | new[] { 39614081257132168796771975168m, 1.5707963267948966192313216916145m, Tolerance }, 35 | new[] { 79228162514264337593543950335m, 1.5707963267948966192313216916271m, Tolerance }, 36 | }; 37 | 38 | [TestCaseSource("TestCases")] 39 | public void Test(decimal d, decimal expected, decimal tolerance) 40 | { 41 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 42 | Assert.That(DecimalEx.ATan(d), Is.EqualTo(expected).Within(tolerance)); 43 | } 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/CosTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class CosTests 8 | { 9 | public const decimal Tolerance = 10m; 10 | 11 | public static decimal[][] SpecialCases = 12 | { 13 | new[] { 0m, 1m, 0m }, 14 | new[] { DecimalEx.TwoPi, 1m, 0m }, 15 | new[] { DecimalEx.Pi, -1m, 0m }, 16 | new[] { DecimalEx.PiHalf, 0m, 0m }, 17 | new[] { DecimalEx.Pi + DecimalEx.PiHalf, 0m, 0m }, 18 | }; 19 | 20 | [TestCaseSource("SpecialCases")] 21 | public void TestSpecialCases(decimal x, decimal expected, decimal tolerance) 22 | { 23 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 24 | Assert.That(DecimalEx.Cos(x), Is.EqualTo(expected).Within(tolerance)); 25 | } 26 | 27 | // I generated the expected value using x % TwoPi 28 | public static decimal[][] TestCases = 29 | { 30 | new[] { 0.5m, 0.87758256189037271611628158260383m, Tolerance }, 31 | new[] { 0.625m, 0.81096311950521790218953480394108m, Tolerance }, 32 | new[] { 1.5m, 0.07073720166770291008818985143427m, Tolerance }, 33 | new[] { 15877m, 0.820065281433903725830087668850260m, 3 * Tolerance }, 34 | new[] { -4538.5m, -0.4523618664556990608284425320411137m, Tolerance }, 35 | new[] { -37m, 0.7654140519453433564910812927706m, 2 * Tolerance }, 36 | new[] { 0.0000000000000007777777777777m, 1m, Tolerance }, 37 | new[] { 1700000000000000000000000000m, -0.115920289925442305587019103419483m, Tolerance }, 38 | new[] { 39614081257132168796771975168m, 0.28745966621903537726698955261768m, Tolerance }, 39 | new[] { 79228162514264337593543950335m, -0.00766925169057786160588762018743m, Tolerance }, 40 | }; 41 | 42 | [TestCaseSource("TestCases")] 43 | public void Test(decimal x, decimal expected, decimal tolerance) 44 | { 45 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 46 | Assert.That(DecimalEx.Cos(x), Is.EqualTo(expected).Within(tolerance)); 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/NormalizeAngleTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class NormalizeTests 8 | { 9 | public static decimal[][] TestCases = 10 | { 11 | new[] {0m, 0m}, 12 | new[] {-DecimalEx.Pi, DecimalEx.Pi}, 13 | new[] {-31419.068128551522177864896476m, 3.1415926535897932384626437666m}, // equivalent to -1001π 14 | new[] {-DecimalEx.TwoPi, 0m}, 15 | new[] {-2*DecimalEx.TwoPi, 2*DecimalEx.SmallestNonZeroDec}, 16 | new[] {15.707963267948966192313216916m, 3.1415926535897932384626433828m}, // equivalent to 5π 17 | new[] {4 * DecimalEx.Pi + DecimalEx.PiHalf, 1.5707963267948966192313216918m}, 18 | new[] {522277854577893m, 4.7016207739845413794891781334m}, 19 | new[] {-522277854577893m, 1.5815645331950450974361086332m}, 20 | }; 21 | 22 | [TestCaseSource("TestCases")] 23 | public void Test(decimal d, decimal expected) 24 | { 25 | Assert.That(DecimalEx.NormalizeAngle(d), Is.EqualTo(expected)); 26 | } 27 | 28 | public static decimal[][] TestCasesDeg = 29 | { 30 | new[] {0m, 0m}, 31 | new[] {-180m, 180m}, 32 | new[] {-10001 * 180m, 180m}, 33 | new[] {-360m, 0m}, 34 | new[] {-2*360m - 100m * DecimalEx.SmallestNonZeroDec, 360m - 100m * DecimalEx.SmallestNonZeroDec}, 35 | new[] {5 * 180m, 180m}, 36 | new[] {4 * 180m + 90m, 90m}, 37 | new[] {522277854577893m, 333m}, 38 | new[] {-522277854577893m, 27m}, 39 | }; 40 | 41 | [TestCaseSource("TestCasesDeg")] 42 | public void TestDeg(decimal d, decimal expected) 43 | { 44 | Assert.That(DecimalEx.NormalizeAngleDeg(d), Is.EqualTo(expected)); 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/DecimalExTrigTests/SinTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests.DecimalExTrigTests 5 | { 6 | 7 | public class SinTests 8 | { 9 | public const decimal Tolerance = 10m; 10 | 11 | public static decimal[][] TestCases = 12 | { 13 | new[] {0m, 0m, 0m}, 14 | new[] {DecimalEx.Pi, 0m, 0m}, 15 | new[] {DecimalEx.TwoPi, 0m, 0m}, 16 | new[] {DecimalEx.PiHalf, 1m, 0m}, 17 | new[] {DecimalEx.Pi + DecimalEx.PiHalf, -1m, 0m}, 18 | new[] {12m, -0.5365729180004349716653742282424m, Tolerance}, 19 | new[] {2.667m, 0.45697615904786257495867623434033m, Tolerance}, 20 | new[] {-12m, 0.5365729180004349716653742282424m, Tolerance}, 21 | new[] {3.1415926535897932384626433832m, 0m, Tolerance}, 22 | }; 23 | 24 | [TestCaseSource("TestCases")] 25 | public void Test(decimal d, decimal expected, decimal tolerance) 26 | { 27 | tolerance = Helper.GetScaledTolerance(expected, (int)tolerance, true); 28 | Assert.That(DecimalEx.Sin(d), Is.EqualTo(expected).Within(tolerance)); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/ExtensionsTest/InRangeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using DecimalMath; 4 | using NUnit.Framework; 5 | 6 | namespace DecimalExTests.ExtensionsTest 7 | { 8 | public class InRangeTests 9 | { 10 | public static IEnumerable InclusiveTestCases 11 | { 12 | get 13 | { 14 | yield return new TestCaseData(3.5m, 1m, 4m).Returns(true); 15 | yield return new TestCaseData(1m, 1m, 4m).Returns(true); 16 | yield return new TestCaseData(4m, 1m, 4m).Returns(true); 17 | yield return new TestCaseData(0m, 1m, 4m).Returns(false); 18 | yield return new TestCaseData(4.1m, 1m, 4m).Returns(false); 19 | } 20 | } 21 | 22 | [TestCaseSource("InclusiveTestCases")] 23 | public bool InclusiveTests(decimal value, decimal lowerLimit, decimal upperLimit) 24 | { 25 | return value.InRangeIncl(lowerLimit, upperLimit); 26 | } 27 | 28 | public static IEnumerable InclusiveTestCasesWithException 29 | { 30 | get 31 | { 32 | yield return new TestCaseData(0m, 4m, 1m); 33 | } 34 | } 35 | 36 | [TestCaseSource("InclusiveTestCasesWithException")] 37 | public void InclusiveTestsWithException(decimal value, decimal lowerLimit, decimal upperLimit) 38 | { 39 | Assert.That(() => value.InRangeIncl(lowerLimit, upperLimit), Throws.Exception); 40 | } 41 | 42 | public static IEnumerable ExclusiveTestCases 43 | { 44 | get 45 | { 46 | yield return new TestCaseData(3.5m, 1m, 4m).Returns(true); 47 | yield return new TestCaseData(1m, 1m, 4m).Returns(false); 48 | yield return new TestCaseData(4m, 1m, 4m).Returns(false); 49 | yield return new TestCaseData(0m, 1m, 4m).Returns(false); 50 | yield return new TestCaseData(4.1m, 1m, 4m).Returns(false); 51 | } 52 | } 53 | 54 | [TestCaseSource("ExclusiveTestCases")] 55 | public bool ExclusiveTests(decimal value, decimal lowerLimit, decimal upperLimit) 56 | { 57 | return value.InRangeExcl(lowerLimit, upperLimit); 58 | } 59 | 60 | public static IEnumerable ExclusiveTestCasesWithException 61 | { 62 | get 63 | { 64 | yield return new TestCaseData(0m, 4m, 1m); 65 | } 66 | } 67 | 68 | [TestCaseSource("ExclusiveTestCasesWithException")] 69 | public void ExclusiveTestsWithException(decimal value, decimal lowerLimit, decimal upperLimit) 70 | { 71 | Assert.That(() => value.InRangeExcl(lowerLimit, upperLimit), Throws.Exception); 72 | } 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/ExtensionsTest/RoundFromZeroTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using DecimalMath; 4 | using NUnit.Framework; 5 | 6 | namespace DecimalExTests.ExtensionsTest 7 | { 8 | 9 | public class RoundFromZeroTests 10 | { 11 | public static IEnumerable TestCases 12 | { 13 | get 14 | { 15 | yield return new TestCaseData(3.5m, 0).Returns(4m); 16 | yield return new TestCaseData(2.8m, 0).Returns(3m); 17 | yield return new TestCaseData(2.5m, 0).Returns(3m); 18 | yield return new TestCaseData(2.1m, 0).Returns(2m); 19 | yield return new TestCaseData(-2.1m, 0).Returns(-2m); 20 | yield return new TestCaseData(-2.5m, 0).Returns(-3m); 21 | yield return new TestCaseData(-2.8m, 0).Returns(-3m); 22 | yield return new TestCaseData(-3.5m, 0).Returns(-4m); 23 | 24 | yield return new TestCaseData(.35m, 1).Returns(.4m); 25 | yield return new TestCaseData(.28m, 1).Returns(.3m); 26 | yield return new TestCaseData(.25m, 1).Returns(.3m); 27 | yield return new TestCaseData(.21m, 1).Returns(.2m); 28 | yield return new TestCaseData(-.21m, 1).Returns(-.2m); 29 | yield return new TestCaseData(-.25m, 1).Returns(-.3m); 30 | yield return new TestCaseData(-.28m, 1).Returns(-.3m); 31 | yield return new TestCaseData(-.35m, 1).Returns(-.4m); 32 | } 33 | } 34 | 35 | [TestCaseSource("TestCases")] 36 | public decimal Test(decimal value, int places) 37 | { 38 | return value.RoundFromZero(places); 39 | } 40 | 41 | [Test] 42 | public void TestArgumentBounds() 43 | { 44 | Assert.Throws(() => 10m.RoundFromZero(-1)); 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /DecimalEx.Tests/Helper.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | 3 | namespace DecimalExTests 4 | { 5 | static class Helper 6 | { 7 | /// 8 | /// Scales a tolerance to match the precision of the expected value. 9 | /// 10 | public static decimal GetScaledTolerance(decimal expected, int tolerance, bool countTrailingZeros) 11 | { 12 | decimal toleranceAtScale = tolerance; 13 | var precision = DecimalEx.GetDecimalPlaces(expected, countTrailingZeros); 14 | for (var i = 0; i < precision; i++) toleranceAtScale /= 10m; 15 | return toleranceAtScale; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DecimalEx.Tests/MatrixTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests 5 | { 6 | public class MatrixTests 7 | { 8 | [Test] 9 | public void TestGetIdentity() 10 | { 11 | var testMatrix = Matrix.GetIdentityMatrix(3); 12 | 13 | Assert.That(testMatrix, 14 | Is.EqualTo(new[,] 15 | { 16 | { 1, 0, 0 }, 17 | { 0, 1, 0 }, 18 | { 0, 0, 1 } 19 | })); 20 | } 21 | 22 | [Test] 23 | public void TestMultiplySameSize() 24 | { 25 | var testMatrix1 = new decimal[,] 26 | { 27 | { 1, 2, 3 }, 28 | { 11, 12, 13 }, 29 | { 21, 22, 23 } 30 | }; 31 | 32 | var testMatrix2 = new decimal[,] 33 | { 34 | { 31, 32, 33 }, 35 | { 34, 35, 36 }, 36 | { 37, 38, 39 } 37 | }; 38 | 39 | var testMatrix3 = Matrix.Multiply(testMatrix1, testMatrix2); 40 | 41 | Assert.That(testMatrix3, 42 | Is.EqualTo(new[,] 43 | { 44 | { 210, 216, 222 }, 45 | { 1230, 1266, 1302 }, 46 | { 2250, 2316, 2382 }, 47 | })); 48 | 49 | testMatrix3 = Matrix.Multiply(testMatrix2, testMatrix1); 50 | 51 | Assert.That(testMatrix3, 52 | Is.EqualTo(new[,] 53 | { 54 | { 1076, 1172, 1268 }, 55 | { 1175, 1280, 1385 }, 56 | { 1274, 1388, 1502 }, 57 | })); 58 | } 59 | 60 | [Test] 61 | public void TestMultiplyDifferentSize() 62 | { 63 | var testMatrix1 = new decimal[,] 64 | { 65 | { 1, 0, -2 }, 66 | { 0, 3, -1 } 67 | }; 68 | var testMatrix2 = new decimal[,] 69 | { 70 | { 0, 3 }, 71 | { -2, -1 }, 72 | { 0, 4 } 73 | }; 74 | 75 | var testMatrix3 = Matrix.Multiply(testMatrix1, testMatrix2); 76 | Assert.That(testMatrix3, Is.EqualTo(new[,] 77 | { 78 | { 0, -5 }, 79 | { -6, -7 } 80 | })); 81 | 82 | testMatrix3 = Matrix.Multiply(testMatrix2, testMatrix1); 83 | Assert.That(testMatrix3, 84 | Is.EqualTo(new[,] 85 | { 86 | { 0, 9, -3 }, 87 | { -2, -3, 5 }, 88 | { 0, 12, -4 } 89 | })); 90 | } 91 | 92 | [Test] 93 | public void TestMultiplyWithColumnMatrix() 94 | { 95 | var testMatrix = new decimal[,] 96 | { 97 | { 1, 2, 3 }, 98 | { 11, 12, 13 }, 99 | { 21, 22, 23 }, 100 | }; 101 | var colMatrix = new decimal[,] 102 | { 103 | { 67 }, 104 | { 45 }, 105 | { 33 }, 106 | }; 107 | 108 | var transformed = Matrix.Multiply(testMatrix, colMatrix); 109 | 110 | Assert.That(transformed, 111 | Is.EqualTo(new decimal[,] 112 | { 113 | { 256 }, 114 | { 1706 }, 115 | { 3156 } 116 | })); 117 | } 118 | 119 | [Test] 120 | public void TestToColumn() 121 | { 122 | var values = new decimal[] { 1, 2, 3 }; 123 | 124 | var columnMatrix = Matrix.ToColumn(values); 125 | 126 | 127 | Assert.That(columnMatrix, 128 | Is.EqualTo(new decimal[,] 129 | { 130 | { 1 }, 131 | { 2 }, 132 | { 3 } 133 | })); 134 | } 135 | 136 | [Test] 137 | public void TestToRow() 138 | { 139 | var values = new decimal[] { 1, 2, 3 }; 140 | 141 | var rowMatrix = Matrix.ToRow(values); 142 | 143 | 144 | Assert.That(rowMatrix, 145 | Is.EqualTo(new decimal[,] 146 | { 147 | { 1, 2, 3 }, 148 | })); 149 | } 150 | 151 | [Test] 152 | public void TestToArray() 153 | { 154 | var rowMatrix = new decimal[,] 155 | { 156 | { 1, 2, 3 }, 157 | }; 158 | var values = Matrix.RowOrColumnToArray(rowMatrix); 159 | Assert.That(values, Is.EqualTo(new decimal[] { 1, 2, 3 })); 160 | 161 | var columnMatrix = new decimal[,] 162 | { 163 | { 1 }, 164 | { 2 }, 165 | { 3 } 166 | }; 167 | values = Matrix.RowOrColumnToArray(columnMatrix); 168 | Assert.That(values, Is.EqualTo(new decimal[] { 1, 2, 3 })); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /DecimalEx.Tests/TransformationMatrixBaseTests.cs: -------------------------------------------------------------------------------- 1 | using DecimalMath; 2 | using NUnit.Framework; 3 | 4 | namespace DecimalExTests 5 | { 6 | public class TransformationMatrixBaseTests 7 | { 8 | private class TransformationTester : TransformationMatrixBase 9 | { 10 | public TransformationTester():base(3) 11 | { } 12 | public TransformationTester(decimal[,] values): base(3, values) 13 | { } 14 | 15 | public decimal[,] GetM() 16 | { 17 | return M; 18 | } 19 | } 20 | 21 | [Test] 22 | public void TestConstructor() 23 | { 24 | var testMatrix = new TransformationTester(); 25 | 26 | // Make sure we've initialized to the identity matrix 27 | Assert.That(testMatrix.GetM(), 28 | Is.EqualTo(new[,] 29 | { 30 | { 1, 0, 0 }, 31 | { 0, 1, 0 }, 32 | { 0, 0, 1 } 33 | })); 34 | } 35 | 36 | [Test] 37 | public void TestConstructorWithValues() 38 | { 39 | var testMatrix = new TransformationTester(new decimal[,] 40 | { 41 | { 11, 12, 13 }, 42 | { 21, 22, 23 }, 43 | { 31, 32, 33 } 44 | }); 45 | 46 | Assert.That(testMatrix.GetM(), 47 | Is.EqualTo(new[,] 48 | { 49 | { 11, 12, 13 }, 50 | { 21, 22, 23 }, 51 | { 31, 32, 33 } 52 | })); 53 | } 54 | 55 | [Test] 56 | public void TestSetRow() 57 | { 58 | var testMatrix = new TransformationTester(); 59 | 60 | testMatrix.SetRow(1, new decimal[] { 4, 5, 6 }); 61 | 62 | Assert.That(testMatrix.GetM(), 63 | Is.EqualTo(new[,] 64 | { 65 | { 1, 0, 0 }, 66 | { 4, 5, 6 }, 67 | { 0, 0, 1 } 68 | })); 69 | } 70 | 71 | [Test] 72 | public void TestMultiply() 73 | { 74 | var testMatrix1 = new TransformationTester(new decimal[,] 75 | { 76 | { 1, 2, 3 }, 77 | { 11, 12, 13 }, 78 | { 21, 22, 23 } 79 | }); 80 | 81 | var testMatrix2 = new TransformationTester(new decimal[,] 82 | { 83 | { 31, 32, 33 }, 84 | { 34, 35, 36 }, 85 | { 37, 38, 39 } 86 | }); 87 | 88 | var testMatrix3 = testMatrix1.Multiply(testMatrix2); 89 | 90 | Assert.That(testMatrix3.GetM(), 91 | Is.EqualTo(new[,] 92 | { 93 | { 210, 216, 222 }, 94 | { 1230, 1266, 1302 }, 95 | { 2250, 2316, 2382 } 96 | })); 97 | } 98 | 99 | [Test] 100 | public void TestTransform() 101 | { 102 | var testMatrix = new TransformationTester(new decimal[,] 103 | { 104 | { 1, 2, 3 }, 105 | { 11, 12, 13 }, 106 | { 21, 22, 23 } 107 | }); 108 | 109 | var transformed = testMatrix.Transform(new decimal[] { 67, 45, 33 }); 110 | 111 | Assert.That(transformed, Is.EqualTo(new[] { 256, 1706, 3156 })); 112 | } 113 | 114 | [Test] 115 | public void TestCopy() 116 | { 117 | var testMatrix = new TransformationTester(new decimal[,] 118 | { 119 | { 1, 2, 3 }, 120 | { 11, 12, 13 }, 121 | { 21, 22, 23 } 122 | }); 123 | 124 | var copyMatrix = testMatrix.Copy(); 125 | 126 | Assert.That(!ReferenceEquals(testMatrix, copyMatrix), "Copy matrix is a shallow copy!"); 127 | 128 | Assert.That(copyMatrix.GetM(), 129 | Is.EqualTo(new[,] 130 | { 131 | { 1, 2, 3 }, 132 | { 11, 12, 13 }, 133 | { 21, 22, 23 } 134 | })); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /DecimalEx/DecimalEx.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | namespace DecimalMath 6 | { 7 | /// 8 | /// Contains mathematical operations performed in Decimal precision. 9 | /// 10 | public static partial class DecimalEx 11 | { 12 | /// 13 | /// Returns the square root of a given number. 14 | /// 15 | /// A non-negative number. 16 | /// 17 | /// Uses an implementation of the "Babylonian Method". 18 | /// See http://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method 19 | /// 20 | public static decimal Sqrt(decimal s) 21 | { 22 | if (s < 0) 23 | throw new ArgumentException("Square root not defined for Decimal data type when less than zero!", "s"); 24 | 25 | // Prevent divide-by-zero errors below. Dividing either 26 | // of the numbers below will yield a recurring 0 value 27 | // for halfS eventually converging on zero. 28 | if (s == 0 || s == SmallestNonZeroDec) return 0; 29 | 30 | decimal x; 31 | var halfS = s / 2m; 32 | var lastX = -1m; 33 | decimal nextX; 34 | 35 | // Begin with an estimate for the square root. 36 | // Use hardware to get us there quickly. 37 | x = (decimal)Math.Sqrt(decimal.ToDouble(s)); 38 | 39 | while (true) 40 | { 41 | nextX = x / 2m + halfS / x; 42 | 43 | // The next check effectively sees if we've ran out of 44 | // precision for our data type. 45 | if (nextX == x || nextX == lastX) break; 46 | 47 | lastX = x; 48 | x = nextX; 49 | } 50 | 51 | return nextX; 52 | } 53 | 54 | /// 55 | /// Returns a specified number raised to the specified power. 56 | /// 57 | /// A number to be raised to a power. 58 | /// A number that specifies a power. 59 | public static decimal Pow(decimal x, decimal y) 60 | { 61 | decimal result; 62 | var isNegativeExponent = false; 63 | 64 | // Handle negative exponents 65 | if (y < 0) 66 | { 67 | isNegativeExponent = true; 68 | y = Math.Abs(y); 69 | } 70 | 71 | if (y == 0) 72 | { 73 | result = 1; 74 | } 75 | else if (y == 1) 76 | { 77 | result = x; 78 | } 79 | else 80 | { 81 | var t = decimal.Truncate(y); 82 | 83 | if (y == t) // Integer powers 84 | { 85 | result = ExpBySquaring(x, y); 86 | } 87 | else // Fractional power < 1 88 | { 89 | // See http://en.wikipedia.org/wiki/Exponent#Real_powers 90 | // The next line is an optimization of Exp(y * Log(x)) for better precision 91 | result = ExpBySquaring(x, t) * Exp((y - t) * Log(x)); 92 | } 93 | } 94 | 95 | if (isNegativeExponent) 96 | { 97 | // Note, for IEEE floats this would be Infinity and not an exception... 98 | if (result == 0) throw new OverflowException("Negative power of 0 is undefined!"); 99 | 100 | result = 1 / result; 101 | } 102 | 103 | return result; 104 | } 105 | 106 | /// 107 | /// Raises one number to an integral power. 108 | /// 109 | /// 110 | /// See http://en.wikipedia.org/wiki/Exponentiation_by_squaring 111 | /// 112 | private static decimal ExpBySquaring(decimal x, decimal y) 113 | { 114 | Debug.Assert(y >= 0 && decimal.Truncate(y) == y, "Only non-negative, integer powers supported."); 115 | if (y < 0) throw new ArgumentOutOfRangeException("y", "Negative exponents not supported!"); 116 | if (decimal.Truncate(y) != y) throw new ArgumentException("Exponent must be an integer!", "y"); 117 | 118 | var result = 1m; 119 | var multiplier = x; 120 | 121 | while (y > 0) 122 | { 123 | if ((y % 2) == 1) 124 | { 125 | result *= multiplier; 126 | y -= 1; 127 | if (y == 0) break; 128 | } 129 | 130 | multiplier *= multiplier; 131 | y /= 2; 132 | } 133 | 134 | return result; 135 | } 136 | 137 | /// 138 | /// Returns e raised to the specified power. 139 | /// 140 | /// A number specifying a power. 141 | public static decimal Exp(decimal d) 142 | { 143 | decimal result; 144 | decimal nextAdd; 145 | int iteration; 146 | bool reciprocal; 147 | decimal t; 148 | 149 | reciprocal = d < 0; 150 | d = Math.Abs(d); 151 | 152 | t = decimal.Truncate(d); 153 | 154 | if (d == 0) 155 | { 156 | result = 1; 157 | } 158 | else if (d == 1) 159 | { 160 | result = E; 161 | } 162 | else if (Math.Abs(d) > 1 && t != d) 163 | { 164 | // Split up into integer and fractional 165 | result = Exp(t) * Exp(d - t); 166 | } 167 | else if (d == t) // Integer power 168 | { 169 | result = ExpBySquaring(E, d); 170 | } 171 | else // Fractional power < 1 172 | { 173 | // See http://mathworld.wolfram.com/ExponentialFunction.html 174 | iteration = 0; 175 | nextAdd = 0; 176 | result = 0; 177 | 178 | while (true) 179 | { 180 | if (iteration == 0) 181 | { 182 | nextAdd = 1; // == Pow(d, 0) / Factorial(0) == 1 / 1 == 1 183 | } 184 | else 185 | { 186 | nextAdd *= d / iteration; // == Pow(d, iteration) / Factorial(iteration) 187 | } 188 | 189 | if (nextAdd == 0) break; 190 | 191 | result += nextAdd; 192 | 193 | iteration += 1; 194 | } 195 | } 196 | 197 | // Take reciprocal if this was a negative power 198 | // Note that result will never be zero at this point. 199 | if (reciprocal) result = 1 / result; 200 | 201 | return result; 202 | } 203 | 204 | /// 205 | /// Returns the natural (base e) logarithm of a specified number. 206 | /// 207 | /// A number whose logarithm is to be found. 208 | /// 209 | /// I'm still not satisfied with the speed. I tried several different 210 | /// algorithms that you can find in a historical version of this 211 | /// source file. The one I settled on was the best of mediocrity. 212 | /// 213 | public static decimal Log(decimal d) 214 | { 215 | if (d < 0) throw new ArgumentException("Natural logarithm is a complex number for values less than zero!", "d"); 216 | if (d == 0) throw new OverflowException("Natural logarithm is defined as negative infinity at zero which the Decimal data type can't represent!"); 217 | 218 | if (d == 1) return 0; 219 | 220 | if (d >= 1) 221 | { 222 | var power = 0m; 223 | 224 | var x = d; 225 | while (x > 1) 226 | { 227 | x /= 10; 228 | power += 1; 229 | } 230 | 231 | return Log(x) + power * Ln10; 232 | } 233 | 234 | // See http://en.wikipedia.org/wiki/Natural_logarithm#Numerical_value 235 | // for more information on this faster-converging series. 236 | 237 | decimal y; 238 | decimal ySquared; 239 | 240 | var iteration = 0; 241 | var exponent = 0m; 242 | var nextAdd = 0m; 243 | var result = 0m; 244 | 245 | y = (d - 1) / (d + 1); 246 | ySquared = y * y; 247 | 248 | while (true) 249 | { 250 | if (iteration == 0) 251 | { 252 | exponent = 2 * y; 253 | } 254 | else 255 | { 256 | exponent = exponent * ySquared; 257 | } 258 | 259 | nextAdd = exponent / (2 * iteration + 1); 260 | 261 | if (nextAdd == 0) break; 262 | 263 | result += nextAdd; 264 | 265 | iteration += 1; 266 | } 267 | 268 | return result; 269 | 270 | } 271 | 272 | /// 273 | /// Returns the logarithm of a specified number in a specified base. 274 | /// 275 | /// A number whose logarithm is to be found. 276 | /// The base of the logarithm. 277 | /// 278 | /// This is a relatively naive implementation that simply divides the 279 | /// natural log of by the natural log of the base. 280 | /// 281 | public static decimal Log(decimal d, decimal newBase) 282 | { 283 | // Short circuit the checks below if d is 1 because 284 | // that will yield 0 in the numerator below and give us 285 | // 0 for any base, even ones that would yield infinity. 286 | if (d == 1) return 0m; 287 | 288 | if (newBase == 1) throw new InvalidOperationException("Logarithm for base 1 is undefined."); 289 | if (d < 0) throw new ArgumentException("Logarithm is a complex number for values less than zero!", nameof(d)); 290 | if (d == 0) throw new OverflowException("Logarithm is defined as negative infinity at zero which the Decimal data type can't represent!"); 291 | if (newBase < 0) throw new ArgumentException("Logarithm base would be a complex number for values less than zero!", nameof(newBase)); 292 | if (newBase == 0) throw new OverflowException("Logarithm base would be negative infinity at zero which the Decimal data type can't represent!"); 293 | 294 | return Log(d) / Log(newBase); 295 | } 296 | 297 | /// 298 | /// Returns the base 10 logarithm of a specified number. 299 | /// 300 | /// A number whose logarithm is to be found. 301 | public static decimal Log10(decimal d) 302 | { 303 | if (d < 0) throw new ArgumentException("Logarithm is a complex number for values less than zero!", nameof(d)); 304 | if (d == 0) throw new OverflowException("Logarithm is defined as negative infinity at zero which the Decimal data type can't represent!"); 305 | 306 | // Shrink precision from the input value and get bits for analysis 307 | var parts = decimal.GetBits(d / 1.000000000000000000000000000000000m); 308 | var scale = (parts[3] >> 16) & 0x7F; 309 | 310 | // Handle special cases of .1, .01, .001, etc. 311 | if (parts[0] == 1 && parts[1] == 0 && parts[2] == 0) 312 | { 313 | return -1 * scale; 314 | } 315 | 316 | // Handle special cases of powers of 10 317 | // Note: A binary search was actually found to be faster on average probably because it takes fewer iterations to find no match. 318 | // It's even faster than doing a modulus 10 check first. 319 | if (scale == 0) 320 | { 321 | var powerOf10 = Array.BinarySearch(PowersOf10, d); 322 | if (powerOf10 >= 0) 323 | { 324 | return powerOf10; 325 | } 326 | } 327 | 328 | return Log(d) / Ln10; 329 | } 330 | 331 | /// 332 | /// Returns the base 2 logarithm of a specified number. 333 | /// 334 | /// A number whose logarithm is to be found. 335 | public static decimal Log2(decimal d) 336 | { 337 | if (d < 0) throw new ArgumentException("Logarithm is a complex number for values less than zero!", nameof(d)); 338 | if (d == 0) throw new OverflowException("Logarithm is defined as negative infinity at zero which the Decimal data type can't represent!"); 339 | 340 | return Log(d) / Ln2; 341 | } 342 | 343 | /// 344 | /// Returns the factorial of a number n expressed as n!. Factorial is 345 | /// calculated as follows: n * (n - 1) * (n - 2) * ... * 1 346 | /// 347 | /// An integer. 348 | /// 349 | /// Only supports non-negative integers. 350 | /// 351 | public static decimal Factorial(decimal n) 352 | { 353 | if (n < 0) throw new ArgumentException("Values less than zero are not supoprted!", "n"); 354 | if (Decimal.Truncate(n) != n) throw new ArgumentException("Fractional values are not supoprted!", "n"); 355 | 356 | var ret = 1m; 357 | 358 | for (var i = n; i >= 2; i += -1) 359 | { 360 | ret *= i; 361 | } 362 | 363 | return ret; 364 | } 365 | 366 | /// 367 | /// Uses the quadratic formula to factor and solve the equation ax^2 + bx + c = 0 368 | /// 369 | /// The coefficient of x^2. 370 | /// The coefficient of x. 371 | /// The constant. 372 | /// 373 | /// Will return empty results where there is no solution and for complex solutions. 374 | /// See http://www.wikihow.com/Factor-Second-Degree-Polynomials-%28Quadratic-Equations%29 375 | /// 376 | public static decimal[] SolveQuadratic(decimal a, decimal b, decimal c) 377 | { 378 | // Horizontal line is either 0 nowhere or everywhere so no solution. 379 | if ((a == 0) && (b == 0)) return new decimal[] { }; 380 | 381 | if ((a == 0)) 382 | { 383 | // This is actually a linear equation. Using quadratic would result in a 384 | // divide by zero so use the following equation. 385 | // 0 = b * x + c 386 | // -c = b * x 387 | // -c / b = x 388 | return new[] { -c / b }; 389 | } 390 | 391 | // If all our coefficients have an absolute value less than 1, 392 | // then we'll lose precision in calculating the discriminant and 393 | // its root. Since we're solving for ax^2 + bx + c = 0 we can 394 | // multiply the coefficients by whatever we want until they are 395 | // in a more favorable range. In this case, we'll make sure here 396 | // that at least one number is greater than 1 or less than -1. 397 | while ((-1 < a && a < 1) && (-1 < b && b < 1) && (-1 < c && c < 1)) 398 | { 399 | a *= 10; 400 | b *= 10; 401 | c *= 10; 402 | } 403 | 404 | var discriminant = b * b - 4 * a * c; 405 | 406 | // Allow for a little rounding error and treat this as 0 407 | if (discriminant == -SmallestNonZeroDec) discriminant = 0; 408 | 409 | // Solution is complex -- shape does not intersect 0. 410 | if (discriminant < 0) return new decimal[] { }; 411 | 412 | var sqrtOfDiscriminant = Sqrt(discriminant); 413 | 414 | // Select quadratic or "citardauq" depending on which one has a matching 415 | // sign between -b and the square root. This improves precision, sometimes 416 | // dramatically. See: http://math.stackexchange.com/a/56982 417 | var h = Math.Sign(b) == -1 ? (-b + sqrtOfDiscriminant) / (2 * a) : (2 * c) / (-b - sqrtOfDiscriminant); 418 | var k = Math.Sign(b) == +1 ? (-b - sqrtOfDiscriminant) / (2 * a) : (2 * c) / (-b + sqrtOfDiscriminant); 419 | 420 | // ax^2 + bx + c = (x - h)(x - k) 421 | // (x - h)(x - k) = 0 means h and k are the values for x 422 | // that will make the equation = 0 423 | return h == k 424 | ? new[] { h } 425 | : new[] { h, k }; 426 | } 427 | 428 | /// 429 | /// Returns the floor of a Decimal value at the given number of digits. 430 | /// 431 | /// A decimal value. 432 | /// An integer representing the maximum number of digits 433 | /// after the decimal point to end up with. 434 | public static decimal Floor(decimal value, int places = 0) 435 | { 436 | if (places < 0) throw new ArgumentOutOfRangeException("places", "Places must be greater than or equal to 0."); 437 | 438 | if (places == 0) return decimal.Floor(value); 439 | 440 | // At or beyond precision of decimal data type 441 | if (places >= 28) return value; 442 | 443 | return decimal.Floor(value * PowersOf10[places]) / PowersOf10[places]; 444 | } 445 | /// 446 | /// Returns the ceiling of a Decimal value at the given number of digits. 447 | /// 448 | /// A decimal value. 449 | /// An integer representing the maximum number of digits 450 | /// after the decimal point to end up with. 451 | public static decimal Ceiling(decimal value, int places = 0) 452 | { 453 | if (places < 0) throw new ArgumentOutOfRangeException("places", "Places must be greater than or equal to 0."); 454 | 455 | if (places == 0) return decimal.Ceiling(value); 456 | 457 | // At or beyond precision of decimal data type 458 | if (places >= 28) return value; 459 | 460 | return decimal.Ceiling(value * PowersOf10[places]) / PowersOf10[places]; 461 | } 462 | 463 | /// 464 | /// Calculates the greatest common factor of a and b to the highest level of 465 | /// precision represented by either number. 466 | /// 467 | /// 468 | /// If either number is not an integer, the factor sought will be at the 469 | /// same precision as the most precise value. 470 | /// For example, 1.2 and 0.42 will yield 0.06. 471 | /// 472 | public static decimal GCF(decimal a, decimal b) 473 | { 474 | // Run Euclid's algorithm 475 | while (true) 476 | { 477 | if (b == 0) break; 478 | var r = a % b; 479 | a = b; 480 | b = r; 481 | } 482 | 483 | return a; 484 | } 485 | 486 | /// 487 | /// Gets the greatest common factor of three or more numbers. 488 | /// 489 | public static decimal GCF(decimal a, decimal b, params decimal[] values) 490 | { 491 | return values.Aggregate(GCF(a, b), (current, value) => GCF(current, value)); 492 | } 493 | 494 | /// 495 | /// Computes arithmetic-geometric mean which is the convergence of the 496 | /// series of the arithmetic and geometric means and their mean values. 497 | /// 498 | /// A number. 499 | /// A number. 500 | /// 501 | /// See http://en.wikipedia.org/wiki/Arithmetic-geometric_mean 502 | /// Originally implemented to try to get a fast approximation of the 503 | /// natural logarithm: http://en.wikipedia.org/wiki/Natural_logarithm#High_precision 504 | /// But it didn't yield a precise enough answer. 505 | /// 506 | public static decimal AGMean(decimal x, decimal y) 507 | { 508 | decimal a; 509 | decimal g; 510 | 511 | // Handle special case 512 | if (x == 0 || y == 0) return 0; 513 | 514 | // Make sure signs match or we'll end up with a complex number 515 | var sign = Math.Sign(x); 516 | if (sign != Math.Sign(y)) 517 | throw new Exception("Arithmetic geometric mean of these values is complex and cannot be expressed in Decimal data type!"); 518 | 519 | // At this point, both signs match. If they're both negative, evaluate ag mean using them 520 | // as positive numbers and multiply result by -1. 521 | if (sign == -1) 522 | { 523 | x = decimal.Negate(x); 524 | y = decimal.Negate(y); 525 | } 526 | 527 | while (true) 528 | { 529 | a = x / 2 + y / 2; 530 | g = Sqrt(x * y); 531 | 532 | if (a == g) break; 533 | if (g == y && a == x) break; 534 | 535 | x = a; 536 | y = g; 537 | } 538 | 539 | return sign == -1 ? -a : a; 540 | } 541 | 542 | /// 543 | /// Calculates the average of the supplied numbers. 544 | /// 545 | /// The numbers to average. 546 | /// 547 | /// Simply uses LINQ's Average function, but switches to a potentially less 548 | /// accurate method of summing each value divided by the number of values. 549 | /// 550 | public static decimal Average(params decimal[] values) 551 | { 552 | decimal avg; 553 | 554 | try 555 | { 556 | avg = values.Average(); 557 | } 558 | catch (OverflowException) 559 | { 560 | // Use less accurate method that won't overflow 561 | avg = values.Sum(v => v / values.Length); 562 | } 563 | 564 | return avg; 565 | } 566 | 567 | /// 568 | /// Gets the number of decimal places in a decimal value. 569 | /// 570 | /// 571 | /// Started with something found here: http://stackoverflow.com/a/6092298/856595 572 | /// 573 | public static int GetDecimalPlaces(decimal dec, bool countTrailingZeros) 574 | { 575 | const int signMask = unchecked((int)0x80000000); 576 | const int scaleMask = 0x00FF0000; 577 | const int scaleShift = 16; 578 | 579 | int[] bits = Decimal.GetBits(dec); 580 | var result = (bits[3] & scaleMask) >> scaleShift; // extract exponent 581 | 582 | // Return immediately for values without a fractional portion or if we're counting trailing zeros 583 | if (countTrailingZeros || (result == 0)) return result; 584 | 585 | // Get a raw version of the decimal's integer 586 | bits[3] = bits[3] & ~unchecked(signMask | scaleMask); // clear out exponent and negative bit 587 | var rawValue = new decimal(bits); 588 | 589 | // Account for trailing zeros 590 | while ((result > 0) && ((rawValue % 10) == 0)) 591 | { 592 | result--; 593 | rawValue /= 10; 594 | } 595 | 596 | return result; 597 | } 598 | 599 | /// 600 | /// Gets the remainder of one number divided by another number in such a way as to retain maximum precision. 601 | /// 602 | public static decimal Remainder(decimal d1, decimal d2) 603 | { 604 | if (Math.Abs(d1) < Math.Abs(d2)) return d1; 605 | 606 | var timesInto = decimal.Truncate(d1 / d2); 607 | var shiftingNumber = d2; 608 | var sign = Math.Sign(d1); 609 | 610 | for (var i = 0; i <= GetDecimalPlaces(d2, true); i++) 611 | { 612 | // Note that first "digit" will be the integer portion of d2 613 | var digit = decimal.Truncate(shiftingNumber); 614 | 615 | d1 -= timesInto * (digit / PowersOf10[i]); 616 | 617 | shiftingNumber = (shiftingNumber - digit) * 10m; // remove used digit and shift for next iteration 618 | if (shiftingNumber == 0m) break; 619 | } 620 | 621 | // If we've crossed zero because of the precision mismatch, 622 | // we need to add a whole d2 to get a correct result. 623 | if (d1 != 0 && Math.Sign(d1) != sign) 624 | { 625 | d1 = Math.Sign(d2) == sign 626 | ? d1 + d2 627 | : d1 - d2; 628 | } 629 | 630 | return d1; 631 | } 632 | } 633 | } 634 | -------------------------------------------------------------------------------- /DecimalEx/DecimalEx.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | Portable math support for Decimal that Microsoft forgot and more. 6 | 7 | Includes Decimal versions of Sqrt, Pow, Exp, and Log as well as the trig functions Sin, Cos, Tan, ASin, ACos, ATan, ATan2. 8 | 9 | Also included is other functionality for working with numbers in Decimal precision. 10 | Nathan P Jones 11 | DecimalMath.DecimalEx 12 | Copyright 2015-2020 - Nathan P Jones 13 | LICENSE 14 | https://github.com/nathanpjones/DecimalMath 15 | c# decimal math trigonometry 16 | 1.0.2.0 17 | 1.0.2.0 18 | $(AssemblyVersion) 19 | Added special-case handling for powers of 10 in Log10. 20 | false 21 | git 22 | https://github.com/nathanpjones/DecimalMath.git 23 | Debug;Release;DebugWithMessages 24 | AnyCPU 25 | 26 | 27 | 28 | true 29 | false 30 | TRACE;DEBUG; 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | True 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /DecimalEx/DecimalExConstants.cs: -------------------------------------------------------------------------------- 1 | namespace DecimalMath 2 | { 3 | public static partial class DecimalEx 4 | { 5 | /// The pi (π) constant. Pi radians is equivalent to 180 degrees. 6 | /// See http://en.wikipedia.org/wiki/Pi 7 | public const decimal Pi = 3.1415926535897932384626433833m; // 180 degrees - see http://en.wikipedia.org/wiki/Pi 8 | /// π/2 - in radians is equivalent to 90 degrees. 9 | public const decimal PiHalf = 1.5707963267948966192313216916m; // 90 degrees 10 | /// π/4 - in radians is equivalent to 45 degrees. 11 | public const decimal PiQuarter = 0.7853981633974483096156608458m; // 45 degrees 12 | /// π/12 - in radians is equivalent to 15 degrees. 13 | public const decimal PiTwelfth = 0.2617993877991494365385536153m; // 15 degrees 14 | /// 2π - in radians is equivalent to 360 degrees. 15 | public const decimal TwoPi = 6.2831853071795864769252867666m; // 360 degrees 16 | 17 | /// 18 | /// Smallest non-zero decimal value. 19 | /// 20 | public const decimal SmallestNonZeroDec = 0.0000000000000000000000000001m; // aka new decimal(1, 0, 0, false, 28); //1e-28m 21 | 22 | /// 23 | /// The e constant, also known as "Euler's number" or "Napier's constant" 24 | /// 25 | /// 26 | /// Full value is 2.718281828459045235360287471352662497757, 27 | /// see http://mathworld.wolfram.com/e.html 28 | /// 29 | public const decimal E = 2.7182818284590452353602874714m; 30 | 31 | /// 32 | /// The value of the natural logarithm of 10. 33 | /// 34 | /// 35 | /// Full value is: 2.30258509299404568401799145468436420760110148862877297603332790096757 36 | /// From: http://oeis.org/A002392/constant 37 | /// 38 | public const decimal Ln10 = 2.3025850929940456840179914547m; 39 | /// 40 | /// The value of the natural logarithm of 2. 41 | /// 42 | /// 43 | /// Full value is: .693147180559945309417232121458176568075500134360255254120680009493393621969694715605863326996418687 44 | /// From: http://oeis.org/A002162/constant 45 | /// 46 | public const decimal Ln2 = 0.6931471805599453094172321215m; 47 | 48 | // Fast access for 10^n 49 | internal static readonly decimal[] PowersOf10 = 50 | { 51 | 1m, 52 | 10m, 53 | 100m, 54 | 1000m, 55 | 10000m, 56 | 100000m, 57 | 1000000m, 58 | 10000000m, 59 | 100000000m, 60 | 1000000000m, 61 | 10000000000m, 62 | 100000000000m, 63 | 1000000000000m, 64 | 10000000000000m, 65 | 100000000000000m, 66 | 1000000000000000m, 67 | 10000000000000000m, 68 | 100000000000000000m, 69 | 1000000000000000000m, 70 | 10000000000000000000m, 71 | 100000000000000000000m, 72 | 1000000000000000000000m, 73 | 10000000000000000000000m, 74 | 100000000000000000000000m, 75 | 1000000000000000000000000m, 76 | 10000000000000000000000000m, 77 | 100000000000000000000000000m, 78 | 1000000000000000000000000000m, 79 | 10000000000000000000000000000m, 80 | }; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /DecimalEx/DecimalExTrig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace DecimalMath 5 | { 6 | public static partial class DecimalEx 7 | { 8 | /// 9 | /// Converts degrees to radians. (π radians = 180 degrees) 10 | /// 11 | /// The degrees to convert. 12 | public static decimal ToRad(decimal degrees) 13 | { 14 | if (degrees % 360m == 0) 15 | { 16 | return (degrees / 360m) * TwoPi; 17 | } 18 | if (degrees % 270m == 0) 19 | { 20 | return (degrees / 270m) * (Pi + PiHalf); 21 | } 22 | if (degrees % 180m == 0) 23 | { 24 | return (degrees / 180m) * Pi; 25 | } 26 | if (degrees % 90m == 0) 27 | { 28 | return (degrees / 90m) * PiHalf; 29 | } 30 | if (degrees % 45m == 0) 31 | { 32 | return (degrees / 45m) * PiQuarter; 33 | } 34 | if (degrees % 15m == 0) 35 | { 36 | return (degrees / 15m) * PiTwelfth; 37 | } 38 | 39 | return degrees * Pi / 180m; 40 | } 41 | 42 | /// 43 | /// Converts radians to degrees. (π radians = 180 degrees) 44 | /// 45 | /// The radians to convert. 46 | public static decimal ToDeg(decimal radians) 47 | { 48 | const decimal ratio = 180m / Pi; 49 | 50 | return radians * ratio; 51 | } 52 | 53 | /// 54 | /// Normalizes an angle in radians to the 0 to 2Pi interval. 55 | /// 56 | /// Angle in radians. 57 | public static decimal NormalizeAngle(decimal radians) 58 | { 59 | radians = Remainder(radians, TwoPi); 60 | if (radians < 0) radians += TwoPi; 61 | return radians; 62 | } 63 | 64 | /// 65 | /// Normalizes an angle in degrees to the 0 to 360 degree interval. 66 | /// 67 | /// Angle in degrees. 68 | public static decimal NormalizeAngleDeg(decimal degrees) 69 | { 70 | degrees = degrees % 360m; 71 | if (degrees < 0) degrees += 360m; 72 | return degrees; 73 | } 74 | 75 | /// 76 | /// Returns the sine of the specified angle. 77 | /// 78 | /// An angle, measured in radians. 79 | /// 80 | /// Uses a Taylor series to calculate sine. See 81 | /// http://en.wikipedia.org/wiki/Trigonometric_functions for details. 82 | /// 83 | public static decimal Sin(decimal x) 84 | { 85 | // Normalize to between -2Pi <= x <= 2Pi 86 | x = Remainder(x, TwoPi); 87 | 88 | if (x == 0 || x == Pi || x == TwoPi) 89 | { 90 | return 0; 91 | } 92 | if (x == PiHalf) 93 | { 94 | return 1; 95 | } 96 | if (x == Pi + PiHalf) 97 | { 98 | return -1; 99 | } 100 | 101 | var result = 0m; 102 | var doubleIteration = 0; // current iteration * 2 103 | var xSquared = x * x; 104 | var nextAdd = 0m; 105 | 106 | while (true) 107 | { 108 | if (doubleIteration == 0) 109 | { 110 | nextAdd = x; 111 | } 112 | else 113 | { 114 | // We multiply by -1 each time so that the sign of the component 115 | // changes each time. The first item is positive and it 116 | // alternates back and forth after that. 117 | // Following is equivalent to: nextAdd *= -1 * x * x / ((2 * iteration) * (2 * iteration + 1)); 118 | nextAdd *= -1 * xSquared / (doubleIteration * doubleIteration + doubleIteration); 119 | } 120 | 121 | #if DEBUGWITHMESSAGES 122 | Debug.WriteLine("{0:000}:{1,33:+0.0000000000000000000000000000;-0.0000000000000000000000000000} ->{2,33:+0.0000000000000000000000000000;-0.0000000000000000000000000000}", 123 | doubleIteration / 2, nextAdd, result + nextAdd); 124 | #endif 125 | if (nextAdd == 0) break; 126 | 127 | result += nextAdd; 128 | 129 | doubleIteration += 2; 130 | } 131 | 132 | return result; 133 | } 134 | 135 | /// 136 | /// Returns the cosine of the specified angle. 137 | /// 138 | /// An angle, measured in radians. 139 | /// 140 | /// Uses a Taylor series to calculate sine. See 141 | /// http://en.wikipedia.org/wiki/Trigonometric_functions for details. 142 | /// 143 | public static decimal Cos(decimal x) 144 | { 145 | // Normalize to between -2Pi <= x <= 2Pi 146 | x = Remainder(x, TwoPi); 147 | 148 | if (x == 0 || x == TwoPi) 149 | { 150 | return 1; 151 | } 152 | if (x == Pi) 153 | { 154 | return -1; 155 | } 156 | if (x == PiHalf || x == Pi + PiHalf) 157 | { 158 | return 0; 159 | } 160 | 161 | var result = 0m; 162 | var doubleIteration = 0; // current iteration * 2 163 | var xSquared = x * x; 164 | var nextAdd = 0m; 165 | 166 | while (true) 167 | { 168 | if (doubleIteration == 0) 169 | { 170 | nextAdd = 1; 171 | } 172 | else 173 | { 174 | // We multiply by -1 each time so that the sign of the component 175 | // changes each time. The first item is positive and it 176 | // alternates back and forth after that. 177 | // Following is equivalent to: nextAdd *= -1 * x * x / ((2 * iteration - 1) * (2 * iteration)); 178 | nextAdd *= -1 * xSquared / (doubleIteration * doubleIteration - doubleIteration); 179 | } 180 | 181 | if (nextAdd == 0) break; 182 | 183 | result += nextAdd; 184 | 185 | doubleIteration += 2; 186 | } 187 | 188 | return result; 189 | } 190 | 191 | /// 192 | /// Returns the tangent of the specified angle. 193 | /// 194 | /// An angle, measured in radians. 195 | /// 196 | /// Uses a Taylor series to calculate sine. See 197 | /// http://en.wikipedia.org/wiki/Trigonometric_functions for details. 198 | /// 199 | public static decimal Tan(decimal radians) 200 | { 201 | try 202 | { 203 | return Sin(radians) / Cos(radians); 204 | } 205 | catch (DivideByZeroException) 206 | { 207 | throw new Exception("Tangent is undefined at this angle!"); 208 | } 209 | } 210 | 211 | /// 212 | /// Returns the angle whose sine is the specified number. 213 | /// 214 | /// A number representing a sine, where -1 ≤d≤ 1. 215 | /// 216 | /// See http://en.wikipedia.org/wiki/Inverse_trigonometric_function 217 | /// and http://mathworld.wolfram.com/InverseSine.html 218 | /// I originally used the Taylor series for ASin, but it was extremely slow 219 | /// around -1 and 1 (millions of iterations) and still ends up being less 220 | /// accurate than deriving from the ATan function. 221 | /// 222 | public static decimal ASin(decimal z) 223 | { 224 | if (z < -1 || z > 1) 225 | { 226 | throw new ArgumentOutOfRangeException("z", "Argument must be in the range -1 to 1 inclusive."); 227 | } 228 | 229 | // Special cases 230 | if (z == -1) return -PiHalf; 231 | if (z == 0) return 0; 232 | if (z == 1) return PiHalf; 233 | 234 | return 2m * ATan(z / (1 + Sqrt(1 - z * z))); 235 | } 236 | 237 | /// 238 | /// Returns the angle whose cosine is the specified number. 239 | /// 240 | /// A number representing a cosine, where -1 ≤d≤ 1. 241 | /// 242 | /// See http://en.wikipedia.org/wiki/Inverse_trigonometric_function 243 | /// and http://mathworld.wolfram.com/InverseCosine.html 244 | /// 245 | public static decimal ACos(decimal z) 246 | { 247 | if (z < -1 || z > 1) 248 | { 249 | throw new ArgumentOutOfRangeException("z", "Argument must be in the range -1 to 1 inclusive."); 250 | } 251 | 252 | // Special cases 253 | if (z == -1) return Pi; 254 | if (z == 0) return PiHalf; 255 | if (z == 1) return 0; 256 | 257 | return 2m * ATan(Sqrt(1 - z * z) / (1 + z)); 258 | } 259 | 260 | /// 261 | /// Returns the angle whose tangent is the quotient of two specified numbers. 262 | /// 263 | /// A number representing a tangent. 264 | /// 265 | /// See http://mathworld.wolfram.com/InverseTangent.html for faster converging 266 | /// series from Euler that was used here. 267 | /// 268 | public static decimal ATan(decimal x) 269 | { 270 | // Special cases 271 | if (x == -1) return -PiQuarter; 272 | if (x == 0) return 0; 273 | if (x == 1) return PiQuarter; 274 | if (x < -1) 275 | { 276 | // Force down to -1 to 1 interval for faster convergence 277 | return -PiHalf - ATan(1 / x); 278 | } 279 | if (x > 1) 280 | { 281 | // Force down to -1 to 1 interval for faster convergence 282 | return PiHalf - ATan(1 / x); 283 | } 284 | 285 | var result = 0m; 286 | var doubleIteration = 0; // current iteration * 2 287 | var y = (x * x) / (1 + x * x); 288 | var nextAdd = 0m; 289 | 290 | while (true) 291 | { 292 | if (doubleIteration == 0) 293 | { 294 | nextAdd = x / (1 + x * x); // is = y / x but this is better for very small numbers where y = 9 295 | } 296 | else 297 | { 298 | // We multiply by -1 each time so that the sign of the component 299 | // changes each time. The first item is positive and it 300 | // alternates back and forth after that. 301 | // Following is equivalent to: nextAdd *= y * (iteration * 2) / (iteration * 2 + 1); 302 | nextAdd *= y * doubleIteration / (doubleIteration + 1); 303 | } 304 | 305 | if (nextAdd == 0) break; 306 | 307 | result += nextAdd; 308 | 309 | doubleIteration += 2; 310 | } 311 | 312 | return result; 313 | } 314 | 315 | /// 316 | /// Returns the angle whose tangent is the quotient of two specified numbers. 317 | /// 318 | /// The y coordinate of a point. 319 | /// The x coordinate of a point. 320 | /// 321 | /// An angle, θ, measured in radians, such that -π≤θ≤π, and tan(θ) = y / x, 322 | /// where (x, y) is a point in the Cartesian plane. Observe the following: 323 | /// For (x, y) in quadrant 1, 0 < θ < π/2. 324 | /// For (x, y) in quadrant 2, π/2 < θ ≤ π. 325 | /// For (x, y) in quadrant 3, -π < θ < -π/2. 326 | /// For (x, y) in quadrant 4, -π/2 < θ < 0. 327 | /// 328 | public static decimal ATan2(decimal y, decimal x) 329 | { 330 | if (x == 0 && y == 0) 331 | { 332 | return 0; // X0, Y0 333 | } 334 | 335 | if (x == 0) 336 | { 337 | return y > 0 338 | ? PiHalf // X0, Y+ 339 | : -PiHalf; // X0, Y- 340 | } 341 | 342 | if (y == 0) 343 | { 344 | return x > 0 345 | ? 0 // X+, Y0 346 | : Pi; // X-, Y0 347 | } 348 | 349 | var aTan = ATan(y / x); 350 | 351 | if (x > 0) return aTan; // Q1&4: X+, Y+- 352 | 353 | return y > 0 354 | ? aTan + Pi // Q2: X-, Y+ 355 | : aTan - Pi; // Q3: X-, Y- 356 | 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /DecimalEx/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DecimalMath 4 | { 5 | /// 6 | /// Extension methods for the Decimal data type. 7 | /// 8 | public static class Extensions 9 | { 10 | /// 11 | /// Tests whether or not a given value is within the upper and lower limit, inclusive. 12 | /// 13 | /// The value to test. 14 | /// The lower limit. 15 | /// The upper limit. 16 | public static bool InRangeIncl(this decimal value, decimal lowerLimit, decimal upperLimit) 17 | { 18 | if (upperLimit < lowerLimit) 19 | throw new Exception("Upper limit is less than lower limit!"); 20 | 21 | return (value >= lowerLimit) && (value <= upperLimit); 22 | } 23 | /// 24 | /// Tests whether or not a given value is within the upper and lower limit, exclusive. 25 | /// 26 | /// The value to test. 27 | /// The lower limit. 28 | /// The upper limit. 29 | public static bool InRangeExcl(this decimal value, decimal lowerLimit, decimal upperLimit) 30 | { 31 | if (upperLimit < lowerLimit) 32 | throw new Exception("Upper limit is less than lower limit!"); 33 | 34 | return (value > lowerLimit) && (value < upperLimit); 35 | } 36 | 37 | /// 38 | /// Rounds a number away from zero to the given number of decimal places. 39 | /// 40 | public static decimal RoundFromZero(this decimal d, int decimals) 41 | { 42 | if (decimals < 0) throw new ArgumentOutOfRangeException("decimals", "Decimals must be greater than or equal to 0."); 43 | 44 | var scaleFactor = DecimalEx.PowersOf10[decimals]; 45 | var roundingFactor = d > 0 ? 0.5m : -0.5m; 46 | 47 | return decimal.Truncate(d * scaleFactor + roundingFactor) / scaleFactor; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /DecimalEx/Helper.cs: -------------------------------------------------------------------------------- 1 | namespace DecimalMath 2 | { 3 | /// 4 | /// Helper functions. 5 | /// 6 | public static class Helper 7 | { 8 | /// 9 | /// Swaps the value between two variables. 10 | /// 11 | public static void Swap(ref T lhs, ref T rhs) 12 | { 13 | var temp = lhs; 14 | lhs = rhs; 15 | rhs = temp; 16 | } 17 | 18 | /// 19 | /// Prime number to use to begin a hash of an object. 20 | /// 21 | /// 22 | /// See: http://stackoverflow.com/questions/263400 23 | /// 24 | public const int HashStart = 17; 25 | private const int HashPrime = 397; 26 | 27 | /// 28 | /// Adds a hash of an object to a running hash value. 29 | /// 30 | /// A running hash value. 31 | /// The object to hash and incorporate into the running hash. 32 | public static int HashObject(this int hash, object obj) 33 | { 34 | unchecked { return hash * HashPrime ^ (ReferenceEquals(null, obj) ? 0 : obj.GetHashCode()); } 35 | } 36 | 37 | /// 38 | /// Adds a hash of a struct to a running hash value. 39 | /// 40 | /// A running hash value. 41 | /// The struct to hash and incorporate into the running hash. 42 | public static int HashValue(this int hash, T value) where T : struct 43 | { 44 | unchecked { return hash * HashPrime ^ value.GetHashCode(); } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /DecimalEx/ITransformable.cs: -------------------------------------------------------------------------------- 1 | namespace DecimalMath 2 | { 3 | /// 4 | /// Interface that defines an object's ability to transform itself using a given matrix type. 5 | /// 6 | /// Supported matrix type. 7 | /// Type that is implementing interface. 8 | public interface ITransformable where TMatrix : TransformationMatrixBase, new() 9 | { 10 | /// 11 | /// Applies a transform on itself using the given transformation matrix. 12 | /// 13 | /// The transformation matrix. 14 | TSelf Transform(TMatrix matrix); 15 | } 16 | } -------------------------------------------------------------------------------- /DecimalEx/Matrix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DecimalMath 4 | { 5 | /// 6 | /// Matrix operations against a matrix stored as a two-dimensional Decimal array where values 7 | /// are addressed as [row, column]. 8 | /// 9 | public static class Matrix 10 | { 11 | /// 12 | /// Gets the identity matrix for this size matrix. 13 | /// 14 | public static decimal[,] GetIdentityMatrix(int size) 15 | { 16 | var m = new decimal[size, size]; 17 | 18 | // For an identity matrix, the diagonal (top left to bot right) 19 | // numbers are 1 and everything else is 0. 20 | for (var i = 0; i < size; i++) 21 | { 22 | m[i, i] = 1; 23 | } 24 | 25 | return m; 26 | } 27 | 28 | /// 29 | /// Multiply two matrices. 30 | /// 31 | /// A matrix. 32 | /// A matrix. 33 | public static decimal[,] Multiply(decimal[,] m1, decimal[,] m2) 34 | { 35 | // Verify that matrices are compatible 36 | var columns1 = m1.GetLength(1); 37 | var rows2 = m2.GetLength(0); 38 | if (columns1 != rows2) 39 | { 40 | throw new Exception(string.Format("Can't multiply a {0}x{1} matrix with a {2}x{3} matrix!", 41 | m1.GetLength(0), m1.GetLength(1), 42 | m2.GetLength(0), m2.GetLength(1))); 43 | } 44 | 45 | var prodRows = m1.GetLength(0); // rows from m1 46 | var prodCols = m2.GetLength(1); // columns from m2 47 | var m = new decimal[prodRows, prodCols]; 48 | 49 | // Select destination row 50 | for (var r = 0; r < prodRows; r++) 51 | { 52 | // Select destination column 53 | for (var c = 0; c < prodCols; c++) 54 | { 55 | m[r, c] = 0; 56 | 57 | for (var i = 0; i < columns1; i++) 58 | { 59 | m[r, c] += m1[r, i] * m2[i, c]; 60 | } 61 | } 62 | } 63 | 64 | return m; 65 | } 66 | 67 | /// 68 | /// Converts a one dimensional array to rows in a two dimensional column matrix. 69 | /// 70 | public static decimal[,] ToColumn(decimal[] m) 71 | { 72 | var rows = m.Length; 73 | var colMatrix = new decimal[rows, 1]; 74 | for (var row = 0; row < rows; row++) 75 | { 76 | colMatrix[row, 0] = m[row]; 77 | } 78 | return colMatrix; 79 | } 80 | 81 | /// 82 | /// Converts a one dimensional array to rows in a two dimensional row matrix. 83 | /// 84 | public static decimal[,] ToRow(decimal[] m) 85 | { 86 | var columns = m.Length; 87 | var rowMatrix = new decimal[1, columns]; 88 | for (var col = 0; col < columns; col++) 89 | { 90 | rowMatrix[0, col] = m[col]; 91 | } 92 | return rowMatrix; 93 | } 94 | 95 | /// 96 | /// Converts a column or row matrix into a simple array. 97 | /// 98 | public static decimal[] RowOrColumnToArray(decimal[,] m) 99 | { 100 | if (m.GetLength(0) == 1) 101 | { 102 | // Convert row to array 103 | var columns = m.GetLength(1); 104 | var ret = new decimal[columns]; 105 | for (var col = 0; col < columns; col++) 106 | { 107 | ret[col] = m[0, col]; 108 | } 109 | return ret; 110 | } 111 | else if (m.GetLength(1) == 1) 112 | { 113 | // Convert column to array 114 | var rows = m.GetLength(0); 115 | var ret = new decimal[rows]; 116 | for (var row = 0; row < rows; row++) 117 | { 118 | ret[row] = m[row, 0]; 119 | } 120 | return ret; 121 | } 122 | else 123 | { 124 | throw new ArgumentException("Matrix is not a single column or row.", "m"); 125 | } 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /DecimalEx/TransformationMatrixBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DecimalMath 4 | { 5 | /// 6 | /// A base class to support implementation of a square matrix to be used for affine transforms. 7 | /// 8 | /// Reference to the type inheriting this base class. 9 | public abstract class TransformationMatrixBase where TSelf : TransformationMatrixBase, new() 10 | { 11 | /// 12 | /// The width / height of the square matrix. 13 | /// 14 | public readonly int Size; 15 | /// 16 | /// The raw matrix as a two-dimensional array. Stored as [row, column]. 17 | /// 18 | protected decimal[,] M; 19 | 20 | /// 21 | /// Constructs a new matrix with height and width of . 22 | /// 23 | /// Size of matrix. 24 | protected TransformationMatrixBase(int size) 25 | { 26 | Size = size; 27 | 28 | // initialize to identity matrix 29 | M = Matrix.GetIdentityMatrix(Size); 30 | } 31 | 32 | /// 33 | /// Constructs a new matrix with height and width of . 34 | /// 35 | /// Size of matrix. 36 | /// Values to use to initialize the matrix. These values are not used 37 | /// directly, but are rather copied. Addressed as [row, column]. 38 | protected TransformationMatrixBase(int size, decimal[,] values) 39 | { 40 | Size = size; 41 | 42 | if (values.GetLength(0) != size || values.GetLength(1) != size) 43 | throw new ArgumentException("Matrix initialization values are not of the correct size!", "values"); 44 | 45 | M = new decimal[size, size]; 46 | Array.Copy(values, M, values.Length); 47 | } 48 | 49 | #region Matrix Operations 50 | 51 | /// 52 | /// Gets the direct value of the matrix at the given row and column. 53 | /// 54 | /// The row at which to get the value. 55 | /// The column at which to get the value. 56 | public decimal this[int row, int column] 57 | { 58 | get { return M[row, column]; } 59 | set { M[row, column] = value; } 60 | } 61 | 62 | /// 63 | /// Sets a row using the given values as if they were a row matrix. 64 | /// 65 | /// The row to set. 66 | /// Values to set the row. Treated as a row matrix. 67 | public void SetRow(int row, decimal[] values) 68 | { 69 | if (values.Length != Size) 70 | throw new ArgumentException("Number of values does not match number of columns in a row.", "values"); 71 | 72 | for (var col = 0; col < Size; col++) 73 | { 74 | M[row, col] = values[col]; 75 | } 76 | } 77 | 78 | /// 79 | /// Multiplies this matrix by another matrix (this x other) and returns a third matrix. 80 | /// 81 | /// The other matrix to multiply by. 82 | public TSelf Multiply(TransformationMatrixBase other) where TOther : TransformationMatrixBase, new() 83 | { 84 | var m = new TSelf(); 85 | 86 | m.M = Matrix.Multiply(M, other.M); 87 | 88 | return m; 89 | } 90 | 91 | /// 92 | /// Applies the transform to a column matrix. 93 | /// 94 | /// Column matrix with length equal to . 95 | public decimal[] Transform(decimal[] columnMatrix) 96 | { 97 | if (columnMatrix.Length != Size) 98 | throw new ArgumentException(string.Format("Length of column matrix should be {0} but is {1}.", Size, columnMatrix.Length), "columnMatrix"); 99 | 100 | var twoDArray = Matrix.ToColumn(columnMatrix); 101 | var matrixResult = Matrix.Multiply(M, twoDArray); 102 | var arrayResult = Matrix.RowOrColumnToArray(matrixResult); 103 | 104 | return arrayResult; 105 | } 106 | 107 | #endregion 108 | 109 | #region Applying To Objects 110 | 111 | /// Transforms an object and returns the result by reference. 112 | public TSelf InPlaceTransform(ref TTrans element) where TTrans : ITransformable 113 | { 114 | element = element.Transform((TSelf)this); 115 | return (TSelf)this; 116 | } 117 | 118 | /// Transforms objects and returns the results by reference. 119 | public TSelf InPlaceTransform(ref TTrans1 element1, ref TTrans2 element2) 120 | where TTrans1 : ITransformable 121 | where TTrans2 : ITransformable 122 | { 123 | element1 = Transform(element1); 124 | element2 = Transform(element2); 125 | return (TSelf)this; 126 | } 127 | 128 | /// Transforms objects and returns the results by reference. 129 | public TSelf InPlaceTransform(ref TTrans1 element1, ref TTrans2 element2, ref TTrans3 element3) 130 | where TTrans1 : ITransformable 131 | where TTrans2 : ITransformable 132 | where TTrans3 : ITransformable 133 | { 134 | element1 = Transform(element1); 135 | element2 = Transform(element2); 136 | element3 = Transform(element3); 137 | return (TSelf)this; 138 | } 139 | 140 | /// Transforms objects and returns the results by reference. 141 | public TSelf InPlaceTransform(ref TTrans1 element1, ref TTrans2 element2, ref TTrans3 element3, ref TTrans4 element4) 142 | where TTrans1 : ITransformable 143 | where TTrans2 : ITransformable 144 | where TTrans3 : ITransformable 145 | where TTrans4 : ITransformable 146 | { 147 | element1 = Transform(element1); 148 | element2 = Transform(element2); 149 | element3 = Transform(element3); 150 | element4 = Transform(element4); 151 | return (TSelf)this; 152 | } 153 | 154 | /// Transforms objects and returns the results by reference. 155 | public TSelf InPlaceTransform(ref TTrans1 element1, ref TTrans2 element2, ref TTrans3 element3, ref TTrans4 element4, ref TTrans5 element5) 156 | where TTrans1 : ITransformable 157 | where TTrans2 : ITransformable 158 | where TTrans3 : ITransformable 159 | where TTrans4 : ITransformable 160 | where TTrans5 : ITransformable 161 | { 162 | element1 = Transform(element1); 163 | element2 = Transform(element2); 164 | element3 = Transform(element3); 165 | element4 = Transform(element4); 166 | element5 = Transform(element5); 167 | return (TSelf)this; 168 | } 169 | 170 | /// 171 | /// Transforms an object that supports transformations by this matrix type. 172 | /// 173 | public TTransformable Transform(ITransformable obj) 174 | { 175 | return obj.Transform((TSelf)this); 176 | } 177 | 178 | #endregion 179 | 180 | /// 181 | /// Creates a deep copy of this matrix. 182 | /// 183 | public TSelf Copy() 184 | { 185 | var m = new TSelf(); 186 | 187 | if (m.Size != Size) 188 | throw new Exception("Parameterless constructor for " + typeof(TSelf).Name + " did not properly set size of matrix!"); 189 | 190 | Array.Copy(M, m.M, M.Length); 191 | 192 | return m; 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /DecimalMath.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29102.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DecimalEx", "DecimalEx\DecimalEx.csproj", "{13287E0A-967F-40AB-BD51-DC5309071EC0}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DecimalEx.Tests", "DecimalEx.Tests\DecimalEx.Tests.csproj", "{5930549A-38DA-4759-A686-A8B8DAAD7E26}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Decimal2D", "Decimal2D\Decimal2D.csproj", "{D8244074-A23E-4B7D-87F1-D2F346987C01}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Decimal2D.Tests", "Decimal2D.Tests\Decimal2D.Tests.csproj", "{EE341DA3-CC60-49CB-9493-4CB826D0A3F5}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | DebugWithMessages|Any CPU = DebugWithMessages|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.DebugWithMessages|Any CPU.ActiveCfg = DebugWithMessages|Any CPU 26 | {13287E0A-967F-40AB-BD51-DC5309071EC0}.DebugWithMessages|Any CPU.Build.0 = DebugWithMessages|Any CPU 27 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.DebugWithMessages|Any CPU.ActiveCfg = DebugWithMessages|Any CPU 32 | {5930549A-38DA-4759-A686-A8B8DAAD7E26}.DebugWithMessages|Any CPU.Build.0 = DebugWithMessages|Any CPU 33 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.DebugWithMessages|Any CPU.ActiveCfg = DebugWithMessages|Any CPU 38 | {D8244074-A23E-4B7D-87F1-D2F346987C01}.DebugWithMessages|Any CPU.Build.0 = DebugWithMessages|Any CPU 39 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.DebugWithMessages|Any CPU.ActiveCfg = DebugWithMessages|Any CPU 44 | {EE341DA3-CC60-49CB-9493-4CB826D0A3F5}.DebugWithMessages|Any CPU.Build.0 = DebugWithMessages|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | GlobalSection(ExtensibilityGlobals) = postSolution 50 | SolutionGuid = {2351AA99-520D-4524-87CE-7978C8EFB2B1} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2020 Nathan P Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decimal Math 2 | *Portable math support for Decimal that Microsoft forgot and more.* 3 | 4 | The .NET Decimal data type is included in .NET, but is often overlooked for scientific calculations. It's high precision and exact up to 28 decimal places and it's available in any .NET environment. 5 | 6 | You might be in a situation where you just need a lot more precision than Double can provide. I used Decimal for calculating locations in space for CNC manufacturing and pick and place control. I've found the increased precision of Decimal reduces overall errors throughout the set of calculations and it also improves the odds of reversing the calculations if necessary for debugging. In that case it ends up being a kind of oversampling. 7 | 8 | Unfortunately, a lot of the usual number functionality is not provided for .NET. For example, you can't calculate a square root or even perform [exponentiation](http://stackoverflow.com/questions/6425501/is-there-a-math-api-for-powdecimal-decimal). You can cast to Double for these operations, but you can end up with a significant loss of precision. 9 | 10 | **Note** All of this I've used in the "real world", but this is also a hobby. Although the library is performant, I've perferred accuracy and readability to raw performance. 11 | 12 | ## Install 13 | 14 | Install via [NuGet](https://www.nuget.org/packages/DecimalMath.DecimalEx) package manager console: 15 | ``` 16 | PM> Install-Package DecimalMath.DecimalEx 17 | ``` 18 | 19 | Or of course you can clone the repository and pull in the projects directly. 20 | 21 | # Libraries 22 | 23 | This project contains two portable libraries. 24 | 25 | #### DecimalEx 26 | Bridges the gap in .NET support. 27 | - Decimal versions of the following functions: 28 | - `Sqrt` 29 | - `Pow` 30 | - `Exp` 31 | - `Log` 32 | - `Sin`, `Cos`, `Tan` 33 | - `ASin`, `ACos`, `ATan`, `ATan2` 34 | - General Decimal functionality: 35 | - `Floor` / `Ceiling` to a given number of decimal places 36 | - Implementations of `GCF`, `AGMean`, and a fault-tolerant `Average` 37 | - Function to get decimal places of a number (looking at bits, NOT by converting to string) 38 | - Other minor helper functions 39 | - Functionality for working with Decimal matrices as 2D arrays and a base class for implementing an NxN matrix for use in affine transformations. 40 | 41 | #### Decimal2D 42 | Provides support for high-accuracy geometric calculations. *Note: This is still a work in progress. It's been used in production, but it still needs some more grooming (was originally VB code) and unit tests.* 43 | - Support for 2D geometric objects 44 | - `Point`, `Line`, and `Vector` 45 | - `Circle` and `Arc` 46 | - Support for geometric relationships, e.g. tangent to, intersects, etc. 47 | - A 2D tranformation matrix with a number of provided transforms. 48 | - An implementation of an abstract right triangle (i.e., the sides and angles but without a location in space). 49 | - Generic right triangle operations. 50 | 51 | ## License 52 | 53 | This project uses the MIT License. See the license file in the same folder as this readme. 54 | -------------------------------------------------------------------------------- /packaging/Build Decimal2D Package.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo ------------------------------------ 3 | echo Building Decimal2D package... 4 | echo ------------------------------------ 5 | if not exist "%~dp0nuget" md "%~dp0nuget" 6 | dotnet pack "%~dp0..\Decimal2D\Decimal2D.csproj" --configuration Release -p:GenerateDocumentationFile=true --include-symbols -p:SymbolPackageFormat=snupkg --output "%~dp0nuget" 7 | echo. 8 | pause -------------------------------------------------------------------------------- /packaging/Build DecimalEx Package.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo ------------------------------------ 3 | echo Building DecimalEx package... 4 | echo ------------------------------------ 5 | if not exist "%~dp0nuget" md "%~dp0nuget" 6 | dotnet pack "%~dp0..\DecimalEx\DecimalEx.csproj" --configuration Release -p:GenerateDocumentationFile=true --include-symbols -p:SymbolPackageFormat=snupkg --output "%~dp0nuget" 7 | echo. 8 | pause --------------------------------------------------------------------------------