├── .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
--------------------------------------------------------------------------------