diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..f3d2ce3 --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-stryker": { + "version": "1.3.1", + "commands": [ + "dotnet-stryker" + ] + } + } +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index ae973bf..125f2c1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -23,10 +23,10 @@ dotnet_separate_import_directive_groups=false dotnet_sort_system_directives_first=true # this. and Me. preferences -dotnet_style_qualification_for_event=true:suggestion -dotnet_style_qualification_for_field=true:suggestion -dotnet_style_qualification_for_method=true:suggestion -dotnet_style_qualification_for_property=true:suggestion +dotnet_style_qualification_for_event=false:warning +dotnet_style_qualification_for_field=false:warning +dotnet_style_qualification_for_method=false:warning +dotnet_style_qualification_for_property=false:warning # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members=true:warning @@ -65,9 +65,7 @@ dotnet_code_quality_unused_parameters=all:suggestion #### C# Coding Conventions #### # var preferences -csharp_style_var_elsewhere=true:warning csharp_style_var_when_type_is_apparent=true:warning -csharp_style_var_for_built_in_types=true:warning # Expression-bodied members csharp_style_expression_bodied_accessors=true:suggestion @@ -107,7 +105,7 @@ csharp_style_unused_value_assignment_preference=discard_variable:suggestion csharp_style_unused_value_expression_statement_preference=discard_variable:silent # 'using' directive preferences -csharp_using_directive_placement=inside_namespace:suggestion +csharp_using_directive_placement=outside_namespace:error #### C# Formatting Rules #### diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..0948312 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,33 @@ +name: .NET + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: "Build & Test" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build --no-restore --configuration Release + + - name: Test + run: dotnet test --no-build --verbosity normal --configuration Release diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml deleted file mode 100644 index f734ea7..0000000 --- a/.github/workflows/dotnetcore.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Setup .NET Core - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 3.1.101 - - name: Install dependencies - run: dotnet restore - - name: Build - run: dotnet build --configuration Release --no-restore - - name: Test - run: dotnet test --no-restore --verbosity normal diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..8291cbb --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,41 @@ +name: Publish Nightly + +on: + push: + branches: + - master + - develop + +jobs: + nightly: + runs-on: ubuntu-latest + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build -c Debug + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='nightly' -p:BuildNumber=${{ github.run_number }} + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml new file mode 100644 index 0000000..1f2cb8d --- /dev/null +++ b/.github/workflows/prerelease.yml @@ -0,0 +1,46 @@ +name: Tagged Pre-Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+-*" + +jobs: + prerelease: + name: "Tagged Pre-Release" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Publish + run: dotnet publish -c Release -r linux-x64 --self-contained true + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build -p:VersionSuffix='prerelease' -p:BuildNumber=${{ github.run_number }} + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate + + - name: Create Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..85ed581 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +name: Tagged Release + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + +jobs: + release: + name: "Tagged Release" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Publish + run: dotnet publish -c Release -r linux-x64 --self-contained true + + - name: Build NuGet package + run: | + mkdir build + dotnet pack X10D -p:SymbolPackageFormat=snupkg --include-symbols --include-source -o build + + - name: Push NuGet Package to GitHub + run: dotnet nuget push "build/*" --source "github" --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate + + - name: Push NuGet Package to nuget.org + run: dotnet nuget push "build/*" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate + + - name: Create Release + uses: "marvinpinto/action-automatic-releases@latest" + with: + repo_token: "${{ secrets.GITHUB_TOKEN }}" + prerelease: false diff --git a/.github/workflows/source_validator.yml b/.github/workflows/source_validator.yml new file mode 100644 index 0000000..4c2d038 --- /dev/null +++ b/.github/workflows/source_validator.yml @@ -0,0 +1,36 @@ +name: Source Validator + +on: + push: + branches: + - master + - develop + pull_request: + types: [opened, synchronize, reopened] + +jobs: + source_validator: + runs-on: ubuntu-latest + if: "!contains(format('{0} {1}', github.event.head_commit.message, github.event.pull_request.title), '[ci-skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 6.0.x + + - name: Add GitHub NuGet source + run: dotnet nuget add source --username oliverbooth --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/oliverbooth/index.json" + + - name: Restore dependencies + run: dotnet restore + + - name: Build + run: dotnet build -c Debug + + - name: Run Source Validation + run: dotnet run --project X10D.SourceValidator ./X10D/src + diff --git a/.gitignore b/.gitignore index 16fe8be..a437a65 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,22 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc +nupkg/ + +# Visual Studio Code +.vscode + +# Rider +.idea # User-specific files -*.idea -*.rsuser *.suo *.user *.userosscache *.sln.docstates -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Mono auto generated files -mono_crash.* - # Build results [Dd]ebug/ [Dd]ebugPublic/ @@ -24,327 +24,14 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -[Aa][Rr][Mm]/ -[Aa][Rr][Mm]64/ +build/ bld/ [Bb]in/ [Oo]bj/ -[Ll]og/ +[Oo]ut/ +msbuild.log +msbuild.err +msbuild.wrn -# Visual Studio 2015/2017 cache/options directory +# Visual Studio 2015 .vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# Visual Studio 2017 auto generated files -Generated\ Files/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUnit -*.VisualState.xml -TestResult.xml -nunit-*.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ - -# StyleCop -StyleCopReport.xml - -# Files built by Visual Studio -*_i.c -*_p.c -*_h.h -*.ilk -*.meta -*.obj -*.iobj -*.pch -*.pdb -*.ipdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*_wpftmp.csproj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# Visual Studio Trace Files -*.e2e - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# NuGet Symbol Packages -*.snupkg -# The packages folder can be ignored because of Package Restore -**/[Pp]ackages/* -# except build/, which is used as an MSBuild target. -!**/[Pp]ackages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/[Pp]ackages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx -*.appxbundle -*.appxupload - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!?*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Including strong name files can present a security risk -# (https://github.com/github/gitignore/pull/2483#issue-259490424) -#*.snk - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm -ServiceFabricBackup/ -*.rptproj.bak - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings -*.rptproj.rsuser -*- [Bb]ackup.rdl -*- [Bb]ackup ([0-9]).rdl -*- [Bb]ackup ([0-9][0-9]).rdl - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# CodeRush personal settings -.cr/personal - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs - -# OpenCover UI analysis results -OpenCover/ - -# Azure Stream Analytics local run output -ASALocalRun/ - -# MSBuild Binary and Structured Log -*.binlog - -# NVidia Nsight GPU debugger configuration file -*.nvuser - -# MFractors (Xamarin productivity tool) working folder -.mfractor/ - -# Local History for Visual Studio -.localhistory/ - -# BeatPulse healthcheck temp database -healthchecksdb - -# Backup folder for Package Reference Convert tool in Visual Studio 2017 -MigrationBackup/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c7d92d1..ed96dd7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,57 +1,229 @@ # Changelog -## [2.6.0] - 2020-10-20 -# Added -- Add `string.AsNullIfEmpty()` - - Returns the current string, or `null` if the current string is null or empty. -- Add `string.AsNullIfWhiteSpace()` - - Returns the current string, or `null` if the current string is null, empty, or consists of only whitespace. -- Add `string.Reverse()` - - Reverses the current string -- Add `string.WithAlternative()` - - Returns the current string, or an alternative value if the current string is null or empty, or optionally if the current string consists of only whitespace. +## [3.0.0] + +In the midst of writing these release notes, I may have missed some important changes. If you notice an API change that is not documented here, +please [open an issue](https://github.com/oliverbooth/X10D/issues)! + +### Added +- Added `T.AsArrayValue()` +- Added `T.AsEnumerableValue()` +- Added `T.RepeatValue(int)` +- Added `T.ToJson([JsonSerializerOptions])` +- Added `string.FromJson([JsonSerializerOptions])` +- Added `T[].AsReadOnly()` +- Added `T[].Clear([Range])` and `T[].Clear(int, int)` +- Added `DateTime.IsLeapYear()` +- Added `DateTimeOffset.IsLeapYear()` +- Added `Endianness` enum +- Added `FileInfo.GetHash()` +- Added `FileInfo.TryWriteHash(Span, out int)` +- Added `IComparable.Clamp(T, T)` - this supersedes `Clamp` defined for hard-coded numeric types (#24) +- Added `IComparable.GreaterThan(T2)` (#22) +- Added `IComparable.GreaterThanOrEqualTo(T2)` (#22) +- Added `IComparable.LessThan(T2)` (#22) +- Added `IComparable.LessThanOrEqualTo(T2)` (#22) +- Added `IComparable.Max(T)` (#23) +- Added `IComparable.Min(T)` (#23) +- Added `IDictionary.AddOrUpdate()` +- Added `IEnumerable.Product()` and `IEnumerable.Product(Func, TResult)` for all built-in numeric types, computing the product of all (optionally transformed) elements +- Added `IList.Fill(T)` and `IList.Fill(T, int, int)` +- Added `IPAddress.IsIPv4()` and `IPAddress.IsIPv6()` +- Added `IReadOnlyList.Pack8Bit()` +- Added `IReadOnlyList.Pack16Bit()` +- Added `IReadOnlyList.Pack32Bit()` +- Added `IReadOnlyList.Pack64Bit()` +- Added `MemberInfo.HasCustomAttribute()` and `MemberInfo.HasCustomAttribute(Type)` +- Added `defaultValue` overload for `MemberInfo.SelectFromCustomAttribute()` +- Added `Random.Next()` (returns a random field from a specified enum type) +- Added `Random.NextByte([byte[, byte]])` +- Added `Random.NextColorArgb()`, returning a random color in RGB space, including random alpha +- Added `Random.NextColorRgb()`, returning a random color in RGB space with alpha 255 +- Added `Random.NextDouble(double[, double])` +- Added `Random.NextInt16([short[, short]])` +- Added `Random.NextSingle(float[, float])` (#34) +- Added `Random.NextUnitVector2()` +- Added `Random.NextUnitVector3()` +- Added `Random.NextRotation()` +- Added `Rune.Repeat(int)` +- Added `byte.IsEven()` +- Added `byte.IsOdd()` +- Added `byte.IsPrime()` +- Added `byte.UnpackBits()` +- Added `byte.RangeTo(byte)`, `byte.RangeTo(short)`, `byte.RangeTo(int)`, and `byte.RangeTo(long)` +- Added `int.Mod(int)` +- Added `int.RangeTo(int)`, and `int.RangeTo(long)` +- Added `int.UnpackBits()` +- Added `long.Mod(long)` +- Added `long.RangeTo(long)` +- Added `long.UnpackBits()` +- Added `sbyte.IsEven()` +- Added `sbyte.IsOdd()` +- Added `sbyte.IsPrime()` +- Added `sbyte.Mod(sbyte)` +- Added `short.IsEven()` +- Added `short.IsOdd()` +- Added `short.Mod(short)` +- Added `short.RangeTo(short)`, `short.RangeTo(int)`, and `short.RangeTo(long)` +- Added `short.UnpackBits()` +- Added `string.IsPalindrome()` +- Added `Stream.GetHash()` +- Added `Stream.TryWriteHash(Span, out int)` +- Added `Stream.ReadInt16([Endian])` +- Added `Stream.ReadInt32([Endian])` +- Added `Stream.ReadInt64([Endian])` +- Added `Stream.ReadUInt16([Endian])` +- Added `Stream.ReadUInt32([Endian])` +- Added `Stream.ReadUInt64([Endian])` +- Added `Stream.Write(short, [Endian])` +- Added `Stream.Write(int, [Endian])` +- Added `Stream.Write(long, [Endian])` +- Added `Stream.Write(ushort, [Endian])` +- Added `Stream.Write(uint, [Endian])` +- Added `Stream.Write(ulong, [Endian])` +- Added `TimeSpan.Ago()` +- Added `TimeSpan.FromNow()` +- Added `TimeSpanParser.TryParse` which supersedes `TimeSpanParser.Parse` +- Added `Sqrt()` and `ComplexSqrt()` for all built-in decimal types +- Added `All()` and `Any()` for `Span` and `ReadOnlySpan`, mimicking the corresponding methods in `System.Linq` +- Added `Sign()` for built-in numeric types. For unsigned types, this never returns -1 +- Added `Type.Implements()` and `Type.Implements(Type)` (#25) +- Added `Type.Inherits()` and `Type.Inherits(Type)` (#25) +- Added `DigitalRoot` function for built-in integer types +- Added `Factorial` function for built-in integer types +- Added `HostToNetworkOrder` and `NetworkToHostOrder` for `short`, `int`, and `long` +- Added `IsLeapYear` function for `DateTime` and `DateTimeOffset`, as well as built-in numeric types +- Added `MultiplicativePersistence` function for built-in integer types +- Added `RotateLeft` and `RotateRight` for built-in integer types +- Added trigonometric functions for built-in numeric types, including `Acos()`, `Asin()`, `Atan()`, `Atan2()`, `Cos()`, `Sin()`, `Tan()` (#49) +- Added time-related extension methods for built-in numeric types, including `Milliseconds()`, `Seconds()`, `Minutes()`, `Hours()`, `Days()`, and `Weeks()`. `Ticks()` is also available, but only for integers; not floating point +- Added `StringBuilderReader` (inheriting `TextReader`) which allows reading from a `StringBuilder` without consuming the underlying string +- Added extension methods for `System.Decimal` ### Changed +- Updated to .NET 6 (#45) +- Methods defined to accept `byte[]` have been changed accept `IReadOnlyList` +- Extension methods are now defined in appropriate child namespaces to reduce the risk of name collisions (#7) +- `char[].Random(int)`, `char[].Random(int, int)`, `IEnumerable.Random(int)`, and `IEnumerable.Random(int, int)` have been redefined as `Random.NextString(IReadOnlyList, int)` +- `IComparable.Between(T, T)` has been redefined as `IComparable.Between(T2, T3, [InclusiveOptions])` to allow comparison of disparate yet comparable types, and also offers inclusivity options +- `DateTime` extensions now wrap `DateTimeOffset` extensions +- `DateTime.ToUnixTimestamp([bool])` has been redefined as `DateTime.ToUnixTimeMilliseconds()` and `DateTime.ToUnixTimeSeconds()` +- `Dictionary.ToGetParameters()`, `IDictionary.ToGetParameters()`, and `IReadOnlyDictionary.ToGetParameters()`, has been redefined as `IEnumerable>.ToGetParameters()` +- `Dictionary.ToConnectionString()`, `IDictionary.ToConnectionString()`, and `IReadOnlyDictionary.ToConnectionString()`, has been redefined as `IEnumerable>.ToConnectionString()` +- `IList.OneOf([Random])` and `IEnumerable.OneOf([Random])` have been redefined as `Random.NextFrom(IEnumerable)` to fall in line with the naming convention of `System.Random` (#21) +- `IList.Shuffle([Random])` now uses a Fisher-Yates shuffle implementation +- `IList.Shuffle([Random])` has been repurposed to shuffle the list in place, rather than returning a new enumerable + - `IEnumerable.Shuffle([Random])` has been renamed to `IEnumerable.Shuffled([Random])` to avoid confusion with `IList.Shuffle([Random])` +- `Random.CoinToss()` has been redefined as `Random.NextBoolean()` to fall in line with the naming convention of `System.Random` +- `Random.OneOf(T[])` and `Random.OneOf(IList)` have been redefined as `Random.NextFrom(IEnumerable)` to fall in line with the naming convention of `System.Random` +- `Enum.Next([bool])` and `Enum.Previous([bool])` have been redefined as `Enum.Next()`, `Enum.Previous()`, `Enum.NextUnchecked()`, `Enum.PreviousUnchecked()`. The `Unchecked` variants of these methods do not perform index validation, and will throw `IndexOutOfRangeException` when attempting to access an invalid index. The checked variants will perform a modulo to wrap the index +- Seperated `string.WithAlternative(string, [bool])` to `string.WithEmptyAlternative(string)` and `string.WithWhiteSpaceAlternative(string)` +- `string.AsNullIfEmpty()` and `string.AsNullIfWhiteSpace()` now accept a nullable `string` +- `IEnumerable.GetString([Encoding])` has been renamed to `IReadOnlyList.ToString` and its `Encoding` parameter has + been made non-optional +- Fixed a bug where `IEnumerable>.ToConnectionString()` would not sanitize types with custom `ToString()` + implementation (#20) + Renamed `string.Random(int[, Random])` to `string.Randomize(int[, Random])` +- Redefined `char[].Random(int)`, `char[].Random(int, Random)`, `IEnumerable.Random(int)`, and `IEnumerable.Random(int, Random)`, as `Random.NextString(IReadOnlyList, int)` +- Improved performance for: + - `string.IsLower()` + - `string.IsUpper()` + - `string.Reverse()` + - `TimeSpanParser` + +### Removed +- Indefinitely suspended Unity support, until such a time that Unity can be updated to a newer version of .NET. See: https://forum.unity.com/threads/unity-future-net-development-status.1092205/ +- Removed `bool.And(bool)` +- Removed `bool.NAnd(bool)` +- Removed `bool.NOr(bool)` +- Removed `bool.Not(bool)` +- Removed `bool.Or(bool)` +- Removed `bool.ToByte()` +- Removed `bool.ToInt16()` +- Removed `bool.ToInt64()` +- Removed `bool.XNOr()` +- Removed `bool.XOr()` +- Removed `IConvertible.To([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrDefault([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrDefault(out T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrNull([IFormatProvider])` (#13) +- Removed `IConvertible.ToOrNull(out T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrOther(T, [IFormatProvider])` (#13) +- Removed `IConvertible.ToOrOther(out T, T, [IFormatProvider])` (#13) +- Removed `IEnumerable.Split(int)` - this functionality is now provided by .NET in the form of the `Chunk` method. See: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.chunk?view=net-6.0 +- Removed `MemberInfo.GetDefaultValue()` (use `SelectFromCustomAttribute()` instead) +- Removed `MemberInfo.GetDescription()` (use `SelectFromCustomAttribute()` instead) +- Removed `int.ToBoolean()` +- Removed `long.ToBoolean()` +- Removed `short.ToBoolean()` +- Removed `uint.ToBoolean()` +- Removed `ushort.ToBoolean()` +- Removed `ulong.ToBoolean()` +- Removed `WaitHandle.WaitOneAsync()`. For suspensions of execution during asynchronous operations, favour `TaskCompletionSource` or `TaskCompletionSource`. See: https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource?view=net-6.0 and https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1?view=net-6.0 + +## [2.6.0] - 2020-10-20 + +### Added + +- Add `string.AsNullIfEmpty()` + - Returns the current string, or `null` if the current string is null or empty. +- Add `string.AsNullIfWhiteSpace()` + - Returns the current string, or `null` if the current string is null, empty, or consists of only whitespace. +- Add `string.Reverse()` + - Reverses the current string +- Add `string.WithAlternative()` + - Returns the current string, or an alternative value if the current string is null or empty, or optionally if the current + string consists of only whitespace. + +### Changed + - n/a ### Removed + - n/a ## [2.5.0] - 2020-07-15 + ### Added + - `WaitHandle.WaitOneAsync()` - - Wraps `WaitHandle.WaitOne` as a `Task` + - Wraps `WaitHandle.WaitOne` as a `Task` - Add support for Unity 2019.4.3f1 - - Add `GameObject.LookAt(GameObject)` - - Rotates the Transform on the current GameObject so that it faces the Transform on another GameObject - - Add `GameObject.LookAt(Transform)` - - Rotates the Transform on the current GameObject so that it faces another transform - - Add `Transform.LookAt(GameObject)` - - Rotates the current Transform so that it faces the Transform on another GameObject - - Add `Vector3.Round([float])` - - Returns a rounded Vector3 by calling `float.Round()` on each component - - Add `Vector3.WithX(float)` - - Returns a Vector3 with a new X component value - - Add `Vector3.WithY(float)` - - Returns a Vector3 with a new Y component value - - Add `Vector3.WithZ(float)` - - Returns a Vector3 with a new Z component value - - Add `Vector3.WithXY(float, float)` - - Returns a Vector3 with new X and Y component values - - Add `Vector3.WithXZ(float, float)` - - Returns a Vector3 with new X and Z component values - - Add `Vector3.WithYZ(float, float)` - - Returns a Vector3 with new Y and Z component values - - Add `BetterBehavior` (experimental wrapper over `MonoBehaviour`) + - Add `GameObject.LookAt(GameObject)` + - Rotates the Transform on the current GameObject so that it faces the Transform on another GameObject + - Add `GameObject.LookAt(Transform)` + - Rotates the Transform on the current GameObject so that it faces another transform + - Add `Transform.LookAt(GameObject)` + - Rotates the current Transform so that it faces the Transform on another GameObject + - Add `Vector3.Round([float])` + - Returns a rounded Vector3 by calling `float.Round()` on each component + - Add `Vector3.WithX(float)` + - Returns a Vector3 with a new X component value + - Add `Vector3.WithY(float)` + - Returns a Vector3 with a new Y component value + - Add `Vector3.WithZ(float)` + - Returns a Vector3 with a new Z component value + - Add `Vector3.WithXY(float, float)` + - Returns a Vector3 with new X and Y component values + - Add `Vector3.WithXZ(float, float)` + - Returns a Vector3 with new X and Z component values + - Add `Vector3.WithYZ(float, float)` + - Returns a Vector3 with new Y and Z component values + - Add `BetterBehavior` (experimental wrapper over `MonoBehaviour`) ### Changed + - n/a ### Removed + - n/a ## [2.2.0] - 2020-04-21 + ### Added + - Add `string.ChangeEncoding(Encoding, Encoding)` - Converts this string from one encoding to another - Add `string.IsLower()` @@ -62,19 +234,24 @@ - Various extension methods with regards to reflection: - `GetDefaultValue` and `GetDefaultValue` - gets the value stored in the member's `DefaultValue` attribute - `GetDescription`- gets the value stored in the member's `Description` attribute - - `SelectFromCustomAttribute` - Internally calls `GetCustomAttribute` and passes it to a `Func` so that specific members may be selected + - `SelectFromCustomAttribute` - Internally calls `GetCustomAttribute` and passes it to a `Func` so that + specific members may be selected ### Changed + - n/a ### Removed + - n/a ## [2.1.0] - 2020-04-18 + ### Added + - Add `bool bool.And(bool)` - Performs logical AND -- Add `bool bool.Or(bool)` +- Add `bool bool.Or(bool)` - Performs logical OR - Add `bool bool.Not(bool)` - Performs logical NOT @@ -94,29 +271,35 @@ - 1 if `true`, 0 otherwise ### Changed + - n/a ### Removed + - n/a ## [2.0.0] - 2020-04-18 ### Added + - Add `IEnumerable.Split(int)` - Performs the same operation as the previously defined `IEnumerable.Chunkify(int)`, except now accepts any type `T` ### Changed + - Fix `DateTime.Last(DayOfWeek)` implementation - Now returns the correct date/time for a given day of the week ### Removed + - Remove `IEnumerable.Chunkify(int)` - Replaced by a method of the same behaviour `IEnumerable.Split(int)` ## Earlier versions + ### ***Not documented*** -[Unreleased]: https://github.com/oliverbooth/X10D/tree/HEAD +[3.0.0]: https://github.com/oliverbooth/X10D/releases/tag/3.0.0 [2.6.0]: https://github.com/oliverbooth/X10D/releases/tag/2.6.0 [2.5.0]: https://github.com/oliverbooth/X10D/releases/tag/2.5.0 [2.2.0]: https://github.com/oliverbooth/X10D/releases/tag/2.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index edf02e4..86d8255 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,25 +2,25 @@ Contributions to this project are always welcome. If you spot a bug, or want to request a new extension method, open a new issue or submit a pull request. ### Pull request guidelines -This project uses C# 8.0 language features, and adheres to the following analyzers: - -- Rozlynator -- FxCop -- StyleCop - -There is an `.editorconfig` and an analyzer `ruleset` file included in this repository. For quick and painless pull requests, ensure that these analyzers do not throw warnings. +This project uses C# 9.0 language features, and adheres to the FxCop analyzer. +There is an `.editorconfig` included in this repository. For quick and painless pull requests, ensure that the analyzer does not throw warnings. ### Code style Below are a few pointers to which you may refer, but keep in mind this is not an exhaustive list: -- Use C# 8.0 features where possible -- Try to ensure code is CLS-compliant +- Use C# 9.0 features where possible +- Try to ensure code is CLS-compliant. Where this is not possible, decorate methods with `CLSCompliantAttribute` and pass `false` - Follow all .NET guidelines for naming conventions -- Make full use of XMLDoc and be thorough - but concise - with all documentation. -- Ensure that no line exceeds 120 characters in length +- Make full use of XMLDoc and be thorough - but concise - with all documentation +- Ensure that no line exceeds 130 characters in length - Do NOT include file headers in any form -- Declare `using` directives within namespace scope -- Try to avoid using exceptions for flow control +- Declare `using` directives outside of namespace scope +- Avoid using exceptions for flow control where possible +- Use braces, even for single-statement bodies +- Use implicit type when the type is apparent or not important +- Use U.S. English throughout the codebase and documentation + +When in doubt, follow .NET guidelines for styling. ### Tests When introducing a new extension method, you must ensure that you have also defined a unit test that asserts its correct behavior. The code style guidelines and code-analysis rules apply to the `X10D.Tests` equally as much as `X10D`, although documentation may be briefer. Refer to existing tests as a guideline. diff --git a/LICENSE.md b/LICENSE.md index b6e3a9a..ed7c8c7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019 Oliver Booth +Copyright (c) 2019-2022 Oliver Booth Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 7df0391..be29bd1 100644 --- a/README.md +++ b/README.md @@ -1,259 +1,46 @@ -# X10D -## Extension methods on crack - -[](https://github.com/oliverbooth/X10D/actions?query=workflow%3A%22.NET+Core%22) -[](https://github.com/oliverbooth/X10D/issues) -[](https://www.nuget.org/packages/X10D/) -[](https://github.com/oliverbooth/X10D/blob/master/LICENSE.md) +

+

+GitHub Workflow Status +GitHub Issues +Coverage +NuGet Downloads +Stable Version +Nightly Version +MIT License +

### About -X10D (pronounced *extend*), is a class library that provides extension methods for numerous .NET types. The purpose of this library is to simplify a codebase by reducing the need for repeated code when performing common operations. Simplify your codebase. Take advantage of .NET. Use extension methods. +X10D (pronounced *extend*), is a .NET package that provides extension methods for numerous types. The purpose of this library is to simplify a codebase by reducing the need for repeated code when performing common operations. Simplify your codebase. Take advantage of .NET. Use extension methods. *(I'm also [dogfooding](https://www.pcmag.com/encyclopedia/term/dogfooding) this library, so there's that.)* ### Table of contents -- [Install](#install) +- [Installation](#installation) + - [Unity installation](#unity-installation) + - [NuGet installation](#nuget-installation) - [Features](#features) - - [Numeric](#numeric) - - [String](#string) - - [DateTime](#datetime) - - [Enumerable](#enumerable) - - [Enum](#enum) - - [Conversion](#conversion) - - [Random](#random) - [Contributing](#contributing) - [License](#license) You can find the list of classes that have extension methods by viewing the `README.md` file in any of the respective library folders. -## Install -Install X10D with NuGet via the following command: +## Installation +### NuGet installation ```ps -Install-Package X10D -Version 2.6.0 +Install-Package X10D -Version 3.0.0 ``` -or by downloading the [latest release](https://github.com/oliverbooth/X10D/releases/latest) from this repository. + +### Manual installation +Download the [latest release](https://github.com/oliverbooth/X10D/releases/latest) from this repository and adding a direct assembly reference for your chosen platform. + +### What happened to X10D.Unity? +I decided it was time for X10D to be migrated to .NET 6. Unity currently supports .NET Framework 4.x / .NET Standard 2.1, and as such is not compatible with X10D at this time. +Unity have announced official support for .NET 6 in the future (see [this forum post](https://forum.unity.com/threads/unity-future-net-development-status.1092205/) for more details), +but until such a time that Unity supports .NET 6, X10D.Unity will not be maintained or innovated upon. ## Features -### Numeric extensions -> 👍 ProTip: *Most* of the extensions available for `int` will also exist for `short`, `long`, and their unsigned counterparts! - -#### `bool` <-> `int` -Convert a `bool` to an `int` by using `ToInt32`. The value returned is 1 if the input is `true`, and 0 if it's `false`. -```cs -bool b = true; - -int i = b.ToInt32(); // 1 -``` -The same also works in reverse. Using `ToBoolean` on an `int` will return `false` if the input is 0, and `true`if the input is anything else. -```cs -int zero = 0; -long nonZero = 1; - -bool b1 = zero.ToBoolean(); // false -bool b2 = nonZero.ToBoolean(); // true -``` - -#### Between -Determine if a value is between other values using `Between` like so: -```cs -int i = 3; - -if (i.Between(2, 4)) -{ - // i is between 2 and 4! -} -``` -Since the signature of this method is defined with a generic constraint of `IComparable`, this will also work for any object that is `IComparable` - not just numeric types! -```cs -bool Between(this T actual, T lower, T upper) where T : IComparable -``` - -#### IsEven (*and IsOdd*) -As the names suggest, this method determines if the input value is evenly divisible by 2. -```cs -int i = 5; -bool b = i.IsEven(); // false -``` -There is also an `IsOdd` extension method, which will return the opposite of that returned by `IsEven`. - -#### IsPrime -Determine if an integral is a prime number by using `IsPrime`. -```cs -bool b = 43.IsPrime(); // true -``` - -#### Clamp -Clamp a value between an upper and lower bound -```cs -int i = 5.Clamp(0, 3); // 3 -``` - -#### Convert degrees <-> radians -Easily convert between radians and degrees -```cs -double rad = 2 * Math.PI; -double deg = rad.RadiansToDegrees(); // 360 - -rad = deg.DegreesToRadians(); // back to 2*pi -``` - -#### Round -Round a value to the nearest whole number: -```cs -var d = 2.75; -var rounded = d.Round(); // 3 -``` -Or specify a value to have it round to the nearest multiple of `x`: -```cs -double a = 8.0.Round(10); // 10 -double b = 2.0.Round(10); // 0 -``` - -### String -#### Repeat value -Repeat a string or a char a specific number of times using `Repeat` -```cs -var c = '-'; -var str = "foo"; - -string repeatedC = c.Repeat(10); // ---------- -string repeatedStr = str.Repeat(5); // foofoofoofoofoo -``` - -#### Base-64 encode/decode -```cs -var base64 = "Hello World".Base64Encode(); // SGVsbG8gV29ybGQ= -var str = "SGVsbG8gV29ybGQ=".Base64Decode(); // Hello World -``` - -### DateTime - -#### Age -Get a rounded integer representing the number of years since a given date. i.e. easily calculate someone's age: -```cs -var dateOfBirth = new DateTime(1960, 7, 16); -int age = dateOfBirth.Age(); // the age as of today -``` -You can also specify a date at which to stop counting the years, by passing an `asOf` date: -```cs -var dateOfBirth = new DateTime(1960, 7, 16); -int age = dateOfBirth.Age(new DateTime(1970, 7, 16)); // 10, the age as of 1970 -``` - -#### To/From Unix Timestamp -Convert to/from a Unix timestamp represented in seconds using `FromUnixTimestamp` on a numeric type, and `ToUnixTimestamp` on a `DateTime`. -```cs -long sec = 1587223415; -DateTime time = sec.FromUnixTimestamp(); // 2020-04-18 15:23:35 -long unix = time.ToUnixTimestamp(); -``` -or represent it with milliseconds by passing `true` for the `isMillis` argument: -```cs -long millis = 1587223415500; -DateTime time = millis.FromUnixTimestamp(true); // 2020-04-18 15:23:35.50 -long unix = time.ToUnixTimestamp(true); -``` - -#### Get first/last day of month -Get the first or last day of the month by using `FirstDayOfMonth` and `LastDayOfMonth` -```cs -var dt = new DateTime(2016, 2, 4); - -DateTime first = dt.FirstDayOfMonth(); // 2016-02-01 -DateTime last = dt.LastDayOfMonth(); // 2016-02-29 (2016 is a leap year) -``` -You can also use `First` or `Last` to get the first or final occurrence of a specific day of the week in a given month: -```cs -var dt = new DateTime(2019, 4, 14); - -DateTime theLastFriday = dt.Last(DayOfWeek.Friday); // 2019-04-24 -DateTime theLastThursday = dt.Last(DayOfWeek.Thursday); // 2019-04-40 -``` - -### Enumerable -#### Split into chunks -Split an `IEnumerable` into an `IEnumerable>`, essentially "chunking" the original IEnumerable into a specific size -```cs -var arr = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }; -var chunks = arr.Split(2); // split into chunks of 2 - -foreach (var chunk in chunks) -{ - Console.WriteLine(string.Join(", ", chunk)); -} - -// Output: -// 1, 2 -// 3, 4 -// 5, 6 -// 7, 8 -``` -This also works for `string`: -```cs -var str = "Hello World"; -var chunks = str.Split(2); // split string into chunks of 2 - -foreach (var chunk in chunks) -{ - Console.WriteLine(string.Join(string.Empty, chunk)); -} - -// Output: -// He -// ll -// o <-- space is included -// Wo -// rl -// d <-- no space! end of string -``` - -### Enum -#### Parse string into enum -You can use the `EnumParse` method to convert a string into a value from an enum, while optionally ignoring case: -```cs -enum Number -{ - Zero, - One, - Two, - Three, -} - -Number num = "two".EnumParse(true); // num == Number.Two -``` - -#### `Next` / `Previous` enum cycling -Cycle through the values in an enum with `Next` and `Previous`: -```cs -Number two = Number.Two; - -Number one = two.Previous(); -Number three = two.Next(); -``` - -### Conversion -Easily convert between types using `To`, `ToOrNull`, `ToOrDefault`, or `ToOrOther`, thereby shortening the call to `Convert.ChangeType` or `Convert.ToX`: -```CS -int i = "43".To(); -int j = "a".ToOrDefault(); // 0 -int k = "a".ToOrOther(100); // 100 -``` - -### Random -Do more with Random including flip a coin, randomly select an element in an array, or shuffle the array entirely. -```cs -var random = new Random(); - -// flip a coin -bool heads = random.CoinToss(); - -// randomly choose an item -var arr = new int[] { 1, 2, 3, 4 }; -var item = random.OneOf(arr); - -// shuffle an array or list -var shuffled = arr.Shuffle(random); -``` +I'm planning on writing complete and extensive documentation in the near future. As of this time, feel free to browse the source or the API using your favourite IDE. +For those familiar with the 2.6.0 API, please read [CHANGELOG.md](CHANGELOG.md) for a complete list of changes. **3.0.0 is a major release and introduces many breaking changes.** ## Contributing Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/X10D.SourceValidator/Program.cs b/X10D.SourceValidator/Program.cs new file mode 100644 index 0000000..2f09427 --- /dev/null +++ b/X10D.SourceValidator/Program.cs @@ -0,0 +1,64 @@ +using System.Text; + +var directories = new Stack(Directory.GetDirectories(args[0])); +var problems = 0; +var files = 0; + +while (directories.Count > 0) +{ + string path = Path.GetFullPath(directories.Pop()); + + foreach (string directory in Directory.EnumerateDirectories(path)) + { + directories.Push(directory); + } + + foreach (string file in Directory.EnumerateFiles(path, "*.cs")) + { + files++; + await using var stream = File.OpenRead(file); + using var reader = new StreamReader(stream, Encoding.UTF8); + var blankLine = false; + + var lineNumber = 1; + while (await reader.ReadLineAsync() is { } line) + { + if (string.IsNullOrWhiteSpace(line)) + { + if (blankLine) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Double blank line"); + Console.ResetColor(); + problems++; + } + + blankLine = true; + } + else + { + blankLine = false; + } + + if (line.Length > 130) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Line is too long ({line.Length})"); + Console.ResetColor(); + problems++; + } + else if (line.Length > 0 && char.IsWhiteSpace(line[^1])) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.Out.WriteLine($"{file}({lineNumber}): Line contains trailing whitespace"); + Console.ResetColor(); + problems++; + } + + lineNumber++; + } + } +} + +Console.Out.WriteLine($"Finished scanning {files} files, {problems} problems encountered."); +return problems; diff --git a/X10D.SourceValidator/X10D.SourceValidator.csproj b/X10D.SourceValidator/X10D.SourceValidator.csproj new file mode 100644 index 0000000..8957a5d --- /dev/null +++ b/X10D.SourceValidator/X10D.SourceValidator.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/X10D.Tests/1000primes.txt b/X10D.Tests/1000primes.txt new file mode 100644 index 0000000..1d931b0 --- /dev/null +++ b/X10D.Tests/1000primes.txt @@ -0,0 +1,1000 @@ +2 +3 +5 +7 +11 +13 +17 +19 +23 +29 +31 +37 +41 +43 +47 +53 +59 +61 +67 +71 +73 +79 +83 +89 +97 +101 +103 +107 +109 +113 +127 +131 +137 +139 +149 +151 +157 +163 +167 +173 +179 +181 +191 +193 +197 +199 +211 +223 +227 +229 +233 +239 +241 +251 +257 +263 +269 +271 +277 +281 +283 +293 +307 +311 +313 +317 +331 +337 +347 +349 +353 +359 +367 +373 +379 +383 +389 +397 +401 +409 +419 +421 +431 +433 +439 +443 +449 +457 +461 +463 +467 +479 +487 +491 +499 +503 +509 +521 +523 +541 +547 +557 +563 +569 +571 +577 +587 +593 +599 +601 +607 +613 +617 +619 +631 +641 +643 +647 +653 +659 +661 +673 +677 +683 +691 +701 +709 +719 +727 +733 +739 +743 +751 +757 +761 +769 +773 +787 +797 +809 +811 +821 +823 +827 +829 +839 +853 +857 +859 +863 +877 +881 +883 +887 +907 +911 +919 +929 +937 +941 +947 +953 +967 +971 +977 +983 +991 +997 +1009 +1013 +1019 +1021 +1031 +1033 +1039 +1049 +1051 +1061 +1063 +1069 +1087 +1091 +1093 +1097 +1103 +1109 +1117 +1123 +1129 +1151 +1153 +1163 +1171 +1181 +1187 +1193 +1201 +1213 +1217 +1223 +1229 +1231 +1237 +1249 +1259 +1277 +1279 +1283 +1289 +1291 +1297 +1301 +1303 +1307 +1319 +1321 +1327 +1361 +1367 +1373 +1381 +1399 +1409 +1423 +1427 +1429 +1433 +1439 +1447 +1451 +1453 +1459 +1471 +1481 +1483 +1487 +1489 +1493 +1499 +1511 +1523 +1531 +1543 +1549 +1553 +1559 +1567 +1571 +1579 +1583 +1597 +1601 +1607 +1609 +1613 +1619 +1621 +1627 +1637 +1657 +1663 +1667 +1669 +1693 +1697 +1699 +1709 +1721 +1723 +1733 +1741 +1747 +1753 +1759 +1777 +1783 +1787 +1789 +1801 +1811 +1823 +1831 +1847 +1861 +1867 +1871 +1873 +1877 +1879 +1889 +1901 +1907 +1913 +1931 +1933 +1949 +1951 +1973 +1979 +1987 +1993 +1997 +1999 +2003 +2011 +2017 +2027 +2029 +2039 +2053 +2063 +2069 +2081 +2083 +2087 +2089 +2099 +2111 +2113 +2129 +2131 +2137 +2141 +2143 +2153 +2161 +2179 +2203 +2207 +2213 +2221 +2237 +2239 +2243 +2251 +2267 +2269 +2273 +2281 +2287 +2293 +2297 +2309 +2311 +2333 +2339 +2341 +2347 +2351 +2357 +2371 +2377 +2381 +2383 +2389 +2393 +2399 +2411 +2417 +2423 +2437 +2441 +2447 +2459 +2467 +2473 +2477 +2503 +2521 +2531 +2539 +2543 +2549 +2551 +2557 +2579 +2591 +2593 +2609 +2617 +2621 +2633 +2647 +2657 +2659 +2663 +2671 +2677 +2683 +2687 +2689 +2693 +2699 +2707 +2711 +2713 +2719 +2729 +2731 +2741 +2749 +2753 +2767 +2777 +2789 +2791 +2797 +2801 +2803 +2819 +2833 +2837 +2843 +2851 +2857 +2861 +2879 +2887 +2897 +2903 +2909 +2917 +2927 +2939 +2953 +2957 +2963 +2969 +2971 +2999 +3001 +3011 +3019 +3023 +3037 +3041 +3049 +3061 +3067 +3079 +3083 +3089 +3109 +3119 +3121 +3137 +3163 +3167 +3169 +3181 +3187 +3191 +3203 +3209 +3217 +3221 +3229 +3251 +3253 +3257 +3259 +3271 +3299 +3301 +3307 +3313 +3319 +3323 +3329 +3331 +3343 +3347 +3359 +3361 +3371 +3373 +3389 +3391 +3407 +3413 +3433 +3449 +3457 +3461 +3463 +3467 +3469 +3491 +3499 +3511 +3517 +3527 +3529 +3533 +3539 +3541 +3547 +3557 +3559 +3571 +3581 +3583 +3593 +3607 +3613 +3617 +3623 +3631 +3637 +3643 +3659 +3671 +3673 +3677 +3691 +3697 +3701 +3709 +3719 +3727 +3733 +3739 +3761 +3767 +3769 +3779 +3793 +3797 +3803 +3821 +3823 +3833 +3847 +3851 +3853 +3863 +3877 +3881 +3889 +3907 +3911 +3917 +3919 +3923 +3929 +3931 +3943 +3947 +3967 +3989 +4001 +4003 +4007 +4013 +4019 +4021 +4027 +4049 +4051 +4057 +4073 +4079 +4091 +4093 +4099 +4111 +4127 +4129 +4133 +4139 +4153 +4157 +4159 +4177 +4201 +4211 +4217 +4219 +4229 +4231 +4241 +4243 +4253 +4259 +4261 +4271 +4273 +4283 +4289 +4297 +4327 +4337 +4339 +4349 +4357 +4363 +4373 +4391 +4397 +4409 +4421 +4423 +4441 +4447 +4451 +4457 +4463 +4481 +4483 +4493 +4507 +4513 +4517 +4519 +4523 +4547 +4549 +4561 +4567 +4583 +4591 +4597 +4603 +4621 +4637 +4639 +4643 +4649 +4651 +4657 +4663 +4673 +4679 +4691 +4703 +4721 +4723 +4729 +4733 +4751 +4759 +4783 +4787 +4789 +4793 +4799 +4801 +4813 +4817 +4831 +4861 +4871 +4877 +4889 +4903 +4909 +4919 +4931 +4933 +4937 +4943 +4951 +4957 +4967 +4969 +4973 +4987 +4993 +4999 +5003 +5009 +5011 +5021 +5023 +5039 +5051 +5059 +5077 +5081 +5087 +5099 +5101 +5107 +5113 +5119 +5147 +5153 +5167 +5171 +5179 +5189 +5197 +5209 +5227 +5231 +5233 +5237 +5261 +5273 +5279 +5281 +5297 +5303 +5309 +5323 +5333 +5347 +5351 +5381 +5387 +5393 +5399 +5407 +5413 +5417 +5419 +5431 +5437 +5441 +5443 +5449 +5471 +5477 +5479 +5483 +5501 +5503 +5507 +5519 +5521 +5527 +5531 +5557 +5563 +5569 +5573 +5581 +5591 +5623 +5639 +5641 +5647 +5651 +5653 +5657 +5659 +5669 +5683 +5689 +5693 +5701 +5711 +5717 +5737 +5741 +5743 +5749 +5779 +5783 +5791 +5801 +5807 +5813 +5821 +5827 +5839 +5843 +5849 +5851 +5857 +5861 +5867 +5869 +5879 +5881 +5897 +5903 +5923 +5927 +5939 +5953 +5981 +5987 +6007 +6011 +6029 +6037 +6043 +6047 +6053 +6067 +6073 +6079 +6089 +6091 +6101 +6113 +6121 +6131 +6133 +6143 +6151 +6163 +6173 +6197 +6199 +6203 +6211 +6217 +6221 +6229 +6247 +6257 +6263 +6269 +6271 +6277 +6287 +6299 +6301 +6311 +6317 +6323 +6329 +6337 +6343 +6353 +6359 +6361 +6367 +6373 +6379 +6389 +6397 +6421 +6427 +6449 +6451 +6469 +6473 +6481 +6491 +6521 +6529 +6547 +6551 +6553 +6563 +6569 +6571 +6577 +6581 +6599 +6607 +6619 +6637 +6653 +6659 +6661 +6673 +6679 +6689 +6691 +6701 +6703 +6709 +6719 +6733 +6737 +6761 +6763 +6779 +6781 +6791 +6793 +6803 +6823 +6827 +6829 +6833 +6841 +6857 +6863 +6869 +6871 +6883 +6899 +6907 +6911 +6917 +6947 +6949 +6959 +6961 +6967 +6971 +6977 +6983 +6991 +6997 +7001 +7013 +7019 +7027 +7039 +7043 +7057 +7069 +7079 +7103 +7109 +7121 +7127 +7129 +7151 +7159 +7177 +7187 +7193 +7207 +7211 +7213 +7219 +7229 +7237 +7243 +7247 +7253 +7283 +7297 +7307 +7309 +7321 +7331 +7333 +7349 +7351 +7369 +7393 +7411 +7417 +7433 +7451 +7457 +7459 +7477 +7481 +7487 +7489 +7499 +7507 +7517 +7523 +7529 +7537 +7541 +7547 +7549 +7559 +7561 +7573 +7577 +7583 +7589 +7591 +7603 +7607 +7621 +7639 +7643 +7649 +7669 +7673 +7681 +7687 +7691 +7699 +7703 +7717 +7723 +7727 +7741 +7753 +7757 +7759 +7789 +7793 +7817 +7823 +7829 +7841 +7853 +7867 +7873 +7877 +7879 +7883 +7901 +7907 +7919 \ No newline at end of file diff --git a/X10D.Tests/X10D.Tests.csproj b/X10D.Tests/X10D.Tests.csproj index bdba516..f6f97d4 100644 --- a/X10D.Tests/X10D.Tests.csproj +++ b/X10D.Tests/X10D.Tests.csproj @@ -1,35 +1,29 @@ - + - netcoreapp3.1 + net6.0 false - ..\X10D.ruleset - true + enable + true - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + - \ No newline at end of file + + + + + + diff --git a/X10D.Tests/src/Assembly.cs b/X10D.Tests/src/Assembly.cs index 8c11453..f547610 100644 --- a/X10D.Tests/src/Assembly.cs +++ b/X10D.Tests/src/Assembly.cs @@ -1,3 +1 @@ -using System; - -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(true)] diff --git a/X10D.Tests/src/Collections/ArrayTests.cs b/X10D.Tests/src/Collections/ArrayTests.cs new file mode 100644 index 0000000..cc053ff --- /dev/null +++ b/X10D.Tests/src/Collections/ArrayTests.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ArrayTests +{ + [TestMethod] + public void AsReadOnlyShouldBeReadOnly() + { + var array = new object[] {1, "f", true}; + var readOnly = array.AsReadOnly(); + Assert.IsNotNull(readOnly); + Assert.IsTrue(readOnly.Count == 3); + + // ReSharper disable once ConvertTypeCheckToNullCheck + Assert.IsTrue(readOnly is IReadOnlyCollection); + } + + [TestMethod] + public void AsReadOnlyNullShouldThrow() + { + object[]? array = null; + Assert.ThrowsException(array!.AsReadOnly); + } + + [TestMethod] + [DataRow] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void ClearShouldFillDefault(params int[] args) + { + args.Clear(); + + int[] clearedArray = Enumerable.Repeat(0, args.Length).ToArray(); + CollectionAssert.AreEqual(clearedArray, args); + } + + [TestMethod] + public void ClearNullShouldThrow() + { + int[]? array = null; + Assert.ThrowsException(array!.Clear); + Assert.ThrowsException(() => array!.Clear(0, 0)); + } +} diff --git a/X10D.Tests/src/Collections/BoolListTests.cs b/X10D.Tests/src/Collections/BoolListTests.cs new file mode 100644 index 0000000..e67aafc --- /dev/null +++ b/X10D.Tests/src/Collections/BoolListTests.cs @@ -0,0 +1,56 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class BoolListTests +{ + [TestMethod] + public void Pack8Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false}; + Assert.AreEqual(85, array.PackByte()); // 01010101 + } + + [TestMethod] + public void Pack16Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt16()); // 101101010101 + } + + [TestMethod] + public void Pack32Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt32()); // 101101010101 + } + + [TestMethod] + public void Pack64Bit_Should_Pack_Correctly() + { + var array = new[] {true, false, true, false, true, false, true, false, true, true, false, true}; + Assert.AreEqual(2901, array.PackInt64()); // 101101010101 + } + + [TestMethod] + public void Pack_ShouldThrow_GivenLargeArray() + { + bool[] array = Enumerable.Repeat(false, 65).ToArray(); + Assert.ThrowsException(() => array.PackByte()); + Assert.ThrowsException(() => array.PackInt16()); + Assert.ThrowsException(() => array.PackInt32()); + Assert.ThrowsException(() => array.PackInt64()); + } + + [TestMethod] + public void Pack_ShouldThrow_GivenNull() + { + bool[]? array = null; + Assert.ThrowsException(() => array!.PackByte()); + Assert.ThrowsException(() => array!.PackInt16()); + Assert.ThrowsException(() => array!.PackInt32()); + Assert.ThrowsException(() => array!.PackInt64()); + } +} diff --git a/X10D.Tests/src/Collections/ByteTests.cs b/X10D.Tests/src/Collections/ByteTests.cs new file mode 100644 index 0000000..15c28a8 --- /dev/null +++ b/X10D.Tests/src/Collections/ByteTests.cs @@ -0,0 +1,57 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = ((byte)0b11010100).Unpack(); + + Assert.AreEqual(8, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[8]; + ((byte)0b11010100).Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, ((byte)0b11010100).Unpack().PackByte()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + ((byte)0b11010100).Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/DictionaryTests.cs b/X10D.Tests/src/Collections/DictionaryTests.cs new file mode 100644 index 0000000..9fb8be2 --- /dev/null +++ b/X10D.Tests/src/Collections/DictionaryTests.cs @@ -0,0 +1,189 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class DictionaryTests +{ + [TestMethod] + public void AddOrUpdate_ShouldAddNewKey_IfNotExists() + { + var dictionary = new Dictionary(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, "one", (_, _) => string.Empty); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, _ => "one", (_, _) => string.Empty); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + + dictionary.AddOrUpdate(1, (_, _) => "one", (_, _, _) => string.Empty, 0); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + } + + [TestMethod] + public void AddOrUpdate_ShouldUpdateKey_IfExists() + { + var dictionary = new Dictionary {[1] = "one"}; + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("one", dictionary[1]); + + dictionary.AddOrUpdate(1, string.Empty, (_, _) => "two"); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + dictionary[1] = "one"; + + dictionary.AddOrUpdate(1, _ => string.Empty, (_, _) => "two"); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + + dictionary.Clear(); + Assert.IsFalse(dictionary.ContainsKey(1)); + dictionary[1] = "one"; + + dictionary.AddOrUpdate(1, (_, _) => string.Empty, (_, _, _) => "two", 0); + Assert.IsTrue(dictionary.ContainsKey(1)); + Assert.AreEqual("two", dictionary[1]); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullDictionary() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.AddOrUpdate(1, string.Empty, (_, _) => string.Empty)); + Assert.ThrowsException(() => + dictionary!.AddOrUpdate(1, _ => string.Empty, (_, _) => string.Empty)); + Assert.ThrowsException(() => + dictionary!.AddOrUpdate(1, (_, _) => string.Empty, (_, _, _) => string.Empty, 0)); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullUpdateValueFactory() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, string.Empty, null!)); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, _ => string.Empty, null!)); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, (_, _) => string.Empty, null!, 0)); + } + + [TestMethod] + public void AddOrUpdate_ShouldThrow_GivenNullAddValueFactory() + { + var dictionary = new Dictionary(); + Func? addValueFactory = null; + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, addValueFactory!, (_, _) => "one")); + Assert.ThrowsException(() => dictionary.AddOrUpdate(1, null!, (_, _, _) => "one", 0)); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(); + Assert.AreEqual("Data Source=localhost;Initial Catalog=test;Integrated Security=True;Foobar=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnTransformedValueConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(v => v?.ToUpperInvariant()); + Assert.AreEqual("Data Source=LOCALHOST;Initial Catalog=TEST;Integrated Security=TRUE;Foobar=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldReturnTransformedKeyValueConnectionString() + { + var dictionary = new Dictionary + { + ["Data Source"] = "localhost", ["Initial Catalog"] = "test", ["Integrated Security"] = "True", ["Foobar"] = null + }; + + string connectionString = dictionary.ToConnectionString(k => k.ToUpper(), v => v?.ToUpperInvariant()); + Assert.AreEqual("DATA SOURCE=LOCALHOST;INITIAL CATALOG=TEST;INTEGRATED SECURITY=TRUE;FOOBAR=", connectionString); + } + + [TestMethod] + public void ToConnectionString_ShouldThrow_GivenNullSource() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.ToConnectionString()); + Assert.ThrowsException(() => dictionary!.ToConnectionString(null!)); + Assert.ThrowsException(() => dictionary!.ToConnectionString(null!, null!)); + } + + [TestMethod] + public void ToConnectionString_ShouldThrow_GivenNullSelector() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.ToConnectionString(null!)); + Assert.ThrowsException(() => dictionary.ToConnectionString(null!, _ => _)); + Assert.ThrowsException(() => dictionary.ToConnectionString(_ => _, null!)); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = "bar"}; + + string queryString = dictionary.ToGetParameters(); + Assert.AreEqual("id=1&user=hello+world&foo=bar", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnTransformedValueParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = null}; + + string queryString = dictionary.ToGetParameters(v => v?.ToUpper()); + Assert.AreEqual("id=1&user=HELLO+WORLD&foo=", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldReturnTransformedKeyValueParameters() + { + var dictionary = new Dictionary {["id"] = "1", ["user"] = "hello world", ["foo"] = null}; + + string queryString = dictionary.ToGetParameters(k => k.ToUpper(), v => v?.ToUpper()); + Assert.AreEqual("ID=1&USER=HELLO+WORLD&FOO=", queryString); + } + + [TestMethod] + public void ToGetParameters_ShouldThrow_GivenNullSource() + { + Dictionary? dictionary = null; + Assert.ThrowsException(() => dictionary!.ToGetParameters()); + Assert.ThrowsException(() => dictionary!.ToGetParameters(null!)); + Assert.ThrowsException(() => dictionary!.ToGetParameters(null!, null!)); + } + + [TestMethod] + public void ToGetParameters_ShouldThrow_GivenNullSelector() + { + var dictionary = new Dictionary(); + Assert.ThrowsException(() => dictionary.ToGetParameters(null!)); + Assert.ThrowsException(() => dictionary.ToGetParameters(null!, _ => _)); + Assert.ThrowsException(() => dictionary.ToGetParameters(_ => _, null!)); + } +} diff --git a/X10D.Tests/src/Collections/EnumerableTests.cs b/X10D.Tests/src/Collections/EnumerableTests.cs new file mode 100644 index 0000000..6d990ee --- /dev/null +++ b/X10D.Tests/src/Collections/EnumerableTests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class EnumerableTests +{ + [TestMethod] + public void Shuffled_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Shuffled()); + } + + [TestMethod] + public void Shuffled_ShouldReorder_GivenNotNull() + { + int[] array = Enumerable.Range(1, 52).ToArray(); // 52! chance of being shuffled to the same order + int[] shuffled = array[..]; + + CollectionAssert.AreEqual(array, shuffled); + + shuffled = array.Shuffled().ToArray(); + CollectionAssert.AreNotEqual(array, shuffled); + } +} diff --git a/X10D.Tests/src/Collections/Int16Tests.cs b/X10D.Tests/src/Collections/Int16Tests.cs new file mode 100644 index 0000000..d077744 --- /dev/null +++ b/X10D.Tests/src/Collections/Int16Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = ((short)0b11010100).Unpack(); + + Assert.AreEqual(16, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 16; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[16]; + ((short)0b11010100).Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 16; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, ((short)0b11010100).Unpack().PackInt16()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + ((short)0b11010100).Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/Int32Tests.cs b/X10D.Tests/src/Collections/Int32Tests.cs new file mode 100644 index 0000000..cd86c75 --- /dev/null +++ b/X10D.Tests/src/Collections/Int32Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = 0b11010100.Unpack(); + + Assert.AreEqual(32, bits.Length); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 32; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[32]; + 0b11010100.Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 32; index++) + { + Assert.IsFalse(bits[index]); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100, 0b11010100.Unpack().PackInt32()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + 0b11010100.Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/Int64Tests.cs b/X10D.Tests/src/Collections/Int64Tests.cs new file mode 100644 index 0000000..2622863 --- /dev/null +++ b/X10D.Tests/src/Collections/Int64Tests.cs @@ -0,0 +1,71 @@ +using System.Diagnostics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void UnpackBits_ShouldUnpackToArrayCorrectly() + { + bool[] bits = 0b11010100L.Unpack(); + + Assert.AreEqual(64, bits.Length); + + Trace.WriteLine(Convert.ToString(0b11010100L, 2)); + Trace.WriteLine(string.Join("", bits.Select(b => b ? "1" : "0"))); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 64; index++) + { + Assert.IsFalse(bits[index], index.ToString()); + } + } + + [TestMethod] + public void UnpackBits_ShouldUnpackToSpanCorrectly() + { + Span bits = stackalloc bool[64]; + 0b11010100L.Unpack(bits); + + Assert.IsFalse(bits[0]); + Assert.IsFalse(bits[1]); + Assert.IsTrue(bits[2]); + Assert.IsFalse(bits[3]); + Assert.IsTrue(bits[4]); + Assert.IsFalse(bits[5]); + Assert.IsTrue(bits[6]); + Assert.IsTrue(bits[7]); + + for (var index = 8; index < 64; index++) + { + Assert.IsFalse(bits[index], index.ToString()); + } + } + + [TestMethod] + public void UnpackBits_ShouldRepackEqually() + { + Assert.AreEqual(0b11010100L, 0b11010100L.Unpack().PackInt64()); + } + + [TestMethod] + public void UnpackBits_ShouldThrow_GivenTooSmallSpan() + { + Assert.ThrowsException(() => + { + Span bits = stackalloc bool[0]; + 0b11010100L.Unpack(bits); + }); + } +} diff --git a/X10D.Tests/src/Collections/ListTests.cs b/X10D.Tests/src/Collections/ListTests.cs new file mode 100644 index 0000000..3be91f8 --- /dev/null +++ b/X10D.Tests/src/Collections/ListTests.cs @@ -0,0 +1,113 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Collections; + +namespace X10D.Tests.Collections; + +[TestClass] +public class ListTests +{ + [TestMethod] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void Fill_ShouldGiveHomogenousList_GivenValue(params int[] args) + { + int[] all42 = Enumerable.Repeat(42, args.Length).ToArray(); + var list = new List(args); + + CollectionAssert.AreEqual(args, list); + + args.Fill(42); + list.Fill(42); + + CollectionAssert.AreEqual(args, list); + CollectionAssert.AreEqual(all42, args); + CollectionAssert.AreEqual(all42, list); + } + + [TestMethod] + [DataRow(1)] + [DataRow(1, 2, 3)] + [DataRow(1, 2, 3, 4, 5)] + public void SlicedFill_ShouldLeaveFirstElement_GivenStartIndex1(params int[] args) + { + int first = args[0]; + args.Fill(1, 1, args.Length - 1); + + int[] comparison = Enumerable.Repeat(1, args.Length - 1).ToArray(); + Assert.AreEqual(first, args[0]); + CollectionAssert.AreEqual(comparison, args[1..]); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenExceededCount() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, 0, 1)); + Assert.ThrowsException(() => list.Fill(0, 0, 1)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNegativeCount() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, 0, -1)); + Assert.ThrowsException(() => list.Fill(0, 0, -1)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNegativeStartIndex() + { + int[] array = Array.Empty(); + var list = new List(); + Assert.ThrowsException(() => array.Fill(0, -1, 0)); + Assert.ThrowsException(() => list.Fill(0, -1, 0)); + } + + [TestMethod] + public void Fill_ShouldThrow_GivenNull() + { + int[]? array = null; + List? list = null; + Assert.ThrowsException(() => array!.Fill(0)); + Assert.ThrowsException(() => list!.Fill(0)); + Assert.ThrowsException(() => array!.Fill(0, 0, 0)); + Assert.ThrowsException(() => list!.Fill(0, 0, 0)); + } + + [TestMethod] + public void Shuffle_ShouldReorder_GivenNotNull() + { + var list = new List(Enumerable.Range(1, 52)); // 52! chance of being shuffled to the same order + var shuffled = new List(list); + + CollectionAssert.AreEqual(list, shuffled); + + shuffled.Shuffle(); + + CollectionAssert.AreNotEqual(list, shuffled); + } + + [TestMethod] + public void Shuffle_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Shuffle()); + } + + [TestMethod] + public void Random_ShouldReturnContainedObject_GivenNotNull() + { + var list = new List(Enumerable.Range(1, 52)); // 52! chance of being shuffled to the same order + int random = list.Random(); + + Assert.IsTrue(list.Contains(random)); + } + + [TestMethod] + public void Random_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((List?)null)!.Random()); + } +} diff --git a/X10D.Tests/src/Core/BooleanTests.cs b/X10D.Tests/src/Core/BooleanTests.cs deleted file mode 100644 index 15bad0f..0000000 --- a/X10D.Tests/src/Core/BooleanTests.cs +++ /dev/null @@ -1,214 +0,0 @@ -namespace X10D.Tests.Core -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class BooleanTests - { - /// - /// Tests for . - /// - [TestMethod] - public void And() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.And(b)); - Assert.IsFalse(b.And(c)); - Assert.IsFalse(c.And(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void NAnd() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.NAnd(b)); - Assert.IsTrue(b.NAnd(c)); - Assert.IsTrue(c.NAnd(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void NOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.NOr(b)); - Assert.IsFalse(b.NOr(c)); - Assert.IsTrue(c.NOr(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Not() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.IsFalse(a.Not()); - Assert.IsTrue(b.Not()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Or() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.Or(b)); - Assert.IsTrue(b.Or(c)); - Assert.IsFalse(c.Or(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToByte() - { - const bool a = true; - const bool b = false; - const byte c = 1; - const byte d = 0; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(c, a.ToByte()); - Assert.AreEqual(d, b.ToByte()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt16() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1, a.ToInt16()); - Assert.AreEqual(0, b.ToInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt32() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1, a.ToInt32()); - Assert.AreEqual(0, b.ToInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToInt64() - { - const bool a = true; - const bool b = false; - - Assert.IsTrue(a); - Assert.IsFalse(b); - Assert.AreEqual(1L, a.ToInt64()); - Assert.AreEqual(0L, b.ToInt64()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void XNOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsTrue(a.XNOr(b)); - Assert.IsFalse(b.XNOr(c)); - Assert.IsTrue(c.XNOr(d)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void XOr() - { - const bool a = true; - const bool b = true; - const bool c = false; - const bool d = false; - - Assert.IsTrue(a); - Assert.IsTrue(b); - Assert.IsFalse(c); - Assert.IsFalse(d); - - Assert.IsFalse(a.XOr(b)); - Assert.IsTrue(b.XOr(c)); - Assert.IsFalse(c.XOr(d)); - } - } -} diff --git a/X10D.Tests/src/Core/ByteTests.cs b/X10D.Tests/src/Core/ByteTests.cs deleted file mode 100644 index e97061a..0000000 --- a/X10D.Tests/src/Core/ByteTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Collections.Generic; - using System.Text; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ByteTests - { - /// - /// Tests for . - /// - [TestMethod] - public void AsString() - { - byte[] a = { 0x00, 0x73, 0xc6, 0xff }; - Assert.AreEqual("00-73-C6-FF", a.AsString()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt16() - { - byte[] a = { 0xF3, 0x3F }; - Assert.AreEqual(16371, a.GetInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt32() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x5F }; - Assert.AreEqual(1595083696, a.GetInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetInt64() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x50, 0x05, 0x31, 0xB0, 0x0B }; - Assert.AreEqual(842227029206305712L, a.GetInt64()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetString() - { - byte[] a = { 0x48, 0xc3, 0xa9, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; - Assert.AreEqual("H\u00e9llo World", a.GetString()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetStringAscii() - { - byte[] a = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64 }; - Assert.AreEqual("Hello World", a.GetString(Encoding.ASCII)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt16() - { - byte[] a = { 0xF3, 0x3F }; - Assert.AreEqual(16371, a.GetUInt16()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt32() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x5F }; - Assert.AreEqual(1595083696U, a.GetUInt32()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetUInt64() - { - byte[] a = { 0xB0, 0x0B, 0x13, 0x50, 0x05, 0x31, 0xB0, 0x0B }; - Assert.AreEqual(842227029206305712UL, a.GetUInt64()); - } - } -} diff --git a/X10D.Tests/src/Core/CharTests.cs b/X10D.Tests/src/Core/CharTests.cs deleted file mode 100644 index d462d59..0000000 --- a/X10D.Tests/src/Core/CharTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class CharTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Random() - { - var set = "abcdefghijklmnopqrstuvwxyz".ToCharArray(); - var random = set.Random(20); - - Assert.IsTrue(random.All(c => Array.IndexOf(set, c) >= 0)); - Assert.IsFalse(random.Any(c => Array.IndexOf(set, c) < -1)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Repeat() - { - Assert.AreEqual("aaaaaaaaaa", 'a'.Repeat(10)); - } - } -} diff --git a/X10D.Tests/src/Core/ComparableTests.cs b/X10D.Tests/src/Core/ComparableTests.cs deleted file mode 100644 index c477f9d..0000000 --- a/X10D.Tests/src/Core/ComparableTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace X10D.Tests.Core -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ComparableTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Between() - { - Assert.IsTrue(5.Between(2, 7)); - Assert.IsTrue(10.Between(9, 11)); - Assert.IsFalse(100.Between(80, 99)); - } - } -} diff --git a/X10D.Tests/src/Core/ConvertibleTests.cs b/X10D.Tests/src/Core/ConvertibleTests.cs deleted file mode 100644 index cc11fc2..0000000 --- a/X10D.Tests/src/Core/ConvertibleTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ConvertibleTests - { - /// - /// Tests for . - /// - [TestMethod] - public void To() - { - Assert.AreEqual(2, "2".To()); - Assert.AreEqual("12.5", 12.50.To()); - Assert.IsTrue("True".To()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrDefault() - { - Assert.AreEqual(2, "2".ToOrDefault()); - Assert.AreEqual("12.5", 12.50.ToOrDefault()); - Assert.IsTrue("True".ToOrDefault()); - Assert.ThrowsException(() => "Foo".ToOrDefault()); - Assert.IsTrue("1.5".ToOrDefault(out float f)); - Assert.AreEqual(1.5f, f); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrNull() - { - Assert.IsFalse("foo".ToOrNull(out ConvertibleTests t)); - Assert.IsNull(t); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToOrOther() - { - Assert.AreEqual(2.0, "Foo".ToOrOther(2.0)); - Assert.IsFalse("Foo".ToOrOther(out var d, 2.0)); - Assert.AreEqual(2.0, d); - } - } -} diff --git a/X10D.Tests/src/Core/CoreTests.cs b/X10D.Tests/src/Core/CoreTests.cs new file mode 100644 index 0000000..57bf03d --- /dev/null +++ b/X10D.Tests/src/Core/CoreTests.cs @@ -0,0 +1,76 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class CoreTests +{ + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsArrayValue_ShouldBeLength1_GivenValue(object o) + { + object[] array = o.AsArrayValue()!; + Assert.IsNotNull(array); + Assert.IsTrue(array.Length == 1); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsArrayValue_ShouldContainValue_Given_Value(object o) + { + object[] array = o.AsArrayValue()!; + Assert.IsNotNull(array); + Assert.AreEqual(o, array[0]); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerableValue_ShouldBeLength1_GivenValue(object o) + { + IEnumerable enumerable = o.AsEnumerableValue()!; + Assert.IsNotNull(enumerable); + Assert.IsTrue(enumerable.Count() == 1); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerableValue_ShouldContainValue_Given_Value(object o) + { + IEnumerable enumerable = o.AsEnumerableValue()!; + Assert.IsNotNull(enumerable); + Assert.AreEqual(o, enumerable.ElementAt(0)); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void RepeatValue_ShouldContainRepeatedValue_GivenValue(object o) + { + IEnumerable enumerable = o.RepeatValue(10); + Assert.IsNotNull(enumerable); + + object[] array = enumerable.ToArray(); + Assert.AreEqual(10, array.Length); + CollectionAssert.AreEqual(new[] {o, o, o, o, o, o, o, o, o, o}, array); + } + + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void RepeatValue_ShouldThrow_GivenNegativeCount(object o) + { + // we must force enumeration via ToArray() to ensure the exception is thrown + Assert.ThrowsException(() => o.RepeatValue(-1).ToArray()); + } +} diff --git a/X10D.Tests/src/Core/DateTimeTests.cs b/X10D.Tests/src/Core/DateTimeTests.cs deleted file mode 100644 index a670af9..0000000 --- a/X10D.Tests/src/Core/DateTimeTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DateTimeTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Age() - { - // no choice but to create dynamic based on today's date. - // age varies with time - var now = DateTime.Now; - var dt = new DateTime(now.Year - 18, 1, 1); - - Assert.AreEqual(18, dt.Age()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void First() - { - var dt = new DateTime(2018, 6, 20); - - Assert.AreEqual(4, dt.First(DayOfWeek.Monday).Day); - } - - /// - /// Tests for . - /// - [TestMethod] - public void FirstDayOfMonth() - { - var dt = new DateTime(2018, 6, 20); - var first = dt.FirstDayOfMonth(); - - Assert.AreEqual(dt.Year, first.Year); - Assert.AreEqual(dt.Month, first.Month); - Assert.AreEqual(1, first.Day); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Last() - { - { - var dt = new DateTime(2019, 12, 1); - var last = dt.Last(DayOfWeek.Wednesday); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(25, last.Day); - } - - { - var dt = new DateTime(2020, 4, 14); - var last = dt.Last(DayOfWeek.Friday); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(24, last.Day); - - last = dt.Last(DayOfWeek.Thursday); - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(30, last.Day); - } - } - - /// - /// Tests for . - /// - [TestMethod] - public void LastDayOfMonth() - { - var dt = new DateTime(2016, 2, 4); - var last = dt.LastDayOfMonth(); - - Assert.AreEqual(dt.Year, last.Year); - Assert.AreEqual(dt.Month, last.Month); - Assert.AreEqual(29, last.Day); // 2016 is a leap year - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToUnixTimestamp() - { - var dt = new DateTime(2015, 10, 21, 1, 0, 0, DateTimeKind.Utc); - var unix = dt.ToUnixTimeStamp(); - - Assert.AreEqual(1445389200L, unix); - } - } -} diff --git a/X10D.Tests/src/Core/DictionaryTests.cs b/X10D.Tests/src/Core/DictionaryTests.cs deleted file mode 100644 index 983fc9b..0000000 --- a/X10D.Tests/src/Core/DictionaryTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Collections.Generic; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DictionaryTests - { - /// - /// Tests for . - /// - [TestMethod] - public void ToConnectionString() - { - var dictionary = new Dictionary - { - { "username", "Foo" }, { "password", "Foo Bar" }, { "port", 3306 }, - }; - - var connectionString = dictionary.ToConnectionString(); - Assert.AreEqual("username=Foo;password=\"Foo Bar\";port=3306", connectionString); - } - - /// - /// Tests for . - /// - [TestMethod] - public void ToGetParameters() - { - var dictionary = new Dictionary - { - { "username", "Foo" }, { "password", "Foo Bar" }, { "port", 3306 }, - }; - - var getParameterString = dictionary.ToGetParameters(); - Assert.AreEqual("username=Foo&password=Foo+Bar&port=3306", getParameterString); - } - } -} diff --git a/X10D.Tests/src/Core/DoubleTests.cs b/X10D.Tests/src/Core/DoubleTests.cs deleted file mode 100644 index 9990370..0000000 --- a/X10D.Tests/src/Core/DoubleTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class DoubleTests - { - /// - /// Tests for . - /// - [TestMethod] - public void Clamp() - { - Assert.AreEqual(2.0, 3.0.Clamp(1.0, 2.0)); - Assert.AreEqual(1.0, (-3.0).Clamp(1.0, 2.0)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void DegreesToRadians() - { - Assert.AreEqual(Math.PI, 180.0.DegreesToRadians()); - Assert.AreEqual(Math.PI * 1.5, 270.0.DegreesToRadians()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void GetBytes() - { - CollectionAssert.AreEqual( - new byte[] { 0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 0x09, 0x40 }, - Math.PI.GetBytes()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void IsEven() - { - Assert.IsTrue(2.0.IsEven()); - Assert.IsFalse(1.0.IsEven()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void IsOdd() - { - Assert.IsFalse(2.0.IsOdd()); - Assert.IsTrue(1.0.IsOdd()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void RadiansToDegrees() - { - Assert.AreEqual(180.0, Math.PI.RadiansToDegrees()); - Assert.AreEqual(360.0, (2.0 * Math.PI).RadiansToDegrees()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Round() - { - Assert.AreEqual(5.0, 3.5.Round(5)); - Assert.AreEqual(5.0, 7.0.Round(5)); - Assert.AreEqual(10.0, 7.5.Round(5)); - } - } -} diff --git a/X10D.Tests/src/Core/EnumTests.cs b/X10D.Tests/src/Core/EnumTests.cs new file mode 100644 index 0000000..8e7889d --- /dev/null +++ b/X10D.Tests/src/Core/EnumTests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class EnumTests +{ + // Microsoft wrongfully decided to have Sunday be 0, Monday be 1, etc. + // I personally hate this, Sunday is not the first day of the week. + // it's clearly Monday as defined by ISO 8601. + // but Microsoft can't fix this without breaking compatibility. + // I have feelings... + + [TestMethod] + public void Next() + { + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.Next()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.Next()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.Next()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.Next()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.Next()); + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.Next()); + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Saturday.Next()); // Saturday is the "last" day. wrap to "first" + } + + [TestMethod] + public void NextUnchecked() + { + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Sunday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Monday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Tuesday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Wednesday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Thursday.NextUnchecked()); + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Friday.NextUnchecked()); + Assert.ThrowsException(() => DayOfWeek.Saturday.NextUnchecked()); + } + + [TestMethod] + public void Previous() + { + Assert.AreEqual(DayOfWeek.Saturday, DayOfWeek.Sunday.Previous()); // Sunday is the "first" day. wrap to "last" + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.Previous()); + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.Previous()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.Previous()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.Previous()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.Previous()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.Previous()); + } + + [TestMethod] + public void PreviousUnchecked() + { + Assert.AreEqual(DayOfWeek.Sunday, DayOfWeek.Monday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Monday, DayOfWeek.Tuesday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Tuesday, DayOfWeek.Wednesday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Wednesday, DayOfWeek.Thursday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Thursday, DayOfWeek.Friday.PreviousUnchecked()); + Assert.AreEqual(DayOfWeek.Friday, DayOfWeek.Saturday.PreviousUnchecked()); + Assert.ThrowsException(() => DayOfWeek.Sunday.PreviousUnchecked()); + } +} diff --git a/X10D.Tests/src/Core/EnumerableTests.cs b/X10D.Tests/src/Core/EnumerableTests.cs index 6da9ac3..5b6deba 100644 --- a/X10D.Tests/src/Core/EnumerableTests.cs +++ b/X10D.Tests/src/Core/EnumerableTests.cs @@ -1,55 +1,20 @@ -namespace X10D.Tests.Core +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class EnumerableTests { - using System.Collections.Generic; - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class EnumerableTests + [TestMethod] + [DataRow(1)] + [DataRow("f")] + [DataRow(true)] + public void AsEnumerable_ShouldWrapElement_GivenValue(object o) { - /// - /// Tests for using an array of . - /// - [TestMethod] - public void SplitByte() - { - byte[] foo = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - IEnumerable> chunks = foo.Split(2).ToArray(); - - Assert.AreEqual(4, chunks.Count()); - CollectionAssert.AreEqual(new byte[] { 0x01, 0x02 }, chunks.ElementAt(0).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x03, 0x04 }, chunks.ElementAt(1).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x05, 0x06 }, chunks.ElementAt(2).ToList()); - CollectionAssert.AreEqual(new byte[] { 0x07, 0x08 }, chunks.ElementAt(3).ToList()); - - // test exceeding chunk size - chunks = foo.Split(foo.Length + 10).ToArray(); - Assert.AreEqual(1, chunks.Count()); - CollectionAssert.AreEqual(foo, chunks.SelectMany(c => c).ToList()); - } - - /// - /// Tests for using an array of . - /// - [TestMethod] - public void SplitInt32() - { - int[] foo = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; - IEnumerable> chunks = foo.Split(2).ToArray(); - - Assert.AreEqual(4, chunks.Count()); - CollectionAssert.AreEqual(new[] { 0x01, 0x02 }, chunks.ElementAt(0).ToList()); - CollectionAssert.AreEqual(new[] { 0x03, 0x04 }, chunks.ElementAt(1).ToList()); - CollectionAssert.AreEqual(new[] { 0x05, 0x06 }, chunks.ElementAt(2).ToList()); - CollectionAssert.AreEqual(new[] { 0x07, 0x08 }, chunks.ElementAt(3).ToList()); - - // test exceeding chunk size - chunks = foo.Split(foo.Length + 10).ToArray(); - Assert.AreEqual(1, chunks.Count()); - CollectionAssert.AreEqual(foo, chunks.SelectMany(c => c).ToList()); - } + IEnumerable array = o.AsEnumerableValue().ToArray(); // prevent multiple enumeration of IEnumerable + Assert.IsNotNull(array); + Assert.IsTrue(array.Count() == 1); + Assert.AreEqual(o, array.ElementAt(0)); } } diff --git a/X10D.Tests/src/Core/RandomTests.cs b/X10D.Tests/src/Core/RandomTests.cs new file mode 100644 index 0000000..41d182b --- /dev/null +++ b/X10D.Tests/src/Core/RandomTests.cs @@ -0,0 +1,245 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Core; + +namespace X10D.Tests.Core; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextBoolean_ShouldBeFalse_GivenSeed1234() + { + var random = new Random(1234); + Assert.IsFalse(random.NextBoolean()); + } + + [TestMethod] + public void NextBoolean_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextBoolean()); + } + + [TestMethod] + public void NextByte_ShouldBe101_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(101, random.NextByte()); + } + + [TestMethod] + public void NextByte_WithMax10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextByte(10)); + } + + [TestMethod] + public void NextByte_WithMin0Max10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextByte(0, 10)); + } + + [TestMethod] + public void NextByte_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextByte()); + Assert.ThrowsException(() => random!.NextByte(10)); + Assert.ThrowsException(() => random!.NextByte(0, 10)); + } + + [TestMethod] + public void NextDouble_WithMax10_ShouldBe3point9908097935797695_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.9908097935797695, random.NextDouble(10)); + } + + [TestMethod] + public void NextDouble_WithMin0Max10_ShouldBe3point9908097935797695_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.9908097935797695, random.NextDouble(0, 10)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextDouble(10)); + Assert.ThrowsException(() => random!.NextDouble(0, 10)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextDouble(-1)); + } + + [TestMethod] + public void NextDouble_ShouldThrow_GivenMaxLessThanMin() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextDouble(0, -1)); + } + + [TestMethod] + public void NextEnum_ShouldBeTuesday_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(DayOfWeek.Tuesday, random.Next()); + } + + [TestMethod] + public void NextEnum_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.Next()); + } + + [TestMethod] + public void NextFrom_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextFrom("")); + } + + [TestMethod] + public void NextFrom_ShouldThrow_GivenNullSource() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextFrom((string?)null!)); + } + + [TestMethod] + public void NextFrom_ShouldEnumerate_GivenNonList() + { + IEnumerable Source() + { + yield return 0; + } + + var random = new Random(1234); + Assert.AreEqual(0, random.NextFrom(Source())); + } + + [TestMethod] + public void NextInt16_ShouldBe13076_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(13076, random.NextInt16()); + } + + [TestMethod] + public void NextInt16_WithMax10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextInt16(10)); + } + + [TestMethod] + public void NextInt16_WithMin0Max10_ShouldBe3_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3, random.NextInt16(0, 10)); + } + + [TestMethod] + public void NextInt16_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextInt16(-1)); + } + + [TestMethod] + public void NextInt16_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextInt16()); + Assert.ThrowsException(() => random!.NextInt16(10)); + Assert.ThrowsException(() => random!.NextInt16(0, 10)); + } + + [TestMethod] + public void NextSingle_WithMax10_ShouldBe3point99081_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.99081f, random.NextSingle(10)); + } + + [TestMethod] + public void NextSingle_WithMin0Max10_ShouldBe3point99081_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(3.99081f, random.NextSingle(0, 10)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextSingle(10)); + Assert.ThrowsException(() => random!.NextSingle(0, 10)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenMaxLessThan0() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextSingle(-1)); + } + + [TestMethod] + public void NextSingle_ShouldThrow_GivenMaxLessThanMin() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextSingle(0, -1)); + } + + [TestMethod] + public void NextString_ShouldBe_kxiyiyvnqi_GivenSeed1234() + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz"; + + var random = new Random(1234); + Assert.AreEqual("kxiyiyvnqi", random.NextString(alphabet.ToCharArray(), 10)); + } + + [TestMethod] + public void NextString_ShouldBeEmpty_GivenLength0() + { + var random = new Random(1234); + Assert.AreEqual(string.Empty, random.NextString(ArraySegment.Empty, 0)); + } + + [TestMethod] + public void NextString_ShouldBeLength1_GivenLength1() + { + var random = new Random(1234); + Assert.AreEqual(1, random.NextString("hello world".ToCharArray(), 1).Length); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextString(ArraySegment.Empty, 0)); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNullSource() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextString(null!, 0)); + } + + [TestMethod] + public void NextString_ShouldThrow_GivenNegativeLength() + { + var random = new Random(1234); + Assert.ThrowsException(() => random.NextString(ArraySegment.Empty, -1)); + } +} diff --git a/X10D.Tests/src/Core/ReflectionTests.cs b/X10D.Tests/src/Core/ReflectionTests.cs deleted file mode 100644 index b182a4b..0000000 --- a/X10D.Tests/src/Core/ReflectionTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using System.ComponentModel; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class ReflectionTests - { - /// - /// Test for . - /// - [TestMethod] - public void GetDefaultValue() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - Assert.AreEqual("Foo", property.GetDefaultValue()); - } - } - - /// - /// Test for . - /// - [TestMethod] - public void GetDescription() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - Assert.AreEqual("Test description", property.GetDescription()); - } - } - - /// - /// Test for . - /// - [TestMethod] - public void SelectFromCustomAttribute() - { - var klass = new TestClass(); - - foreach (var property in klass.GetType().GetProperties()) - { - var value = property.SelectFromCustomAttribute(a => a.TestValue); - Assert.AreEqual("Bar", value); - } - } - - [AttributeUsage(AttributeTargets.Property)] - private sealed class TestAttribute : Attribute - { - public string TestValue { get; set; } - } - - private sealed class TestClass - { - [System.ComponentModel.Description("Test description")] - [DefaultValue("Foo")] - [Test(TestValue = "Bar")] - public string TestProperty { get; set; } - } - } -} diff --git a/X10D.Tests/src/Core/StringTests.cs b/X10D.Tests/src/Core/StringTests.cs deleted file mode 100644 index 6d58576..0000000 --- a/X10D.Tests/src/Core/StringTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace X10D.Tests.Core -{ - using System.Linq; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class StringTests - { - /// - /// Tests for . - /// - [TestMethod] - public void AsNullIfEmpty() - { - Assert.AreEqual(null, string.Empty.AsNullIfEmpty()); - Assert.AreEqual(null, ((string)null).AsNullIfEmpty()); - Assert.AreEqual(" ", " ".AsNullIfEmpty()); - Assert.AreEqual("foo", "foo".AsNullIfEmpty()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void AsNullIfWhiteSpace() - { - Assert.AreEqual(null, string.Empty.AsNullIfWhiteSpace()); - Assert.AreEqual(null, ((string)null).AsNullIfWhiteSpace()); - Assert.AreEqual(null, " ".AsNullIfWhiteSpace()); - Assert.AreEqual("foo", "foo".AsNullIfWhiteSpace()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Repeat() - { - Assert.AreEqual("foofoofoofoofoo", "foo".Repeat(5)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Reverse() - { - Assert.AreEqual("dlroW olleH", StringExtensions.Reverse("Hello World")); - Assert.AreEqual("Foobar", StringExtensions.Reverse("rabooF")); - } - - /// - /// Tests for . - /// - [TestMethod] - public void Split() - { - const string str = "Hello World"; - - // ReSharper disable once SuggestVarOrType_Elsewhere - var arr = str.Split(2).ToArray(); - CollectionAssert.AreEqual(new[] { "He", "ll", "o ", "Wo", "rl", "d" }, arr); - } - - /// - /// Tests for . - /// - [TestMethod] - public void WithAlternative() - { - Assert.AreEqual("Hello", "Hello".WithAlternative("Discarded")); - Assert.AreEqual("Alternative", string.Empty.WithAlternative("Alternative")); - Assert.AreEqual(" ", " ".WithAlternative("Discarded")); - Assert.AreEqual("Alternative", " ".WithAlternative("Alternative", true)); - Assert.AreEqual("Alternative", ((string)null).WithAlternative("Alternative")); - } - } -} diff --git a/X10D.Tests/src/Core/TimeSpanParserTests.cs b/X10D.Tests/src/Core/TimeSpanParserTests.cs deleted file mode 100644 index bbbb5e7..0000000 --- a/X10D.Tests/src/Core/TimeSpanParserTests.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace X10D.Tests.Core -{ - using System; - using Microsoft.VisualStudio.TestTools.UnitTesting; - - /// - /// Tests for . - /// - [TestClass] - public class TimeSpanParserTests - { - /// - /// Tests for . - /// - [TestMethod] - public void TestParser() - { - Assert.AreEqual(TimeSpan.FromHours(3), "3h".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromMinutes(2.5), "2.5m".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromHours(1), "60m".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(1), "1d".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(8), "1w 1d".ToTimeSpan()); - Assert.AreEqual(TimeSpan.FromDays(8), "1w1d".ToTimeSpan()); - } - } -} diff --git a/X10D.Tests/src/Drawing/RandomTests.cs b/X10D.Tests/src/Drawing/RandomTests.cs new file mode 100644 index 0000000..07d3360 --- /dev/null +++ b/X10D.Tests/src/Drawing/RandomTests.cs @@ -0,0 +1,37 @@ +using System.Drawing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Drawing; + +namespace X10D.Tests.Drawing; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextColorArgb_ShouldReturn331515e5_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(Color.FromArgb(51, 21, 21, 229), random.NextColorArgb()); + } + + [TestMethod] + public void NextColorArgb_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextColorArgb()); + } + + [TestMethod] + public void NextColorRgb_ShouldReturn1515e5_GivenSeed1234() + { + var random = new Random(1234); + Assert.AreEqual(Color.FromArgb(255, 21, 21, 229), random.NextColorRgb()); + } + + [TestMethod] + public void NextColorRgb_ShouldThrow_GivenNull() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextColorRgb()); + } +} diff --git a/X10D.Tests/src/IO/BooleanTests.cs b/X10D.Tests/src/IO/BooleanTests.cs new file mode 100644 index 0000000..8b4701d --- /dev/null +++ b/X10D.Tests/src/IO/BooleanTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class BooleanTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContaining1() + { + const bool value = true; + CollectionAssert.AreEqual(new byte[] {1}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContaining1_GivenLargeEnoughSpan() + { + const bool value = true; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new byte[] {1}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const bool value = true; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/ByteTests.cs b/X10D.Tests/src/IO/ByteTests.cs new file mode 100644 index 0000000..47c14cb --- /dev/null +++ b/X10D.Tests/src/IO/ByteTests.cs @@ -0,0 +1,32 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContainingItself() + { + const byte value = 0xFF; + CollectionAssert.AreEqual(new[] {value}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContainingItself_GivenLargeEnoughSpan() + { + const byte value = 0xFF; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new[] {value}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const byte value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/DoubleTests.cs b/X10D.Tests/src/IO/DoubleTests.cs new file mode 100644 index 0000000..616fd79 --- /dev/null +++ b/X10D.Tests/src/IO/DoubleTests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const double value = 42.5; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0, 0, 0, 0x40, 0x45, 0x40} + : new byte[] {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const double value = 42.5; + byte[] littleEndian = {0, 0, 0, 0, 0, 0x40, 0x45, 0x40}; + byte[] bigEndian = {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const double value = 42.5; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0, 0, 0, 0x40, 0x45, 0x40} + : new byte[] {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const double value = 42.5; + byte[] littleEndian = {0, 0, 0, 0, 0, 0x40, 0x45, 0x40}; + byte[] bigEndian = {0x40, 0x45, 0x40, 0, 0, 0, 0, 0}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const double value = 42.5; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/FileInfoTests.cs b/X10D.Tests/src/IO/FileInfoTests.cs new file mode 100644 index 0000000..a1047ba --- /dev/null +++ b/X10D.Tests/src/IO/FileInfoTests.cs @@ -0,0 +1,93 @@ +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class FileInfoTests +{ + [TestMethod] + public void GetHashSha1ShouldBeCorrect() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + File.WriteAllText(fileName, "Hello World"); + Assert.IsTrue(File.Exists(fileName)); + + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + try + { + byte[] hash = new FileInfo(fileName).GetHash(); + CollectionAssert.AreEqual(expectedHash, hash); + } + finally + { + File.Delete(fileName); // cleanup is important + } + } + + [TestMethod] + public void TryWriteHashSha1ShouldBeCorrect() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + File.WriteAllText(fileName, "Hello World"); + Assert.IsTrue(File.Exists(fileName)); + + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + try + { + Span hash = stackalloc byte[20]; + new FileInfo(fileName).TryWriteHash(hash, out int bytesWritten); + Assert.AreEqual(expectedHash.Length, bytesWritten); + CollectionAssert.AreEqual(expectedHash, hash.ToArray()); + } + finally + { + File.Delete(fileName); // cleanup is important + } + } + + [TestMethod] + public void GetHashNullShouldThrow() + { + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => ((FileInfo?)null)!.GetHash()); + Assert.ThrowsException(() => ((FileInfo?)null)!.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void GetHashInvalidFileShouldThrow() + { + string fileName = $"temp.{DateTimeOffset.Now.ToUnixTimeSeconds()}.bin"; + if (File.Exists(fileName)) + { + Assert.Fail("Temporary file already exists"); + } + + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => new FileInfo(fileName).GetHash()); + Assert.ThrowsException(() => new FileInfo(fileName).TryWriteHash(Span.Empty, out _)); + } +} diff --git a/X10D.Tests/src/IO/Int16Tests.cs b/X10D.Tests/src/IO/Int16Tests.cs new file mode 100644 index 0000000..346e95a --- /dev/null +++ b/X10D.Tests/src/IO/Int16Tests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const short value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const short value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const short value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const short value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const short value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/Int32Tests.cs b/X10D.Tests/src/IO/Int32Tests.cs new file mode 100644 index 0000000..7135b7c --- /dev/null +++ b/X10D.Tests/src/IO/Int32Tests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const int value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const int value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const int value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const int value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const int value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/Int64Tests.cs b/X10D.Tests/src/IO/Int64Tests.cs new file mode 100644 index 0000000..1f7623b --- /dev/null +++ b/X10D.Tests/src/IO/Int64Tests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const long value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const long value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const long value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const long value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const long value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/ListOfByteTests.cs b/X10D.Tests/src/IO/ListOfByteTests.cs new file mode 100644 index 0000000..9fe2056 --- /dev/null +++ b/X10D.Tests/src/IO/ListOfByteTests.cs @@ -0,0 +1,168 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class ListOfByteTests +{ + [TestMethod] + public void AsString_ShouldReturnBytes_GivenBytes() + { + var bytes = new byte[] {0x01, 0x02, 0x03, 0x04, 0x05}; + Assert.AreEqual("01-02-03-04-05", bytes.AsString()); + } + + [TestMethod] + public void AsString_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.AsString()); + } + + [TestMethod] + public void ToDouble_ShouldReturnDouble_GivenBytes() + { + var bytes = new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x7A, 0x40}; + + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + Assert.AreEqual(420.0, bytes.ToDouble(), 1e-6); + } + + [TestMethod] + public void ToDouble_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToDouble()); + } + + [TestMethod] + public void ToInt16_ShouldReturnInt16_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01}; + Assert.AreEqual(420, bytes.ToInt16()); + } + + [TestMethod] + public void ToInt16_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt16()); + } + + [TestMethod] + public void ToInt32_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00}; + Assert.AreEqual(420, bytes.ToInt32()); + } + + [TestMethod] + public void ToInt32_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt32()); + } + + [TestMethod] + public void ToInt64_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Assert.AreEqual(420L, bytes.ToInt64()); + } + + [TestMethod] + public void ToInt64_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToInt64()); + } + + [TestMethod] + public void ToSingle_ShouldReturnDouble_GivenBytes() + { + var bytes = new byte[] {0x00, 0x00, 0xD2, 0x43}; + + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + + Assert.AreEqual(420.0, bytes.ToSingle(), 1e-6); + } + + [TestMethod] + public void ToSingle_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToSingle()); + } + + [TestMethod] + public void ToString_ShouldReturnHelloWorld_GivenUTF8() + { + var bytes = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + Assert.AreEqual("Hello World", bytes.ToString(Encoding.UTF8)); + } + + [TestMethod] + public void ToString_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToString(Encoding.UTF8)); + } + + [TestMethod] + public void ToString_ShouldThrow_GivenNullEncoding() + { + var bytes = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + Assert.ThrowsException(() => bytes.ToString(null!)); + } + + [TestMethod] + public void ToUInt16_ShouldReturnInt16_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01}; + Assert.AreEqual((ushort)420, bytes.ToUInt16()); + } + + [TestMethod] + public void ToUInt16_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt16()); + } + + [TestMethod] + public void ToUInt32_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00}; + Assert.AreEqual(420U, bytes.ToUInt32()); + } + + [TestMethod] + public void ToUInt32_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt32()); + } + + [TestMethod] + public void ToUInt64_ShouldReturnInt32_GivenBytes() + { + var bytes = new byte[] {0xA4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + Assert.AreEqual(420UL, bytes.ToUInt64()); + } + + [TestMethod] + public void ToUInt64_ShouldThrow_GivenNullArray() + { + byte[]? bytes = null; + Assert.ThrowsException(() => bytes!.ToUInt64()); + } +} diff --git a/X10D.Tests/src/IO/SByteTests.cs b/X10D.Tests/src/IO/SByteTests.cs new file mode 100644 index 0000000..8f28976 --- /dev/null +++ b/X10D.Tests/src/IO/SByteTests.cs @@ -0,0 +1,33 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void GetBytes_ReturnsArrayContainingItself() + { + const sbyte value = 0x0F; + CollectionAssert.AreEqual(new[] {(byte)value}, value.GetBytes()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanContainingItself_GivenLargeEnoughSpan() + { + const sbyte value = 0x0F; + Span buffer = stackalloc byte[1]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(new[] {(byte)value}, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const sbyte value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/SingleTests.cs b/X10D.Tests/src/IO/SingleTests.cs new file mode 100644 index 0000000..0bca2ce --- /dev/null +++ b/X10D.Tests/src/IO/SingleTests.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const float value = 42.5f; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0x2A, 0x42} + : new byte[] {0x42, 0x2A, 0, 0}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const float value = 42.5f; + byte[] littleEndian = {0, 0, 0x2A, 0x42}; + byte[] bigEndian = {0x42, 0x2A, 0, 0}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const float value = 42.5f; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0, 0, 0x2A, 0x42} + : new byte[] {0x42, 0x2A, 0, 0}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const float value = 42.5f; + byte[] littleEndian = {0, 0, 0x2A, 0x42}; + byte[] bigEndian = {0x42, 0x2A, 0, 0}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const float value = 42.5f; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/StreamTests.cs b/X10D.Tests/src/IO/StreamTests.cs new file mode 100644 index 0000000..77809fd --- /dev/null +++ b/X10D.Tests/src/IO/StreamTests.cs @@ -0,0 +1,526 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +public class StreamTests +{ + [TestMethod] + public void GetHashSha1ShouldBeCorrect() + { + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + using var stream = new MemoryStream(); + stream.Write(Encoding.UTF8.GetBytes("Hello World")); + stream.Position = 0; + + byte[] hash = stream.GetHash(); + Trace.WriteLine($"Hash: {BitConverter.ToString(hash)}"); + Trace.WriteLine($"Expected: {BitConverter.ToString(expectedHash)}"); + CollectionAssert.AreEqual(expectedHash, hash); + } + + [TestMethod] + public void GetHashNullShouldThrow() + { + // any HashAlgorithm will do, but SHA1 is used above. so to remain consistent, we use it here + Assert.ThrowsException(() => ((Stream?)null)!.GetHash()); + Assert.ThrowsException(() => ((Stream?)null)!.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void TryWriteHashSha1_ShouldBeCorrect() + { + // SHA-1 + byte[] expectedHash = + { + 0x0A, 0x4D, 0x55, 0xA8, 0xD7, 0x78, 0xE5, 0x02, 0x2F, 0xAB, 0x70, 0x19, 0x77, 0xC5, 0xD8, 0x40, 0xBB, 0xC4, 0x86, + 0xD0 + }; + + using var stream = new MemoryStream(); + stream.Write(Encoding.UTF8.GetBytes("Hello World")); + stream.Position = 0; + + Span hash = stackalloc byte[20]; + stream.TryWriteHash(hash, out int bytesWritten); + Assert.AreEqual(expectedHash.Length, bytesWritten); + CollectionAssert.AreEqual(expectedHash, hash.ToArray()); + } + + [TestMethod] + public void GetHash_TryWriteHash_ShouldThrow_GivenNonReadableStream() + { + Assert.ThrowsException(() => + { + using var stream = new DummyStream(); + stream.GetHash(); + }); + + Assert.ThrowsException(() => + { + using var stream = new DummyStream(); + stream.TryWriteHash(Span.Empty, out _); + }); + } + + [TestMethod] + public void LargeStreamShouldThrow() + { + Assert.ThrowsException(() => + { + using var stream = new DummyStream(true); + stream.TryWriteHash(Span.Empty, out _); + }); + } + + [TestMethod] + public void NullCreateMethodShouldThrow() + { + Assert.ThrowsException(() => Stream.Null.GetHash()); + Assert.ThrowsException(() => + Stream.Null.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void NoCreateMethodShouldThrow() + { + Assert.ThrowsException(() => Stream.Null.GetHash()); + Assert.ThrowsException(() => + Stream.Null.TryWriteHash(Span.Empty, out _)); + } + + [TestMethod] + public void Write_ShouldThrow_GivenUndefinedEndianness() + { + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0f, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0.0m, (Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write((short)0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0L, (Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write((ushort)0, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0U, (Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.Write(0UL, (Endianness)(-1)); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNullStream() + { + Stream? stream = null; + Assert.ThrowsException(() => stream!.ReadSingle()); + Assert.ThrowsException(() => stream!.ReadDouble()); + Assert.ThrowsException(() => stream!.ReadDecimal()); + Assert.ThrowsException(() => stream!.ReadInt16()); + Assert.ThrowsException(() => stream!.ReadInt32()); + Assert.ThrowsException(() => stream!.ReadInt64()); + Assert.ThrowsException(() => stream!.ReadUInt16()); + Assert.ThrowsException(() => stream!.ReadUInt32()); + Assert.ThrowsException(() => stream!.ReadUInt64()); + } + + [TestMethod] + public void Write_ShouldThrow_GivenNullStream() + { + Stream? stream = null; + Assert.ThrowsException(() => stream!.Write(0.0f, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write(0.0, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write(0.0m, Endianness.LittleEndian)); + Assert.ThrowsException(() => stream!.Write((short)0)); + Assert.ThrowsException(() => stream!.Write(0)); + Assert.ThrowsException(() => stream!.Write(0L)); + Assert.ThrowsException(() => stream!.Write((ushort)0)); + Assert.ThrowsException(() => stream!.Write(0U)); + Assert.ThrowsException(() => stream!.Write(0UL)); + } + + [TestMethod] + public void Read_ShouldThrow_GivenUndefinedEndianness() + { + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadSingle((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadDouble((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadDecimal((Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt16((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt32((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadInt64((Endianness)(-1)); + }); + + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt16((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt32((Endianness)(-1)); + }); + Assert.ThrowsException(() => + { + using var stream = new MemoryStream(); + return stream.ReadUInt64((Endianness)(-1)); + }); + } + + [TestMethod] + public void ReadDouble_WriteDouble_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(), 1e-6); + + stream.Position = 0; + stream.Write(420.0, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(Endianness.LittleEndian), 1e-6); + + stream.Position = 0; + stream.Write(420.0, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0, stream.ReadDouble(Endianness.BigEndian), 1e-6); + } + + [TestMethod] + public void ReadDecimal_WriteSingle_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0m, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal()); + + stream.Position = 0; + stream.Write(420.0m, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420.0m, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0m, stream.ReadDecimal(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadSingle_WriteSingle_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420.0f, BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(), 1e-6f); + + stream.Position = 0; + stream.Write(420.0f, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(Endianness.LittleEndian), 1e-6f); + + stream.Position = 0; + stream.Write(420.0f, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420.0f, stream.ReadSingle(Endianness.BigEndian), 1e-6f); + } + + [TestMethod] + public void ReadInt16_WriteInt16_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write((short)420); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16()); + + stream.Position = 0; + stream.Write((short)420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write((short)420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt16(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadInt32_WriteInt32_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32()); + + stream.Position = 0; + stream.Write(420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420, stream.ReadInt32(Endianness.BigEndian)); + } + + [TestMethod] + public void ReadInt64_WriteInt64_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420L); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64()); + + stream.Position = 0; + stream.Write(420L, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420L, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420L, stream.ReadInt64(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt16_WriteUInt16_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write((ushort)420); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16()); + + stream.Position = 0; + stream.Write((ushort)420, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write((ushort)420, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual((ushort)420, stream.ReadUInt16(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt32_WriteUInt32_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420U); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32()); + + stream.Position = 0; + stream.Write(420U, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420U, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420U, stream.ReadUInt32(Endianness.BigEndian)); + } + + [TestMethod] + [CLSCompliant(false)] + public void ReadUInt64_WriteUInt64_ShouldBeSymmetric() + { + using var stream = new MemoryStream(); + stream.Write(420UL); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64()); + + stream.Position = 0; + stream.Write(420UL, Endianness.LittleEndian); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64(Endianness.LittleEndian)); + + stream.Position = 0; + stream.Write(420UL, Endianness.BigEndian); + + stream.Position = 0; + Assert.AreEqual(420UL, stream.ReadUInt64(Endianness.BigEndian)); + } + + private class DummyStream : Stream + { + public DummyStream(bool readable = false) + { + CanRead = readable; + CanSeek = readable; + CanWrite = readable; + } + + public override void Flush() + { + } + + public override int Read(byte[] buffer, int offset, int count) + { + return 0; + } + + public override long Seek(long offset, SeekOrigin origin) + { + return 0; + } + + public override void SetLength(long value) + { + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + + public override bool CanRead { get; } + + public override bool CanSeek { get; } + + public override bool CanWrite { get; } + + public override long Length + { + get => long.MaxValue; + } + + public override long Position + { + get => 0; + set { } + } + } + + private class HashAlgorithmTestClass : HashAlgorithm + { + public static new HashAlgorithmTestClass? Create() + { + return null; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + throw new NotImplementedException(); + } + + protected override byte[] HashFinal() + { + throw new NotImplementedException(); + } + + public override void Initialize() + { + throw new NotImplementedException(); + } + } + + private class HashAlgorithmTestClassNoCreateMethod : HashAlgorithm + { + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + throw new NotImplementedException(); + } + + protected override byte[] HashFinal() + { + throw new NotImplementedException(); + } + + public override void Initialize() + { + throw new NotImplementedException(); + } + } +} diff --git a/X10D.Tests/src/IO/UInt16Tests.cs b/X10D.Tests/src/IO/UInt16Tests.cs new file mode 100644 index 0000000..08c0e61 --- /dev/null +++ b/X10D.Tests/src/IO/UInt16Tests.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const ushort value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const ushort value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const ushort value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0} : new byte[] {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const ushort value = 0x0F; + byte[] littleEndian = {0x0F, 0}; + byte[] bigEndian = {0, 0x0F}; + + Span buffer = stackalloc byte[2]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const ushort value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/UInt32Tests.cs b/X10D.Tests/src/IO/UInt32Tests.cs new file mode 100644 index 0000000..a9b3cc9 --- /dev/null +++ b/X10D.Tests/src/IO/UInt32Tests.cs @@ -0,0 +1,63 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const uint value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const uint value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const uint value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian ? new byte[] {0x0F, 0, 0, 0} : new byte[] {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const uint value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[4]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const uint value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/IO/UInt64Tests.cs b/X10D.Tests/src/IO/UInt64Tests.cs new file mode 100644 index 0000000..d31a209 --- /dev/null +++ b/X10D.Tests/src/IO/UInt64Tests.cs @@ -0,0 +1,67 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.IO; + +namespace X10D.Tests.IO; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void GetBytes_ReturnsCorrectValue() + { + const ulong value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + CollectionAssert.AreEqual(bytes, value.GetBytes()); + } + + [TestMethod] + public void GetBytes_ReturnsCorrectValue_WithEndianness() + { + const ulong value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + CollectionAssert.AreEqual(littleEndian, value.GetBytes(Endianness.LittleEndian)); + CollectionAssert.AreEqual(bigEndian, value.GetBytes(Endianness.BigEndian)); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan() + { + const ulong value = 0x0F; + byte[] bytes = BitConverter.IsLittleEndian + ? new byte[] {0x0F, 0, 0, 0, 0, 0, 0, 0} + : new byte[] {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + Assert.IsTrue(value.TryWriteBytes(buffer)); + CollectionAssert.AreEqual(bytes, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsTrue_FillsSpanCorrectly_GivenLargeEnoughSpan_WithEndianness() + { + const ulong value = 0x0F; + byte[] littleEndian = {0x0F, 0, 0, 0, 0, 0, 0, 0}; + byte[] bigEndian = {0, 0, 0, 0, 0, 0, 0, 0x0F}; + + Span buffer = stackalloc byte[8]; + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.LittleEndian)); + CollectionAssert.AreEqual(littleEndian, buffer.ToArray()); + + Assert.IsTrue(value.TryWriteBytes(buffer, Endianness.BigEndian)); + CollectionAssert.AreEqual(bigEndian, buffer.ToArray()); + } + + [TestMethod] + public void TryWriteBytes_ReturnsFalse_GivenSmallSpan() + { + const ulong value = 0x0F; + Span buffer = stackalloc byte[0]; + Assert.IsFalse(value.TryWriteBytes(buffer)); + } +} diff --git a/X10D.Tests/src/Linq/ByteTests.cs b/X10D.Tests/src/Linq/ByteTests.cs new file mode 100644 index 0000000..1c269b4 --- /dev/null +++ b/X10D.Tests/src/Linq/ByteTests.cs @@ -0,0 +1,96 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + byte Cast(int i) => (byte)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + + // 6! will overflow for byte + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + byte Double(int i) => (byte)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=4 for byte + } + + [TestMethod] + public void RangeTo_Byte_ShouldYieldCorrectValues() + { + const byte start = 1; + const byte end = 10; + + byte current = 1; + foreach (byte value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int16_ShouldYieldCorrectValues() + { + const byte start = 1; + const short end = 10; + + short current = 1; + foreach (short value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const byte start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const byte start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/DecimalTests.cs b/X10D.Tests/src/Linq/DecimalTests.cs new file mode 100644 index 0000000..abb3f7b --- /dev/null +++ b/X10D.Tests/src/Linq/DecimalTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + decimal Cast(int i) => i; + + Assert.AreEqual(0m, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1m, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2m, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6m, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24m, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120m, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720m, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040m, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320m, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880m, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800m, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + decimal Double(int i) => i * 2m; + + Assert.AreEqual(0m, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2m, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8m, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48m, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384m, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840m, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080m, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120m, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920m, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560m, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200m, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/DoubleTests.cs b/X10D.Tests/src/Linq/DoubleTests.cs new file mode 100644 index 0000000..8e33bdd --- /dev/null +++ b/X10D.Tests/src/Linq/DoubleTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + double Cast(int i) => i; + + Assert.AreEqual(0.0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1.0, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2.0, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6.0, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24.0, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120.0, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720.0, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040.0, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320.0, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880.0, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800.0, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + double Double(int i) => i * 2.0; + + Assert.AreEqual(0.0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2.0, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8.0, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48.0, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384.0, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840.0, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080.0, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120.0, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920.0, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560.0, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200.0, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/Int16Tests.cs b/X10D.Tests/src/Linq/Int16Tests.cs new file mode 100644 index 0000000..c0f0636 --- /dev/null +++ b/X10D.Tests/src/Linq/Int16Tests.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + short Cast(int i) => (short)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Select(Cast).Product()); + + // 8! will overflow for short + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + short Double(int i) => (short)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=6 for short + } + + [TestMethod] + public void RangeTo_Int16_ShouldYieldCorrectValues() + { + const short start = 1; + const short end = 10; + + short current = 1; + foreach (short value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const short start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const short start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/Int32Tests.cs b/X10D.Tests/src/Linq/Int32Tests.cs new file mode 100644 index 0000000..efc0870 --- /dev/null +++ b/X10D.Tests/src/Linq/Int32Tests.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + Assert.AreEqual(0, Enumerable.Range(0, 10).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Product()); + Assert.AreEqual(40320, Enumerable.Range(1, 8).Product()); + Assert.AreEqual(362880, Enumerable.Range(1, 9).Product()); + Assert.AreEqual(3628800, Enumerable.Range(1, 10).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + int Double(int i) => i * 2; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560, Enumerable.Range(1, 9).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=10 for int + } + + [TestMethod] + public void RangeTo_Int32_ShouldYieldCorrectValues() + { + const int start = 1; + const int end = 10; + + int current = 1; + foreach (int value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const int start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/Int64Tests.cs b/X10D.Tests/src/Linq/Int64Tests.cs new file mode 100644 index 0000000..5535eeb --- /dev/null +++ b/X10D.Tests/src/Linq/Int64Tests.cs @@ -0,0 +1,59 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + long Cast(int i) => i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + long Double(int i) => i * 2; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200, Enumerable.Range(1, 10).Product(Double)); + } + + [TestMethod] + public void RangeTo_Int64_ShouldYieldCorrectValues() + { + const long start = 1; + const long end = 10; + + long current = 1; + foreach (long value in start.RangeTo(end)) + { + Assert.AreEqual(current++, value); + } + + Assert.AreEqual(current, end); + } +} diff --git a/X10D.Tests/src/Linq/ReadOnlySpanTests.cs b/X10D.Tests/src/Linq/ReadOnlySpanTests.cs new file mode 100644 index 0000000..e33f5cd --- /dev/null +++ b/X10D.Tests/src/Linq/ReadOnlySpanTests.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class ReadOnlySpanTests +{ + [TestMethod] + public void AllShouldReturnTrueForEmptySpan() + { + var span = new ReadOnlySpan(); + Assert.IsTrue(span.All(x => x > 0)); + } + + [TestMethod] + public void AllShouldBeCorrect() + { + var span = new ReadOnlySpan(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.All(x => x % 2 == 0)); + Assert.IsFalse(span.All(x => x % 2 == 1)); + } + + [TestMethod] + public void AnyShouldReturnFalseForEmptySpan() + { + var span = new ReadOnlySpan(); + Assert.IsFalse(span.Any(x => x > 0)); + } + + [TestMethod] + public void AnyShouldBeCorrect() + { + var span = new ReadOnlySpan(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.Any(x => x % 2 == 0)); + Assert.IsFalse(span.Any(x => x % 2 == 1)); + } + + [TestMethod] + public void AllNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new ReadOnlySpan(); + return span.All(null!); + }); + } + + [TestMethod] + public void AnyNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new ReadOnlySpan(); + return span.Any(null!); + }); + } +} diff --git a/X10D.Tests/src/Linq/SByteExtensions.cs b/X10D.Tests/src/Linq/SByteExtensions.cs new file mode 100644 index 0000000..59d01a7 --- /dev/null +++ b/X10D.Tests/src/Linq/SByteExtensions.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + sbyte Cast(int i) => (sbyte)i; + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product()); + Assert.AreEqual(1, Enumerable.Range(1, 1).Product()); + Assert.AreEqual(2, Enumerable.Range(1, 2).Product()); + Assert.AreEqual(6, Enumerable.Range(1, 3).Product()); + Assert.AreEqual(24, Enumerable.Range(1, 4).Product()); + Assert.AreEqual(120, Enumerable.Range(1, 5).Product()); + + // 6! will overflow for sbyte + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + sbyte Double(int i) => (sbyte)(i * 2); + + Assert.AreEqual(0, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48, Enumerable.Range(1, 3).Product(Double)); + + // Π_(i=1)^(n(i*2)) will overflow at i=4 for sbyte + } +} diff --git a/X10D.Tests/src/Linq/SingleTests.cs b/X10D.Tests/src/Linq/SingleTests.cs new file mode 100644 index 0000000..b504972 --- /dev/null +++ b/X10D.Tests/src/Linq/SingleTests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + float Cast(int i) => i; + + Assert.AreEqual(0f, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1f, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2f, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6f, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24f, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120f, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720f, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040f, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320f, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880f, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800f, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + float Double(int i) => i * 2f; + + Assert.AreEqual(0f, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2f, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8f, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48f, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384f, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840f, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080f, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120f, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920f, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560f, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200f, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/SpanTests.cs b/X10D.Tests/src/Linq/SpanTests.cs new file mode 100644 index 0000000..98c5fe3 --- /dev/null +++ b/X10D.Tests/src/Linq/SpanTests.cs @@ -0,0 +1,58 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +public class SpanTests +{ + [TestMethod] + public void AllShouldReturnTrueForEmptySpan() + { + var span = new Span(); + Assert.IsTrue(span.All(x => x > 0)); + } + + [TestMethod] + public void AllShouldBeCorrect() + { + var span = new Span(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.All(x => x % 2 == 0)); + Assert.IsFalse(span.All(x => x % 2 == 1)); + } + + [TestMethod] + public void AnyShouldReturnFalseForEmptySpan() + { + var span = new Span(); + Assert.IsFalse(span.Any(x => x > 0)); + } + + [TestMethod] + public void AnyShouldBeCorrect() + { + var span = new Span(new[] {2, 4, 6, 8, 10}); + Assert.IsTrue(span.Any(x => x % 2 == 0)); + Assert.IsFalse(span.Any(x => x % 2 == 1)); + } + + [TestMethod] + public void AllNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new Span(); + return span.All(null!); + }); + } + + [TestMethod] + public void AnyNullPredicateShouldThrow() + { + Assert.ThrowsException(() => + { + var span = new Span(); + return span.Any(null!); + }); + } +} diff --git a/X10D.Tests/src/Linq/UInt16Tests.cs b/X10D.Tests/src/Linq/UInt16Tests.cs new file mode 100644 index 0000000..bf496e8 --- /dev/null +++ b/X10D.Tests/src/Linq/UInt16Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ushort Cast(int i) => (ushort)i; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1U, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2U, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6U, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24U, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120U, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720U, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040U, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320U, Enumerable.Range(1, 8).Select(Cast).Product()); + + // 9! will overflow for ushort + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + ushort Double(int i) => (ushort)(i * 2); + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2U, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8U, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48U, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384U, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840U, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080U, Enumerable.Range(1, 6).Product(Double)); + + // Π_(i=1)^n (2i) will overflow at i=7 for ushort + } +} diff --git a/X10D.Tests/src/Linq/UInt32Tests.cs b/X10D.Tests/src/Linq/UInt32Tests.cs new file mode 100644 index 0000000..060592e --- /dev/null +++ b/X10D.Tests/src/Linq/UInt32Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ulong Cast(int i) => (ulong)i; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1U, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2U, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6U, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24U, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120U, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720U, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040U, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320U, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880U, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800U, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + uint Double(int i) => (uint)i * 2; + + Assert.AreEqual(0U, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2U, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8U, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48U, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384U, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840U, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080U, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120U, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920U, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560U, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200U, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Linq/UInt64Tests.cs b/X10D.Tests/src/Linq/UInt64Tests.cs new file mode 100644 index 0000000..c9c2409 --- /dev/null +++ b/X10D.Tests/src/Linq/UInt64Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Linq; + +namespace X10D.Tests.Linq; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void ProductShouldBeCorrect() + { + ulong Cast(int i) => (ulong)i; + + Assert.AreEqual(0UL, Enumerable.Range(0, 10).Select(Cast).Product()); + Assert.AreEqual(1UL, Enumerable.Range(1, 1).Select(Cast).Product()); + Assert.AreEqual(2UL, Enumerable.Range(1, 2).Select(Cast).Product()); + Assert.AreEqual(6UL, Enumerable.Range(1, 3).Select(Cast).Product()); + Assert.AreEqual(24UL, Enumerable.Range(1, 4).Select(Cast).Product()); + Assert.AreEqual(120UL, Enumerable.Range(1, 5).Select(Cast).Product()); + Assert.AreEqual(720UL, Enumerable.Range(1, 6).Select(Cast).Product()); + Assert.AreEqual(5040UL, Enumerable.Range(1, 7).Select(Cast).Product()); + Assert.AreEqual(40320UL, Enumerable.Range(1, 8).Select(Cast).Product()); + Assert.AreEqual(362880UL, Enumerable.Range(1, 9).Select(Cast).Product()); + Assert.AreEqual(3628800UL, Enumerable.Range(1, 10).Select(Cast).Product()); + } + + [TestMethod] + public void ProductOfDoublesShouldBeCorrect() + { + ulong Double(int i) => (ulong)i * 2; + + Assert.AreEqual(0UL, Enumerable.Range(0, 10).Product(Double)); + Assert.AreEqual(2UL, Enumerable.Range(1, 1).Product(Double)); + Assert.AreEqual(8UL, Enumerable.Range(1, 2).Product(Double)); + Assert.AreEqual(48UL, Enumerable.Range(1, 3).Product(Double)); + Assert.AreEqual(384UL, Enumerable.Range(1, 4).Product(Double)); + Assert.AreEqual(3840UL, Enumerable.Range(1, 5).Product(Double)); + Assert.AreEqual(46080UL, Enumerable.Range(1, 6).Product(Double)); + Assert.AreEqual(645120UL, Enumerable.Range(1, 7).Product(Double)); + Assert.AreEqual(10321920UL, Enumerable.Range(1, 8).Product(Double)); + Assert.AreEqual(185794560UL, Enumerable.Range(1, 9).Product(Double)); + Assert.AreEqual(3715891200UL, Enumerable.Range(1, 10).Product(Double)); + } +} diff --git a/X10D.Tests/src/Math/ByteTests.cs b/X10D.Tests/src/Math/ByteTests.cs new file mode 100644 index 0000000..cfc4386 --- /dev/null +++ b/X10D.Tests/src/Math/ByteTests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const byte value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((byte)0).Factorial()); + Assert.AreEqual(1L, ((byte)1).Factorial()); + Assert.AreEqual(2L, ((byte)2).Factorial()); + Assert.AreEqual(6L, ((byte)3).Factorial()); + Assert.AreEqual(24L, ((byte)4).Factorial()); + Assert.AreEqual(120L, ((byte)5).Factorial()); + Assert.AreEqual(720L, ((byte)6).Factorial()); + Assert.AreEqual(5040L, ((byte)7).Factorial()); + Assert.AreEqual(40320L, ((byte)8).Factorial()); + Assert.AreEqual(362880L, ((byte)9).Factorial()); + Assert.AreEqual(3628800L, ((byte)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const byte one = 1; + const byte two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const byte one = 1; + const byte two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((byte)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((byte)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((byte)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((byte)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((byte)77).MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/ComparableTests.cs b/X10D.Tests/src/Math/ComparableTests.cs new file mode 100644 index 0000000..dd71c7d --- /dev/null +++ b/X10D.Tests/src/Math/ComparableTests.cs @@ -0,0 +1,201 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class ComparableTests +{ + private class ComparableTestClass : IComparable + { + public int CompareTo(ComparableTestClass? other) + { + return 0; + } + } + + private readonly int _lower = 1; + private readonly int _upper = 10; + private readonly int _value = 5; + + [TestMethod] + public void Between_5_1_10_ShouldBeTrue() + { + Assert.IsTrue(_value.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_1_1_10_ShouldBeFalse() + { + // default option is exclusive + Assert.IsFalse(_lower.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_1_1_10_Inclusive_ShouldBeTrue() + { + Assert.IsTrue(_lower.Between(_lower, _upper, InclusiveOptions.Inclusive)); + Assert.IsTrue(_lower.Between(_lower, _upper, InclusiveOptions.LowerInclusive)); + Assert.IsFalse(_lower.Between(_lower, _upper, InclusiveOptions.UpperInclusive)); + } + + [TestMethod] + public void Between_10_1_10_ShouldBeFalse() + { + // default option is exclusive + Assert.IsFalse(_upper.Between(_lower, _upper)); + } + + [TestMethod] + public void Between_10_1_10_Inclusive_ShouldBeTrue() + { + Assert.IsTrue(_upper.Between(_lower, _upper, InclusiveOptions.Inclusive)); + Assert.IsTrue(_upper.Between(_lower, _upper, InclusiveOptions.UpperInclusive)); + Assert.IsFalse(_upper.Between(_lower, _upper, InclusiveOptions.LowerInclusive)); + } + + [TestMethod] + public void Between_1_10_5_ShouldThrow() + { + Assert.ThrowsException(() => _lower.Between(_upper, _value)); + } + + [TestMethod] + public void Between_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => ((ComparableTestClass?)null)!.Between(nullPointer!, nullPointer!)); + } + + [TestMethod] + public void Clamp_3_1_5_ShouldBe3() + { + Assert.AreEqual(3, 3.Clamp(1, 5)); + } + + [TestMethod] + public void Clamp_10_1_5_ShouldBe5() + { + Assert.AreEqual(5, 10.Clamp(1, 5)); + } + + [TestMethod] + public void Clamp_n_6_5_ShouldThrow() + { + Assert.ThrowsException(() => 0.Clamp(6, 5)); + } + + [TestMethod] + public void GreaterThan_5_6_ShouldBeFalse() + { + Assert.IsFalse(5.GreaterThan(6)); + } + + [TestMethod] + public void GreaterThan_6_5_ShouldBeTrue() + { + Assert.IsTrue(6.GreaterThan(5)); + } + + [TestMethod] + public void GreaterThan_5_5_ShouldBeFalse() + { + Assert.IsFalse(5.LessThan(5)); + } + + [TestMethod] + public void GreaterThan_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.GreaterThan(nullPointer!)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_5_5_ShouldBeTrue() + { + Assert.IsTrue(5.GreaterThanOrEqualTo(5)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_6_5_ShouldBeTrue() + { + Assert.IsTrue(6.GreaterThanOrEqualTo(5)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_5_6_ShouldBeFalse() + { + Assert.IsFalse(5.GreaterThanOrEqualTo(6)); + } + + [TestMethod] + public void GreaterThanOrEqualTo_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.GreaterThanOrEqualTo(nullPointer!)); + } + + [TestMethod] + public void LessThan_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.LessThan(nullPointer!)); + } + + [TestMethod] + public void LessThan_6_5_ShouldBeFalse() + { + Assert.IsFalse(6.LessThan(5)); + } + + [TestMethod] + public void LessThan_5_6_ShouldBeTrue() + { + Assert.IsTrue(5.LessThan(6)); + } + + [TestMethod] + public void LessThan_5_5_ShouldBeFalse() + { + Assert.IsFalse(5.LessThan(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_5_5_ShouldBeTrue() + { + Assert.IsTrue(5.LessThanOrEqualTo(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_5_6_ShouldBeTrue() + { + Assert.IsTrue(5.LessThanOrEqualTo(6)); + } + + [TestMethod] + public void LessThanOrEqualTo_6_5_ShouldBeFalse() + { + Assert.IsFalse(6.LessThanOrEqualTo(5)); + } + + [TestMethod] + public void LessThanOrEqualTo_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.LessThanOrEqualTo(nullPointer!)); + } + + [TestMethod] + public void Max_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.Max(nullPointer!)); + } + + [TestMethod] + public void Min_Null_ShouldThrow() + { + ComparableTestClass? nullPointer = null; + Assert.ThrowsException(() => nullPointer!.Min(nullPointer!)); + } +} diff --git a/X10D.Tests/src/Math/DecimalTests.cs b/X10D.Tests/src/Math/DecimalTests.cs new file mode 100644 index 0000000..a8ce150 --- /dev/null +++ b/X10D.Tests/src/Math/DecimalTests.cs @@ -0,0 +1,122 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0, 0.0m.ComplexSqrt()); + Assert.AreEqual(1.4142135623730951, 2.0m.ComplexSqrt()); + Assert.AreEqual(3.0, 9.0m.ComplexSqrt()); + Assert.AreEqual(4.0, 16.0m.ComplexSqrt()); + Assert.AreEqual(100.0, 10000.0m.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135623730951), (-2.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0), (-9.0m).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0), (-16.0m).ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0m).IsEven()); + Assert.IsFalse((-1.0m).IsEven()); + Assert.IsFalse(1.0m.IsEven()); + Assert.IsFalse(3.0m.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0m).IsEven()); + Assert.IsTrue((-2.0m).IsEven()); + Assert.IsTrue(0.0m.IsEven()); + Assert.IsTrue(2.0m.IsEven()); + Assert.IsTrue(4.0m.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0m).IsOdd()); + Assert.IsFalse((-2.0m).IsOdd()); + Assert.IsFalse(0.0m.IsOdd()); + Assert.IsFalse(2.0m.IsOdd()); + Assert.IsFalse(4.0m.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0m).IsOdd()); + Assert.IsTrue((-1.0m).IsOdd()); + Assert.IsTrue(1.0m.IsOdd()); + Assert.IsTrue(3.0m.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0m, 3.5m.Round()); + Assert.AreEqual(7.0m, 6.8m.Round()); + Assert.AreEqual(7.0m, 7.2m.Round()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0m, 3.5m.Round(5)); + Assert.AreEqual(5.0m, 7.0m.Round(5)); + Assert.AreEqual(10.0m, 7.5m.Round(5)); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0m.Sign()); + Assert.AreEqual(-1, -2.0m.Sign()); + Assert.AreEqual(-1, -3.0m.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0m.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0m.Sign()); + Assert.AreEqual(1, 2.0m.Sign()); + Assert.AreEqual(1, 3.0m.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0m, 0.0m.Sqrt()); + Assert.AreEqual(1.4142135623730950488016887242m, 2.0m.Sqrt()); + Assert.AreEqual(3.0m, 9.0m.Sqrt()); + Assert.AreEqual(4.0m, 16.0m.Sqrt()); + Assert.AreEqual(100.0m, 10000.0m.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldThrow_GivenNegativeValue() + { + Assert.ThrowsException(() => (-1.0m).Sqrt()); + Assert.ThrowsException(() => (-2.0m).Sqrt()); + Assert.ThrowsException(() => (-3.0m).Sqrt()); + } +} diff --git a/X10D.Tests/src/Math/DoubleTests.cs b/X10D.Tests/src/Math/DoubleTests.cs new file mode 100644 index 0000000..2496348 --- /dev/null +++ b/X10D.Tests/src/Math/DoubleTests.cs @@ -0,0 +1,242 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class DoubleTests +{ + [TestMethod] + public void DegreesToRadians_ShouldBeCorrect() + { + Assert.AreEqual(System.Math.PI, 180.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(System.Math.PI * 1.5, 270.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.0, 0.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.017453292519943295, 1.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.10471975511965978, 6.0.DegreesToRadians(), 1e-6); + Assert.AreEqual(0.20943951023931956, 12.0.DegreesToRadians(), 1e-6); + } + + [TestMethod] + public void RadiansToDegrees_ShouldBeCorrect() + { + Assert.AreEqual(180.0, System.Math.PI.RadiansToDegrees(), 1e-6); + Assert.AreEqual(360.0, (2.0 * System.Math.PI).RadiansToDegrees(), 1e-6); + Assert.AreEqual(0.0, 0.0.RadiansToDegrees(), 1e-6); + Assert.AreEqual(1.0, 0.017453292519943295.RadiansToDegrees(), 1e-6); + Assert.AreEqual(6.000000000000001, 0.10471975511965978.RadiansToDegrees(), 1e-6); // rounding errors are fun + Assert.AreEqual(12.0, 0.20943951023931953.RadiansToDegrees(), 1e-6); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0, 0.0.ComplexSqrt()); + Assert.AreEqual(1.4142135623730951, 2.0.ComplexSqrt()); + Assert.AreEqual(3.0, 9.0.ComplexSqrt()); + Assert.AreEqual(4.0, 16.0.ComplexSqrt()); + Assert.AreEqual(100.0, 10000.0.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135623730951), (-2.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0), (-9.0).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0), (-16.0).ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeComplexInfinity_GivenInfinity() + { + Assert.AreEqual(Complex.Infinity, double.NegativeInfinity.ComplexSqrt()); + Assert.AreEqual(Complex.Infinity, double.PositiveInfinity.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(Complex.NaN, double.NaN.ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0).IsEven()); + Assert.IsFalse((-1.0).IsEven()); + Assert.IsFalse(1.0.IsEven()); + Assert.IsFalse(3.0.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0).IsEven()); + Assert.IsTrue((-2.0).IsEven()); + Assert.IsTrue(0.0.IsEven()); + Assert.IsTrue(2.0.IsEven()); + Assert.IsTrue(4.0.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0).IsOdd()); + Assert.IsFalse((-2.0).IsOdd()); + Assert.IsFalse(0.0.IsOdd()); + Assert.IsFalse(2.0.IsOdd()); + Assert.IsFalse(4.0.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0).IsOdd()); + Assert.IsTrue((-1.0).IsOdd()); + Assert.IsTrue(1.0.IsOdd()); + Assert.IsTrue(3.0.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0, 3.5.Round(), 1e-6); + Assert.AreEqual(7.0, 6.8.Round(), 1e-6); + Assert.AreEqual(7.0, 7.2.Round(), 1e-6); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0, 3.5.Round(5), 1e-6); + Assert.AreEqual(5.0, 7.0.Round(5), 1e-6); + Assert.AreEqual(10.0, 7.5.Round(5), 1e-6); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0.Sign()); + Assert.AreEqual(-1, -2.0.Sign()); + Assert.AreEqual(-1, -3.0.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0.Sign()); + Assert.AreEqual(1, 2.0.Sign()); + Assert.AreEqual(1, 3.0.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0, 0.0.Sqrt(), 1e-6); + Assert.AreEqual(1.414213562373095, 2.0.Sqrt(), 1e-6); + Assert.AreEqual(3.0, 9.0.Sqrt(), 1e-6); + Assert.AreEqual(4.0, 16.0.Sqrt(), 1e-6); + Assert.AreEqual(100.0, 10000.0.Sqrt(), 1e-6); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(double.NaN, double.NaN.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNegativeValue() + { + Assert.AreEqual(double.NaN, (-1.0).Sqrt()); + Assert.AreEqual(double.NaN, (-2.0).Sqrt()); + Assert.AreEqual(double.NaN, (-3.0).Sqrt()); + Assert.AreEqual(double.NaN, double.NegativeInfinity.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBePositiveInfinity_GivenPositiveInfinity() + { + Assert.AreEqual(double.PositiveInfinity, double.PositiveInfinity.Sqrt()); + } + + [TestMethod] + public void Acos_ShouldBeCorrect() + { + Assert.AreEqual(1.0471975511965979, 0.5.Acos(), 1e-6); + } + + [TestMethod] + public void Acosh_ShouldBeCorrect() + { + Assert.AreEqual(0.9624236501192069, 1.5.Acosh(), 1e-6); + } + + [TestMethod] + public void Asin_ShouldBeCorrect() + { + Assert.AreEqual(0.5235987755982989, 0.5.Asin(), 1e-6); + } + + [TestMethod] + public void Asinh_ShouldBeCorrect() + { + Assert.AreEqual(1.1947632172871094, 1.5.Asinh(), 1e-6); + } + + [TestMethod] + public void Atan_ShouldBeCorrect() + { + Assert.AreEqual(0.4636476090008061, 0.5.Atan(), 1e-6); + } + + [TestMethod] + public void Atanh_ShouldBeCorrect() + { + Assert.AreEqual(0.5493061443340549, 0.5.Atanh(), 1e-6); + } + + [TestMethod] + public void Cos_ShouldBeCorrect() + { + Assert.AreEqual(0.8775825618903728, 0.5.Cos(), 1e-6); + } + + [TestMethod] + public void Cosh_ShouldBeCorrect() + { + Assert.AreEqual(2.352409615243247, 1.5.Cosh(), 1e-6); + } + + [TestMethod] + public void Sin_ShouldBeCorrect() + { + Assert.AreEqual(0.479425538604203, 0.5.Sin(), 1e-6); + } + + [TestMethod] + public void Sinh_ShouldBeCorrect() + { + Assert.AreEqual(2.1292794550948173, 1.5.Sinh(), 1e-6); + } + + [TestMethod] + public void Tan_ShouldBeCorrect() + { + Assert.AreEqual(0.5463024898437905, 0.5.Tan(), 1e-6); + } + + [TestMethod] + public void Tanh_ShouldBeCorrect() + { + Assert.AreEqual(0.46211715726000974, 0.5.Tanh(), 1e-6); + } +} diff --git a/X10D.Tests/src/Math/Int16Tests.cs b/X10D.Tests/src/Math/Int16Tests.cs new file mode 100644 index 0000000..15edb9a --- /dev/null +++ b/X10D.Tests/src/Math/Int16Tests.cs @@ -0,0 +1,80 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const short value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((short)0).Factorial()); + Assert.AreEqual(1L, ((short)1).Factorial()); + Assert.AreEqual(2L, ((short)2).Factorial()); + Assert.AreEqual(6L, ((short)3).Factorial()); + Assert.AreEqual(24L, ((short)4).Factorial()); + Assert.AreEqual(120L, ((short)5).Factorial()); + Assert.AreEqual(720L, ((short)6).Factorial()); + Assert.AreEqual(5040L, ((short)7).Factorial()); + Assert.AreEqual(40320L, ((short)8).Factorial()); + Assert.AreEqual(362880L, ((short)9).Factorial()); + Assert.AreEqual(3628800L, ((short)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const short one = 1; + const short two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const short one = 1; + const short two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((short)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((short)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((short)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((short)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((short)77).MultiplicativePersistence()); + Assert.AreEqual(5, ((short)679).MultiplicativePersistence()); + Assert.AreEqual(6, ((short)6788).MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => ((short)-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const short one = 1; + const short zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/Int32Tests.cs b/X10D.Tests/src/Math/Int32Tests.cs new file mode 100644 index 0000000..db0d5fb --- /dev/null +++ b/X10D.Tests/src/Math/Int32Tests.cs @@ -0,0 +1,83 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const int value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, 0.Factorial()); + Assert.AreEqual(1L, 1.Factorial()); + Assert.AreEqual(2L, 2.Factorial()); + Assert.AreEqual(6L, 3.Factorial()); + Assert.AreEqual(24L, 4.Factorial()); + Assert.AreEqual(120L, 5.Factorial()); + Assert.AreEqual(720L, 6.Factorial()); + Assert.AreEqual(5040L, 7.Factorial()); + Assert.AreEqual(40320L, 8.Factorial()); + Assert.AreEqual(362880L, 9.Factorial()); + Assert.AreEqual(3628800L, 10.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const int one = 1; + const int two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const int one = 1; + const int two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0.MultiplicativePersistence()); + Assert.AreEqual(1, 10.MultiplicativePersistence()); + Assert.AreEqual(2, 25.MultiplicativePersistence()); + Assert.AreEqual(3, 39.MultiplicativePersistence()); + Assert.AreEqual(4, 77.MultiplicativePersistence()); + Assert.AreEqual(5, 679.MultiplicativePersistence()); + Assert.AreEqual(6, 6788.MultiplicativePersistence()); + Assert.AreEqual(7, 68889.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999.MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => (-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const int one = 1; + const int zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/Int64Tests.cs b/X10D.Tests/src/Math/Int64Tests.cs new file mode 100644 index 0000000..44acb55 --- /dev/null +++ b/X10D.Tests/src/Math/Int64Tests.cs @@ -0,0 +1,85 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const long value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, 0L.Factorial()); + Assert.AreEqual(1L, 1L.Factorial()); + Assert.AreEqual(2L, 2L.Factorial()); + Assert.AreEqual(6L, 3L.Factorial()); + Assert.AreEqual(24L, 4L.Factorial()); + Assert.AreEqual(120L, 5L.Factorial()); + Assert.AreEqual(720L, 6L.Factorial()); + Assert.AreEqual(5040L, 7L.Factorial()); + Assert.AreEqual(40320L, 8L.Factorial()); + Assert.AreEqual(362880L, 9L.Factorial()); + Assert.AreEqual(3628800L, 10L.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const long one = 1; + const long two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const long one = 1; + const long two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0L.MultiplicativePersistence()); + Assert.AreEqual(1, 10L.MultiplicativePersistence()); + Assert.AreEqual(2, 25L.MultiplicativePersistence()); + Assert.AreEqual(3, 39L.MultiplicativePersistence()); + Assert.AreEqual(4, 77L.MultiplicativePersistence()); + Assert.AreEqual(5, 679L.MultiplicativePersistence()); + Assert.AreEqual(6, 6788L.MultiplicativePersistence()); + Assert.AreEqual(7, 68889L.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889L.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999L.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999L.MultiplicativePersistence()); + Assert.AreEqual(11, 277777788888899L.MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => (-1L).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const long one = 1; + const long zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/IsPrimeTests.cs b/X10D.Tests/src/Math/IsPrimeTests.cs new file mode 100644 index 0000000..acc577b --- /dev/null +++ b/X10D.Tests/src/Math/IsPrimeTests.cs @@ -0,0 +1,154 @@ +using System.Reflection; +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class IsPrimeTests +{ + private IReadOnlyList _primeNumbers = ArraySegment.Empty; + + [TestInitialize] + public void Initialize() + { + _primeNumbers = LoadPrimes(); + Assert.AreEqual(1000, _primeNumbers.Count); + } + + [TestMethod] + public void First1000Primes() + { + for (var index = 0; index < _primeNumbers.Count; index++) + { + int value = _primeNumbers[index]; + + Assert.IsTrue(value.IsPrime()); + Assert.IsTrue(((long)value).IsPrime()); + + if (value is >= byte.MinValue and <= byte.MaxValue) + { + Assert.IsTrue(((byte)value).IsPrime()); + } + + if (value is >= short.MinValue and <= short.MaxValue) + { + Assert.IsTrue(((short)value).IsPrime()); + } + + if (value is >= byte.MinValue and <= byte.MaxValue) + { + Assert.IsTrue(((byte)value).IsPrime()); + } + + if (value is >= ushort.MinValue and <= ushort.MaxValue) + { + Assert.IsTrue(((ushort)value).IsPrime()); + } + + if (value is >= sbyte.MinValue and <= sbyte.MaxValue) + { + Assert.IsTrue(((sbyte)value).IsPrime()); + } + + if (value >= 0) + { + Assert.IsTrue(((uint)value).IsPrime()); + Assert.IsTrue(((ulong)value).IsPrime()); + } + } + } + + [TestMethod] + public void Negatives() + { + for (var value = short.MinValue; value < 0; value++) + { + Assert.IsFalse(value.IsPrime()); + Assert.IsFalse(((int)value).IsPrime()); + Assert.IsFalse(((long)value).IsPrime()); + + if (value is >= sbyte.MinValue and <= sbyte.MaxValue) + { + Assert.IsFalse(((sbyte)value).IsPrime()); + } + } + } + + [TestMethod] + public void LessThan2() + { + for (var value = 0; value < 2; value++) + { + Assert.IsFalse(value.IsPrime()); + Assert.IsFalse(((byte)value).IsPrime()); + Assert.IsFalse(((short)value).IsPrime()); + Assert.IsFalse(((long)value).IsPrime()); + + Assert.IsFalse(((sbyte)value).IsPrime()); + Assert.IsFalse(((ushort)value).IsPrime()); + Assert.IsFalse(((uint)value).IsPrime()); + Assert.IsFalse(((ulong)value).IsPrime()); + } + } + + [TestMethod] + public void ZeroTo7919() + { + for (var value = 0; value < 7920; value++) + { + bool expected = _primeNumbers.Contains(value); + + Assert.AreEqual(expected, ((short)value).IsPrime()); + Assert.AreEqual(expected, value.IsPrime()); + Assert.AreEqual(expected, ((long)value).IsPrime()); + + Assert.AreEqual(expected, ((ushort)value).IsPrime()); + Assert.AreEqual(expected, ((uint)value).IsPrime()); + Assert.AreEqual(expected, ((ulong)value).IsPrime()); + } + } + + [TestMethod] + public void ZeroToByteMaxValue() + { + for (byte value = 0; value < byte.MaxValue; value++) + { + bool expected = _primeNumbers.Contains(value); + + Assert.AreEqual(expected, value.IsPrime()); + Assert.AreEqual(expected, ((short)value).IsPrime()); + Assert.AreEqual(expected, ((int)value).IsPrime()); + Assert.AreEqual(expected, ((long)value).IsPrime()); + + Assert.AreEqual(expected, ((ushort)value).IsPrime()); + Assert.AreEqual(expected, ((uint)value).IsPrime()); + Assert.AreEqual(expected, ((ulong)value).IsPrime()); + + if (value < sbyte.MaxValue) + { + Assert.AreEqual(expected, ((sbyte)value).IsPrime()); + } + } + } + + private static IReadOnlyList LoadPrimes() + { + using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("X10D.Tests.1000primes.txt"); + Assert.IsNotNull(stream); + + using var reader = new StreamReader(stream, Encoding.UTF8); + var primes = new List(); + + while (reader.ReadLine() is { } line) + { + if (int.TryParse(line, out int prime)) + { + primes.Add(prime); + } + } + + return primes.AsReadOnly(); + } +} diff --git a/X10D.Tests/src/Math/SByteTests.cs b/X10D.Tests/src/Math/SByteTests.cs new file mode 100644 index 0000000..73b97a2 --- /dev/null +++ b/X10D.Tests/src/Math/SByteTests.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const sbyte value = 127; // sbyte.MaxValue. can't use 238 like the other tests + Assert.AreEqual(1, value.DigitalRoot()); + Assert.AreEqual(1, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1L, ((sbyte)0).Factorial()); + Assert.AreEqual(1L, ((sbyte)1).Factorial()); + Assert.AreEqual(2L, ((sbyte)2).Factorial()); + Assert.AreEqual(6L, ((sbyte)3).Factorial()); + Assert.AreEqual(24L, ((sbyte)4).Factorial()); + Assert.AreEqual(120L, ((sbyte)5).Factorial()); + Assert.AreEqual(720L, ((sbyte)6).Factorial()); + Assert.AreEqual(5040L, ((sbyte)7).Factorial()); + Assert.AreEqual(40320L, ((sbyte)8).Factorial()); + Assert.AreEqual(362880L, ((sbyte)9).Factorial()); + Assert.AreEqual(3628800L, ((sbyte)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const sbyte one = 1; + const sbyte two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const sbyte one = 1; + const sbyte two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((sbyte)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((sbyte)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((sbyte)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((sbyte)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((sbyte)77).MultiplicativePersistence()); + } + + [TestMethod] + public void NegativeFactorialShouldThrow() + { + Assert.ThrowsException(() => ((sbyte)-1).Factorial()); + } + + [TestMethod] + public void SignShouldBeCorrect() + { + const sbyte one = 1; + const sbyte zero = 0; + Assert.AreEqual(one, one.Sign()); + Assert.AreEqual(zero, zero.Sign()); + Assert.AreEqual(-one, (-one).Sign()); + } +} diff --git a/X10D.Tests/src/Math/SingleTests.cs b/X10D.Tests/src/Math/SingleTests.cs new file mode 100644 index 0000000..12b1ae2 --- /dev/null +++ b/X10D.Tests/src/Math/SingleTests.cs @@ -0,0 +1,242 @@ +using System.Numerics; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void DegreesToRadians_ShouldBeCorrect() + { + Assert.AreEqual(MathF.PI, 180.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(MathF.PI * 1.5f, 270.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.0f, 0.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.017453292f, 1.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.10471976f, 6.0f.DegreesToRadians(), 1e-6f); + Assert.AreEqual(0.20943952f, 12.0f.DegreesToRadians(), 1e-6f); + } + + [TestMethod] + public void RadiansToDegrees_ShouldBeCorrect() + { + Assert.AreEqual(180.0f, MathF.PI.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(270.0f, (MathF.PI * 1.5f).RadiansToDegrees(), 1e-6f); + Assert.AreEqual(0.0, 0.0f.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(0.99999994f, 0.017453292f.RadiansToDegrees(), 1e-6f); // rounding errors are fun + Assert.AreEqual(6.0f, 0.10471976f.RadiansToDegrees(), 1e-6f); + Assert.AreEqual(12.0f, 0.20943952f.RadiansToDegrees(), 1e-6f); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeCorrect_GivenReal() + { + Assert.AreEqual(0.0f, 0.0f.ComplexSqrt()); + Assert.AreEqual(1.4142135f, 2.0f.ComplexSqrt()); + Assert.AreEqual(3.0f, 9.0f.ComplexSqrt()); + Assert.AreEqual(4.0f, 16.0f.ComplexSqrt()); + Assert.AreEqual(100.0f, 10000.0f.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeImaginary_GivenNegativeValue() + { + Assert.AreEqual(new Complex(0, 1), (-1.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 1.4142135f), (-2.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 3.0f), (-9.0f).ComplexSqrt()); + Assert.AreEqual(new Complex(0, 4.0f), (-16.0f).ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeComplexInfinity_GivenInfinity() + { + Assert.AreEqual(Complex.Infinity, float.NegativeInfinity.ComplexSqrt()); + Assert.AreEqual(Complex.Infinity, float.PositiveInfinity.ComplexSqrt()); + } + + [TestMethod] + public void ComplexSqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(Complex.NaN, float.NaN.ComplexSqrt()); + } + + [TestMethod] + public void IsEven_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse((-3.0f).IsEven()); + Assert.IsFalse((-1.0f).IsEven()); + Assert.IsFalse(1.0f.IsEven()); + Assert.IsFalse(3.0f.IsEven()); + } + + [TestMethod] + public void IsEven_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-4.0f).IsEven()); + Assert.IsTrue((-2.0f).IsEven()); + Assert.IsTrue(0.0f.IsEven()); + Assert.IsTrue(2.0f.IsEven()); + Assert.IsTrue(4.0f.IsEven()); + } + + [TestMethod] + public void IsOdd_ShouldBeFalse_GivenEvenNumber() + { + Assert.IsFalse((-4.0f).IsOdd()); + Assert.IsFalse((-2.0f).IsOdd()); + Assert.IsFalse(0.0f.IsOdd()); + Assert.IsFalse(2.0f.IsOdd()); + Assert.IsFalse(4.0f.IsOdd()); + } + + [TestMethod] + public void IsOdd_ShouldBeTrue_GivenOddNumber() + { + Assert.IsTrue((-3.0f).IsOdd()); + Assert.IsTrue((-1.0f).IsOdd()); + Assert.IsTrue(1.0f.IsOdd()); + Assert.IsTrue(3.0f.IsOdd()); + } + + [TestMethod] + public void Round_ShouldRoundToNearestInteger() + { + Assert.AreEqual(4.0f, 3.5f.Round(), 1e-6f); + Assert.AreEqual(7.0f, 6.8f.Round(), 1e-6f); + Assert.AreEqual(7.0f, 7.2f.Round(), 1e-6f); + } + + [TestMethod] + public void Round_ShouldRoundToNearestMultiple() + { + Assert.AreEqual(5.0f, 3.5f.Round(5), 1e-6f); + Assert.AreEqual(5.0f, 7.0f.Round(5), 1e-6f); + Assert.AreEqual(10.0f, 7.5f.Round(5), 1e-6f); + } + + [TestMethod] + public void Sign_ShouldBeMinus1_GivenNegative() + { + Assert.AreEqual(-1, -1.0f.Sign()); + Assert.AreEqual(-1, -2.0f.Sign()); + Assert.AreEqual(-1, -3.0f.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe0_Given0() + { + Assert.AreEqual(0, 0.0f.Sign()); + } + + [TestMethod] + public void Sign_ShouldBe1_GivenPositive() + { + Assert.AreEqual(1, 1.0f.Sign()); + Assert.AreEqual(1, 2.0f.Sign()); + Assert.AreEqual(1, 3.0f.Sign()); + } + + [TestMethod] + public void Sqrt_ShouldBeCorrect_GivenValue() + { + Assert.AreEqual(0.0f, 0.0f.Sqrt(), 1e-6f); + Assert.AreEqual(1.4142135f, 2.0f.Sqrt(), 1e-6f); + Assert.AreEqual(3.0f, 9.0f.Sqrt(), 1e-6f); + Assert.AreEqual(4.0f, 16.0f.Sqrt(), 1e-6f); + Assert.AreEqual(100.0f, 10000.0f.Sqrt(), 1e-6f); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNaN() + { + Assert.AreEqual(float.NaN, float.NaN.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBeNaN_GivenNegativeValue() + { + Assert.AreEqual(float.NaN, (-1.0f).Sqrt()); + Assert.AreEqual(float.NaN, (-2.0f).Sqrt()); + Assert.AreEqual(float.NaN, (-3.0f).Sqrt()); + Assert.AreEqual(float.NaN, float.NegativeInfinity.Sqrt()); + } + + [TestMethod] + public void Sqrt_ShouldBePositiveInfinity_GivenPositiveInfinity() + { + Assert.AreEqual(float.PositiveInfinity, float.PositiveInfinity.Sqrt()); + } + + [TestMethod] + public void Acos_ShouldBeCorrect() + { + Assert.AreEqual(1.0471975803375244f, 0.5f.Acos(), 1e-6f); + } + + [TestMethod] + public void Acosh_ShouldBeCorrect() + { + Assert.AreEqual(0.9624236822128296f, 1.5f.Acosh(), 1e-6f); + } + + [TestMethod] + public void Asin_ShouldBeCorrect() + { + Assert.AreEqual(0.5235987901687622f, 0.5f.Asin(), 1e-6f); + } + + [TestMethod] + public void Asinh_ShouldBeCorrect() + { + Assert.AreEqual(1.19476318359375f, 1.5f.Asinh(), 1e-6f); + } + + [TestMethod] + public void Atan_ShouldBeCorrect() + { + Assert.AreEqual(0.46364760398864746, 0.5f.Atan(), 1e-6f); + } + + [TestMethod] + public void Atanh_ShouldBeCorrect() + { + Assert.AreEqual(0.5493061542510986f, 0.5f.Atanh(), 1e-6f); + } + + [TestMethod] + public void Cos_ShouldBeCorrect() + { + Assert.AreEqual(0.8775825500488281f, 0.5f.Cos(), 1e-6f); + } + + [TestMethod] + public void Cosh_ShouldBeCorrect() + { + Assert.AreEqual(2.352409601211548f, 1.5f.Cosh(), 1e-6f); + } + + [TestMethod] + public void Sin_ShouldBeCorrect() + { + Assert.AreEqual(0.4794255495071411, 0.5f.Sin(), 1e-6f); + } + + [TestMethod] + public void Sinh_ShouldBeCorrect() + { + Assert.AreEqual(2.129279375076294f, 1.5f.Sinh(), 1e-6f); + } + + [TestMethod] + public void Tan_ShouldBeCorrect() + { + Assert.AreEqual(0.4794255495071411f, 0.5f.Tan(), 1e-6f); + } + + [TestMethod] + public void Tanh_ShouldBeCorrect() + { + Assert.AreEqual(0.46211716532707214f, 0.5f.Tanh(), 1e-6f); + } +} diff --git a/X10D.Tests/src/Math/UInt16Tests.cs b/X10D.Tests/src/Math/UInt16Tests.cs new file mode 100644 index 0000000..ac8575a --- /dev/null +++ b/X10D.Tests/src/Math/UInt16Tests.cs @@ -0,0 +1,65 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const ushort value = 238; + Assert.AreEqual(4, value.DigitalRoot()); + Assert.AreEqual(4, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, ((ushort)0).Factorial()); + Assert.AreEqual(1UL, ((ushort)1).Factorial()); + Assert.AreEqual(2UL, ((ushort)2).Factorial()); + Assert.AreEqual(6UL, ((ushort)3).Factorial()); + Assert.AreEqual(24UL, ((ushort)4).Factorial()); + Assert.AreEqual(120UL, ((ushort)5).Factorial()); + Assert.AreEqual(720UL, ((ushort)6).Factorial()); + Assert.AreEqual(5040UL, ((ushort)7).Factorial()); + Assert.AreEqual(40320UL, ((ushort)8).Factorial()); + Assert.AreEqual(362880UL, ((ushort)9).Factorial()); + Assert.AreEqual(3628800UL, ((ushort)10).Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const ushort one = 1; + const ushort two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const ushort one = 1; + const ushort two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, ((ushort)0).MultiplicativePersistence()); + Assert.AreEqual(1, ((ushort)10).MultiplicativePersistence()); + Assert.AreEqual(2, ((ushort)25).MultiplicativePersistence()); + Assert.AreEqual(3, ((ushort)39).MultiplicativePersistence()); + Assert.AreEqual(4, ((ushort)77).MultiplicativePersistence()); + Assert.AreEqual(5, ((ushort)679).MultiplicativePersistence()); + Assert.AreEqual(6, ((ushort)6788).MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/UInt32Tests.cs b/X10D.Tests/src/Math/UInt32Tests.cs new file mode 100644 index 0000000..6c06407 --- /dev/null +++ b/X10D.Tests/src/Math/UInt32Tests.cs @@ -0,0 +1,69 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const uint value = 238; + Assert.AreEqual(4U, value.DigitalRoot()); + Assert.AreEqual(4U, (-value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, 0U.Factorial()); + Assert.AreEqual(1UL, 1U.Factorial()); + Assert.AreEqual(2UL, 2U.Factorial()); + Assert.AreEqual(6UL, 3U.Factorial()); + Assert.AreEqual(24UL, 4U.Factorial()); + Assert.AreEqual(120UL, 5U.Factorial()); + Assert.AreEqual(720UL, 6U.Factorial()); + Assert.AreEqual(5040UL, 7U.Factorial()); + Assert.AreEqual(40320UL, 8U.Factorial()); + Assert.AreEqual(362880UL, 9U.Factorial()); + Assert.AreEqual(3628800UL, 10U.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const uint one = 1; + const uint two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const uint one = 1; + const uint two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0U.MultiplicativePersistence()); + Assert.AreEqual(1, 10U.MultiplicativePersistence()); + Assert.AreEqual(2, 25U.MultiplicativePersistence()); + Assert.AreEqual(3, 39U.MultiplicativePersistence()); + Assert.AreEqual(4, 77U.MultiplicativePersistence()); + Assert.AreEqual(5, 679U.MultiplicativePersistence()); + Assert.AreEqual(6, 6788U.MultiplicativePersistence()); + Assert.AreEqual(7, 68889U.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889U.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999U.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999U.MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Math/UInt64Tests.cs b/X10D.Tests/src/Math/UInt64Tests.cs new file mode 100644 index 0000000..d18a7d6 --- /dev/null +++ b/X10D.Tests/src/Math/UInt64Tests.cs @@ -0,0 +1,74 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Math; + +namespace X10D.Tests.Math; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void DigitalRootShouldBeCorrect() + { + const ulong value = 238; + Assert.AreEqual(4U, value.DigitalRoot()); + + // -ulong operator not defined because it might exceed long.MinValue, + // so instead, cast to long and then negate. + // HAX. + Assert.AreEqual(4U, (-(long)value).DigitalRoot()); + } + + [TestMethod] + public void FactorialShouldBeCorrect() + { + Assert.AreEqual(1UL, 0UL.Factorial()); + Assert.AreEqual(1UL, 1UL.Factorial()); + Assert.AreEqual(2UL, 2UL.Factorial()); + Assert.AreEqual(6UL, 3UL.Factorial()); + Assert.AreEqual(24UL, 4UL.Factorial()); + Assert.AreEqual(120UL, 5UL.Factorial()); + Assert.AreEqual(720UL, 6UL.Factorial()); + Assert.AreEqual(5040UL, 7UL.Factorial()); + Assert.AreEqual(40320UL, 8UL.Factorial()); + Assert.AreEqual(362880UL, 9UL.Factorial()); + Assert.AreEqual(3628800UL, 10UL.Factorial()); + } + + [TestMethod] + public void IsEvenShouldBeCorrect() + { + const ulong one = 1; + const ulong two = 2; + + Assert.IsFalse(one.IsEven()); + Assert.IsTrue(two.IsEven()); + } + + [TestMethod] + public void IsOddShouldBeCorrect() + { + const ulong one = 1; + const ulong two = 2; + + Assert.IsTrue(one.IsOdd()); + Assert.IsFalse(two.IsOdd()); + } + + [TestMethod] + public void MultiplicativePersistence_ShouldBeCorrect_ForRecordHolders() + { + Assert.AreEqual(0, 0UL.MultiplicativePersistence()); + Assert.AreEqual(1, 10UL.MultiplicativePersistence()); + Assert.AreEqual(2, 25UL.MultiplicativePersistence()); + Assert.AreEqual(3, 39UL.MultiplicativePersistence()); + Assert.AreEqual(4, 77UL.MultiplicativePersistence()); + Assert.AreEqual(5, 679UL.MultiplicativePersistence()); + Assert.AreEqual(6, 6788UL.MultiplicativePersistence()); + Assert.AreEqual(7, 68889UL.MultiplicativePersistence()); + Assert.AreEqual(8, 2677889UL.MultiplicativePersistence()); + Assert.AreEqual(9, 26888999UL.MultiplicativePersistence()); + Assert.AreEqual(10, 3778888999UL.MultiplicativePersistence()); + Assert.AreEqual(11, 277777788888899UL.MultiplicativePersistence()); + } +} diff --git a/X10D.Tests/src/Net/EndPointTests.cs b/X10D.Tests/src/Net/EndPointTests.cs new file mode 100644 index 0000000..8b508d9 --- /dev/null +++ b/X10D.Tests/src/Net/EndPointTests.cs @@ -0,0 +1,76 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class EndPointTests +{ + [TestMethod] + public void GetHost_ShouldBeLocalhost_GivenLocalhostDnsEndPoint() + { + var endPoint = new DnsEndPoint("localhost", 1234); + Assert.AreEqual("localhost", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldBe127001_GivenLoopbackIPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.Loopback, 1234); + Assert.AreEqual("127.0.0.1", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldBeColonColon1_GivenIPv6LoopBackIPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.IPv6Loopback, 1234); + Assert.AreEqual("::1", endPoint.GetHost()); + } + + [TestMethod] + public void GetHost_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((IPEndPoint?)null)!.GetHost()); + Assert.ThrowsException(() => ((DnsEndPoint?)null)!.GetHost()); + } + + [TestMethod] + public void GetPort_ShouldBe1234_Given1234IPEndPoint() + { + var endPoint = new IPEndPoint(IPAddress.Loopback, 1234); + Assert.AreEqual(1234, endPoint.GetPort()); + } + + [TestMethod] + public void GetPort_ShouldBe1234_Given1234DnsEndPoint() + { + var endPoint = new DnsEndPoint("localhost", 1234); + Assert.AreEqual(1234, endPoint.GetPort()); + } + + [TestMethod] + public void GetPort_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((IPEndPoint?)null)!.GetPort()); + Assert.ThrowsException(() => ((DnsEndPoint?)null)!.GetPort()); + } + + [TestMethod] + public void GetHost_ShouldBeEmpty_GivenInvalidEndPoint() + { + var endPoint = new DummyEndPoint(); + Assert.AreEqual(string.Empty, endPoint.GetHost()); + } + + [TestMethod] + public void GetPort_ShouldBe0_GivenInvalidEndPoint() + { + var endPoint = new DummyEndPoint(); + Assert.AreEqual(0, endPoint.GetPort()); + } + + private class DummyEndPoint : EndPoint + { + } +} diff --git a/X10D.Tests/src/Net/IPAddressTests.cs b/X10D.Tests/src/Net/IPAddressTests.cs new file mode 100644 index 0000000..ac86bf6 --- /dev/null +++ b/X10D.Tests/src/Net/IPAddressTests.cs @@ -0,0 +1,43 @@ +using System.Net; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class IPAddressTests +{ + private IPAddress _ipv4Address = null!; + private IPAddress _ipv6Address = null!; + + [TestInitialize] + public void Initialize() + { + _ipv4Address = IPAddress.Parse("127.0.0.1"); + _ipv6Address = IPAddress.Parse("::1"); + } + + [TestMethod] + public void IsIPv4_ShouldBeTrue_GivenIPv4() + { + Assert.IsTrue(_ipv4Address.IsIPv4()); + } + + [TestMethod] + public void IsIPv4_ShouldBeFalse_GivenIPv6() + { + Assert.IsFalse(_ipv6Address.IsIPv4()); + } + + [TestMethod] + public void IsIPv6_ShouldBeFalse_GivenIPv4() + { + Assert.IsFalse(_ipv4Address.IsIPv6()); + } + + [TestMethod] + public void IsIPv6_ShouldBeTrue_GivenIPv6() + { + Assert.IsTrue(_ipv6Address.IsIPv6()); + } +} diff --git a/X10D.Tests/src/Net/Int16Tests.cs b/X10D.Tests/src/Net/Int16Tests.cs new file mode 100644 index 0000000..9dea812 --- /dev/null +++ b/X10D.Tests/src/Net/Int16Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const short hostOrder = 0x0102; + const short networkOrder = 0x0201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const short hostOrder = 0x0102; + const short networkOrder = 0x0201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Net/Int32Tests.cs b/X10D.Tests/src/Net/Int32Tests.cs new file mode 100644 index 0000000..7157eef --- /dev/null +++ b/X10D.Tests/src/Net/Int32Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const int hostOrder = 0x01020304; + const int networkOrder = 0x04030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const int hostOrder = 0x01020304; + const int networkOrder = 0x04030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Net/Int64Tests.cs b/X10D.Tests/src/Net/Int64Tests.cs new file mode 100644 index 0000000..c207125 --- /dev/null +++ b/X10D.Tests/src/Net/Int64Tests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Net; + +namespace X10D.Tests.Net; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void HostToNetworkOrder_ReturnsCorrectValue() + { + const long hostOrder = 0x0102030405060708; + const long networkOrder = 0x0807060504030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? networkOrder : hostOrder, hostOrder.HostToNetworkOrder()); + } + + [TestMethod] + public void NetworkToHostOrder_ReturnsCorrectValue() + { + const long hostOrder = 0x0102030405060708; + const long networkOrder = 0x0807060504030201; + + Assert.AreEqual(BitConverter.IsLittleEndian ? hostOrder : networkOrder, networkOrder.NetworkToHostOrder()); + } +} diff --git a/X10D.Tests/src/Numerics/ByteTests.cs b/X10D.Tests/src/Numerics/ByteTests.cs new file mode 100644 index 0000000..d4890b2 --- /dev/null +++ b/X10D.Tests/src/Numerics/ByteTests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const byte value = 181; // 10110101 + const byte expected = 91; // 01011011 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(4)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const byte value = 181; // 10110101 + Assert.AreEqual(value, value.RotateLeft(8)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const byte value = 181; // 10110101 + const byte expected = 91; // 01011011 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(4)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const byte value = 181; // 10110101 + Assert.AreEqual(value, value.RotateRight(8)); + } +} diff --git a/X10D.Tests/src/Numerics/Int16Tests.cs b/X10D.Tests/src/Numerics/Int16Tests.cs new file mode 100644 index 0000000..11a4a3d --- /dev/null +++ b/X10D.Tests/src/Numerics/Int16Tests.cs @@ -0,0 +1,44 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const short value = 2896; // 00001011 01010000 + const short expected = 27137; // 01101010 00000001 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(5)); + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const short value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const short value = 2896; // 00001011 01010000 + const short expected = -32678; // 01111111 10100110 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(5)); + Assert.AreEqual(value, value.RotateRight(16)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const short value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateRight(16)); + } +} diff --git a/X10D.Tests/src/Numerics/Int32Tests.cs b/X10D.Tests/src/Numerics/Int32Tests.cs new file mode 100644 index 0000000..874b5af --- /dev/null +++ b/X10D.Tests/src/Numerics/Int32Tests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + const int expected = -1336016888; // 10110000 01011110 00000000 00001000 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(17)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateLeft(32)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + const int expected = 739737602; // 00101100 00010111 10000000 00000010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(17)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const int value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateRight(32)); + } +} diff --git a/X10D.Tests/src/Numerics/Int64Tests.cs b/X10D.Tests/src/Numerics/Int64Tests.cs new file mode 100644 index 0000000..2dff9cf --- /dev/null +++ b/X10D.Tests/src/Numerics/Int64Tests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const long expected = -1588168355691398970; // 11101001 11110101 10110001 01001011 10000011 01111111 01111000 11000110 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(42)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateLeft(64)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const long expected = -608990218894919625; // 11110111 10001100 01101110 10011111 01011011 00010100 10111000 00110111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(42)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const long value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateRight(64)); + } +} diff --git a/X10D.Tests/src/Numerics/RandomTests.cs b/X10D.Tests/src/Numerics/RandomTests.cs new file mode 100644 index 0000000..3864574 --- /dev/null +++ b/X10D.Tests/src/Numerics/RandomTests.cs @@ -0,0 +1,68 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +public class RandomTests +{ + [TestMethod] + public void NextUnitVector2_ShouldReturnVector_WithMagnitude1() + { + var random = new Random(); + var vector = random.NextUnitVector2(); + Assert.AreEqual(1, vector.Length(), 1e-6); + } + + [TestMethod] + public void NextUnitVector2_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextUnitVector2()); + } + + [TestMethod] + public void NextUnitVector3_ShouldReturnVector_WithMagnitude1() + { + var random = new Random(); + var vector = random.NextUnitVector3(); + Assert.AreEqual(1, vector.Length(), 1e-6); + } + + [TestMethod] + public void NextUnitVector3_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextUnitVector3()); + } + + [TestMethod] + public void NextRotation_ShouldReturnQuaternion_WithMagnitude1() + { + var random = new Random(); + var rotation = random.NextRotation(); + Assert.AreEqual(1, rotation.Length(), 1e-6); + } + + [TestMethod] + public void NextRotation_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextRotation()); + } + + [TestMethod] + public void NextRotationUniform_ShouldReturnQuaternion_WithMagnitude1() + { + var random = new Random(); + var rotation = random.NextRotationUniform(); + Assert.AreEqual(1, rotation.Length(), 1e-6); + } + + [TestMethod] + public void NextRotationUniform_ShouldThrow_GivenNullRandom() + { + Random? random = null; + Assert.ThrowsException(() => random!.NextRotationUniform()); + } +} diff --git a/X10D.Tests/src/Numerics/SByteTests.cs b/X10D.Tests/src/Numerics/SByteTests.cs new file mode 100644 index 0000000..c24ebcf --- /dev/null +++ b/X10D.Tests/src/Numerics/SByteTests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const sbyte value = 117; // 01110101 + const sbyte expected = 87; // 01010111 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(4)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const sbyte value = 117; // 01110101 + Assert.AreEqual(value, value.RotateLeft(8)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const sbyte value = 117; // 01110101 + const sbyte expected = 87; // 01010111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(4)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const sbyte value = 117; // 01110101 + Assert.AreEqual(value, value.RotateRight(8)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt16Tests.cs b/X10D.Tests/src/Numerics/UInt16Tests.cs new file mode 100644 index 0000000..23a5d39 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt16Tests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const ushort value = 2896; // 00001011 01010000 + const ushort expected = 27137; // 01101010 00000001 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(5)); + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const ushort value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateLeft(16)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const ushort value = 2896; // 00001011 01010000 + const ushort expected = 32858; // 10000000 01011010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(5)); + Assert.AreEqual(value, value.RotateRight(16)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const ushort value = 2896; // 00001011 01010000 + Assert.AreEqual(value, value.RotateRight(16)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt32Tests.cs b/X10D.Tests/src/Numerics/UInt32Tests.cs new file mode 100644 index 0000000..5686953 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt32Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + const uint expected = 2958950408; // 10110000 01011110 00000000 00001000 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(17)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateLeft(32)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + const uint expected = 739737602; // 00101100 00010111 10000000 00000010 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(17)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const uint value = 284719; // 00000000 00000100 01011000 00101111 + Assert.AreEqual(value, value.RotateRight(32)); + } +} diff --git a/X10D.Tests/src/Numerics/UInt64Tests.cs b/X10D.Tests/src/Numerics/UInt64Tests.cs new file mode 100644 index 0000000..7d2f026 --- /dev/null +++ b/X10D.Tests/src/Numerics/UInt64Tests.cs @@ -0,0 +1,43 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Numerics; + +namespace X10D.Tests.Numerics; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void RotateLeft_ShouldRotateCorrectly() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const ulong expected = 16858575718018152646; // 11101001 11110101 10110001 01001011 10000011 01111111 01111000 11000110 + + Assert.AreEqual(value, value.RotateLeft(0)); + Assert.AreEqual(expected, value.RotateLeft(42)); + } + + [TestMethod] + public void RotateLeft_ShouldModForLargeCount() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateLeft(64)); + } + + [TestMethod] + public void RotateRight_ShouldRotateCorrectly() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + const ulong expected = 17837753854814631991; // 11110111 10001100 01101110 10011111 01011011 00010100 10111000 00110111 + + Assert.AreEqual(value, value.RotateRight(0)); + Assert.AreEqual(expected, value.RotateRight(42)); + } + + [TestMethod] + public void RotateRight_ShouldModForLargeCount() + { + const ulong value = 5972019251303316844; // 01010010 11100000 11011111 11011110 00110001 10111010 01111101 01101100 + Assert.AreEqual(value, value.RotateRight(64)); + } +} diff --git a/X10D.Tests/src/Reflection/MemberInfoTests.cs b/X10D.Tests/src/Reflection/MemberInfoTests.cs new file mode 100644 index 0000000..24d2675 --- /dev/null +++ b/X10D.Tests/src/Reflection/MemberInfoTests.cs @@ -0,0 +1,78 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Reflection; + +namespace X10D.Tests.Reflection; + +[TestClass] +public class MemberInfoTests +{ + [TestMethod] + public void HasCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnUnsignedTypes() + { + Assert.IsTrue(typeof(sbyte).HasCustomAttribute(typeof(CLSCompliantAttribute))); // okay, sbyte is signed. I know. + Assert.IsTrue(typeof(ushort).HasCustomAttribute(typeof(CLSCompliantAttribute))); + Assert.IsTrue(typeof(uint).HasCustomAttribute(typeof(CLSCompliantAttribute))); + Assert.IsTrue(typeof(ulong).HasCustomAttribute(typeof(CLSCompliantAttribute))); + } + + [TestMethod] + public void HasCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnUnsignedTypes_Generic() + { + Assert.IsTrue(typeof(sbyte).HasCustomAttribute()); // okay, sbyte is signed. I know. + Assert.IsTrue(typeof(ushort).HasCustomAttribute()); + Assert.IsTrue(typeof(uint).HasCustomAttribute()); + Assert.IsTrue(typeof(ulong).HasCustomAttribute()); + } + + [TestMethod] + public void HasCustomAttribute_ShouldThrow_GivenNull() + { + Type? type = null; + Assert.ThrowsException(() => type!.HasCustomAttribute()); + Assert.ThrowsException(() => type!.HasCustomAttribute(typeof(CLSCompliantAttribute))); + + Assert.ThrowsException(() => typeof(object).HasCustomAttribute(null!)); + } + + [TestMethod] + public void HasCustomAttribute_ShouldThrow_GivenNonAttribute() + { + Assert.ThrowsException(() => typeof(object).HasCustomAttribute(typeof(object))); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldBeFalse_GivenCLSCompliantAttributeOnUnsignedTypes() + { + Assert.IsFalse(typeof(sbyte).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(ushort).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(uint).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + Assert.IsFalse(typeof(ulong).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant)); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldBeTrue_GivenCLSCompliantAttributeOnSignedTypes() + { + Assert.IsTrue(typeof(byte).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(short).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(int).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + Assert.IsTrue(typeof(long).SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true)); + } + + [TestMethod] + public void SelectFromCustomAttribute_ShouldThrow_GivenNull() + { + Type? type = null; + + Assert.ThrowsException(() => + (type!.SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant))); + + Assert.ThrowsException(() => + (type!.SelectFromCustomAttribute((CLSCompliantAttribute attribute) => attribute.IsCompliant, true))); + + Assert.ThrowsException(() => + { + Func? selector = null; + typeof(int).SelectFromCustomAttribute(selector!); + }); + } +} diff --git a/X10D.Tests/src/Reflection/TypeTests.cs b/X10D.Tests/src/Reflection/TypeTests.cs new file mode 100644 index 0000000..4324afb --- /dev/null +++ b/X10D.Tests/src/Reflection/TypeTests.cs @@ -0,0 +1,59 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Reflection; + +namespace X10D.Tests.Reflection; + +[TestClass] +public class TypeTests +{ + [TestMethod] + public void Inherits_ShouldBeTrue_GivenStringInheritsObject() + { + Assert.IsTrue(typeof(string).Inherits(typeof(object))); + Assert.IsTrue(typeof(string).Inherits()); + } + + [TestMethod] + public void Inherits_ShouldBeFalse_GivenObjectInheritsString() + { + Assert.IsFalse(typeof(object).Inherits(typeof(string))); + Assert.IsFalse(typeof(object).Inherits()); + } + + [TestMethod] + public void Inherits_ShouldThrow_GivenValueType() + { + Assert.ThrowsException(() => typeof(int).Inherits(typeof(object))); + Assert.ThrowsException(() => typeof(object).Inherits(typeof(int))); + } + + [TestMethod] + public void Inherits_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => typeof(object).Inherits(null!)); + Assert.ThrowsException(() => ((Type?)null)!.Inherits(typeof(object))); + } + + [TestMethod] + public void Implements_ShouldBeTrue_GivenInt32ImplementsIComparable() + { + Assert.IsTrue(typeof(int).Implements()); + Assert.IsTrue(typeof(int).Implements>()); + Assert.IsTrue(typeof(int).Implements(typeof(IComparable))); + Assert.IsTrue(typeof(int).Implements(typeof(IComparable))); + } + + [TestMethod] + public void Implements_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => typeof(object).Implements(null!)); + Assert.ThrowsException(() => ((Type?)null)!.Implements(typeof(object))); + } + + [TestMethod] + public void Implements_ShouldThrow_GivenNonInterface() + { + Assert.ThrowsException(() => typeof(string).Implements()); + Assert.ThrowsException(() => typeof(string).Implements(typeof(object))); + } +} diff --git a/X10D.Tests/src/Text/CharTests.cs b/X10D.Tests/src/Text/CharTests.cs new file mode 100644 index 0000000..cea0606 --- /dev/null +++ b/X10D.Tests/src/Text/CharTests.cs @@ -0,0 +1,37 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class CharTests +{ + [TestMethod] + public void RepeatShouldBeCorrect() + { + const string expected = "aaaaaaaaaa"; + string actual = 'a'.Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void RepeatOneCountShouldBeLength1String() + { + string repeated = 'a'.Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void RepeatZeroCountShouldBeEmpty() + { + Assert.AreEqual(string.Empty, 'a'.Repeat(0)); + } + + [TestMethod] + public void RepeatNegativeCountShouldThrow() + { + Assert.ThrowsException(() => 'a'.Repeat(-1)); + } +} diff --git a/X10D.Tests/src/Text/CoreTests.cs b/X10D.Tests/src/Text/CoreTests.cs new file mode 100644 index 0000000..f6d9aea --- /dev/null +++ b/X10D.Tests/src/Text/CoreTests.cs @@ -0,0 +1,26 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class CoreTests +{ + [TestMethod] + public void ToJsonShouldNotBeEmpty() + { + object? obj = null; + string json = obj.ToJson(); + Assert.IsFalse(string.IsNullOrEmpty(json)); + } + + [TestMethod] + public void ToJsonShouldDeserializeEquivalent() + { + int[] source = Enumerable.Range(1, 100).ToArray(); + string json = source.ToJson(); + int[]? target = json.FromJson(); + CollectionAssert.AreEqual(source, target); + CollectionAssert.AreEquivalent(source, target); + } +} diff --git a/X10D.Tests/src/Text/RuneTests.cs b/X10D.Tests/src/Text/RuneTests.cs new file mode 100644 index 0000000..7d3315d --- /dev/null +++ b/X10D.Tests/src/Text/RuneTests.cs @@ -0,0 +1,39 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class RuneTests +{ + [TestMethod] + public void RepeatShouldBeCorrect() + { + const string expected = "aaaaaaaaaa"; + var rune = new Rune('a'); + string actual = rune.Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void RepeatOneCountShouldBeLength1String() + { + string repeated = new Rune('a').Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void RepeatZeroCountShouldBeEmpty() + { + Assert.AreEqual(string.Empty, new Rune('a').Repeat(0)); + } + + [TestMethod] + public void RepeatNegativeCountShouldThrow() + { + Assert.ThrowsException(() => new Rune('a').Repeat(-1)); + } +} diff --git a/X10D.Tests/src/Text/StringBuilderReaderTests.cs b/X10D.Tests/src/Text/StringBuilderReaderTests.cs new file mode 100644 index 0000000..bdd0848 --- /dev/null +++ b/X10D.Tests/src/Text/StringBuilderReaderTests.cs @@ -0,0 +1,271 @@ +using System.Text; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class StringBuilderReaderTests +{ + [TestMethod] + public void Peek_ShouldReturnNextChar_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual('H', reader.Peek()); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldReturnNextChar_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual('H', reader.Read()); + Assert.AreEqual('e', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('o', reader.Read()); + Assert.AreEqual('\n', reader.Read()); + Assert.AreEqual('W', reader.Read()); + Assert.AreEqual('o', reader.Read()); + Assert.AreEqual('r', reader.Read()); + Assert.AreEqual('l', reader.Read()); + Assert.AreEqual('d', reader.Read()); + Assert.AreEqual(-1, reader.Read()); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.Read(array, 0, 5); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void Read_ShouldReturnNegative1_GivenEndOfReader() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[11]; + reader.Read(array); + Assert.AreEqual(-1, reader.Read(array, 0, 1)); + reader.Close(); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNullArray() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + reader.Read(null!, 0, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNegativeIndex() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[5]; + reader.Read(array, -1, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenNegativeCount() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[5]; + reader.Read(array, 0, -1); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldThrow_GivenSmallBuffer() + { + Assert.ThrowsException(() => + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + var array = new char[1]; + reader.Read(array, 0, 5); + reader.Close(); + }); + } + + [TestMethod] + public void Read_ShouldPopulateSpan_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Span span = stackalloc char[5]; + int read = reader.Read(span); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), span.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadAsync_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadAsync(array, 0, 5).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadAsync_ShouldPopulateMemory_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Memory memory = new char[5]; + int read = reader.ReadAsync(memory).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), memory.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadBlock(array, 0, 5); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldPopulateSpan_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Span span = stackalloc char[5]; + int read = reader.ReadBlock(span); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), span.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadBlock_ShouldReturnNegative1_GivenEndOfReader() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[11]; + reader.Read(array); + + int read = reader.ReadBlock(array, 0, 5); + Assert.AreEqual(-1, read); + + reader.Close(); + } + + [TestMethod] + public void ReadBlockAsync_ShouldPopulateArray_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + var array = new char[5]; + int read = reader.ReadBlockAsync(array, 0, 5).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), array); + + reader.Close(); + } + + [TestMethod] + public void ReadBlockAsync_ShouldPopulateMemory_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Memory memory = new char[5]; + int read = reader.ReadBlockAsync(memory).GetAwaiter().GetResult(); + Assert.AreEqual(5, read); + + CollectionAssert.AreEqual("Hello".ToCharArray(), memory.ToArray()); + + reader.Close(); + } + + [TestMethod] + public void ReadToEnd_ShouldReturnSourceString_GivenBuilder() + { + const string value = "Hello World"; + using var reader = new StringBuilderReader(new StringBuilder(value)); + Assert.AreEqual(value, reader.ReadToEnd()); + + reader.Close(); + } + + [TestMethod] + public void ReadToEndAsync_ShouldReturnSourceString_GivenBuilder() + { + const string value = "Hello World"; + using var reader = new StringBuilderReader(new StringBuilder(value)); + Assert.AreEqual(value, reader.ReadToEndAsync().GetAwaiter().GetResult()); + + reader.Close(); + } + + [TestMethod] + public void ReadLine_ShouldReturnSourceString_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual("Hello", reader.ReadLine()); + Assert.AreEqual("World", reader.ReadLine()); + Assert.AreEqual(null, reader.ReadLine()); + + Assert.AreEqual(-1, reader.Peek()); + + reader.Close(); + } + + [TestMethod] + public void ReadLineAsync_ShouldReturnSourceString_GivenBuilder() + { + using var reader = new StringBuilderReader(new StringBuilder("Hello\nWorld")); + + Assert.AreEqual("Hello", reader.ReadLineAsync().GetAwaiter().GetResult()); + Assert.AreEqual("World", reader.ReadLineAsync().GetAwaiter().GetResult()); + Assert.AreEqual(null, reader.ReadLineAsync().GetAwaiter().GetResult()); + + Assert.AreEqual(-1, reader.Peek()); + + reader.Close(); + } +} diff --git a/X10D.Tests/src/Text/StringTests.cs b/X10D.Tests/src/Text/StringTests.cs new file mode 100644 index 0000000..5d509f0 --- /dev/null +++ b/X10D.Tests/src/Text/StringTests.cs @@ -0,0 +1,449 @@ +using System.Text; +using System.Text.Json.Serialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Text; + +namespace X10D.Tests.Text; + +[TestClass] +public class StringTests +{ + [TestMethod] + public void AsNullIfEmpty_ShouldBeCorrect() + { + const string sampleString = "Hello World"; + const string whitespaceString = " "; + const string emptyString = ""; + const string? nullString = null; + + string sampleResult = sampleString.AsNullIfEmpty(); + string whitespaceResult = whitespaceString.AsNullIfEmpty(); + string emptyResult = emptyString.AsNullIfEmpty(); + string? nullResult = nullString.AsNullIfEmpty(); + + Assert.AreEqual(sampleString, sampleResult); + Assert.AreEqual(whitespaceString, whitespaceResult); + Assert.AreEqual(nullString, emptyResult); + Assert.AreEqual(nullString, nullResult); + } + + [TestMethod] + public void AsNullIfWhiteSpace_ShouldBeCorrect() + { + const string sampleString = "Hello World"; + const string whitespaceString = " "; + const string emptyString = ""; + const string? nullString = null; + + string sampleResult = sampleString.AsNullIfWhiteSpace(); + string whitespaceResult = whitespaceString.AsNullIfWhiteSpace(); + string emptyResult = emptyString.AsNullIfWhiteSpace(); + string? nullResult = nullString.AsNullIfWhiteSpace(); + + Assert.AreEqual(sampleString, sampleResult); + Assert.AreEqual(nullString, whitespaceResult); + Assert.AreEqual(nullString, emptyResult); + Assert.AreEqual(nullString, nullResult); + } + + [TestMethod] + public void Base64Decode_ShouldReturnHelloWorld_GivenBase64String() + { + Assert.AreEqual("Hello World", "SGVsbG8gV29ybGQ=".Base64Decode()); + } + + + [TestMethod] + public void Base64Decode_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Base64Decode()); + } + + + [TestMethod] + public void Base64Encode_ShouldReturnBase64String_GivenHelloWorld() + { + Assert.AreEqual("SGVsbG8gV29ybGQ=", "Hello World".Base64Encode()); + } + + [TestMethod] + public void Base64Encode_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Base64Encode()); + } + + [TestMethod] + public void ChangeEncoding_ShouldReturnAsciiString_GivenUtf8() + { + Assert.AreEqual("Hello World", "Hello World".ChangeEncoding(Encoding.UTF8, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.ChangeEncoding(Encoding.UTF8, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullSourceEncoding() + { + Assert.ThrowsException(() => "Hello World".ChangeEncoding(null!, Encoding.ASCII)); + } + + [TestMethod] + public void ChangeEncoding_ShouldThrow_GivenNullDestinationEncoding() + { + Assert.ThrowsException(() => "Hello World".ChangeEncoding(Encoding.UTF8, null!)); + } + + [TestMethod] + public void EnumParse_ShouldReturnCorrectValue_GivenString() + { + Assert.AreEqual(DayOfWeek.Monday, "Monday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Tuesday, "Tuesday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Wednesday, "Wednesday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Thursday, "Thursday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Friday, "Friday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Saturday, "Saturday".EnumParse(false)); + Assert.AreEqual(DayOfWeek.Sunday, "Sunday".EnumParse(false)); + } + + [TestMethod] + public void EnumParse_ShouldTrim() + { + Assert.AreEqual(DayOfWeek.Monday, " Monday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Tuesday, " Tuesday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Wednesday, " Wednesday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Thursday, " Thursday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Friday, " Friday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Saturday, " Saturday ".EnumParse()); + Assert.AreEqual(DayOfWeek.Sunday, " Sunday ".EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldReturnCorrectValue_GivenString_Generic() + { + Assert.AreEqual(DayOfWeek.Monday, "Monday".EnumParse()); + Assert.AreEqual(DayOfWeek.Tuesday, "Tuesday".EnumParse()); + Assert.AreEqual(DayOfWeek.Wednesday, "Wednesday".EnumParse()); + Assert.AreEqual(DayOfWeek.Thursday, "Thursday".EnumParse()); + Assert.AreEqual(DayOfWeek.Friday, "Friday".EnumParse()); + Assert.AreEqual(DayOfWeek.Saturday, "Saturday".EnumParse()); + Assert.AreEqual(DayOfWeek.Sunday, "Sunday".EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.EnumParse()); + } + + [TestMethod] + public void EnumParse_ShouldThrow_GivenEmptyOrWhiteSpaceString() + { + Assert.ThrowsException(() => string.Empty.EnumParse()); + Assert.ThrowsException(() => " ".EnumParse()); + } + + [TestMethod] + public void FromJson_ShouldDeserializeCorrectly_GivenJsonString() + { + const string json = "{\"values\": [1, 2, 3]}"; + var target = json.FromJson(); + Assert.IsInstanceOfType(target, typeof(SampleStructure)); + Assert.IsNotNull(target); + Assert.IsNotNull(target.Values); + Assert.AreEqual(3, target.Values.Length); + Assert.AreEqual(1, target.Values[0]); + Assert.AreEqual(2, target.Values[1]); + Assert.AreEqual(3, target.Values[2]); + } + + [TestMethod] + public void GetBytes_ShouldReturnUtf8Bytes_GivenHelloWorld() + { + var expected = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + byte[] actual = "Hello World".GetBytes(); + + CollectionAssert.AreEqual(expected, actual); + } + + [TestMethod] + public void GetBytes_ShouldReturnAsciiBytes_GivenHelloWorld() + { + var expected = new byte[] {0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64}; + byte[] actual = "Hello World".GetBytes(Encoding.ASCII); + + CollectionAssert.AreEqual(expected, actual); + } + + [TestMethod] + public void GetBytes_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.GetBytes()); + Assert.ThrowsException(() => value!.GetBytes(Encoding.ASCII)); + } + + [TestMethod] + public void GetBytes_ShouldThrow_GivenNullEncoding() + { + Assert.ThrowsException(() => "Hello World".GetBytes(null!)); + } + + [TestMethod] + public void IsLower_ShouldReturnTrue_GivenLowercaseString() + { + Assert.IsTrue("hello world".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldReturnFalse_GivenMixedCaseString() + { + Assert.IsFalse("Hello World".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldReturnFalse_GivenUppercaseString() + { + Assert.IsFalse("HELLO WORLD".IsLower()); + } + + [TestMethod] + public void IsLower_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.IsLower()); + } + + [TestMethod] + public void IsPalindrome_ShouldBeCorrect_GivenString() + { + const string inputA = "Race car"; + const string inputB = "Racecar"; + const string inputC = "A man, a plan, a canal, panama"; + const string inputD = "Jackdaws love my big sphinx of quartz"; + const string inputE = "Y"; + const string inputF = "1"; + + Assert.IsTrue(inputA.IsPalindrome(), inputA); + Assert.IsTrue(inputB.IsPalindrome(), inputB); + Assert.IsTrue(inputC.IsPalindrome(), inputC); + Assert.IsFalse(inputD.IsPalindrome(), inputD); + Assert.IsTrue(inputE.IsPalindrome(), inputE); + Assert.IsTrue(inputF.IsPalindrome(), inputF); + } + + [TestMethod] + public void IsPalindrome_ShouldReturnFalse_GivenEmptyString() + { + Assert.IsFalse(string.Empty.IsPalindrome()); + } + + [TestMethod] + public void IsPalindrome_ShouldThrow_GivenNull() + { + Assert.ThrowsException(() => ((string?)null)!.IsPalindrome()); + } + + [TestMethod] + public void IsUpper_ShouldReturnFalse_GivenLowercaseString() + { + Assert.IsFalse("hello world".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldReturnFalse_GivenMixedCaseString() + { + Assert.IsFalse("Hello World".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldReturnTrue_GivenUppercaseString() + { + Assert.IsTrue("HELLO WORLD".IsUpper()); + } + + [TestMethod] + public void IsUpper_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.IsUpper()); + } + + [TestMethod] + public void Randomize_ShouldReorder_GivenString() + { + const string input = "Hello World"; + var random = new Random(1); + Assert.AreEqual("le rooldeoH", input.Randomize(input.Length, random)); + } + + [TestMethod] + public void Randomize_ShouldReturnEmptyString_GivenLength1() + { + Assert.AreEqual(string.Empty, "Hello World".Randomize(0)); + } + + [TestMethod] + public void Randomize_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Randomize(1)); + } + + [TestMethod] + public void Randomize_ShouldThrow_GivenNegativeLength() + { + Assert.ThrowsException(() => string.Empty.Randomize(-1)); + } + + [TestMethod] + public void Repeat_ShouldReturnRepeatedString_GivenString() + { + const string expected = "aaaaaaaaaa"; + string actual = "a".Repeat(10); + + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void Repeat_ShouldReturnEmptyString_GivenCount0() + { + Assert.AreEqual(string.Empty, "a".Repeat(0)); + } + + [TestMethod] + public void Repeat_ShouldReturnItself_GivenCount1() + { + string repeated = "a".Repeat(1); + Assert.AreEqual(1, repeated.Length); + Assert.AreEqual("a", repeated); + } + + [TestMethod] + public void Repeat_ShouldThrow_GivenNegativeCount() + { + Assert.ThrowsException(() => "a".Repeat(-1)); + } + + [TestMethod] + public void Repeat_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Repeat(0)); + } + + [TestMethod] + public void Reverse_ShouldBeCorrect() + { + const string input = "Hello World"; + const string expected = "dlroW olleH"; + + string result = input.Reverse(); + + Assert.AreEqual(string.Empty.Reverse(), string.Empty); + Assert.AreEqual(" ".Reverse(), " "); + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void Reverse_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Reverse()); + } + + [TestMethod] + public void Shuffled_ShouldReorder_GivenString() + { + const string alphabet = "abcdefghijklmnopqrstuvwxyz"; + string shuffled = alphabet; + + Assert.AreEqual(alphabet, shuffled); + + shuffled = alphabet.Shuffled(); + + Assert.AreNotEqual(alphabet, shuffled); + } + + [TestMethod] + public void Shuffled_ShouldThrow_GivenNull() + { + string? value = null; + Assert.ThrowsException(() => value!.Shuffled()); + } + + [TestMethod] + public void Split_ShouldYieldCorrectStrings_GivenString() + { + string[] chunks = "Hello World".Split(2).ToArray(); + Assert.AreEqual(6, chunks.Length); + Assert.AreEqual("He", chunks[0]); + Assert.AreEqual("ll", chunks[1]); + Assert.AreEqual("o ", chunks[2]); + Assert.AreEqual("Wo", chunks[3]); + Assert.AreEqual("rl", chunks[4]); + Assert.AreEqual("d", chunks[5]); + } + + [TestMethod] + public void Split_ShouldYieldEmptyString_GivenChunkSize0() + { + string[] chunks = "Hello World".Split(0).ToArray(); + Assert.AreEqual(1, chunks.Length); + Assert.AreEqual(string.Empty, chunks[0]); + } + + [TestMethod] + public void Split_ShouldThrow_GivenNullString() + { + string? value = null; + + // forcing enumeration with ToArray is required for the exception to be thrown + Assert.ThrowsException(() => value!.Split(0).ToArray()); + } + + [TestMethod] + public void WithEmptyAlternative_ShouldBeCorrect() + { + const string inputA = "Hello World"; + const string inputB = " "; + const string inputC = ""; + const string? inputD = null; + const string alternative = "ALTERNATIVE"; + + string resultA = inputA.WithEmptyAlternative(alternative); + string resultB = inputB.WithEmptyAlternative(alternative); + string resultC = inputC.WithEmptyAlternative(alternative); + string resultD = inputD.WithEmptyAlternative(alternative); + + Assert.AreEqual(resultA, inputA); + Assert.AreEqual(resultB, inputB); + Assert.AreEqual(resultC, alternative); + Assert.AreEqual(resultD, alternative); + Assert.AreEqual(alternative, ((string?)null).WithEmptyAlternative(alternative)); + } + + [TestMethod] + public void WithWhiteSpaceAlternative_ShouldBeCorrect() + { + const string input = " "; + const string alternative = "ALTERNATIVE"; + + string result = input.WithWhiteSpaceAlternative(alternative); + + Assert.AreEqual(result, alternative); + Assert.AreEqual(alternative, ((string?)null).WithWhiteSpaceAlternative(alternative)); + } + + private struct SampleStructure + { + [JsonPropertyName("values")] + public int[] Values { get; set; } + } +} diff --git a/X10D.Tests/src/Time/ByteTests.cs b/X10D.Tests/src/Time/ByteTests.cs new file mode 100644 index 0000000..705a149 --- /dev/null +++ b/X10D.Tests/src/Time/ByteTests.cs @@ -0,0 +1,73 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class ByteTests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((byte)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((byte)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((byte)100).IsLeapYear()); + Assert.IsFalse(((byte)200).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((byte)1).IsLeapYear()); + Assert.IsFalse(((byte)101).IsLeapYear()); + Assert.IsFalse(((byte)201).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4() + { + Assert.IsTrue(((byte)4).IsLeapYear()); + Assert.IsTrue(((byte)104).IsLeapYear()); + Assert.IsTrue(((byte)204).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((byte)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((byte)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((byte)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((byte)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/DateTimeOffsetTests.cs b/X10D.Tests/src/Time/DateTimeOffsetTests.cs new file mode 100644 index 0000000..4deba4b --- /dev/null +++ b/X10D.Tests/src/Time/DateTimeOffsetTests.cs @@ -0,0 +1,147 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DateTimeOffsetTests +{ + [TestMethod] + public void Age_ShouldBeDifference_Given1Jan2000() + { + DateTimeOffset birthday = new DateTime(2000, 1, 1); + DateTimeOffset today = DateTime.Now.Date; + + Assert.AreEqual(today.Year - birthday.Year, birthday.Age()); + } + + [TestMethod] + public void First_ShouldBeSaturday_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 1), date.First(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 2), date.First(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 3), date.First(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 4), date.First(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 5), date.First(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 6), date.First(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 7), date.First(DayOfWeek.Friday)); + } + + [TestMethod] + public void FirstDayOfMonth_ShouldBe1st_GivenToday() + { + DateTimeOffset today = DateTime.Now.Date; + + Assert.AreEqual(new DateTime(today.Year, today.Month, 1), today.FirstDayOfMonth()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given1999() + { + DateTimeOffset date = new DateTime(1999, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_Given2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + Assert.IsTrue(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2001() + { + DateTimeOffset date = new DateTime(2001, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2100() + { + DateTimeOffset date = new DateTime(2100, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void LastSaturday_ShouldBe29th_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 29), date.Last(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 30), date.Last(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 31), date.Last(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 25), date.Last(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 26), date.Last(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 27), date.Last(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 28), date.Last(DayOfWeek.Friday)); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary1999() + { + DateTimeOffset february = new DateTime(1999, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe29th_GivenFebruary2000() + { + DateTimeOffset february = new DateTime(2000, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 29), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary2001() + { + DateTimeOffset february = new DateTime(2001, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe30th_GivenAprilJuneSeptemberNovember() + { + DateTimeOffset april = new DateTime(2000, 4, 1); + DateTimeOffset june = new DateTime(2000, 6, 1); + DateTimeOffset september = new DateTime(2000, 9, 1); + DateTimeOffset november = new DateTime(2000, 11, 1); + + Assert.AreEqual(new DateTime(april.Year, april.Month, 30), april.LastDayOfMonth()); + Assert.AreEqual(new DateTime(june.Year, june.Month, 30), june.LastDayOfMonth()); + Assert.AreEqual(new DateTime(september.Year, september.Month, 30), september.LastDayOfMonth()); + Assert.AreEqual(new DateTime(november.Year, november.Month, 30), november.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe31st_GivenJanuaryMarchMayJulyAugustOctoberDecember() + { + DateTimeOffset january = new DateTime(2000, 1, 1); + DateTimeOffset march = new DateTime(2000, 3, 1); + DateTimeOffset may = new DateTime(2000, 5, 1); + DateTimeOffset july = new DateTime(2000, 7, 1); + DateTimeOffset august = new DateTime(2000, 8, 1); + DateTimeOffset october = new DateTime(2000, 10, 1); + DateTimeOffset december = new DateTime(2000, 12, 1); + + Assert.AreEqual(new DateTime(january.Year, january.Month, 31), january.LastDayOfMonth()); + Assert.AreEqual(new DateTime(march.Year, march.Month, 31), march.LastDayOfMonth()); + Assert.AreEqual(new DateTime(may.Year, may.Month, 31), may.LastDayOfMonth()); + Assert.AreEqual(new DateTime(july.Year, july.Month, 31), july.LastDayOfMonth()); + Assert.AreEqual(new DateTime(august.Year, august.Month, 31), august.LastDayOfMonth()); + Assert.AreEqual(new DateTime(october.Year, october.Month, 31), october.LastDayOfMonth()); + Assert.AreEqual(new DateTime(december.Year, december.Month, 31), december.LastDayOfMonth()); + } + + [TestMethod] + public void NextSaturday_ShouldBe8th_Given1Jan2000() + { + DateTimeOffset date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 8), date.Next(DayOfWeek.Saturday)); + } +} diff --git a/X10D.Tests/src/Time/DateTimeTests.cs b/X10D.Tests/src/Time/DateTimeTests.cs new file mode 100644 index 0000000..b6c3174 --- /dev/null +++ b/X10D.Tests/src/Time/DateTimeTests.cs @@ -0,0 +1,163 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DateTimeTests +{ + [TestMethod] + public void Age_ShouldBeDifference_Given1Jan2000() + { + var birthday = new DateTime(2000, 1, 1); + DateTime today = DateTime.Now.Date; + + Assert.AreEqual(today.Year - birthday.Year, birthday.Age()); + } + + [TestMethod] + public void First_ShouldBeSaturday_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 1), date.First(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 2), date.First(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 3), date.First(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 4), date.First(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 5), date.First(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 6), date.First(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 7), date.First(DayOfWeek.Friday)); + } + + [TestMethod] + public void FirstDayOfMonth_ShouldBe1st_GivenToday() + { + DateTime today = DateTime.Now.Date; + + Assert.AreEqual(new DateTime(today.Year, today.Month, 1), today.FirstDayOfMonth()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given1999() + { + var date = new DateTime(1999, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_Given2000() + { + var date = new DateTime(2000, 1, 1); + Assert.IsTrue(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2001() + { + var date = new DateTime(2001, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_Given2100() + { + var date = new DateTime(2100, 1, 1); + Assert.IsFalse(date.IsLeapYear()); + } + + [TestMethod] + public void LastSaturday_ShouldBe29th_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 29), date.Last(DayOfWeek.Saturday)); + Assert.AreEqual(new DateTime(2000, 1, 30), date.Last(DayOfWeek.Sunday)); + Assert.AreEqual(new DateTime(2000, 1, 31), date.Last(DayOfWeek.Monday)); + Assert.AreEqual(new DateTime(2000, 1, 25), date.Last(DayOfWeek.Tuesday)); + Assert.AreEqual(new DateTime(2000, 1, 26), date.Last(DayOfWeek.Wednesday)); + Assert.AreEqual(new DateTime(2000, 1, 27), date.Last(DayOfWeek.Thursday)); + Assert.AreEqual(new DateTime(2000, 1, 28), date.Last(DayOfWeek.Friday)); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary1999() + { + var february = new DateTime(1999, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe29th_GivenFebruary2000() + { + var february = new DateTime(2000, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 29), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe28th_GivenFebruary2001() + { + var february = new DateTime(2001, 2, 1); + + Assert.AreEqual(new DateTime(february.Year, february.Month, 28), february.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe30th_GivenAprilJuneSeptemberNovember() + { + var april = new DateTime(2000, 4, 1); + var june = new DateTime(2000, 6, 1); + var september = new DateTime(2000, 9, 1); + var november = new DateTime(2000, 11, 1); + + Assert.AreEqual(new DateTime(april.Year, april.Month, 30), april.LastDayOfMonth()); + Assert.AreEqual(new DateTime(june.Year, june.Month, 30), june.LastDayOfMonth()); + Assert.AreEqual(new DateTime(september.Year, september.Month, 30), september.LastDayOfMonth()); + Assert.AreEqual(new DateTime(november.Year, november.Month, 30), november.LastDayOfMonth()); + } + + [TestMethod] + public void LastDayOfMonth_ShouldBe31st_GivenJanuaryMarchMayJulyAugustOctoberDecember() + { + var january = new DateTime(2000, 1, 1); + var march = new DateTime(2000, 3, 1); + var may = new DateTime(2000, 5, 1); + var july = new DateTime(2000, 7, 1); + var august = new DateTime(2000, 8, 1); + var october = new DateTime(2000, 10, 1); + var december = new DateTime(2000, 12, 1); + + Assert.AreEqual(new DateTime(january.Year, january.Month, 31), january.LastDayOfMonth()); + Assert.AreEqual(new DateTime(march.Year, march.Month, 31), march.LastDayOfMonth()); + Assert.AreEqual(new DateTime(may.Year, may.Month, 31), may.LastDayOfMonth()); + Assert.AreEqual(new DateTime(july.Year, july.Month, 31), july.LastDayOfMonth()); + Assert.AreEqual(new DateTime(august.Year, august.Month, 31), august.LastDayOfMonth()); + Assert.AreEqual(new DateTime(october.Year, october.Month, 31), october.LastDayOfMonth()); + Assert.AreEqual(new DateTime(december.Year, december.Month, 31), december.LastDayOfMonth()); + } + + [TestMethod] + public void NextSaturday_ShouldBe8th_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(new DateTime(2000, 1, 8), date.Next(DayOfWeek.Saturday)); + } + + [TestMethod] + public void ToUnixTimeMilliseconds_ShouldBe946684800000_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(946684800000, date.ToUnixTimeMilliseconds()); + } + + [TestMethod] + public void ToUnixTimeSeconds_ShouldBe946684800_Given1Jan2000() + { + var date = new DateTime(2000, 1, 1); + + Assert.AreEqual(946684800, date.ToUnixTimeSeconds()); + } +} diff --git a/X10D.Tests/src/Time/DecimalTests.cs b/X10D.Tests/src/Time/DecimalTests.cs new file mode 100644 index 0000000..cd8cf8b --- /dev/null +++ b/X10D.Tests/src/Time/DecimalTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DecimalTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0m.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0m.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0m.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0m.Days()); + Assert.AreEqual(TimeSpan.Zero, 0m.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0m.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1m.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1m.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1m.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1m.Days() > TimeSpan.Zero); + Assert.IsTrue(1m.Hours() > TimeSpan.Zero); + Assert.IsTrue(1m.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1m).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1m).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1m).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1m).Days() < TimeSpan.Zero); + Assert.IsTrue((-1m).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1m).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/DoubleTests.cs b/X10D.Tests/src/Time/DoubleTests.cs new file mode 100644 index 0000000..17c7f2e --- /dev/null +++ b/X10D.Tests/src/Time/DoubleTests.cs @@ -0,0 +1,53 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class DoubleTests +{ + private Half _negativeOne; + private Half _one; + private Half _zero; + + [TestInitialize] + public void Initialize() + { + _negativeOne = (Half)(-1); + _one = (Half)1; + _zero = (Half)0; + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, _zero.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, _zero.Seconds()); + Assert.AreEqual(TimeSpan.Zero, _zero.Minutes()); + Assert.AreEqual(TimeSpan.Zero, _zero.Days()); + Assert.AreEqual(TimeSpan.Zero, _zero.Hours()); + Assert.AreEqual(TimeSpan.Zero, _zero.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(_one.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(_one.Seconds() > TimeSpan.Zero); + Assert.IsTrue(_one.Minutes() > TimeSpan.Zero); + Assert.IsTrue(_one.Days() > TimeSpan.Zero); + Assert.IsTrue(_one.Hours() > TimeSpan.Zero); + Assert.IsTrue(_one.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((_negativeOne).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Seconds() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Minutes() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Days() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Hours() < TimeSpan.Zero); + Assert.IsTrue((_negativeOne).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/HalfTests.cs b/X10D.Tests/src/Time/HalfTests.cs new file mode 100644 index 0000000..a70aed2 --- /dev/null +++ b/X10D.Tests/src/Time/HalfTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class HalfTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0.0.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Days()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0.0.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1.0.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1.0.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1.0.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1.0.Days() > TimeSpan.Zero); + Assert.IsTrue(1.0.Hours() > TimeSpan.Zero); + Assert.IsTrue(1.0.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1.0).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Days() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1.0).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/Int16Tests.cs b/X10D.Tests/src/Time/Int16Tests.cs new file mode 100644 index 0000000..f2434fe --- /dev/null +++ b/X10D.Tests/src/Time/Int16Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int16Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((short)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((short)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((short)100).IsLeapYear()); + Assert.IsFalse(((short)-100).IsLeapYear()); + Assert.IsFalse(((short)1900).IsLeapYear()); + Assert.IsFalse(((short)2100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((short)1).IsLeapYear()); + Assert.IsFalse(((short)101).IsLeapYear()); + Assert.IsFalse(((short)-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(((short)-401).IsLeapYear()); + Assert.IsTrue(((short)-105).IsLeapYear()); + Assert.IsTrue(((short)4).IsLeapYear()); + Assert.IsTrue(((short)104).IsLeapYear()); + Assert.IsTrue(((short)400).IsLeapYear()); + Assert.IsTrue(((short)2000).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((short)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue(((short)-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Days() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Hours() < TimeSpan.Zero); + Assert.IsTrue(((short)-1).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((short)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((short)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((short)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((short)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/Int32Tests.cs b/X10D.Tests/src/Time/Int32Tests.cs new file mode 100644 index 0000000..7d6670b --- /dev/null +++ b/X10D.Tests/src/Time/Int32Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int32Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100.IsLeapYear()); + Assert.IsFalse((-100).IsLeapYear()); + Assert.IsFalse(1900.IsLeapYear()); + Assert.IsFalse(2100.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1.IsLeapYear()); + Assert.IsFalse(101.IsLeapYear()); + Assert.IsFalse((-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue((-401).IsLeapYear()); + Assert.IsTrue((-105).IsLeapYear()); + Assert.IsTrue(4.IsLeapYear()); + Assert.IsTrue(104.IsLeapYear()); + Assert.IsTrue(400.IsLeapYear()); + Assert.IsTrue(2000.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue((-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1).Days() < TimeSpan.Zero); + Assert.IsTrue((-1).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1.Days() > TimeSpan.Zero); + Assert.IsTrue(1.Hours() > TimeSpan.Zero); + Assert.IsTrue(1.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0.Days()); + Assert.AreEqual(TimeSpan.Zero, 0.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/Int64Tests.cs b/X10D.Tests/src/Time/Int64Tests.cs new file mode 100644 index 0000000..f0459eb --- /dev/null +++ b/X10D.Tests/src/Time/Int64Tests.cs @@ -0,0 +1,90 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class Int64Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0L.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0L.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100L.IsLeapYear()); + Assert.IsFalse((-100L).IsLeapYear()); + Assert.IsFalse(1900L.IsLeapYear()); + Assert.IsFalse(2100L.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1L.IsLeapYear()); + Assert.IsFalse(101L.IsLeapYear()); + Assert.IsFalse((-101L).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue((-401L).IsLeapYear()); + Assert.IsTrue((-105L).IsLeapYear()); + Assert.IsTrue(4L.IsLeapYear()); + Assert.IsTrue(104L.IsLeapYear()); + Assert.IsTrue(400L.IsLeapYear()); + Assert.IsTrue(2000L.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0L.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1L).Ticks() < TimeSpan.Zero); + Assert.IsTrue((-1L).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1L).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1L).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1L).Days() < TimeSpan.Zero); + Assert.IsTrue((-1L).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1L).Weeks() < TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1L.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1L.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1L.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1L.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1L.Days() > TimeSpan.Zero); + Assert.IsTrue(1L.Hours() > TimeSpan.Zero); + Assert.IsTrue(1L.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0L.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0L.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0L.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0L.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0L.Days()); + Assert.AreEqual(TimeSpan.Zero, 0L.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0L.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/SByteTests.cs b/X10D.Tests/src/Time/SByteTests.cs new file mode 100644 index 0000000..8189c6f --- /dev/null +++ b/X10D.Tests/src/Time/SByteTests.cs @@ -0,0 +1,86 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class SByteTests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((sbyte)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((sbyte)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((sbyte)100).IsLeapYear()); + Assert.IsFalse(((sbyte)-100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((sbyte)1).IsLeapYear()); + Assert.IsFalse(((sbyte)101).IsLeapYear()); + Assert.IsFalse(((sbyte)-101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4() + { + Assert.IsTrue(((sbyte)4).IsLeapYear()); + Assert.IsTrue(((sbyte)104).IsLeapYear()); + Assert.IsTrue(((sbyte)-105).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((sbyte)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((sbyte)0).Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((sbyte)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((sbyte)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue(((sbyte)-1).Ticks() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Seconds() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Minutes() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Days() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Hours() < TimeSpan.Zero); + Assert.IsTrue(((sbyte)-1).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/SingleTests.cs b/X10D.Tests/src/Time/SingleTests.cs new file mode 100644 index 0000000..6feb82f --- /dev/null +++ b/X10D.Tests/src/Time/SingleTests.cs @@ -0,0 +1,41 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class SingleTests +{ + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0f.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0f.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0f.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0f.Days()); + Assert.AreEqual(TimeSpan.Zero, 0f.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0f.Weeks()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1f.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1f.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1f.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1f.Days() > TimeSpan.Zero); + Assert.IsTrue(1f.Hours() > TimeSpan.Zero); + Assert.IsTrue(1f.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeNegative_GivenMinusOne() + { + Assert.IsTrue((-1f).Milliseconds() < TimeSpan.Zero); + Assert.IsTrue((-1f).Seconds() < TimeSpan.Zero); + Assert.IsTrue((-1f).Minutes() < TimeSpan.Zero); + Assert.IsTrue((-1f).Days() < TimeSpan.Zero); + Assert.IsTrue((-1f).Hours() < TimeSpan.Zero); + Assert.IsTrue((-1f).Weeks() < TimeSpan.Zero); + } +} diff --git a/X10D.Tests/src/Time/StringTests.cs b/X10D.Tests/src/Time/StringTests.cs new file mode 100644 index 0000000..9b5e68c --- /dev/null +++ b/X10D.Tests/src/Time/StringTests.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class StringTests +{ + [TestMethod] + public void ToTimeSpan_ShouldReturnCorrectTimeSpan_GivenString() + { + const string value = "1y 1mo 1w 1d 1h 1m 1s 1ms"; + + TimeSpan expected = TimeSpan.FromMilliseconds(1); + expected += TimeSpan.FromSeconds(1); + expected += TimeSpan.FromMinutes(1); + expected += TimeSpan.FromHours(1); + expected += TimeSpan.FromDays(1); + expected += TimeSpan.FromDays(7); + expected += TimeSpan.FromDays(30); + expected += TimeSpan.FromDays(365); + + Assert.AreEqual(expected, value.ToTimeSpan()); + } + + [TestMethod] + public void ToTimeSpan_ShouldReturnZero_GivenInvalidString() + { + Assert.AreEqual(TimeSpan.Zero, "Hello World".ToTimeSpan()); + } + + [TestMethod] + public void ToTimeSpan_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => value!.ToTimeSpan()); + } +} diff --git a/X10D.Tests/src/Time/TimeSpanParserTests.cs b/X10D.Tests/src/Time/TimeSpanParserTests.cs new file mode 100644 index 0000000..b671070 --- /dev/null +++ b/X10D.Tests/src/Time/TimeSpanParserTests.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class TimeSpanParserTests +{ + [TestMethod] + public void TryParse_ShouldThrow_GivenNullString() + { + string? value = null; + Assert.ThrowsException(() => TimeSpanParser.TryParse(value!, out _)); + } +} diff --git a/X10D.Tests/src/Time/TimeSpanTests.cs b/X10D.Tests/src/Time/TimeSpanTests.cs new file mode 100644 index 0000000..cc34618 --- /dev/null +++ b/X10D.Tests/src/Time/TimeSpanTests.cs @@ -0,0 +1,42 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +public class TimeSpanTests +{ + private TimeSpan _timeSpan; + + [TestInitialize] + public void Initialize() + { + _timeSpan = new TimeSpan(1, 2, 3, 4, 5); + } + + [TestMethod] + public void Ago_ShouldBeInPast_GivenNow() + { + Assert.IsTrue(_timeSpan.Ago() < DateTime.Now); + } + + [TestMethod] + public void FromNow_ShouldBeInFuture_GivenNow() + { + Assert.IsTrue(_timeSpan.FromNow() > DateTime.Now); + } + + [TestMethod] + public void Ago_ShouldBeYesterday_GivenYesterday() + { + DateTime yesterday = DateTime.Now.AddDays(-1); + Assert.AreEqual(yesterday.Date, 1.Days().Ago().Date); + } + + [TestMethod] + public void FromNow_ShouldBeTomorrow_GivenTomorrow() + { + DateTime tomorrow = DateTime.Now.AddDays(1); + Assert.AreEqual(tomorrow.Date, 1.Days().FromNow().Date); + } +} diff --git a/X10D.Tests/src/Time/UInt16Tests.cs b/X10D.Tests/src/Time/UInt16Tests.cs new file mode 100644 index 0000000..e63d596 --- /dev/null +++ b/X10D.Tests/src/Time/UInt16Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt16Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((ushort)0).FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), ((ushort)0).FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(((ushort)100).IsLeapYear()); + Assert.IsFalse(((ushort)1900).IsLeapYear()); + Assert.IsFalse(((ushort)2100).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(((ushort)1).IsLeapYear()); + Assert.IsFalse(((ushort)101).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(((ushort)4).IsLeapYear()); + Assert.IsTrue(((ushort)104).IsLeapYear()); + Assert.IsTrue(((ushort)400).IsLeapYear()); + Assert.IsTrue(((ushort)2000).IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => ((ushort)0).IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(((ushort)1).Ticks() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Seconds() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Minutes() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Days() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Hours() > TimeSpan.Zero); + Assert.IsTrue(((ushort)1).Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Ticks()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Seconds()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Minutes()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Days()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Hours()); + Assert.AreEqual(TimeSpan.Zero, ((ushort)0).Weeks()); + } +} diff --git a/X10D.Tests/src/Time/UInt32Tests.cs b/X10D.Tests/src/Time/UInt32Tests.cs new file mode 100644 index 0000000..86eef87 --- /dev/null +++ b/X10D.Tests/src/Time/UInt32Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt32Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0U.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0U.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100U.IsLeapYear()); + Assert.IsFalse(1900U.IsLeapYear()); + Assert.IsFalse(2100U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1U.IsLeapYear()); + Assert.IsFalse(101U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(4U.IsLeapYear()); + Assert.IsTrue(104U.IsLeapYear()); + Assert.IsTrue(400U.IsLeapYear()); + Assert.IsTrue(2000U.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0U.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1U.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1U.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1U.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1U.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1U.Days() > TimeSpan.Zero); + Assert.IsTrue(1U.Hours() > TimeSpan.Zero); + Assert.IsTrue(1U.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0U.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0U.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0U.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0U.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0U.Days()); + Assert.AreEqual(TimeSpan.Zero, 0U.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0U.Weeks()); + } +} diff --git a/X10D.Tests/src/Time/UInt64Tests.cs b/X10D.Tests/src/Time/UInt64Tests.cs new file mode 100644 index 0000000..9720f3a --- /dev/null +++ b/X10D.Tests/src/Time/UInt64Tests.cs @@ -0,0 +1,75 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using X10D.Time; + +namespace X10D.Tests.Time; + +[TestClass] +[CLSCompliant(false)] +public class UInt64Tests +{ + [TestMethod] + public void FromUnixTimeMilliseconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0UL.FromUnixTimeMilliseconds()); + } + + [TestMethod] + public void FromUnixTimeSeconds_ShouldBeEpoch_GivenZero() + { + Assert.AreEqual(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc), 0UL.FromUnixTimeSeconds()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenMultipleOf100() + { + Assert.IsFalse(100UL.IsLeapYear()); + Assert.IsFalse(1900UL.IsLeapYear()); + Assert.IsFalse(2100UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeFalse_GivenOddNumber() + { + Assert.IsFalse(1UL.IsLeapYear()); + Assert.IsFalse(101UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldBeTrue_GivenMultipleOf4Or400() + { + Assert.IsTrue(4UL.IsLeapYear()); + Assert.IsTrue(104UL.IsLeapYear()); + Assert.IsTrue(400UL.IsLeapYear()); + Assert.IsTrue(2000UL.IsLeapYear()); + } + + [TestMethod] + public void IsLeapYear_ShouldThrow_GivenZero() + { + Assert.ThrowsException(() => 0UL.IsLeapYear()); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBePositive_GivenOne() + { + Assert.IsTrue(1UL.Ticks() > TimeSpan.Zero); + Assert.IsTrue(1UL.Milliseconds() > TimeSpan.Zero); + Assert.IsTrue(1UL.Seconds() > TimeSpan.Zero); + Assert.IsTrue(1UL.Minutes() > TimeSpan.Zero); + Assert.IsTrue(1UL.Days() > TimeSpan.Zero); + Assert.IsTrue(1UL.Hours() > TimeSpan.Zero); + Assert.IsTrue(1UL.Weeks() > TimeSpan.Zero); + } + + [TestMethod] + public void TicksMillisecondsSecondsMinutesDaysHoursWeeks_ShouldBeZero_GivenZero() + { + Assert.AreEqual(TimeSpan.Zero, 0UL.Ticks()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Milliseconds()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Seconds()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Minutes()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Days()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Hours()); + Assert.AreEqual(TimeSpan.Zero, 0UL.Weeks()); + } +} diff --git a/X10D.Tests/src/Unity/Vector3Tests.cs b/X10D.Tests/src/Unity/Vector3Tests.cs deleted file mode 100644 index 2e02cf0..0000000 --- a/X10D.Tests/src/Unity/Vector3Tests.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D.Tests.Unity -{ - using Microsoft.VisualStudio.TestTools.UnitTesting; - using UnityEngine; - using X10D.Unity; - - /// - /// Tests for . - /// - [TestClass] - public class Vector3Tests - { - /// - /// Tests for by rounding to the nearest 0.5. - /// - [TestMethod] - public void TestRoundHalf() - { - var vector = new Vector3(1.8f, 2.1f, 3.37f); - Assert.AreEqual(new Vector3(2, 2, 3.5f), vector.Round(0.5f)); - } - - /// - /// Tests for by rounding to the nearest integer. - /// - [TestMethod] - public void TestRoundInteger() - { - var vector = new Vector3(1.8f, 2.1f, 3.37f); - Assert.AreEqual(new Vector3(2, 2, 3), vector.Round()); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithX() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(4, 2, 3), vector.WithX(4)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithXY() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(8, 10, 3), vector.WithXY(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithXZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(8, 2, 10), vector.WithXZ(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithY() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 4, 3), vector.WithY(4)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithYZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 8, 10), vector.WithYZ(8, 10)); - } - - /// - /// Tests for . - /// - [TestMethod] - public void TestWithZ() - { - var vector = new Vector3(1, 2, 3); - Assert.AreEqual(new Vector3(1, 2, 4), vector.WithZ(4)); - } - } -} diff --git a/X10D.Unity/X10D.Unity.csproj b/X10D.Unity/X10D.Unity.csproj deleted file mode 100644 index 8b4ce74..0000000 --- a/X10D.Unity/X10D.Unity.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - netstandard2.0 - 8.0 - Oliver Booth - en - true - https://github.com/oliverbooth/X10D - git - Extension methods on crack. - LICENSE.md - icon.png - - dotnet extension-methods unity - 2.6.0 - ..\X10D.ruleset - true - 2.6.0 - 2.6.0 - 2.6.0 - - - - - True - - - - True - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - - - - - - - - - - True - True - Resource.resx - - - - - - ResXFileCodeGenerator - Resource.Designer.cs - - - - \ No newline at end of file diff --git a/X10D.Unity/src/BetterBehavior.cs b/X10D.Unity/src/BetterBehavior.cs deleted file mode 100644 index 6069676..0000000 --- a/X10D.Unity/src/BetterBehavior.cs +++ /dev/null @@ -1,107 +0,0 @@ -namespace X10D.Unity -{ - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - using UnityEngine; - - /// - /// Represents a class which inherits to offer wider functionality. - /// - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", Justification = "Unity property")] - [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global", Justification = "Unity property")] - [SuppressMessage("ReSharper", "SA1300", Justification = "Unity API-compliant property")] - [SuppressMessage("ReSharper", "CA2007", Justification = "Unnecessary")] - [SuppressMessage("ReSharper", "RCS1090", Justification = "Unnecessary")] - [SuppressMessage("ReSharper", "RCS1213", Justification = "Unity method")] - [SuppressMessage("ReSharper", "UnusedParameter.Global", Justification = "Override method")] - public abstract class BetterBehavior : MonoBehaviour - { - /// - /// Gets the component attached to this object. - /// - public new Transform transform { get; private set; } - - /// - /// Awake is called when the script instance is being loaded. - /// - protected virtual void Awake() - { - this.transform = this.GetComponent(); - } - - /// - /// Frame-rate independent messaging for physics calculations. - /// - /// A snapshot of timing values. - protected virtual void OnFixedUpdate(in GameTime gameTime) - { - } - - /// - /// Frame-rate independent messaging for physics calculations. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnFixedUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - /// - /// Called once per frame, after all Update calls. - /// - /// A snapshot of timing values. - protected virtual void OnLateUpdate(in GameTime gameTime) - { - } - - /// - /// Called once per frame, after all Update calls. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnLateUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - /// - /// Called once per frame. - /// - /// A snapshot of timing values. - protected virtual void OnUpdate(in GameTime gameTime) - { - } - - /// - /// Called once per frame. - /// - /// A snapshot of timing values. - /// Returns a task representing the result of the operation. - protected virtual Task OnUpdateAsync(GameTime gameTime) - { - return Task.CompletedTask; - } - - private async void FixedUpdate() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnFixedUpdate(time); - await this.OnFixedUpdateAsync(time); - } - - private async void LateUpdate() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnLateUpdate(time); - await this.OnLateUpdateAsync(time); - } - - private async void Update() - { - var time = GameTime.CreateFromCurrentTimes(); - this.OnUpdate(time); - await this.OnUpdateAsync(time); - } - } -} diff --git a/X10D.Unity/src/GameObjectExtensions.cs b/X10D.Unity/src/GameObjectExtensions.cs deleted file mode 100644 index 3341e87..0000000 --- a/X10D.Unity/src/GameObjectExtensions.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace X10D.Unity -{ - using System; - using JetBrains.Annotations; - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class GameObjectExtensions - { - /// - /// Rotates the component on the current such that is is facing another - /// . - /// - /// The current game object. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this GameObject gameObject, [NotNull] GameObject other) - { - if (gameObject is null) - { - throw new ArgumentNullException(nameof(gameObject)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - gameObject.LookAt(other.transform); - } - - /// - /// Rotates the component on the current such that is is facing another - /// . - /// - /// The current game object. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this GameObject gameObject, [NotNull] Transform other) - { - if (gameObject is null) - { - throw new ArgumentNullException(nameof(gameObject)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - gameObject.transform.LookAt(other); - } - } -} diff --git a/X10D.Unity/src/GameTime.cs b/X10D.Unity/src/GameTime.cs deleted file mode 100644 index f59a6df..0000000 --- a/X10D.Unity/src/GameTime.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace X10D.Unity -{ - using System; - using UnityEngine; - - /// - /// Represents a struct which contains game timing information. - /// - public readonly struct GameTime - { - private GameTime(float totalTime, float deltaTime, float fixedDeltaTime, int frameCount, float timeScale) - { - this.TotalTime = TimeSpan.FromSeconds(totalTime); - this.DeltaTime = TimeSpan.FromSeconds(deltaTime); - this.FixedDeltaTime = TimeSpan.FromSeconds(fixedDeltaTime); - this.FrameCount = frameCount; - this.TimeScale = timeScale; - } - - /// - /// Gets the time since the last frame was rendered. - /// - public TimeSpan DeltaTime { get; } - - /// - /// Gets the time since the last physics time step. - /// - public TimeSpan FixedDeltaTime { get; } - - /// - /// Gets the total number of frames which have been rendered. - /// - public int FrameCount { get; } - - /// - /// Gets the total time for which the game has been running. - /// - public TimeSpan TotalTime { get; } - - /// - /// Gets the time scale. - /// - public float TimeScale { get; } - - /// - /// Creates a new instance of the struct by creating a snapshot of values offered by . - /// - /// An instance of . - public static GameTime CreateFromCurrentTimes() - { - return new GameTime(Time.time, Time.deltaTime, Time.fixedDeltaTime, Time.frameCount, Time.timeScale); - } - } -} diff --git a/X10D.Unity/src/TransformExtensions.cs b/X10D.Unity/src/TransformExtensions.cs deleted file mode 100644 index 6cdb7e3..0000000 --- a/X10D.Unity/src/TransformExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace X10D.Unity -{ - using System; - using JetBrains.Annotations; - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class TransformExtensions - { - /// - /// Rotates the current such that is is facing another . - /// - /// The current transform. - /// The target. - /// - /// is null - /// - or - - /// is null. - /// - public static void LookAt([NotNull] this Transform transform, [NotNull] GameObject other) - { - if (transform is null) - { - throw new ArgumentNullException(nameof(transform)); - } - - if (other is null) - { - throw new ArgumentNullException(nameof(other)); - } - - transform.LookAt(other.transform); - } - } -} diff --git a/X10D.Unity/src/Vector3Extensions.cs b/X10D.Unity/src/Vector3Extensions.cs deleted file mode 100644 index 3f9b1eb..0000000 --- a/X10D.Unity/src/Vector3Extensions.cs +++ /dev/null @@ -1,111 +0,0 @@ -namespace X10D.Unity -{ - using UnityEngine; - - /// - /// Extension methods for . - /// - public static class Vector3Extensions - { - /// - /// Rounds a by calling on each of the components. - /// - /// The vector to round. - /// The nearest value. - /// rounded to the nearest . - public static Vector3 Round(this Vector3 vector, float nearest = 1) - { - return new Vector3(vector.x.Round(nearest), vector.y.Round(nearest), vector.z.Round(nearest)); - } - - /// - /// Returns a vector whose Y and Z components match that of a provided vector, and sets the X component to a provided value. - /// - /// The input vector. - /// The new X value. - /// - /// Returns a whose Y and Z components match that of , - /// but with the component set to . - /// - public static Vector3 WithX(this Vector3 vector, float x) - { - return new Vector3(x, vector.y, vector.z); - } - - /// - /// Returns a vector whose Z component matches that of a provided vector, and sets the X and Y components to provided values. - /// - /// The input vector. - /// The new X value. - /// The new Y value. - /// - /// Returns a whose Z component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithXY(this Vector3 vector, float x, float y) - { - return new Vector3(x, y, vector.z); - } - - /// - /// Returns a vector whose Y component matches that of a provided vector, and sets the X and Z components to provided values. - /// - /// The input vector. - /// The new X value. - /// The new Z value. - /// - /// Returns a whose Y component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithXZ(this Vector3 vector, float x, float z) - { - return new Vector3(x, vector.y, z); - } - - /// - /// Returns a vector whose X and Z components match that of a provided vector, and sets the Y component to a provided value. - /// - /// The input vector. - /// The new Y value. - /// - /// Returns a whose X and Z components match that of , - /// but with the component set to . - /// - public static Vector3 WithY(this Vector3 vector, float y) - { - return new Vector3(vector.x, y, vector.z); - } - - /// - /// Returns a vector whose X component matches that of a provided vector, and sets the Y and Z components to provided values. - /// - /// The input vector. - /// The new Y value. - /// The new Z value. - /// - /// Returns a whose X component matches that of , - /// but with the and components set to and - /// respectively. - /// - public static Vector3 WithYZ(this Vector3 vector, float y, float z) - { - return new Vector3(vector.x, y, z); - } - - /// - /// Returns a vector whose X and Y components match that of a provided vector, and sets the Z component to a provided value. - /// - /// The input vector. - /// The new Z value. - /// - /// Returns a whose X and Y components match that of , - /// but with the component set to . - /// - public static Vector3 WithZ(this Vector3 vector, float z) - { - return new Vector3(vector.x, vector.y, z); - } - } -} diff --git a/X10D.sln b/X10D.sln index d20ed46..067e80e 100644 --- a/X10D.sln +++ b/X10D.sln @@ -10,9 +10,15 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FF6E59AB-1A23-4981-834C-47BEB5A46DC1}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE.md = LICENSE.md + README.md = README.md + icon.png = icon.png EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.Unity", "X10D.Unity\X10D.Unity.csproj", "{C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "X10D.SourceValidator", "X10D.SourceValidator\X10D.SourceValidator.csproj", "{84750149-9068-4780-AFDE-CDA1AC57007D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -28,10 +34,10 @@ Global {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF228EA2-D8EC-4A40-8917-E1E62E3B7D8E}.Release|Any CPU.Build.0 = Release|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C21ABC58-68D6-4CA0-9CE6-A2E96C5E89AE}.Release|Any CPU.Build.0 = Release|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84750149-9068-4780-AFDE-CDA1AC57007D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/X10D/README.md b/X10D/README.md deleted file mode 100644 index 3596d22..0000000 --- a/X10D/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# X10D - -## About -The X10D library contains extension methods for standard .NET library types. - -## Extended Classes -Below is a list of the number of extension methods written for a given type. Overloaded methods are not counted. - -| Type | Library | Method count | -| :--- | :--- | :--- | -| `bool` | `X10D` | 1 | -| `byte` / `byte[]` | `X10D` | 8 | -| `char` | `X10D` | 1 | -| `IComparable` | `X10D` | 1 | -| `IConvertible` | `X10D` | 4 | -| `DateTime` | `X10D` | 11 | -| `EndPoint` | `X10D` | 2 | -| `double` | `X10D` | 7 | -| `enum` | `X10D` | 2 | -| `float` | `X10D` | 7 | -| `short` / `ushort` | `X10D` | 11 | -| `int` / `uint` | `X10D` | 25 | -| `long` / `ulong` | `X10D` | 13 | -| `IList` | `X10D` | 2 | -| `Random` | `X10D` | 2 | -| `string` / `SecureString` | `X10D` | 8 | -| `Dictionary` | `X10D` | 2 | \ No newline at end of file diff --git a/X10D/X10D.csproj b/X10D/X10D.csproj index e83b965..565369e 100644 --- a/X10D/X10D.csproj +++ b/X10D/X10D.csproj @@ -1,51 +1,57 @@  - netstandard2.0 - 8.0 + net6.0 + 10.0 + true + true Oliver Booth en - true https://github.com/oliverbooth/X10D git Extension methods on crack. LICENSE.md icon.png - + dotnet extension-methods - 2.6.0 - ..\X10D.ruleset true - 2.6.0 - 2.6.0 - 2.6.0 + 3.0.0 enable + true + true + + + + $(VersionPrefix)-$(VersionSuffix) + $(VersionPrefix).0 + $(VersionPrefix).0 + + + + $(VersionPrefix)-$(VersionSuffix).$(BuildNumber) + $(VersionPrefix).$(BuildNumber) + $(VersionPrefix).$(BuildNumber) + + + + $(VersionPrefix) + $(VersionPrefix).0 + $(VersionPrefix).0 True - + True - + + + + True + - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -54,6 +60,11 @@ True Resource.resx + + True + True + ExceptionMessages.resx + @@ -61,12 +72,10 @@ ResXFileCodeGenerator Resource.Designer.cs - - - - - CHANGELOG.md - + + ResXFileCodeGenerator + ExceptionMessages.Designer.cs + \ No newline at end of file diff --git a/X10D/X10D.csproj.DotSettings b/X10D/X10D.csproj.DotSettings new file mode 100644 index 0000000..04bd7e2 --- /dev/null +++ b/X10D/X10D.csproj.DotSettings @@ -0,0 +1,27 @@ + + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + diff --git a/X10D/src/Assembly.cs b/X10D/src/Assembly.cs index 8c11453..f547610 100644 --- a/X10D/src/Assembly.cs +++ b/X10D/src/Assembly.cs @@ -1,3 +1 @@ -using System; - -[assembly: CLSCompliant(true)] +[assembly: CLSCompliant(true)] diff --git a/X10D/src/BooleanExtensions.cs b/X10D/src/BooleanExtensions.cs deleted file mode 100644 index fbdab76..0000000 --- a/X10D/src/BooleanExtensions.cs +++ /dev/null @@ -1,145 +0,0 @@ -namespace X10D -{ - /// - /// Extension methods for . - /// - public static class BooleanExtensions - { - /// - /// Performs logical AND on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if AND - /// evaluate to , or otherwise. - /// - public static bool And(this bool value, bool comparison) - { - return value && comparison; - } - - /// - /// Performs logical NAND on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if NAND - /// evaluate to , or otherwise. - /// - public static bool NAnd(this bool value, bool comparison) - { - return !(value && comparison); - } - - /// - /// Performs logical NOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if NOR - /// evaluate to , or otherwise. - /// - public static bool NOr(this bool value, bool comparison) - { - return !(value || comparison); - } - - /// - /// Performs logical NOT on this . - /// - /// The boolean. - /// - /// Returns if is , - /// or otherwise. - /// - public static bool Not(this bool value) - { - return !value; - } - - /// - /// Performs logical OR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if OR - /// evaluate to , or otherwise. - /// - public static bool Or(this bool value, bool comparison) - { - return value || comparison; - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static byte ToByte(this bool value) - { - return (byte)value.ToInt32(); - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static short ToInt16(this bool value) - { - return (short)value.ToInt32(); - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , or 0 otherwise. - public static int ToInt32(this bool value) - { - return value ? 1 : 0; - } - - /// - /// Gets the value of this boolean as represented by . - /// - /// The boolean. - /// Returns 1 if is , 0 otherwise. - public static long ToInt64(this bool value) - { - return value.ToInt32(); - } - - /// - /// Performs logical XNOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if XNOR - /// evaluate to , or otherwise. - /// - public static bool XNOr(this bool value, bool comparison) - { - return !(value ^ comparison); - } - - /// - /// Performs logical XOR on this and another . - /// - /// The boolean. - /// The boolean comparator. - /// - /// Returns if XOR - /// evaluate to , or otherwise. - /// - public static bool XOr(this bool value, bool comparison) - { - return value ^ comparison; - } - } -} diff --git a/X10D/src/ByteExtensions.cs b/X10D/src/ByteExtensions.cs deleted file mode 100644 index dd1f634..0000000 --- a/X10D/src/ByteExtensions.cs +++ /dev/null @@ -1,116 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// Extension methods for . - /// - public static class ByteExtensions - { - /// - /// Gets a literally representing the raw values in the []. - /// - /// The bytes to get. - /// Returns a . - public static string AsString(this IEnumerable bytes) - { - return BitConverter.ToString(bytes.ToArray()); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static short GetInt16(this IEnumerable bytes) - { - return BitConverter.ToInt16(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static int GetInt32(this IEnumerable bytes) - { - return BitConverter.ToInt32(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - public static long GetInt64(this IEnumerable bytes) - { - return BitConverter.ToInt64(bytes.ToArray(), 0); - } - - /// - /// Gets a representing the value the [] with - /// encoding. - /// - /// The bytes to convert. - /// Returns a . - public static string GetString(this IEnumerable bytes) - { - return bytes.GetString(Encoding.UTF8); - } - - /// - /// Gets a representing the value the [] with the provided encoding. - /// - /// The bytes to convert. - /// The encoding to use. - /// Returns a . - /// is . - public static string GetString(this IEnumerable bytes, Encoding encoding) - { - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding)); - } - - // ReSharper disable once SuggestVarOrType_Elsewhere - var array = bytes.ToArray(); - return encoding.GetString(array, 0, array.Length); - } - - /// - /// Converts the [] to a . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static ushort GetUInt16(this IEnumerable bytes) - { - return BitConverter.ToUInt16(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static uint GetUInt32(this IEnumerable bytes) - { - return BitConverter.ToUInt32(bytes.ToArray(), 0); - } - - /// - /// Converts the [] to an . - /// - /// The bytes to convert. - /// Returns an . - [CLSCompliant(false)] - public static ulong GetUInt64(this IEnumerable bytes) - { - return BitConverter.ToUInt64(bytes.ToArray(), 0); - } - } -} diff --git a/X10D/src/CharExtensions.cs b/X10D/src/CharExtensions.cs deleted file mode 100644 index cbdd49e..0000000 --- a/X10D/src/CharExtensions.cs +++ /dev/null @@ -1,90 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - - /// - /// Extension methods for . - /// - public static class CharExtensions - { - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this char[] chars, int length) - { - return chars.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - /// is . - public static string Random(this char[] chars, int length, Random random) - { - if (chars is null) - { - throw new ArgumentNullException(nameof(chars)); - } - - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - var builder = new StringBuilder(length); - for (var i = 0; i < length; i++) - { - builder.Append(chars[random.Next(0, chars.Length)]); - } - - return builder.ToString(); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this IEnumerable chars, int length) - { - return chars.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - public static string Random(this IEnumerable chars, int length, Random random) - { - return chars.ToArray().Random(length, random); - } - - /// - /// Repeats a character a specified number of times. - /// - /// The character to repeat. - /// The repeat count. - /// - /// Returns a whose value is repeated - /// times. - /// - public static string Repeat(this char c, int count) - { - return new string(c, count); - } - } -} diff --git a/X10D/src/Collections/ArrayExtensions.cs b/X10D/src/Collections/ArrayExtensions.cs new file mode 100644 index 0000000..053d27d --- /dev/null +++ b/X10D/src/Collections/ArrayExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Extension methods for . +/// +public static class ArrayExtensions +{ + /// + /// Returns a read-only wrapper for the array. + /// + /// The one-dimensional, zero-based array to wrap in a read-only wrapper. + /// The type of the elements in the array. + /// A wrapper for the specified array. + /// is . + [Pure] + public static IReadOnlyCollection AsReadOnly(this T[] array) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + return Array.AsReadOnly(array); + } + + /// + /// Clears the contents of an array. + /// + /// The array to clear. + /// The type of the elements in the array. + /// is . + public static void Clear(this T?[] array) + { + array.Clear(..); + } + + /// + /// Sets a range of elements in an array to the default value of each element type. + /// + /// The array whose elements need to be cleared. + /// A range defining the start index and number of elements to clear. + /// The type of the elements in the array. + /// is . + public static void Clear(this T?[] array, Range range) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + int index = range.Start.Value; + int end = range.End.Value; + if (range.End.IsFromEnd) + { + end = array.Length - end; + } + + array.Clear(index, end - index); + } + + /// + /// Sets a range of elements in an array to the default value of each element type. + /// + /// The array whose elements need to be cleared. + /// The starting index of the range of elements to clear. + /// The number of elements to clear. + /// The type of the elements in the array. + /// is . + /// + /// is less than the lower bound of . + /// -or- + /// is less zero. + /// -or- + /// The sum of and is greater than the size of array. + /// + public static void Clear(this T?[] array, int index, int length) + { + if (array is null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (length == 0 || array.Length == 0) + { + return; + } + + Array.Clear(array, index, length); + } +} diff --git a/X10D/src/Collections/BoolListExtensions.cs b/X10D/src/Collections/BoolListExtensions.cs new file mode 100644 index 0000000..365778a --- /dev/null +++ b/X10D/src/Collections/BoolListExtensions.cs @@ -0,0 +1,130 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for of . +/// +public static class BoolListExtensions +{ + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// An 8-bit unsigned integer containing the packed booleans. + /// is . + /// contains more than 8 elements. + /// Alpha Anar + [Pure] + public static byte PackByte(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 8) + { + throw new ArgumentException("Source cannot contain more than than 8 elements.", nameof(source)); + } + + byte result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= (byte)(source[i] ? 1 << i : 0); + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 16-bit signed integer containing the packed booleans. + /// is . + /// contains more than 16 elements. + [Pure] + public static short PackInt16(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 16) + { + throw new ArgumentException("Source cannot contain more than than 16 elements.", nameof(source)); + } + + short result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= (short)(source[i] ? 1 << i : 0); + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 32-bit signed integer containing the packed booleans. + /// is . + /// contains more than 32 elements. + [Pure] + public static int PackInt32(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 32) + { + throw new ArgumentException("Source cannot contain more than than 32 elements.", nameof(source)); + } + + var result = 0; + + for (var i = 0; i < source.Count; i++) + { + result |= source[i] ? 1 << i : 0; + } + + return result; + } + + /// + /// Packs a collection of booleans into a . + /// + /// The collection of booleans to pack. + /// A 64-bit signed integer containing the packed booleans. + /// is . + /// contains more than 64 elements. + [Pure] + public static long PackInt64(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source.Count > 64) + { + throw new ArgumentException("Source cannot contain more than than 64 elements.", nameof(source)); + } + + var result = 0L; + + for (var i = 0; i < source.Count; i++) + { + result |= source[i] ? 1L << i : 0; + } + + return result; + } +} diff --git a/X10D/src/Collections/ByteExtensions.cs b/X10D/src/Collections/ByteExtensions.cs new file mode 100644 index 0000000..be77738 --- /dev/null +++ b/X10D/src/Collections/ByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class ByteExtensions +{ + private const int Size = sizeof(byte) * 8; + + /// + /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 8. + [Pure] + public static bool[] Unpack(this byte value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this byte value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/DictionaryExtensions.cs b/X10D/src/Collections/DictionaryExtensions.cs new file mode 100644 index 0000000..fe0db27 --- /dev/null +++ b/X10D/src/Collections/DictionaryExtensions.cs @@ -0,0 +1,435 @@ +using System.Diagnostics.Contracts; +using System.Web; + +namespace X10D.Collections; + +/// +/// Extension methods for and similar types. +/// +public static class DictionaryExtensions +{ + /// + /// Adds a key/value pair to the if the key does not already exist, or updates a + /// key/value pair in the by using the specified function if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The value to be added for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be (if the key was absent) or the result + /// of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, TValue addValue, + Func updateValueFactory) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key]); + } + else + { + dictionary.Add(key, addValue); + } + + return dictionary[key]; + } + + /// + /// Uses the specified functions to add a key/value pair to the if the key does + /// not already exist, or to update a key/value pair in the if the key already + /// exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key]); + } + else + { + dictionary.Add(key, addValueFactory(key)); + } + + return dictionary[key]; + } + + /// + /// Uses the specified functions and argument to add a key/value pair to the if + /// the key does not already exist, or to update a key/value pair in the if th + /// key already exists. + /// + /// The dictionary to update. + /// The key to be added or whose value should be updated. + /// The function used to generate a value for an absent key. + /// + /// The function used to generate a new value for an existing key based on the key's existing value. + /// + /// + /// An argument to pass into and . + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary. + /// + /// The type of an argument to pass into and . + /// + /// + /// The new value for the key. This will be either be the result of (if the key was + /// absent) or the result of (if the key was present). + /// + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + public static TValue AddOrUpdate(this IDictionary dictionary, TKey key, + Func addValueFactory, Func updateValueFactory, TArg factoryArgument) + where TKey : notnull + { + if (dictionary is null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (addValueFactory is null) + { + throw new ArgumentNullException(nameof(addValueFactory)); + } + + if (updateValueFactory is null) + { + throw new ArgumentNullException(nameof(updateValueFactory)); + } + + if (dictionary.ContainsKey(key)) + { + dictionary[key] = updateValueFactory(key, dictionary[key], factoryArgument); + } + else + { + dictionary.Add(key, addValueFactory(key, factoryArgument)); + } + + return dictionary[key]; + } + + /// + /// Converts an of to a data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// A representing the dictionary as a key=value set, concatenated with ;. + /// is . + [Pure] + public static string ToConnectionString(this IEnumerable> source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + static string GetQueryParameter(KeyValuePair pair) + { + return $"{pair.Key}={SanitizeValue(pair.Value?.ToString())}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with ;. + /// + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToConnectionString(this IEnumerable> source, + Func selector) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + string GetQueryParameter(KeyValuePair pair) + { + return $"{pair.Key}={SanitizeValue(selector(pair.Value))}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to an data connection + /// string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with ;. + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToConnectionString(this IEnumerable> source, + Func keySelector, Func valueSelector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector is null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (valueSelector is null) + { + throw new ArgumentNullException(nameof(valueSelector)); + } + + static string SanitizeValue(string? value) + { + if (value is null) + { + return string.Empty; + } + + return value.Contains(' ') ? $"\"{value}\"" : value; + } + + string GetQueryParameter(KeyValuePair pair) + { + return $"{keySelector(pair.Key)}={SanitizeValue(valueSelector(pair.Value))}"; + } + + return string.Join(';', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// A representing the dictionary as a key=value set, concatenated with &. + /// is . + [Pure] + public static string ToGetParameters(this IEnumerable> source) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + static string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(pair.Key.ToString())!; + string? value = HttpUtility.UrlEncode(pair.Value?.ToString()); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with &. + /// + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToGetParameters(this IEnumerable> source, + Func selector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + // can't static here because of 'selector' parameter + string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(pair.Key.ToString())!; + string? value = HttpUtility.UrlEncode(selector(pair.Value)); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } + + /// + /// Converts an of to a HTTP GET query string. + /// + /// The type of the key element of the key/value pair. + /// The type of the value element of the key/value pair. + /// The source dictionary. + /// + /// A transform function to apply to the of each element. + /// + /// + /// A transform function to apply to the of each element. + /// + /// A representing the dictionary as a key=value set, concatenated with &. + /// + /// is . + /// -or- + /// is . + /// -or- + /// is . + /// + [Pure] + public static string ToGetParameters(this IEnumerable> source, + Func keySelector, Func valueSelector) + where TKey : notnull + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (keySelector is null) + { + throw new ArgumentNullException(nameof(keySelector)); + } + + if (valueSelector is null) + { + throw new ArgumentNullException(nameof(valueSelector)); + } + + // can't static here because of selector parameters + string GetQueryParameter(KeyValuePair pair) + { + string key = HttpUtility.UrlEncode(keySelector(pair.Key)); + string? value = HttpUtility.UrlEncode(valueSelector(pair.Value)); + return $"{key}={value}"; + } + + return string.Join('&', source.Select(GetQueryParameter)); + } +} diff --git a/X10D/src/Collections/EnumerableExtensions.cs b/X10D/src/Collections/EnumerableExtensions.cs new file mode 100644 index 0000000..baa8d19 --- /dev/null +++ b/X10D/src/Collections/EnumerableExtensions.cs @@ -0,0 +1,23 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Extension methods for . +/// +public static class EnumerableExtensions +{ + /// + /// Reorganizes the elements in an enumerable by implementing a Fisher-Yates shuffle, and returns th shuffled result. + /// + /// The element type. + /// The to shuffle. + /// Optional. The instance to use for the shuffling. + [Pure] + public static IReadOnlyCollection Shuffled(this IEnumerable source, Random? random = null) + { + var list = new List(source); + list.Shuffle(random); + return list.AsReadOnly(); + } +} diff --git a/X10D/src/Collections/Int16Extensions.cs b/X10D/src/Collections/Int16Extensions.cs new file mode 100644 index 0000000..366cb12 --- /dev/null +++ b/X10D/src/Collections/Int16Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int16Extensions +{ + private const int Size = sizeof(short) * 8; + + /// + /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 16. + [Pure] + public static bool[] Unpack(this short value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this short value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/Int32Extensions.cs b/X10D/src/Collections/Int32Extensions.cs new file mode 100644 index 0000000..82da040 --- /dev/null +++ b/X10D/src/Collections/Int32Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int32Extensions +{ + private const int Size = sizeof(int) * 8; + + /// + /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 32. + [Pure] + public static bool[] Unpack(this int value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this int value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1 << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/Int64Extensions.cs b/X10D/src/Collections/Int64Extensions.cs new file mode 100644 index 0000000..d9c07ba --- /dev/null +++ b/X10D/src/Collections/Int64Extensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class Int64Extensions +{ + private const int Size = sizeof(long) * 8; + + /// + /// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with length 64. + [Pure] + public static bool[] Unpack(this long value) + { + Span buffer = stackalloc bool[Size]; + value.Unpack(buffer); + return buffer.ToArray(); + } + + /// + /// Unpacks this 64-bit signed integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + public static void Unpack(this long value, Span destination) + { + if (destination.Length < Size) + { + throw new ArgumentException($"Destination must be at least {Size} in length.", nameof(destination)); + } + + for (var index = 0; index < Size; index++) + { + destination[index] = (value & (1L << index)) != 0; + } + } +} diff --git a/X10D/src/Collections/ListExtensions.cs b/X10D/src/Collections/ListExtensions.cs new file mode 100644 index 0000000..d943395 --- /dev/null +++ b/X10D/src/Collections/ListExtensions.cs @@ -0,0 +1,136 @@ +using System.Diagnostics.Contracts; +using X10D.Core; + +namespace X10D.Collections; + +/// +/// Extension methods for and . +/// +public static class ListExtensions +{ + /// + /// Assigns the given value to each element of the list. + /// + /// The list to be filled. + /// The value to assign to each list element. + /// The type of the elements in the list. + /// is . + public static void Fill(this IList source, T value) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + for (var i = 0; i < source.Count; i++) + { + source[i] = value; + } + } + + /// + /// Assigns the given value to the elements of the list which are within the range of + /// (inclusive) and the next number of indices. + /// + /// The list to be filled. + /// The value to assign to each list element. + /// A 32-bit integer that represents the index in the list at which filling begins. + /// The number of elements to fill. + /// The type of the elements in the list. + /// is . + /// + /// is less than 0. + /// -or- + /// is less than 0. + /// -or- + /// + exceeds the bounds of the list. + /// + public static void Fill(this IList source, T value, int startIndex, int count) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (startIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(startIndex)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (startIndex + count > source.Count) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0 || source.Count == 0) + { + return; + } + + for (int index = startIndex; index < startIndex + count; index++) + { + source[index] = value; + } + } + + /// + /// Returns a random element from the current list using a specified instance. + /// + /// The element type. + /// The source collection from which to draw. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// A random element of type from . + /// is . + /// + /// + /// var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + /// var number = list.Random(); + /// + /// + [Pure] + public static T Random(this IReadOnlyList source, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + random ??= System.Random.Shared; + return random.NextFrom(source); + } + + /// + /// Reorganizes the elements in a list by implementing a Fisher-Yates shuffle. + /// + /// The element type. + /// The to shuffle. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// is . + public static void Shuffle(this IList source, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + random ??= System.Random.Shared; + + int count = source.Count; + while (count > 0) + { + int index = random.Next(count--); + (source[count], source[index]) = (source[index], source[count]); + } + } +} diff --git a/X10D/src/ComparableExtensions.cs b/X10D/src/ComparableExtensions.cs deleted file mode 100644 index 798112b..0000000 --- a/X10D/src/ComparableExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class ComparableExtensions - { - /// - /// Determines if is between and . - /// - /// The comparable type. - /// The value to compare. - /// The exclusive lower bound. - /// The exclusive upper bound. - /// - /// Returns if the value is between the bounds, - /// otherwise. - /// - public static bool Between(this T actual, T lower, T upper) - where T : IComparable - { - return actual.CompareTo(lower) > 0 && actual.CompareTo(upper) < 0; - } - } -} diff --git a/X10D/src/ConvertibleExtensions.cs b/X10D/src/ConvertibleExtensions.cs deleted file mode 100644 index bf2a3ce..0000000 --- a/X10D/src/ConvertibleExtensions.cs +++ /dev/null @@ -1,161 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class ConvertibleExtensions - { - /// - /// Converts the object to another type. - /// - /// The type to convert to. - /// The object to convert. - /// An object that supplies culture-specific formatting information. - /// Returns the value converted to . - /// - /// This conversion is not supported. - /// -or- - /// is and is a value type. - /// - [CLSCompliant(false)] - public static T To(this IConvertible value, IFormatProvider provider = null) - { - if (value is null) - { - return default; - } - - return (T)Convert.ChangeType(value, typeof(T), provider); - } - - /// - /// Converts the object to another type, returning the default value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The format provider. - /// Returns the value converted to . - /// This conversion is not supported. - [CLSCompliant(false)] - public static T ToOrDefault(this IConvertible value, IFormatProvider provider = null) - { - return value is null ? default : To(value, provider); - } - - /// - /// Converts the object to another type, returning the default value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// An object that supplies culture-specific formatting information. - /// Returns on success, on failure. - [CLSCompliant(false)] - public static bool ToOrDefault(this IConvertible value, out T newObj, IFormatProvider provider = null) - { - if (value is null) - { - newObj = default; - return false; - } - - try - { - newObj = To(value, provider); - return true; - } - catch (InvalidCastException) - { - newObj = default; - return false; - } - } - - /// - /// Converts the object to another type, returning on failure. - /// - /// The type to convert to. - /// The object to convert. - /// An object that supplies culture-specific formatting information. - /// Returns a or . - [CLSCompliant(false)] - public static T ToOrNull(this IConvertible value, IFormatProvider provider = null) - where T : class - { - return value.ToOrNull(out T v, provider) ? v : null; - } - - /// - /// Converts the object to another type, returning on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// An object that supplies culture-specific formatting information. - /// Returns a or . - [CLSCompliant(false)] - public static bool ToOrNull(this IConvertible value, out T newObj, IFormatProvider provider = null) - where T : class - { - return ToOrOther(value, out newObj, null, provider); - } - - /// - /// Converts the object to another type, returning a different value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The backup value. - /// An object that supplies culture-specific formatting information. - /// Returns the value converted to . - [CLSCompliant(false)] - public static T ToOrOther(this IConvertible value, T other, IFormatProvider provider = null) - { - if (value is null) - { - return other; - } - - try - { - return To(value, provider); - } - catch (Exception ex) when (ex is InvalidCastException || ex is FormatException) - { - return other; - } - } - - /// - /// Converts the object to another type, returning a different value on failure. - /// - /// The type to convert to. - /// The object to convert. - /// The parameter where the result should be sent. - /// The backup value. - /// An object that supplies culture-specific formatting information. - /// Returns on success, on failure. - [CLSCompliant(false)] - public static bool ToOrOther(this IConvertible value, out T newObj, T other, IFormatProvider provider = null) - { - if (value is null) - { - newObj = other; - return false; - } - - try - { - newObj = To(value, provider); - return true; - } - catch (Exception ex) when (ex is InvalidCastException || ex is FormatException) - { - newObj = other; - return false; - } - } - } -} diff --git a/X10D/src/Core/EnumExtensions.cs b/X10D/src/Core/EnumExtensions.cs new file mode 100644 index 0000000..fbba9b4 --- /dev/null +++ b/X10D/src/Core/EnumExtensions.cs @@ -0,0 +1,89 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Core; + +/// +/// Extension methods for types. +/// +public static class EnumExtensions +{ + /// + /// Returns the value which is defined proceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose proceeding value to retrieve. + /// + /// A value of that is considered to be the next value defined after , + /// or the first value if is the final field of the enumeration. + /// + [Pure] + public static T Next(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) + 1; + index %= values.Length; + return values[index]; + } + + /// + /// Returns the value which is defined proceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose proceeding value to retrieve. + /// + /// A value of that is considered to be the next value defined after + /// . + /// + /// is the final field of the enumeration. + [Pure] + public static T NextUnchecked(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) + 1; + return values[index]; + } + + /// + /// Returns the value which is defined preceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose preceeding value to retrieve. + /// + /// A value of that is considered to be the previous value defined after + /// , or the last value if is the first field of the enumeration. + /// + [Pure] + public static T Previous(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) - 1; + int length = values.Length; + + // negative modulo is not supported in C#. workaround: https://stackoverflow.com/a/1082938/1467293 + // sure, simply checking for index < 0 is enough, but this expression is so fucking cool! + index = (index % length + length) % length; + return values[index]; + } + + /// + /// Returns the value which is defined preceeding this value in the enumeration. + /// + /// The type of the enumeration. + /// The value whose preceeding value to retrieve. + /// + /// A value of that is considered to be the previous value defined after + /// , or the last value if is the first field of the enumeration. + /// + /// is the first field of the enumeration. + [Pure] + public static T PreviousUnchecked(this T value) + where T : struct, Enum + { + T[] values = Enum.GetValues(); + int index = Array.IndexOf(values, value) - 1; + return values[index]; + } +} diff --git a/X10D/src/Core/Extensions.cs b/X10D/src/Core/Extensions.cs new file mode 100644 index 0000000..21348d6 --- /dev/null +++ b/X10D/src/Core/Extensions.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Core; + +/// +/// Extension methods which apply to all types. +/// +public static class Extensions +{ + /// + /// Returns an array containing the specified value. + /// + /// The value to encapsulate. + /// The type of . + /// + /// An array of type with length 1, whose only element is . + /// + [Pure] + public static T?[] AsArrayValue(this T? value) + { + return new[] {value}; + } + + /// + /// Returns an enumerable collection containing the specified value. + /// + /// The value to encapsulate. + /// The type of . + /// + /// An enumerable collection of type , whose only element is . + /// + [Pure] + public static IEnumerable AsEnumerableValue(this T? value) + { + yield return value; + } + + /// + /// Returns an enumerable collection containing the current value repeated a specified number of times. + /// + /// The value to repeat. + /// The number of times to repeat . + /// The type of . + /// An enumerable collection containing repeated times. + /// is less than 0. + [Pure] + public static IEnumerable RepeatValue(this T value, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + } + + for (var i = 0; i < count; i++) + { + yield return value; + } + } +} diff --git a/X10D/src/Core/RandomExtensions.cs b/X10D/src/Core/RandomExtensions.cs new file mode 100644 index 0000000..a2c4ac9 --- /dev/null +++ b/X10D/src/Core/RandomExtensions.cs @@ -0,0 +1,418 @@ +using System.Globalization; +using System.Text; +using X10D.Math; + +namespace X10D.Core; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a random value that defined in a specified enum. + /// + /// The instance. + /// An enum type. + /// + /// A value at index n where n = . + /// + /// is . + public static T Next(this Random random) + where T : struct, Enum + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + var values = Enum.GetValues(typeof(T)); + return (T)values.GetValue(random.Next(values.Length))!; + } + + /// + /// Returns either or based on the next generation of the current + /// . + /// + /// The instance. + /// + /// if the return value from is greater than or + /// equal to 0.5 + /// -or- + /// otherwise. + /// + /// is . + public static bool NextBoolean(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextDouble() >= 0.5; + } + + /// + /// Returns a non-negative random double-precision floating point number that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to 0. + /// + /// + /// A random double-precision floating point number that is greater than or equal to 0, and less than + /// . + /// + /// is . + /// is less than 0. + public static double NextDouble(this Random random, double maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(ExceptionMessages.MaxValueGreaterThanEqualTo0); + } + + return random.NextDouble(0, maxValue); + } + + /// + /// Returns a random double-precision floating point number that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number returned. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to + /// . + /// + /// + /// A random double-precision floating point number between and + /// . + /// + /// is . + /// + /// is less than . + /// + public static double NextDouble(this Random random, double minValue, double maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < minValue) + { + throw new ArgumentException(ExceptionMessages.MaxValueGreaterThanEqualToMinValue); + } + + return MathUtility.Lerp(minValue, maxValue, random.NextDouble()); + } + + /// + /// Returns a random element from using the instance. + /// + /// The element type. + /// The instance. + /// The source collection from which to draw. + /// A random element of type from . + /// + /// is is + /// -or- + /// is . + /// + /// + /// + /// var list = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + /// var random = new Random(); + /// var number = random.NextFrom(list); + /// + /// + public static T NextFrom(this Random random, IEnumerable source) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (source is T[] array) + { + return array[random.Next(array.Length)]; + } + + if (source is not IReadOnlyList list) + { + list = source.ToList(); + } + + return list[random.Next(list.Count)]; + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// An 8-bit unsigned integer that is greater than or equal to 0, and less than . + /// + /// is . + public static byte NextByte(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextByte(byte.MaxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to 0. + /// + /// + /// An 8-bit unsigned integer that is greater than or equal to 0, and less than ; that is, the + /// range of return values ordinarily includes 0 but not . However, if + /// equals 0, is returned. + /// + /// is . + public static byte NextByte(this Random random, byte maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextByte(0, maxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// The inclusive lower bound of the random number to be generated. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to . + /// + /// + /// An 8-bit unsigned integer greater than or equal to and less than + /// ; that is, the range of return values includes but not + /// . If equals , + /// is returned. + /// + /// is . + /// + /// is greater than . + /// + public static byte NextByte(this Random random, byte minValue, byte maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return (byte)random.Next(minValue, maxValue); + } + + /// + /// Returns a non-negative random integer. + /// + /// The instance. + /// + /// An 16-bit signed integer that is greater than or equal to 0, and less than . + /// + /// is . + public static short NextInt16(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return random.NextInt16(short.MaxValue); + } + + /// + /// Returns a non-negative random integer that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to 0. + /// + /// + /// A 16-bit signed integer that is greater than or equal to 0, and less than ; that is, the + /// range of return values ordinarily includes 0 but not . However, if + /// equals 0, is returned. + /// + /// is . + /// is less than 0. + public static short NextInt16(this Random random, short maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxValue)); + } + + return random.NextInt16(0, maxValue); + } + + /// + /// Returns a random integer that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number to be generated. + /// + /// The exclusive upper bound of the random number to be generated. must be greater than or + /// equal to . + /// + /// + /// An 8-bit unsigned integer greater than or equal to and less than + /// ; that is, the range of return values includes but not + /// . If equals , + /// is returned. + /// + /// + /// is greater than . + /// + /// is . + public static short NextInt16(this Random random, short minValue, short maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + return (short)random.Next(minValue, maxValue); + } + + /// + /// Returns a non-negative random single-precision floating point number that is less than the specified maximum. + /// + /// The instance. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to 0. + /// + /// + /// A random single-precision floating point number that is greater than or equal to 0, and less than + /// . + /// + /// is . + /// is less than 0. + public static float NextSingle(this Random random, float maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxValue)); + } + + return random.NextSingle(0, maxValue); + } + + /// + /// Returns a random single-precision floating point number that is within a specified range. + /// + /// The instance. + /// The inclusive lower bound of the random number returned. + /// + /// The exclusive upper bound of the random number returned. This value must be greater than or equal to + /// . + /// + /// + /// A random single-precision floating point number between and + /// . + /// + /// is . + /// + /// is less than . + /// + public static float NextSingle(this Random random, float minValue, float maxValue) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (maxValue < minValue) + { + throw new ArgumentException(ExceptionMessages.MaxValueGreaterThanEqualToMinValue); + } + + return MathUtility.Lerp(minValue, maxValue, random.NextSingle()); + } + + /// + /// Returns a new string of a specified length which is composed of specified characters. + /// + /// The instance. + /// The source collection of characters to poll. + /// The length of the new string to generate. + /// + /// A whose length is equal to that of , composed of characters + /// specified by the characters in . + /// + /// + /// is . + /// -or- + /// is . + /// + /// is less than 0. + public static string NextString(this Random random, IReadOnlyList source, int length) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), ExceptionMessages.LengthGreaterThanOrEqualTo0); + } + + if (length == 0) + { + return string.Empty; + } + + if (length == 1) + { + return source[random.Next(0, source.Count)].ToString(CultureInfo.InvariantCulture); + } + + var builder = new StringBuilder(length); + for (var i = 0; i < length; i++) + { + builder.Append(source[random.Next(0, source.Count)]); + } + + return builder.ToString(); + } +} diff --git a/X10D/src/DateTimeExtensions.cs b/X10D/src/DateTimeExtensions.cs deleted file mode 100644 index 84a559d..0000000 --- a/X10D/src/DateTimeExtensions.cs +++ /dev/null @@ -1,124 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class DateTimeExtensions - { - /// - /// Returns a rounded integer of the number of years since a given date as of today. - /// - /// The date from which to start. - /// Returns the number of years since as of today. - public static int Age(this DateTime date) - { - return date.Age(DateTime.Today); - } - - /// - /// Returns a rounded integer of the number of years since a given date as of another given date. - /// - /// The date from which to start. - /// The date at which to stop counting. - /// - /// Returns the integer number of years since as of - /// . - /// - public static int Age(this DateTime date, DateTime asOf) - { - return (int)(((asOf.Date - TimeSpan.FromDays(1) - date.Date).TotalDays + 1) / 365.2425); - } - - /// - /// Gets a DateTime representing the first occurence of a specified day in the current month. - /// - /// The current day. - /// The current day of week. - /// Returns a date representing the first occurence of . - public static DateTime First(this DateTime current, DayOfWeek dayOfWeek) - { - var first = current.FirstDayOfMonth(); - - if (first.DayOfWeek != dayOfWeek) - { - first = first.Next(dayOfWeek); - } - - return first; - } - - /// - /// Gets a representing the first day in the current month. - /// - /// The current date. - /// Returns a date representing the first day of the month>. - public static DateTime FirstDayOfMonth(this DateTime current) - { - return current.AddDays(1 - current.Day); - } - - /// - /// Gets a representing the last specified day in the current month. - /// - /// The current date. - /// The current day of week. - /// Returns a date representing the final occurence of . - public static DateTime Last(this DateTime current, DayOfWeek dayOfWeek) - { - var last = current.LastDayOfMonth(); - var lastDayOfWeek = last.DayOfWeek; - - var diff = dayOfWeek - lastDayOfWeek; - var offset = diff > 0 ? diff - 7 : diff; - - return last.AddDays(offset); - } - - /// - /// Gets a representing the last day in the current month. - /// - /// The current date. - /// Returns a date representing the last day of the month>. - public static DateTime LastDayOfMonth(this DateTime current) - { - var daysInMonth = DateTime.DaysInMonth(current.Year, current.Month); - return new DateTime(current.Year, current.Month, daysInMonth); - } - - /// - /// Gets a representing the first date following the current date which falls on the - /// given day of the week. - /// - /// The current date. - /// The day of week for the next date to get. - /// Returns a date representing the next occurence of . - public static DateTime Next(this DateTime current, DayOfWeek dayOfWeek) - { - var offsetDays = dayOfWeek - current.DayOfWeek; - - if (offsetDays <= 0) - { - offsetDays += 7; - } - - return current.AddDays(offsetDays); - } - - /// - /// Converts the to a Unix timestamp. - /// - /// The instance. - /// - /// Optional. Whether or not the return value should be represented as milliseconds. - /// Defaults to . - /// - /// Returns a Unix timestamp representing the provided . - public static long ToUnixTimeStamp(this DateTime time, bool useMillis = false) - { - DateTimeOffset offset = time; - return useMillis ? offset.ToUnixTimeMilliseconds() : offset.ToUnixTimeSeconds(); - } - } -} diff --git a/X10D/src/DictionaryExtensions.cs b/X10D/src/DictionaryExtensions.cs deleted file mode 100644 index 8f12bee..0000000 --- a/X10D/src/DictionaryExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -namespace X10D -{ - using System.Collections.Generic; - using System.Linq; - using System.Text.RegularExpressions; - using System.Web; - - /// - /// A set of extension methods for . - /// - public static class DictionaryExtensions - { - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this IReadOnlyDictionary dictionary) - { - static string SanitizeValue(T value) - { - return value is string str && - Regex.IsMatch(str, "\\s") - ? $"\"{str}\"" - : value.ToString(); - } - - var strings = dictionary.Select(o => $"{o.Key}={SanitizeValue(o.Value)}"); - return string.Join(";", strings); - } - - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this IDictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToConnectionString(); - } - - /// - /// Converts a to an object-relational-safe connection string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value; set. - public static string ToConnectionString(this Dictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToConnectionString(); - } - - /// - /// Converts an to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this IReadOnlyDictionary dictionary) - { - static string Sanitize(KeyValuePair kvp) - { - var key = HttpUtility.UrlEncode(kvp.Key.ToString()); - var value = HttpUtility.UrlEncode(kvp.Value.ToString()); - return $"{key}={value}"; - } - - return string.Join("&", dictionary.Select(Sanitize)); - } - - /// - /// Converts an to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this IDictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToGetParameters(); - } - - /// - /// Converts a to a HTTP GET parameter string. - /// - /// The key type. - /// The value type. - /// The dictionary. - /// Returns a representing the dictionary as a key=value& set. - public static string ToGetParameters(this Dictionary dictionary) - { - return ((IReadOnlyDictionary)dictionary).ToGetParameters(); - } - } -} diff --git a/X10D/src/DoubleExtensions.cs b/X10D/src/DoubleExtensions.cs deleted file mode 100644 index bd6e531..0000000 --- a/X10D/src/DoubleExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class DoubleExtensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static double Clamp(this double value, double min, double max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts an angle from degrees to radians. - /// - /// The angle in degrees. - /// Returns in radians. - public static double DegreesToRadians(this double angle) - { - return (Math.PI * angle) / 180.0; - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this double number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this double number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this double number) - { - return !number.IsEven(); - } - - /// - /// Converts an angle from radians to degrees. - /// - /// The angle in radians. - /// Returns in degrees. - public static double RadiansToDegrees(this double angle) - { - return angle * (180.0 / Math.PI); - } - - /// - /// Rounds to the nearest value. - /// - /// The value to round. - /// The nearest value. - /// Returns the rounded value. - public static double Round(this double v, double nearest = 1) - { - return Math.Round(v / nearest) * nearest; - } - } -} diff --git a/X10D/src/Drawing/RandomExtensions.cs b/X10D/src/Drawing/RandomExtensions.cs new file mode 100644 index 0000000..32cf1e8 --- /dev/null +++ b/X10D/src/Drawing/RandomExtensions.cs @@ -0,0 +1,43 @@ +using System.Drawing; + +namespace X10D.Drawing; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a color of random components for red, green, and blue. + /// + /// The instance. + /// A whose red, green, and blue components are all random, and whose alpha is 255 + /// is . + public static Color NextColorRgb(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int rgb = random.Next(); + return Color.FromArgb(0xFF, (byte)(rgb >> 16 & 0xFF), (byte)(rgb >> 8 & 0xFF), (byte)(rgb & 0xFF)); + } + + /// + /// Returns a color composed of random components for apha, red, green, and blue. + /// + /// The instance. + /// A whose alpha, red, green, and blue components are all random. + /// is . + public static Color NextColorArgb(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int argb = random.Next(); + return Color.FromArgb(argb); + } +} diff --git a/X10D/src/EndPointExtensions.cs b/X10D/src/EndPointExtensions.cs deleted file mode 100644 index 7da5099..0000000 --- a/X10D/src/EndPointExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace X10D -{ - using System.Net; - - /// - /// Extension methods for and derived types. - /// - public static class EndPointExtensions - { - /// - /// Gets the endpoint hostname. - /// - /// The endpoint whose hostname to get. - /// - /// Returns a representing the hostname, which may be an IP or a DNS, or empty - /// string on failure. - /// - public static string GetHost(this EndPoint endPoint) - { - return endPoint switch - { - IPEndPoint ip => ip.Address.ToString(), - DnsEndPoint dns => dns.Host, - var _ => string.Empty, - }; - } - - /// - /// Gets the endpoint port. - /// - /// The endpoint whose port to get. - /// Returns an representing the port, or 0 on failure. - public static int GetPort(this EndPoint endPoint) - { - return endPoint switch - { - IPEndPoint ip => ip.Port, - DnsEndPoint dns => dns.Port, - var _ => 0, - }; - } - } -} diff --git a/X10D/src/Endianness.cs b/X10D/src/Endianness.cs new file mode 100644 index 0000000..3b23c07 --- /dev/null +++ b/X10D/src/Endianness.cs @@ -0,0 +1,19 @@ +using System.ComponentModel; + +namespace X10D; + +/// +/// Represents an enumeration of endianness values. +/// +public enum Endianness +{ + /// + /// The value should be read as though it uses little endian encoding. + /// + [Description("The value should be read as though it uses little endian encoding.")] LittleEndian, + + /// + /// The value should be read as though it uses big endian encoding. + /// + [Description("The value should be read as though it uses big endian encoding.")] BigEndian +} diff --git a/X10D/src/EnumerableExtensions.cs b/X10D/src/EnumerableExtensions.cs deleted file mode 100644 index 9d09e43..0000000 --- a/X10D/src/EnumerableExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace X10D -{ - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class EnumerableExtensions - { - /// - /// Splits into chunks of size . - /// - /// Any type. - /// The collection to split. - /// The maximum length of the nested collection. - /// - /// Returns an of of - /// values. - /// - public static IEnumerable> Split(this IEnumerable value, int chunkSize) - { - var enumerable = value.ToArray(); - var count = enumerable.LongCount(); - chunkSize = chunkSize.Clamp(1, enumerable.Length); - - for (var i = 0; i < (int)(count / chunkSize); i++) - { - yield return enumerable.Skip(i * chunkSize).Take(chunkSize); - } - } - } -} diff --git a/X10D/src/ExceptionMessages.Designer.cs b/X10D/src/ExceptionMessages.Designer.cs new file mode 100644 index 0000000..7a62adb --- /dev/null +++ b/X10D/src/ExceptionMessages.Designer.cs @@ -0,0 +1,198 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace X10D { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class ExceptionMessages { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ExceptionMessages() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("X10D.src.ExceptionMessages", typeof(ExceptionMessages).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to The buffer is too small to contain the data.. + /// + internal static string BufferTooSmall { + get { + return ResourceManager.GetString("BufferTooSmall", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to count must be greater than or equal to 0.. + /// + internal static string CountMustBeGreaterThanOrEqualTo0 { + get { + return ResourceManager.GetString("CountMustBeGreaterThanOrEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HashAlgorithm's Create method returned null reference.. + /// + internal static string HashAlgorithmCreateReturnedNull { + get { + return ResourceManager.GetString("HashAlgorithmCreateReturnedNull", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to HashAlgorithm does not offer Create method.. + /// + internal static string HashAlgorithmNoCreateMethod { + get { + return ResourceManager.GetString("HashAlgorithmNoCreateMethod", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Length must be greater than or equal to 0.. + /// + internal static string LengthGreaterThanOrEqualTo0 { + get { + return ResourceManager.GetString("LengthGreaterThanOrEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} cannot be greater than {1}. + /// + internal static string LowerCannotBeGreaterThanUpper { + get { + return ResourceManager.GetString("LowerCannotBeGreaterThanUpper", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to maxValue must be greater than or equal to 0. + /// + internal static string MaxValueGreaterThanEqualTo0 { + get { + return ResourceManager.GetString("MaxValueGreaterThanEqualTo0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to maxValue must be greater than or equal to minValue. + /// + internal static string MaxValueGreaterThanEqualToMinValue { + get { + return ResourceManager.GetString("MaxValueGreaterThanEqualToMinValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream does not support reading.. + /// + internal static string StreamDoesNotSupportReading { + get { + return ResourceManager.GetString("StreamDoesNotSupportReading", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The stream does not support writing.. + /// + internal static string StreamDoesNotSupportWriting { + get { + return ResourceManager.GetString("StreamDoesNotSupportWriting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The length of the stream is too large.. + /// + internal static string StreamTooLarge { + get { + return ResourceManager.GetString("StreamTooLarge", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} does not inherit {1}. + /// + internal static string TypeDoesNotInheritAttribute { + get { + return ResourceManager.GetString("TypeDoesNotInheritAttribute", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not a class.. + /// + internal static string TypeIsNotClass { + get { + return ResourceManager.GetString("TypeIsNotClass", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} is not an interface.. + /// + internal static string TypeIsNotInterface { + get { + return ResourceManager.GetString("TypeIsNotInterface", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Year cannot be zero.. + /// + internal static string YearCannotBeZero { + get { + return ResourceManager.GetString("YearCannotBeZero", resourceCulture); + } + } + } +} diff --git a/X10D/src/ExceptionMessages.resx b/X10D/src/ExceptionMessages.resx new file mode 100644 index 0000000..e5beb21 --- /dev/null +++ b/X10D/src/ExceptionMessages.resx @@ -0,0 +1,71 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, + PublicKeyToken=b77a5c561934e089 + + + + The buffer is too small to contain the data. + + + {0} is not a class. + + + {0} is not an interface. + + + {0} does not inherit {1} + + + HashAlgorithm does not offer Create method. + + + HashAlgorithm's Create method returned null reference. + + + Length must be greater than or equal to 0. + + + The stream does not support reading. + + + The stream does not support writing. + + + The length of the stream is too large. + + + maxValue must be greater than or equal to 0 + + + maxValue must be greater than or equal to minValue + + + {0} cannot be greater than {1} + + + count must be greater than or equal to 0. + + + Year cannot be zero. + + \ No newline at end of file diff --git a/X10D/src/IO/BooleanExtensions.cs b/X10D/src/IO/BooleanExtensions.cs new file mode 100644 index 0000000..f5bc878 --- /dev/null +++ b/X10D/src/IO/BooleanExtensions.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// Extension methods for . +/// +public static class BooleanExtensions +{ + /// + /// Returns the current boolean value as an array of bytes. + /// + /// The value to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this bool value) + { + return BitConverter.GetBytes(value); + } + + /// + /// Converts a into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this bool value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } +} diff --git a/X10D/src/IO/ByteExtensions.cs b/X10D/src/IO/ByteExtensions.cs new file mode 100644 index 0000000..eace285 --- /dev/null +++ b/X10D/src/IO/ByteExtensions.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.Contracts; +using X10D.Core; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Returns the current 8-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this byte value) + { + return value.AsArrayValue(); + } + + /// + /// Converts a into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this byte value, Span destination) + { + if (destination.Length < 1) + { + return false; + } + + destination[0] = value; + return true; + } +} diff --git a/X10D/src/IO/DoubleExtensions.cs b/X10D/src/IO/DoubleExtensions.cs new file mode 100644 index 0000000..b33df1e --- /dev/null +++ b/X10D/src/IO/DoubleExtensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class DoubleExtensions +{ + /// + /// Returns the current double-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this double value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current double-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this double value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current double-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this double value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current double-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this double value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteDoubleBigEndian(destination, value) + : BinaryPrimitives.TryWriteDoubleLittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/FileInfoExtensions.cs b/X10D/src/IO/FileInfoExtensions.cs new file mode 100644 index 0000000..272c99a --- /dev/null +++ b/X10D/src/IO/FileInfoExtensions.cs @@ -0,0 +1,76 @@ +using System.Diagnostics.Contracts; +using System.Security.Cryptography; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class FileInfoExtensions +{ + /// + /// Computes the hash of a file using the specified hash algorithm. + /// + /// The file whose hash to compute. + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// The hash of represented as an array of bytes. + /// is . + /// The specified file was not found. + /// The opened file stream cannot be read. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + [Pure] + public static byte[] GetHash(this FileInfo value) + where T : HashAlgorithm + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + using FileStream stream = value.OpenRead(); + return stream.GetHash(); + } + + /// + /// Computes the hash of a file using the specified hash algorithm. + /// + /// The file whose hash to compute. + /// When this method returns, contains the computed hash of . + /// + /// When this method returns, the total number of bytes written into destination. This parameter is treated as + /// uninitialized. + /// + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// + /// if the destination is long enough to receive the hash; otherwise, . + /// + /// is . + /// The specified file was not found. + /// The opened file stream cannot be read. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static bool TryWriteHash(this FileInfo value, Span destination, out int bytesWritten) + where T : HashAlgorithm + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + using FileStream stream = value.OpenRead(); + return stream.TryWriteHash(destination, out bytesWritten); + } +} diff --git a/X10D/src/IO/Int16Extensions.cs b/X10D/src/IO/Int16Extensions.cs new file mode 100644 index 0000000..61be94a --- /dev/null +++ b/X10D/src/IO/Int16Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Returns the current 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this short value) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this short value, Endianness endianness) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this short value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 16-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this short value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt16BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt16LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/Int32Extensions.cs b/X10D/src/IO/Int32Extensions.cs new file mode 100644 index 0000000..57668fb --- /dev/null +++ b/X10D/src/IO/Int32Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Returns the current 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this int value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this int value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 32-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this int value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 32-bit signed integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this int value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt32BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt32LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/Int64Extensions.cs b/X10D/src/IO/Int64Extensions.cs new file mode 100644 index 0000000..2fd7f05 --- /dev/null +++ b/X10D/src/IO/Int64Extensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Returns the current 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this long value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this long value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 64-bit signed integer a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this long value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 64-bit signed integer a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this long value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteInt64BigEndian(destination, value) + : BinaryPrimitives.TryWriteInt64LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/ListOfByteExtensions.cs b/X10D/src/IO/ListOfByteExtensions.cs new file mode 100644 index 0000000..dbec6e1 --- /dev/null +++ b/X10D/src/IO/ListOfByteExtensions.cs @@ -0,0 +1,290 @@ +using System.Text; + +namespace X10D.IO; + +/// +/// Extension methods for array. +/// +public static class ListOfByteExtensions +{ + /// + /// Converts the numeric value of each element of a specified list of bytes to its equivalent hexadecimal string + /// representation. + /// + /// The source list of bytes. + /// + /// A string of hexadecimal pairs separated by hyphens, where each pair represents the corresponding element in + /// ; for example, "7F-2C-4A-00". + /// + /// is . + public static string AsString(this IReadOnlyList source) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToString(source.ToArray()); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes. + /// + /// The source list of bytes. + /// A double-precision floating point number formed by eight bytes. + /// is . + public static double ToDouble(this IReadOnlyList source) + { + return ToDouble(source, 0); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes at a specified position in a list of + /// bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// + /// A double-precision floating point number formed by eight bytes beginning at . + /// + /// is . + public static double ToDouble(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToDouble(source.ToArray(), startIndex); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes. + /// + /// The source list of bytes. + /// A 16-bit signed integer formed by two bytes. + /// is . + public static short ToInt16(this IReadOnlyList source) + { + return ToInt16(source, 0); + } + + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 16-bit signed integer formed by two bytes beginning at . + /// is . + public static short ToInt16(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt16(source.ToArray(), startIndex); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes. + /// + /// The source list of bytes. + /// A 32-bit signed integer formed by four bytes. + /// is . + public static int ToInt32(this IReadOnlyList source) + { + return ToInt32(source, 0); + } + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 32-bit signed integer formed by four bytes beginning at . + /// is . + public static int ToInt32(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt32(source.ToArray(), startIndex); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes. + /// + /// The source list of bytes. + /// A 64-bit signed integer formed by eight bytes. + /// is . + public static long ToInt64(this IReadOnlyList source) + { + return ToInt64(source, 0); + } + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 64-bit signed integer formed by eight bytes beginning at . + /// is . + public static long ToInt64(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToInt64(source.ToArray(), startIndex); + } + + /// + /// Returns a single-precision floating point number converted from four bytes. + /// + /// The source list of bytes. + /// A single-precision floating point number formed by four bytes. + /// is . + public static float ToSingle(this IReadOnlyList source) + { + return ToSingle(source, 0); + } + + /// + /// Returns a single-precision floating point number converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// + /// A single-precision floating point number formed by four bytes beginning at . + /// + /// is . + public static float ToSingle(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToSingle(source.ToArray(), startIndex); + } + + /// + /// Decodes all the bytes within the current list of bytes to a string, using a specified encoding. + /// + /// The source list of bytes. + /// The encoding which should be used to decode . + /// A string that contains the results of decoding the specified sequence of bytes. + /// + /// is . + /// -or- + /// is . + /// + public static string ToString(this IReadOnlyList source, Encoding encoding) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (encoding is null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return encoding.GetString(source.ToArray()); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes. + /// + /// The source list of bytes. + /// A 16-bit unsigned integer formed by two bytes. + /// is . + [CLSCompliant(false)] + public static ushort ToUInt16(this IReadOnlyList source) + { + return ToUInt16(source, 0); + } + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 16-bit unsigned integer formed by two bytes beginning at . + /// is . + [CLSCompliant(false)] + public static ushort ToUInt16(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt16(source.ToArray(), startIndex); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes. + /// + /// The source list of bytes. + /// A 32-bit unsigned integer formed by four bytes. + /// is . + [CLSCompliant(false)] + public static uint ToUInt32(this IReadOnlyList source) + { + return ToUInt32(source, 0); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 32-bit unsigned integer formed by four bytes beginning at . + /// is . + [CLSCompliant(false)] + public static uint ToUInt32(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt32(source.ToArray(), startIndex); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes. + /// + /// The source list of bytes. + /// A 64-bit unsigned integer formed by eight bytes. + /// is . + [CLSCompliant(false)] + public static ulong ToUInt64(this IReadOnlyList source) + { + return ToUInt64(source, 0); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a list of bytes. + /// + /// The source list of bytes. + /// The starting position within . + /// A 64-bit unsigned integer formed by eight bytes beginning at . + /// is . + [CLSCompliant(false)] + public static ulong ToUInt64(this IReadOnlyList source, int startIndex) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + return BitConverter.ToUInt64(source.ToArray(), startIndex); + } +} diff --git a/X10D/src/IO/SByteExtensions.cs b/X10D/src/IO/SByteExtensions.cs new file mode 100644 index 0000000..d3e02c5 --- /dev/null +++ b/X10D/src/IO/SByteExtensions.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 1. + [Pure] + public static byte[] GetBytes(this sbyte value) + { + Span buffer = stackalloc byte[1]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this sbyte value, Span destination) + { + if (destination.Length < 1) + { + return false; + } + + destination[0] = (byte)value; + return true; + } +} diff --git a/X10D/src/IO/SingleExtensions.cs b/X10D/src/IO/SingleExtensions.cs new file mode 100644 index 0000000..ea9ae4b --- /dev/null +++ b/X10D/src/IO/SingleExtensions.cs @@ -0,0 +1,62 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns the current single-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this float value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current single-precision floating-point value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this float value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current single-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this float value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current single-precision floating-point into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this float value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteSingleBigEndian(destination, value) + : BinaryPrimitives.TryWriteSingleLittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/StreamExtensions.cs b/X10D/src/IO/StreamExtensions.cs new file mode 100644 index 0000000..ab353de --- /dev/null +++ b/X10D/src/IO/StreamExtensions.cs @@ -0,0 +1,877 @@ +using System.Buffers.Binary; +using System.Reflection; +using System.Security.Cryptography; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +public static class StreamExtensions +{ + private static readonly Endianness DefaultEndianness = + BitConverter.IsLittleEndian ? Endianness.LittleEndian : Endianness.BigEndian; + + /// + /// Returns the hash of the current stream as an array of bytes using the specified hash algorithm. + /// + /// The stream whose hash is to be computed. + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// The hash of represented as an array of bytes. + /// is + /// does not support reading. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static byte[] GetHash(this Stream stream) + where T : HashAlgorithm + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead) + { + throw new IOException(ExceptionMessages.StreamDoesNotSupportReading); + } + + Type type = typeof(T); + + MethodInfo? createMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(c => c.Name == "Create" && c.GetParameters().Length == 0); + if (createMethod is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmNoCreateMethod)); + } + + using var crypt = createMethod.Invoke(null, null) as T; + if (crypt is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmCreateReturnedNull)); + } + + return crypt.ComputeHash(stream); + } + + /// + /// Reads a decimal value from the current stream using the system's default endian encoding, and advances the stream + /// position by sixteen bytes. + /// + /// The stream to read. + /// A sixteen-byte decimal value read from the stream. + public static decimal ReadDecimal(this Stream stream) + { + return stream.ReadDecimal(DefaultEndianness); + } + + /// + /// Reads a decimal value from the current stream using a specified endian encoding, and advances the stream position + /// by sixteen bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A decimal value read from the stream. + public static decimal ReadDecimal(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + const int decimalSize = sizeof(decimal); + const int int32Size = sizeof(int); + const int partitionSize = decimalSize / int32Size; + + var bits = new int[partitionSize]; + for (var index = 0; index < partitionSize; index++) + { + bits[index] = stream.ReadInt32(endianness); + } + + if (endianness != DefaultEndianness) + { + Array.Reverse(bits); + } + + return new decimal(bits); + } + + /// + /// Reads a double-precision floating point value from the current stream using the system's default endian encoding, + /// and advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// A double-precision floating point value read from the stream. + public static double ReadDouble(this Stream stream) + { + return stream.ReadDouble(DefaultEndianness); + } + + /// + /// Reads a double-precision floating point value from the current stream using a specified endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A double-precision floating point value read from the stream. + public static double ReadDouble(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(double)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadDoubleLittleEndian(buffer) + : BinaryPrimitives.ReadDoubleBigEndian(buffer); + } + + /// + /// Reads a two-byte signed integer from the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream from which the value should be read. + /// An two-byte signed integer read from the stream. + public static short ReadInt16(this Stream stream) + { + return stream.ReadInt16(DefaultEndianness); + } + + /// + /// Reads a two-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An two-byte unsigned integer read from the stream. + public static short ReadInt16(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(short)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt16LittleEndian(buffer) + : BinaryPrimitives.ReadInt16BigEndian(buffer); + } + + /// + /// Reads a four-byte signed integer from the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// An four-byte signed integer read from the stream. + public static int ReadInt32(this Stream stream) + { + return stream.ReadInt32(DefaultEndianness); + } + + /// + /// Reads a four-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An four-byte unsigned integer read from the stream. + public static int ReadInt32(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(int)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt32LittleEndian(buffer) + : BinaryPrimitives.ReadInt32BigEndian(buffer); + } + + /// + /// Reads an eight-byte signed integer from the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// An eight-byte signed integer read from the stream. + public static long ReadInt64(this Stream stream) + { + return stream.ReadInt64(DefaultEndianness); + } + + /// + /// Reads an eight-byte signed integer from the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An eight-byte unsigned integer read from the stream. + public static long ReadInt64(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(long)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadInt64LittleEndian(buffer) + : BinaryPrimitives.ReadInt64BigEndian(buffer); + } + + /// + /// Reads a single-precision floating point value from the current stream using the system's default endian encoding, + /// and advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// A single-precision floating point value read from the stream. + public static double ReadSingle(this Stream stream) + { + return stream.ReadSingle(DefaultEndianness); + } + + /// + /// Reads a double-precision floating point value from the current stream using a specified endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// A single-precision floating point value read from the stream. + public static double ReadSingle(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(float)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadSingleLittleEndian(buffer) + : BinaryPrimitives.ReadSingleBigEndian(buffer); + } + + /// + /// Reads a two-byte unsigned integer from the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream from which the value should be read. + /// An two-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ushort ReadUInt16(this Stream stream) + { + return stream.ReadUInt16(DefaultEndianness); + } + + /// + /// Reads a two-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An two-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ushort ReadUInt16(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ushort)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt16LittleEndian(buffer) + : BinaryPrimitives.ReadUInt16BigEndian(buffer); + } + + /// + /// Reads a four-byte unsigned integer from the current stream using the system's default endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream from which the value should be read. + /// An four-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static uint ReadUInt32(this Stream stream) + { + return stream.ReadUInt32(DefaultEndianness); + } + + /// + /// Reads a four-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An four-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static uint ReadUInt32(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(uint)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt32LittleEndian(buffer) + : BinaryPrimitives.ReadUInt32BigEndian(buffer); + } + + /// + /// Reads an eight-byte unsigned integer from the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// An eight-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ulong ReadUInt64(this Stream stream) + { + return stream.ReadUInt64(DefaultEndianness); + } + + /// + /// Reads an eight-byte unsigned integer from the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream from which the value should be read. + /// The endian encoding to use. + /// An eight-byte unsigned integer read from the stream. + [CLSCompliant(false)] + public static ulong ReadUInt64(this Stream stream, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ulong)]; + stream.Read(buffer); + + return endianness == Endianness.LittleEndian + ? BinaryPrimitives.ReadUInt64LittleEndian(buffer) + : BinaryPrimitives.ReadUInt64BigEndian(buffer); + } + + /// + /// Returns the hash of the current stream as an array of bytes using the specified hash algorithm. + /// + /// The stream whose hash is to be computed. + /// When this method returns, contains the computed hash of . + /// + /// When this method returns, the total number of bytes written into destination. This parameter is treated as + /// uninitialized. + /// + /// + /// The type of the whose is to be used for + /// computing the hash. + /// + /// + /// if the destination is long enough to receive the hash; otherwise, . + /// + /// is + /// does not support reading. + /// + /// The specified does not offer a public, static. parameterless Create method, or its + /// Create method returns a type that is not assignable to . + /// + /// The stream has already been disposed. + public static bool TryWriteHash(this Stream stream, Span destination, out int bytesWritten) + where T : HashAlgorithm + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!stream.CanRead) + { + throw new IOException(ExceptionMessages.StreamDoesNotSupportReading); + } + + Type type = typeof(T); + + MethodInfo? createMethod = type.GetMethods(BindingFlags.Public | BindingFlags.Static) + .FirstOrDefault(c => c.Name == "Create" && c.GetParameters().Length == 0); + if (createMethod is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmNoCreateMethod)); + } + + using var crypt = createMethod.Invoke(null, null) as T; + if (crypt is null) + { + throw new TypeInitializationException(type.FullName, + new ArgumentException(ExceptionMessages.HashAlgorithmCreateReturnedNull)); + } + + if (stream.Length > int.MaxValue) + { + throw new ArgumentException(ExceptionMessages.StreamTooLarge); + } + + Span buffer = stackalloc byte[(int)stream.Length]; + _ = stream.Read(buffer); // we don't care about the number of bytes read. we can ignore MustUseReturnValue + return crypt.TryComputeHash(buffer, destination, out bytesWritten); + } + + /// + /// Writes a two-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, short value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(short)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt16LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt16BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a four-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, int value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(int)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt32LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt32BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the system's default endian encoding, and advances + /// the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, long value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(long)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteInt64LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteInt64BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a two-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by two bytes. + /// + /// The stream to which the value should be written. + /// The two-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ushort value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ushort)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt16BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the system's default endian encoding, and advances + /// the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes a four-byte unsigned integer to the current stream using the specified endian encoding, and advances the + /// stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The four-byte unsigned integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, uint value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(uint)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt32BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes an eight-byte unsigned integer to the current stream using the system's default endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte unsigned integer to write. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value) + { + return stream.Write(value, DefaultEndianness); + } + + /// + /// Writes an eight-byte signed integer to the current stream using the specified endian encoding, and advances the + /// stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The eight-byte signed integer to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + [CLSCompliant(false)] + public static int Write(this Stream stream, ulong value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(ulong)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteUInt64BigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a single-precision floating point value to the current stream using the specified endian encoding, and + /// advances the stream position by four bytes. + /// + /// The stream to which the value should be written. + /// The single-precision floating point value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, float value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(float)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteSingleLittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteSingleBigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a double-precision floating point value to the current stream using the specified endian encoding, and + /// advances the stream position by eight bytes. + /// + /// The stream to which the value should be written. + /// The double-precision floating point value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, double value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + Span buffer = stackalloc byte[sizeof(double)]; + + if (endianness == Endianness.LittleEndian) + { + BinaryPrimitives.WriteDoubleLittleEndian(buffer, value); + } + else + { + BinaryPrimitives.WriteDoubleBigEndian(buffer, value); + } + + return stream.WriteInternal(buffer); + } + + /// + /// Writes a decimal value to the current stream using the specified endian encoding, and advances the stream position + /// by sixteen bytes. + /// + /// The stream to which the value should be written. + /// The decimal value to write. + /// The endian encoding to use. + /// The number of bytes written to the stream. + public static int Write(this Stream stream, decimal value, Endianness endianness) + { + if (stream is null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (!Enum.IsDefined(endianness)) + { + throw new ArgumentOutOfRangeException(nameof(endianness)); + } + + int[] bits = decimal.GetBits(value); + long preWritePosition = stream.Position; + + if (endianness != DefaultEndianness) + { + Array.Reverse(bits); + } + + foreach (int section in bits) + { + stream.Write(section, endianness); + } + + return (int)(stream.Position - preWritePosition); + } + + private static int WriteInternal(this Stream stream, Span value) + { + long preWritePosition = stream.Position; + stream.Write(value); + return (int)(stream.Position - preWritePosition); + } +} diff --git a/X10D/src/IO/UInt16Extensions.cs b/X10D/src/IO/UInt16Extensions.cs new file mode 100644 index 0000000..5eb12ae --- /dev/null +++ b/X10D/src/IO/UInt16Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this ushort value) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 2. + [Pure] + public static byte[] GetBytes(this ushort value, Endianness endianness) + { + Span buffer = stackalloc byte[2]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ushort value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 16-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ushort value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt16BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt16LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/UInt32Extensions.cs b/X10D/src/IO/UInt32Extensions.cs new file mode 100644 index 0000000..c4f3c30 --- /dev/null +++ b/X10D/src/IO/UInt32Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Returns the current 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this uint value) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 4. + [Pure] + public static byte[] GetBytes(this uint value, Endianness endianness) + { + Span buffer = stackalloc byte[4]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 32-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this uint value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 32-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this uint value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt32BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt32LittleEndian(destination, value); + } +} diff --git a/X10D/src/IO/UInt64Extensions.cs b/X10D/src/IO/UInt64Extensions.cs new file mode 100644 index 0000000..1f48869 --- /dev/null +++ b/X10D/src/IO/UInt64Extensions.cs @@ -0,0 +1,63 @@ +using System.Buffers.Binary; +using System.Diagnostics.Contracts; + +namespace X10D.IO; + +/// +/// IO-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Returns the current 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this ulong value) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer); + return buffer.ToArray(); + } + + /// + /// Returns the current 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// The endianness with which to write the bytes. + /// An array of bytes with length 8. + [Pure] + public static byte[] GetBytes(this ulong value, Endianness endianness) + { + Span buffer = stackalloc byte[8]; + value.TryWriteBytes(buffer, endianness); + return buffer.ToArray(); + } + + /// + /// Converts the current 64-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ulong value, Span destination) + { + return BitConverter.TryWriteBytes(destination, value); + } + + /// + /// Converts the current 64-bit unsigned integer into a span of bytes. + /// + /// The value. + /// When this method returns, the bytes representing the converted . + /// The endianness with which to write the bytes. + /// if the conversion was successful; otherwise, . + public static bool TryWriteBytes(this ulong value, Span destination, Endianness endianness) + { + return endianness == Endianness.BigEndian + ? BinaryPrimitives.TryWriteUInt64BigEndian(destination, value) + : BinaryPrimitives.TryWriteUInt64LittleEndian(destination, value); + } +} diff --git a/X10D/src/Int16Extensions.cs b/X10D/src/Int16Extensions.cs deleted file mode 100644 index 264891f..0000000 --- a/X10D/src/Int16Extensions.cs +++ /dev/null @@ -1,189 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int16Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static short Clamp(this short value, short min, short max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static ushort Clamp(this ushort value, ushort min, ushort max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to .. - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this short timestamp, bool isMillis = false) - { - return ((long)timestamp).FromUnixTimestamp(isMillis); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this ushort number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this short number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this short number) - { - return ((long)number).IsEven(); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this ushort number) - { - return ((ulong)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this short number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this ushort number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this short number) - { - return ((long)number).IsPrime(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsPrime(this ushort number) - { - return ((ulong)number).IsPrime(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this short value) - { - return ((long)value).ToBoolean(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this ushort value) - { - return ((ulong)value).ToBoolean(); - } - } -} diff --git a/X10D/src/Int32Extensions.cs b/X10D/src/Int32Extensions.cs deleted file mode 100644 index 309cfe5..0000000 --- a/X10D/src/Int32Extensions.cs +++ /dev/null @@ -1,175 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int32Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static int Clamp(this int value, int min, int max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static uint Clamp(this uint value, uint min, uint max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to .. - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this int timestamp, bool isMillis = false) - { - return ((long)timestamp).FromUnixTimestamp(isMillis); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this uint number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this int number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this int number) - { - return ((long)number).IsEven(); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this uint number) - { - return ((ulong)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this int number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this uint number) - { - return !number.IsEven(); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this int number) - { - return ((long)number).IsPrime(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this int value) - { - return ((long)value).ToBoolean(); - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this uint value) - { - return ((ulong)value).ToBoolean(); - } - } -} diff --git a/X10D/src/Int64Extensions.cs b/X10D/src/Int64Extensions.cs deleted file mode 100644 index 8a54030..0000000 --- a/X10D/src/Int64Extensions.cs +++ /dev/null @@ -1,241 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class Int64Extensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static long Clamp(this long value, long min, long max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - [CLSCompliant(false)] - public static ulong Clamp(this ulong value, ulong min, ulong max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts the to a treating it as a Unix timestamp. - /// - /// The timestamp. - /// - /// Optional. Whether or not the input value should be treated as milliseconds. Defaults - /// to . - /// - /// - /// Returns a representing seconds since the Unix - /// epoch. - /// - public static DateTime FromUnixTimestamp(this long timestamp, bool isMillis = false) - { - var offset = isMillis - ? DateTimeOffset.FromUnixTimeMilliseconds(timestamp) - : DateTimeOffset.FromUnixTimeSeconds(timestamp); - - return offset.DateTime; - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - [CLSCompliant(false)] - public static byte[] GetBytes(this ulong number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this long number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this long number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsEven(this ulong number) - { - return Math.Abs(number % 2.0) < double.Epsilon; - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this long number) - { - return !IsEven(number); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsOdd(this ulong number) - { - return !IsEven(number); - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - public static bool IsPrime(this long number) - { - if (number <= 1) - { - return false; - } - - if (number == 2) - { - return true; - } - - if (number % 2 == 0) - { - return false; - } - - var boundary = (long)Math.Floor(Math.Sqrt(number)); - for (var i = 3; i <= boundary; i += 2) - { - if (number % i == 0) - { - return false; - } - } - - return true; - } - - /// - /// Determines if the is a prime number. - /// - /// The number. - /// - /// Returns if is prime, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool IsPrime(this ulong number) - { - if (number <= 1) - { - return false; - } - - if (number == 2) - { - return true; - } - - if (number % 2 == 0) - { - return false; - } - - var boundary = (ulong)Math.Floor(Math.Sqrt(number)); - for (uint i = 3; i <= boundary; i += 2) - { - if (number % i == 0) - { - return false; - } - } - - return true; - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - public static bool ToBoolean(this long value) - { - return value != 0; - } - - /// - /// Gets an boolean value that represents this integer. - /// - /// The integer. - /// - /// Returns if is 0, - /// otherwise. - /// - [CLSCompliant(false)] - public static bool ToBoolean(this ulong value) - { - return value != 0; - } - } -} diff --git a/X10D/src/Linq/ByteExtensions.cs b/X10D/src/Linq/ByteExtensions.cs new file mode 100644 index 0000000..ee049ff --- /dev/null +++ b/X10D/src/Linq/ByteExtensions.cs @@ -0,0 +1,137 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class ByteExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static byte Product(this IEnumerable source) + { + return source.Aggregate((byte)1, (current, value) => (byte)(current * value)); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static sbyte Product(this IEnumerable source) + { + return source.Aggregate((sbyte)1, (current, value) => (sbyte)(current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static byte Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static sbyte Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 8-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 8-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, byte end) + { + byte start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (byte current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 16-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 16-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, short end) + { + short start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (short current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this byte value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/DecimalExtensions.cs b/X10D/src/Linq/DecimalExtensions.cs new file mode 100644 index 0000000..bcdf193 --- /dev/null +++ b/X10D/src/Linq/DecimalExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class DecimalExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static decimal Product(this IEnumerable source) + { + return source.Aggregate(1m, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static decimal Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/DoubleExtensions.cs b/X10D/src/Linq/DoubleExtensions.cs new file mode 100644 index 0000000..70ac1ca --- /dev/null +++ b/X10D/src/Linq/DoubleExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class DoubleExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static double Product(this IEnumerable source) + { + return source.Aggregate(1.0, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static double Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/Int16Extensions.cs b/X10D/src/Linq/Int16Extensions.cs new file mode 100644 index 0000000..c6c3872 --- /dev/null +++ b/X10D/src/Linq/Int16Extensions.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int16Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static short Product(this IEnumerable source) + { + return source.Aggregate((short)1, (current, value) => (short)(current * value)); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static ushort Product(this IEnumerable source) + { + return source.Aggregate((ushort)1, (current, value) => (ushort)(current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static short Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static ushort Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 16-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 16-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, short end) + { + short start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (short current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this short value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/Int32Extensions.cs b/X10D/src/Linq/Int32Extensions.cs new file mode 100644 index 0000000..923470e --- /dev/null +++ b/X10D/src/Linq/Int32Extensions.cs @@ -0,0 +1,97 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int32Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static int Product(this IEnumerable source) + { + return source.Aggregate(1, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static uint Product(this IEnumerable source) + { + return source.Aggregate(1u, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static int Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static uint Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 32-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 32-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this int value, int end) + { + int start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (int current = start; current < end; current++) + { + yield return current; + } + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this int value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/Int64Extensions.cs b/X10D/src/Linq/Int64Extensions.cs new file mode 100644 index 0000000..4870456 --- /dev/null +++ b/X10D/src/Linq/Int64Extensions.cs @@ -0,0 +1,77 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class Int64Extensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static long Product(this IEnumerable source) + { + return source.Aggregate(1L, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + [CLSCompliant(false)] + public static ulong Product(this IEnumerable source) + { + return source.Aggregate(1UL, (current, value) => current * value); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function on + /// each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static long Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + [CLSCompliant(false)] + public static ulong Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } + + /// + /// Returns an enumerable sequence of 64-bit integers ranging from the current value to a specified value. + /// + /// The starting value of the sequence. + /// The ending value of the sequence. + /// + /// An enumerable collection of 64-bit integers, ranging from to . + /// + [Pure] + public static IEnumerable RangeTo(this long value, long end) + { + long start = System.Math.Min(value, end); + end = System.Math.Max(value, end); + + for (long current = start; current < end; current++) + { + yield return current; + } + } +} diff --git a/X10D/src/Linq/ReadOnlySpanExtensions.cs b/X10D/src/Linq/ReadOnlySpanExtensions.cs new file mode 100644 index 0000000..3bbbfca --- /dev/null +++ b/X10D/src/Linq/ReadOnlySpanExtensions.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// Extension methods for . +/// +public static class ReadOnlySpanExtensions +{ + /// + /// Determines whether all elements of a read-only span satisfy a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if every element of the source sequence passes the test in the specified predicate, or if the + /// span is empty; otherwise, . + /// + /// is . + [Pure] + public static bool All(this ReadOnlySpan source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return true; + } + + for (var index = 0; index < source.Length; index++) + { + if (!predicate(source[index])) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether any element of a read-only span satisfies a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if the source span is not empty and at least one of its elements passes the test in the + /// specified predicate; otherwise, . + /// + /// is . + [Pure] + public static bool Any(this ReadOnlySpan source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return false; + } + + for (var index = 0; index < source.Length; index++) + { + if (predicate(source[index])) + { + return true; + } + } + + return false; + } +} diff --git a/X10D/src/Linq/SingleExtensions.cs b/X10D/src/Linq/SingleExtensions.cs new file mode 100644 index 0000000..1797eaf --- /dev/null +++ b/X10D/src/Linq/SingleExtensions.cs @@ -0,0 +1,30 @@ +namespace X10D.Linq; + +/// +/// LINQ-inspired extension methods for of . +/// +public static class SingleExtensions +{ + /// + /// Computes the product of a sequence of values. + /// + /// A sequence of values that are used to calculate the product. + /// The product the values in the sequence. + public static float Product(this IEnumerable source) + { + return source.Aggregate(1f, (current, value) => (current * value)); + } + + /// + /// Computes the product of a sequence of values that are obtained by invoking a transform function + /// on each element of the input sequence. + /// + /// A sequence of values that are used to calculate a product. + /// A transform function to apply to each element. + /// The type of the elements of . + /// The product of the projected values. + public static float Product(this IEnumerable source, Func selector) + { + return source.Select(selector).Product(); + } +} diff --git a/X10D/src/Linq/SpanExtensions.cs b/X10D/src/Linq/SpanExtensions.cs new file mode 100644 index 0000000..6de52f3 --- /dev/null +++ b/X10D/src/Linq/SpanExtensions.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.Contracts; + +namespace X10D.Linq; + +/// +/// Extension methods for . +/// +public static class SpanExtensions +{ + /// + /// Determines whether all elements of a span satisfy a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if every element of the source sequence passes the test in the specified predicate, or if the + /// span is empty; otherwise, . + /// + /// is . + [Pure] + public static bool All(this Span source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return true; + } + + for (var index = 0; index < source.Length; index++) + { + if (!predicate(source[index])) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether any element of a span satisfies a condition. + /// + /// A that contains the elements to apply the predicate to. + /// A function to test each element for a condition. + /// The type of the elements of . + /// + /// if the source span is not empty and at least one of its elements passes the test in the + /// specified predicate; otherwise, . + /// + /// is . + [Pure] + public static bool Any(this Span source, Predicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + if (source.IsEmpty) + { + return false; + } + + for (var index = 0; index < source.Length; index++) + { + if (predicate(source[index])) + { + return true; + } + } + + return false; + } +} diff --git a/X10D/src/ListExtensions.cs b/X10D/src/ListExtensions.cs deleted file mode 100644 index e4e5086..0000000 --- a/X10D/src/ListExtensions.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class ListExtensions - { - /// - /// Returns a random element from using a new instance. - /// - /// The collection type. - /// The collection to draw from. - /// Returns a random element of type from . - public static T OneOf(this IEnumerable source) - { - return source.OneOf(new Random()); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The collection to draw from. - /// The instance. - /// Returns a random element of type from . - public static T OneOf(this IEnumerable source, Random random) - { - return source.ToList().OneOf(random); - } - - /// - /// Returns a random element from using a new instance. - /// - /// The collection type. - /// The collection to draw from. - /// Returns a random element of type from . - public static T OneOf(this IList source) - { - return source.OneOf(new Random()); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The collection to draw from. - /// The instance. - /// Returns a random element of type from . - public static T OneOf(this IList source, Random random) - { - return random.OneOf(source); - } - - /// - /// Shuffles an enumerable. - /// - /// The collection type. - /// The collection to shuffle. - /// Returns shuffled. - public static IEnumerable Shuffle(this IEnumerable source) - { - return source.Shuffle(new Random()); - } - - /// - /// Shuffles an enumerable. - /// - /// The collection type. - /// The collection to shuffle. - /// The instance. - /// Returns shuffled. - public static IEnumerable Shuffle(this IEnumerable source, Random random) - { - return source.OrderBy(_ => random.Next()); - } - - /// - /// Shuffles a list. - /// - /// The collection type. - /// The collection to shuffle. - /// Returns shuffled. - public static IEnumerable Shuffle(this IList source) - { - return source.Shuffle(new Random()); - } - - /// - /// Shuffles a list. - /// - /// The collection type. - /// The collection to shuffle. - /// The instance. - /// Returns shuffled. - public static IEnumerable Shuffle(this IList source, Random random) - { - return source.OrderBy(_ => random.Next()); - } - } -} diff --git a/X10D/src/Math/ByteExtensions.cs b/X10D/src/Math/ByteExtensions.cs new file mode 100644 index 0000000..8e25088 --- /dev/null +++ b/X10D/src/Math/ByteExtensions.cs @@ -0,0 +1,109 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Computes the digital root of this 16-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte DigitalRoot(this byte value) + { + int root = value % 9; + return (byte)(root == 0 ? 9 : root); + } + + /// + /// Returns the factorial of the current 8-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this byte value) + { + if (value == 0) + { + return 1; + } + + var result = 1L; + for (byte i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this byte value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this byte value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + public static bool IsPrime(this byte value) + { + return ((long)value).IsPrime(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this byte value) + { + return ((long)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/ComparableExtensions.cs b/X10D/src/Math/ComparableExtensions.cs new file mode 100644 index 0000000..53bbc0e --- /dev/null +++ b/X10D/src/Math/ComparableExtensions.cs @@ -0,0 +1,321 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class ComparableExtensions +{ + /// + /// Determines if a specified value falls exclusively between a specified lower bound and upper bound. + /// + /// An type. + /// The first comparison operand type. + /// The second comparison operand type. + /// The value to compare. + /// The exclusive lower bound. + /// The exclusive upper bound. + /// The comparison clusivity. + /// + /// if is between the and + /// + /// -or- + /// otherwise. + /// + /// + /// + /// int firstValue = 42; + /// int secondValue = 15; + /// + /// int lower = 0; + /// int upper = 20; + /// + /// Console.WriteLine($"{firstValue} between {lower} and {upper}?"); + /// Console.WriteLine(firstValue.Between(lower, upper)); + /// + /// Console.WriteLine($"{secondValue} between {lower} and {upper}?"); + /// Console.WriteLine(secondValue.Between(lower, upper)); + /// + /// // This will output the following: + /// // 42 between 0 and 20? + /// // False + /// // 15 between 0 and 20? + /// // True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Between(this T1 value, T2 lower, T3 upper, + InclusiveOptions inclusiveOptions = InclusiveOptions.None) + where T1 : IComparable, IComparable + where T2 : IComparable + where T3 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (lower.GreaterThan(upper)) + { + throw new ArgumentException( + string.Format(CultureInfo.CurrentCulture, ExceptionMessages.LowerCannotBeGreaterThanUpper, lower, upper), + nameof(lower)); + } + + bool lowerComparison = (inclusiveOptions & InclusiveOptions.LowerInclusive) != 0 + ? value.CompareTo(lower) >= 0 + : value.CompareTo(lower) > 0; + + bool upperComparison = (inclusiveOptions & InclusiveOptions.UpperInclusive) != 0 + ? value.CompareTo(upper) <= 0 + : value.CompareTo(upper) < 0; + + return lowerComparison && upperComparison; + } + + /// + /// Returns the current value clamped to the inclusive range of and . + /// + /// The value to be clamped. + /// The lower bound of the result. + /// The upper bound of the result. + /// An type. + /// + /// if . + /// -or- + /// if < . + /// -or- + /// if < . + /// + /// is greater than . + /// + /// + /// int value = 42; + /// int lower = 0; + /// int upper = 20; + /// + /// int clamped = value.Clamp(lower, upper); + /// // clamped will be 20 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Clamp(this T value, T lower, T upper) + where T : IComparable + { + if (lower.GreaterThan(upper)) + { + throw new ArgumentException( + string.Format(ExceptionMessages.LowerCannotBeGreaterThanUpper, lower, upper), + nameof(lower)); + } + + return value.Max(lower).Min(upper); + } + + /// + /// Determines if the current value is greater than another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is greater than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.GreaterThan(second); + /// // result will be False + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool GreaterThan(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) > 0; + } + + /// + /// Determines if the current value is greater than or equal to another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is greater than or equal to + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.GreaterThanOrEqualTo(second); + /// // result will be False + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool GreaterThanOrEqualTo(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) >= 0; + } + + /// + /// Determines if the current value is less than another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is less than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.LessThan(second); + /// // result will be True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool LessThan(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) < 0; + } + + /// + /// Determines if the current value is less than or equal to another value. + /// + /// The first value. + /// The second value. + /// An type. + /// The comparison operand type. + /// + /// if is less than or equal to + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// bool result = first.LessThanOrEqualTo(second); + /// // result will be True + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool LessThanOrEqualTo(this T1 value, T2 other) + where T1 : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.CompareTo(other) <= 0; + } + + /// + /// Returns the maximum of two values. + /// + /// The first value. + /// The second value. + /// A type which implements . + /// + /// if is greater than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// int max = first.Max(second); + /// // max will be 10 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Max(this T value, T other) + where T : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.GreaterThan(other) ? value : other; + } + + /// + /// Returns the minimum of two values. + /// + /// The first value. + /// The second value. + /// A type which implements . + /// + /// if is less than + /// -or- + /// otherwise. + /// + /// + /// + /// int first = 5; + /// int second = 10; + /// + /// int min = first.Min(second); + /// // min will be 5 + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T Min(this T value, T other) + where T : IComparable + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return value.LessThan(other) ? value : other; + } +} diff --git a/X10D/src/Math/DecimalExtensions.cs b/X10D/src/Math/DecimalExtensions.cs new file mode 100644 index 0000000..a6dfe60 --- /dev/null +++ b/X10D/src/Math/DecimalExtensions.cs @@ -0,0 +1,173 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class DecimalExtensions +{ + /// + /// Returns the complex square root of this decimal number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this decimal value) + { + return Complex.Sqrt((double)value); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this decimal value) + { + return value % 2.0m == 0.0m; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this decimal value) + { + return !value.IsEven(); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Round(this decimal value) + { + return value.Round(1.0m); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Round(this decimal value, decimal nearest) + { + return System.Math.Round(value / nearest) * nearest; + } + + /// + /// Returns an integer that indicates the sign of this decimal number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this decimal value) + { + return System.Math.Sign(value); + } + + /// + /// Returns the square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// is negative. + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static decimal Sqrt(this decimal value) + { + switch (value) + { + case 0: + return 0; + case < 0: + throw new ArgumentException("value cannot be negative", nameof(value)); + } + + decimal previous; + var current = (decimal)System.Math.Sqrt((double)value); + do + { + previous = current; + if (previous == 0.0m) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (System.Math.Abs(previous - current) > 0.0m); + + return current; + } +} diff --git a/X10D/src/Math/DoubleExtensions.cs b/X10D/src/Math/DoubleExtensions.cs new file mode 100644 index 0000000..5b393a7 --- /dev/null +++ b/X10D/src/Math/DoubleExtensions.cs @@ -0,0 +1,420 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Mathematical extension methods. +/// +public static class DoubleExtensions +{ + /// + /// Returns the arccosine of the specified value. + /// + /// + /// The value representing a cosine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ π. If + /// is equal to , less than -1, or greater than 1, is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Acos(this double value) + { + return System.Math.Acos(value); + } + + /// + /// Returns the hyperbolic arccosine of the specified value. + /// + /// + /// The value representing a hyperbolic cosine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ ∞. If + /// is less than 1 or equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Acosh(this double value) + { + return System.Math.Acosh(value); + } + + /// + /// Returns the arcsine of the specified value. + /// + /// + /// The value representing a sine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Asin(this double value) + { + return System.Math.Asin(value); + } + + /// + /// Returns the hyperbolic arcsine of the specified value. + /// + /// + /// The value representing a hyperbolic sine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , measured in radians. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Asinh(this double value) + { + return System.Math.Asinh(value); + } + + /// + /// Returns the arctangent of the specified value. + /// + /// + /// The value representing a tangent, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arctangent of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Atan(this double value) + { + return System.Math.Atan(value); + } + + /// + /// Returns the hyperbolic arctangent of the specified value. + /// + /// + /// The value representing a hyperbolic tangent, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arctangent of , θ, measured in radians; such that -∞ < θ < -1, or 1 < + /// θ < ∞. If is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Atanh(this double value) + { + return System.Math.Atanh(value); + } + + /// + /// Returns the complex square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this double value) + { + switch (value) + { + case double.PositiveInfinity: + case double.NegativeInfinity: + return Complex.Infinity; + case double.NaN: + return Complex.NaN; + + case 0: + return Complex.Zero; + case > 0: + return new Complex(System.Math.Sqrt(value), 0); + case < 0: + return new Complex(0, System.Math.Sqrt(-value)); + } + } + + /// + /// Returns the cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The cosine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Cos(this double value) + { + return System.Math.Cos(value); + } + + /// + /// Returns the hyperbolic cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic cosine of . If is equal to + /// or , + /// is returned. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Cosh(this double value) + { + return System.Math.Cosh(value); + } + + /// + /// Converts the current angle in degrees to its equivalent represented in radians. + /// + /// The angle in degrees to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double DegreesToRadians(this double value) + { + return value * (System.Math.PI / 180.0); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this double value) + { + return System.Math.Abs(value % 2.0) < double.Epsilon; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this double value) + { + return !value.IsEven(); + } + + /// + /// Converts the current angle in radians to its equivalent represented in degrees. + /// + /// The angle in radians to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double RadiansToDegrees(this double value) + { + return value * (180.0 / System.Math.PI); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Round(this double value) + { + return value.Round(1.0); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Round(this double value, double nearest) + { + return System.Math.Round(value / nearest) * nearest; + } + + /// + /// Returns the sine of the specified angle. + /// + /// The angle, in radians. + /// + /// The sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sin(this double value) + { + return System.Math.Sin(value); + } + + /// + /// Returns the hyperbolic sine of the specified angle. + /// + /// The angle, in radians. + /// + /// The hyperbolic sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sinh(this double value) + { + return System.Math.Sinh(value); + } + + /// + /// Returns an integer that indicates the sign of this double-precision floating-point number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + /// is equal to . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this double value) + { + return System.Math.Sign(value); + } + + /// + /// Returns the square root of this double-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + /// SLenik https://stackoverflow.com/a/6755197/1467293 + /// CC BY-SA 3.0 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Sqrt(this double value) + { + switch (value) + { + case 0: + return 0; + case < 0 or double.NaN: + return double.NaN; + case double.PositiveInfinity: + return double.PositiveInfinity; + } + + double previous; + double current = System.Math.Sqrt(value); + do + { + previous = current; + if (previous == 0.0) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (System.Math.Abs(previous - current) > double.Epsilon); + + return current; + } + + /// + /// Returns the tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The tangent of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Tan(this double value) + { + return System.Math.Tan(value); + } + + /// + /// Returns the hyperbolic tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic tangent of . If is equal to + /// , this method returns -1. If is equal to + /// , this method returns 1. If is equal to + /// , this method returns . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Tanh(this double value) + { + return System.Math.Tanh(value); + } +} diff --git a/X10D/src/Math/InclusiveOptions.cs b/X10D/src/Math/InclusiveOptions.cs new file mode 100644 index 0000000..1c78197 --- /dev/null +++ b/X10D/src/Math/InclusiveOptions.cs @@ -0,0 +1,28 @@ +namespace X10D.Math; + +/// +/// Provides options for clusivity. +/// +[Flags] +public enum InclusiveOptions : byte +{ + /// + /// Indicates that the comparison will be exclusive. + /// + None = 0, + + /// + /// Indicates that the comparison will treat the upper bound as exclusive. + /// + UpperInclusive = 1, + + /// + /// Indicates that the comparison will treat the lower bound as exclusive. + /// + LowerInclusive = 1 << 1, + + /// + /// Indicates that the comparison will treat both the upper and lower bound as exclusive. + /// + Inclusive = UpperInclusive | LowerInclusive +} diff --git a/X10D/src/Math/Int16Extensions.cs b/X10D/src/Math/Int16Extensions.cs new file mode 100644 index 0000000..731c285 --- /dev/null +++ b/X10D/src/Math/Int16Extensions.cs @@ -0,0 +1,171 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Computes the digital root of this 16-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short DigitalRoot(this short value) + { + short root = System.Math.Abs(value).Mod(9); + return root < 1 ? (short)(9 - root) : root; + } + + /// + /// Returns the factorial of the current 16-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this short value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (short i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this short value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this short value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this short value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short Mod(this short dividend, short divisor) + { + int r = dividend % divisor; + return (short)(r < 0 ? r + divisor : r); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this short value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 16-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this short value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/Int32Extensions.cs b/X10D/src/Math/Int32Extensions.cs new file mode 100644 index 0000000..faaeb5d --- /dev/null +++ b/X10D/src/Math/Int32Extensions.cs @@ -0,0 +1,171 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Computes the digital root of this 32-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int DigitalRoot(this int value) + { + int root = System.Math.Abs(value).Mod(9); + return root < 1 ? 9 - root : root; + } + + /// + /// Returns the factorial of the current 32-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this int value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (var i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this int value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this int value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this int value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Mod(this int dividend, int divisor) + { + int r = dividend % divisor; + return r < 0 ? r + divisor : r; + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this int value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 32-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this int value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/Int64Extensions.cs b/X10D/src/Math/Int64Extensions.cs new file mode 100644 index 0000000..48d5ed3 --- /dev/null +++ b/X10D/src/Math/Int64Extensions.cs @@ -0,0 +1,227 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Computes the digital root of this 64-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long DigitalRoot(this long value) + { + long root = System.Math.Abs(value).Mod(9L); + return root < 1L ? 9L - root : root; + } + + /// + /// Returns the factorial of the current 64-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this long value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (var i = 1L; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this long value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this long value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this long value) + { + switch (value) + { + case < 2: return false; + case 2: + case 3: return true; + } + + if (value % 2 == 0 || value % 3 == 0) + { + return false; + } + + if ((value + 1) % 6 != 0 && (value - 1) % 6 != 0) + { + return false; + } + + for (var iterator = 5L; iterator * iterator <= value; iterator += 6) + { + if (value % iterator == 0 || value % (iterator + 2) == 0) + { + return false; + } + } + + return true; + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Mod(this long dividend, long divisor) + { + long r = dividend % divisor; + return r < 0 ? r + divisor : r; + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this long value) + { + var persistence = 0; + long product = System.Math.Abs(value); + + while (product > 9) + { + if (value % 10 == 0) + { + return persistence + 1; + } + + while (value > 9) + { + value /= 10; + if (value % 10 == 0) + { + return persistence + 1; + } + } + + long newProduct = 1; + long currentProduct = product; + while (currentProduct > 0) + { + newProduct *= currentProduct % 10; + currentProduct /= 10; + } + + product = newProduct; + persistence++; + } + + return persistence; + } + + /// + /// Returns an integer that indicates the sign of this 64-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this long value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/MathUtility.cs b/X10D/src/Math/MathUtility.cs new file mode 100644 index 0000000..a7aa41c --- /dev/null +++ b/X10D/src/Math/MathUtility.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Provides static helpers methods for mathematical functions not found in the .NET class. +/// +public static class MathUtility +{ + /// + /// Linearly interpolates from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// + /// The interpolation result as determined by (1 - alpha) * value + alpha * target. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Lerp(float value, float target, float alpha) + { + // rookie mistake: a + t * (b - a) + // "precise" method: (1 - t) * a + t * b + return ((1.0f - alpha) * value) + (alpha * target); + } + + /// + /// Linearly interpolates from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// + /// The interpolation result as determined by (1 - alpha) * value + alpha * target. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static double Lerp(double value, double target, double alpha) + { + // rookie mistake: a + t * (b - a) + // "precise" method: (1 - t) * a + t * b + return ((1.0 - alpha) * value) + (alpha * target); + } +} diff --git a/X10D/src/Math/SByteExtensions.cs b/X10D/src/Math/SByteExtensions.cs new file mode 100644 index 0000000..8e640a0 --- /dev/null +++ b/X10D/src/Math/SByteExtensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Computes the digital root of this 32-bit integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte DigitalRoot(this sbyte value) + { + int root = System.Math.Abs(value).Mod(9); + return (sbyte)(root < 1 ? 9 - root : root); + } + + /// + /// Returns the factorial of the current 8-bit signed integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long Factorial(this sbyte value) + { + if (value < 0) + { + throw new ArithmeticException(nameof(value)); + } + + if (value == 0) + { + return 1; + } + + var result = 1L; + for (ushort i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this sbyte value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this sbyte value) + { + return !value.IsEven(); + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this sbyte value) + { + return ((long)value).IsPrime(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte Mod(this sbyte dividend, sbyte divisor) + { + int r = dividend % divisor; + return (sbyte)(r < 0 ? r + divisor : r); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this sbyte value) + { + return ((long)value).MultiplicativePersistence(); + } + + /// + /// Returns an integer that indicates the sign of this 8-bit signed integer. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this sbyte value) + { + return System.Math.Sign(value); + } +} diff --git a/X10D/src/Math/SingleExtensions.cs b/X10D/src/Math/SingleExtensions.cs new file mode 100644 index 0000000..df6b8b8 --- /dev/null +++ b/X10D/src/Math/SingleExtensions.cs @@ -0,0 +1,419 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns the arccosine of the specified value. + /// + /// + /// The value representing a cosine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ π. If + /// is equal to , less than -1, or greater than 1, is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Acos(this float value) + { + return MathF.Acos(value); + } + + /// + /// Returns the hyperbolic arccosine of the specified value. + /// + /// + /// The value representing a hyperbolic cosine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , θ, measured in radians; such that 0 ≤ θ ≤ ∞. If + /// is less than 1 or equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Acosh(this float value) + { + return MathF.Acosh(value); + } + + /// + /// Returns the arcsine of the specified value. + /// + /// + /// The value representing a sine, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arccosine of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Asin(this float value) + { + return MathF.Asin(value); + } + + /// + /// Returns the hyperbolic arcsine of the specified value. + /// + /// + /// The value representing a hyperbolic sine, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arccosine of , measured in radians. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Asinh(this float value) + { + return MathF.Asinh(value); + } + + /// + /// Returns the arctangent of the specified value. + /// + /// + /// The value representing a tangent, which must be greater than or equal to -1, but less than or equal to 1. + /// + /// + /// The arctangent of , θ, measured in radians; such that π/2 ≤ θ ≤ π/2. If + /// is equal to , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Atan(this float value) + { + return MathF.Atan(value); + } + + /// + /// Returns the hyperbolic arctangent of the specified value. + /// + /// + /// The value representing a hyperbolic tangent, which must be greater than or equal to 1, but less than or equal to + /// . + /// + /// + /// The hyperbolic arctangent of , θ, measured in radians; such that -∞ < θ < -1, or 1 < + /// θ < ∞. If is equal to , less than -1, or greater than 1, + /// is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Atanh(this float value) + { + return MathF.Atanh(value); + } + + /// + /// Returns the complex square root of this single-precision floating-point number. + /// + /// The number whose square root is to be found. + /// The square root of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Complex ComplexSqrt(this float value) + { + switch (value) + { + case float.PositiveInfinity: + case float.NegativeInfinity: + return Complex.Infinity; + case float.NaN: + return Complex.NaN; + + case 0: + return Complex.Zero; + case > 0: + return new Complex(MathF.Sqrt(value), 0); + case < 0: + return new Complex(0, MathF.Sqrt(-value)); + } + } + + /// + /// Returns the cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The cosine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Cos(this float value) + { + return MathF.Cos(value); + } + + /// + /// Returns the hyperbolic cosine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic cosine of . If is equal to + /// or , + /// is returned. If is equal to + /// , is returned. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Cosh(this float value) + { + return MathF.Cosh(value); + } + + /// + /// Converts the current angle in degrees to its equivalent represented in radians. + /// + /// The angle in degrees to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float DegreesToRadians(this float value) + { + return value * (MathF.PI / 180.0f); + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this float value) + { + return value % 2 == 0; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this float value) + { + return !value.IsEven(); + } + + /// + /// Converts the current angle in radians to its equivalent represented in degrees. + /// + /// The angle in radians to convert. + /// The result of π * / 180. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float RadiansToDegrees(this float value) + { + return value * (180.0f / MathF.PI); + } + + /// + /// Rounds the current value to the nearest whole number. + /// + /// The value to round. + /// rounded to the nearest whole number. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Round(this float value) + { + return value.Round(1.0f); + } + + /// + /// Rounds the current value to the nearest multiple of a specified number. + /// + /// The value to round. + /// The nearest multiple to which should be rounded. + /// rounded to the nearest multiple of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Round(this float value, float nearest) + { + return MathF.Round(value / nearest) * nearest; + } + + /// + /// Returns an integer that indicates the sign of this single-precision floating-point number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Sign(this float value) + { + return MathF.Sign(value); + } + + /// + /// Returns the square root of this single-precision floating-point number. + /// + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// The positive square root of . + /// is greater than or equal to 0. + /// + /// + /// + /// is equal to or is negative. + /// + /// + /// + /// is equal to . + /// + /// + /// + /// + /// For negative input, this method returns . To receive a complex number, see + /// . + /// + /// + /// SLenik https://stackoverflow.com/a/6755197/1467293 + /// CC BY-SA 3.0 + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sqrt(this float value) + { + switch (value) + { + case 0: + return 0; + case < 0 or float.NaN: + return float.NaN; + case float.PositiveInfinity: + return float.PositiveInfinity; + } + + float previous; + float current = MathF.Sqrt(value); + do + { + previous = current; + if (previous == 0.0f) + { + return 0; + } + + current = (previous + value / previous) / 2; + } while (MathF.Abs(previous - current) > float.Epsilon); + + return current; + } + + /// + /// Returns the sine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sin(this float value) + { + return MathF.Sin(value); + } + + /// + /// Returns the hyperbolic sine of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic sine of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Sinh(this float value) + { + return MathF.Sinh(value); + } + + /// + /// Returns the tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The tangent of . If is equal to , + /// , or , this method returns + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Tan(this float value) + { + return MathF.Sin(value); + } + + /// + /// Returns the hyperbolic tangent of the specified angle. + /// + /// The angle, measured in radians. + /// + /// The hyperbolic tangent of . If is equal to + /// , this method returns -1. If is equal to + /// , this method returns 1. If is equal to + /// , this method returns . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static float Tanh(this float value) + { + return MathF.Tanh(value); + } +} diff --git a/X10D/src/Math/UInt16Extensions.cs b/X10D/src/Math/UInt16Extensions.cs new file mode 100644 index 0000000..e781942 --- /dev/null +++ b/X10D/src/Math/UInt16Extensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Computes the digital root of the current 16-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort DigitalRoot(this ushort value) + { + var root = (ushort)(value % 9); + return (ushort)(root == 0 ? 9 : root); + } + + /// + /// Returns the factorial of the current 16-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this ushort value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (ushort i = 1; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this ushort value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this ushort value) + { + return ((ulong)value).IsPrime(); + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this ushort value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this ushort value) + { + return ((ulong)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/UInt32Extensions.cs b/X10D/src/Math/UInt32Extensions.cs new file mode 100644 index 0000000..65217a4 --- /dev/null +++ b/X10D/src/Math/UInt32Extensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Computes the digital root of the current 32-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint DigitalRoot(this uint value) + { + uint root = value % 9; + return root == 0 ? 9 : root; + } + + /// + /// Returns the factorial of the current 32-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this uint value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (var i = 1U; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this uint value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this uint value) + { + return ((ulong)value).IsPrime(); + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this uint value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this uint value) + { + return ((ulong)value).MultiplicativePersistence(); + } +} diff --git a/X10D/src/Math/UInt64Extensions.cs b/X10D/src/Math/UInt64Extensions.cs new file mode 100644 index 0000000..08f88ba --- /dev/null +++ b/X10D/src/Math/UInt64Extensions.cs @@ -0,0 +1,166 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Math; + +/// +/// Extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Computes the digital root of the current 64-bit unsigned integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong DigitalRoot(this ulong value) + { + ulong root = value % 9; + return root == 0 ? 9 : root; + } + + /// + /// Returns the factorial of the current 64-bit unsigned integer. + /// + /// The value whose factorial to compute. + /// The factorial of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong Factorial(this ulong value) + { + if (value == 0) + { + return 1; + } + + var result = 1UL; + for (var i = 1UL; i <= value; i++) + { + result *= i; + } + + return result; + } + + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsEven(this ulong value) + { + return (value & 1) == 0; + } + + /// + /// Returns a value indicating whether the current value is a prime number. + /// + /// The value whose primality to check. + /// + /// if is prime; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPrime(this ulong value) + { + switch (value) + { + case < 2: return false; + case 2: + case 3: return true; + } + + if (value % 2 == 0 || value % 3 == 0) + { + return false; + } + + if ((value + 1) % 6 != 0 && (value - 1) % 6 != 0) + { + return false; + } + + for (var iterator = 5UL; iterator * iterator <= value; iterator += 6) + { + if (value % iterator == 0 || value % (iterator + 2) == 0) + { + return false; + } + } + + return true; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsOdd(this ulong value) + { + return !value.IsEven(); + } + + /// + /// Returns the multiplicative persistence of a specified value. + /// + /// The value whose multiplicative persistence to calculate. + /// The multiplicative persistence. + /// + /// Multiplicative persistence is defined as the recursive digital product until that product is a single digit. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int MultiplicativePersistence(this ulong value) + { + var persistence = 0; + ulong product = value; + + while (product > 9) + { + if (value % 10 == 0) + { + return persistence + 1; + } + + while (value > 9) + { + value /= 10; + if (value % 10 == 0) + { + return persistence + 1; + } + } + + ulong newProduct = 1; + ulong currentProduct = product; + while (currentProduct > 0) + { + newProduct *= currentProduct % 10; + currentProduct /= 10; + } + + product = newProduct; + persistence++; + } + + return persistence; + } +} diff --git a/X10D/src/Net/EndPointExtensions.cs b/X10D/src/Net/EndPointExtensions.cs new file mode 100644 index 0000000..b7ef2d7 --- /dev/null +++ b/X10D/src/Net/EndPointExtensions.cs @@ -0,0 +1,69 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Extension methods for and derived types. +/// +public static class EndPointExtensions +{ + /// + /// Returns the hostname for the current . + /// + /// The endpoint whose hostname to get. + /// + /// if is . + /// -or- + /// if is . + /// -or- + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string GetHost(this EndPoint endPoint) + { + if (endPoint is null) + { + throw new ArgumentNullException(nameof(endPoint)); + } + + return endPoint switch + { + IPEndPoint ip => ip.Address.ToString(), + DnsEndPoint dns => dns.Host, + _ => string.Empty + }; + } + + /// + /// Returns the port number for the current . + /// + /// The endpoint whose port number to get. + /// + /// if is . + /// -or- + /// if is . + /// -or- + /// 0 otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int GetPort(this EndPoint endPoint) + { + if (endPoint is null) + { + throw new ArgumentNullException(nameof(endPoint)); + } + + return endPoint switch + { + IPEndPoint ip => ip.Port, + DnsEndPoint dns => dns.Port, + _ => 0 + }; + } +} diff --git a/X10D/src/Net/IPAddressExtensions.cs b/X10D/src/Net/IPAddressExtensions.cs new file mode 100644 index 0000000..0b8358d --- /dev/null +++ b/X10D/src/Net/IPAddressExtensions.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Net.Sockets; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Extension methods for . +/// +public static class IPAddressExtensions +{ + /// + /// Returns a value indicating whether the specified IP address is a valid IPv4 address. + /// + /// The IP address to check. + /// + /// if the specified IP address is a valid IPv4 address; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsIPv4(this IPAddress address) + { + return address.AddressFamily == AddressFamily.InterNetwork; + } + + /// + /// Returns a value indicating whether the specified IP address is a valid IPv6 address. + /// + /// The IP address to check. + /// + /// if the specified IP address is a valid IPv6 address; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsIPv6(this IPAddress address) + { + return address.AddressFamily == AddressFamily.InterNetworkV6; + } +} diff --git a/X10D/src/Net/Int16Extensions.cs b/X10D/src/Net/Int16Extensions.cs new file mode 100644 index 0000000..60b648b --- /dev/null +++ b/X10D/src/Net/Int16Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// Network-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Converts a 16-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short HostToNetworkOrder(this short value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 16-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short NetworkToHostOrder(this short value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Net/Int32Extensions.cs b/X10D/src/Net/Int32Extensions.cs new file mode 100644 index 0000000..7c7acbe --- /dev/null +++ b/X10D/src/Net/Int32Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// IO-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Converts a 32-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int HostToNetworkOrder(this int value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 32-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int NetworkToHostOrder(this int value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Net/Int64Extensions.cs b/X10D/src/Net/Int64Extensions.cs new file mode 100644 index 0000000..a586ca8 --- /dev/null +++ b/X10D/src/Net/Int64Extensions.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.Contracts; +using System.Net; +using System.Runtime.CompilerServices; + +namespace X10D.Net; + +/// +/// IO-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Converts a 64-bit signed integer value from host byte order to network byte order. + /// + /// The value to convert, expressed in host byte order. + /// An integer value, expressed in network byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long HostToNetworkOrder(this long value) + { + return IPAddress.HostToNetworkOrder(value); + } + + /// + /// Converts a 64-bit signed integer value from network byte order to host byte order. + /// + /// The value to convert, expressed in network byte order. + /// An integer value, expressed in host byte order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long NetworkToHostOrder(this long value) + { + return IPAddress.NetworkToHostOrder(value); + } +} diff --git a/X10D/src/Numerics/ByteExtensions.cs b/X10D/src/Numerics/ByteExtensions.cs new file mode 100644 index 0000000..ccd97a3 --- /dev/null +++ b/X10D/src/Numerics/ByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte RotateLeft(this byte value, int count) + { + count = count.Mod(8); + return (byte)((value << count) | (value >> (8 - count))); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte RotateRight(this byte value, int count) + { + count = count.Mod(8); + return (byte)((value >> count) | (value << (8 - count))); + } +} diff --git a/X10D/src/Numerics/Int16Extensions.cs b/X10D/src/Numerics/Int16Extensions.cs new file mode 100644 index 0000000..789eeb5 --- /dev/null +++ b/X10D/src/Numerics/Int16Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short RotateLeft(this short value, int count) + { + var unsigned = unchecked((ushort)value); + return unchecked((short)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static short RotateRight(this short value, int count) + { + var unsigned = unchecked((ushort)value); + return unchecked((short)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/Int32Extensions.cs b/X10D/src/Numerics/Int32Extensions.cs new file mode 100644 index 0000000..75dfea8 --- /dev/null +++ b/X10D/src/Numerics/Int32Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int RotateLeft(this int value, int count) + { + var unsigned = unchecked((uint)value); + return unchecked((int)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int RotateRight(this int value, int count) + { + var unsigned = unchecked((uint)value); + return unchecked((int)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/Int64Extensions.cs b/X10D/src/Numerics/Int64Extensions.cs new file mode 100644 index 0000000..5f25a59 --- /dev/null +++ b/X10D/src/Numerics/Int64Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long RotateLeft(this long value, int count) + { + var unsigned = unchecked((ulong)value); + return unchecked((long)unsigned.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long RotateRight(this long value, int count) + { + var unsigned = unchecked((ulong)value); + return unchecked((long)unsigned.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/RandomExtensions.cs b/X10D/src/Numerics/RandomExtensions.cs new file mode 100644 index 0000000..b89a5e2 --- /dev/null +++ b/X10D/src/Numerics/RandomExtensions.cs @@ -0,0 +1,117 @@ +using System.Numerics; +using X10D.Core; + +namespace X10D.Numerics; + +/// +/// Extension methods for . +/// +public static class RandomExtensions +{ + /// + /// Returns a randomly generated rotation as represented by a . + /// + /// The instance. + /// + /// A constructed from 3 random single-precision floating point numbers representing the + /// yaw, pitch, and roll. + /// + /// is . + public static Quaternion NextRotation(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + + float x = seededRandom.NextSingle(0, 360); + float y = seededRandom.NextSingle(0, 360); + float z = seededRandom.NextSingle(0, 360); + + return Quaternion.CreateFromYawPitchRoll(y, x, z); + } + + /// + /// Returns a randomly generated rotation with uniform distribution. + /// + /// The instance. + /// A constructed with uniform distribution. + /// is . + public static Quaternion NextRotationUniform(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + float normal, w, x, y, z; + + do + { + w = seededRandom.NextSingle(-1f, 1f); + x = seededRandom.NextSingle(-1f, 1f); + y = seededRandom.NextSingle(-1f, 1f); + z = seededRandom.NextSingle(-1f, 1f); + normal = (w * w) + (x * x) + (y * y) + (z * z); + } while (normal is 0f or > 1f); + + normal = MathF.Sqrt(normal); + return new Quaternion(x / normal, y / normal, z / normal, w / normal); + } + + /// + /// Returns a with magnitude 1 whose components indicate a random point on the unit circle. + /// + /// The instance + /// + /// A whose returns 1, and whose components indicate a random + /// point on the unit circle. + /// + public static Vector2 NextUnitVector2(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + // no need to construct a seeded random here, since we only call Next once + + float angle = random.NextSingle(0, MathF.PI * 2.0f); + float x = MathF.Cos(angle); + float y = MathF.Sin(angle); + + return new Vector2(x, y); + } + + /// + /// Returns a with magnitude 1 whose components indicate a random point on the unit sphere. + /// + /// The instance + /// + /// A whose returns 1, and whose components indicate a random + /// point on the unit sphere. + /// + public static Vector3 NextUnitVector3(this Random random) + { + if (random is null) + { + throw new ArgumentNullException(nameof(random)); + } + + int seed = random.Next(); + var seededRandom = new Random(seed); + + float angle = seededRandom.NextSingle(0, MathF.PI * 2.0f); + float z = seededRandom.NextSingle(-1, 1); + float mp = MathF.Sqrt(1 - (z * z)); + float x = mp * MathF.Cos(angle); + float y = mp * MathF.Sin(angle); + + return new Vector3(x, y, z); + } +} diff --git a/X10D/src/Numerics/SByteExtensions.cs b/X10D/src/Numerics/SByteExtensions.cs new file mode 100644 index 0000000..e3d9d24 --- /dev/null +++ b/X10D/src/Numerics/SByteExtensions.cs @@ -0,0 +1,43 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte RotateLeft(this sbyte value, int count) + { + var signed = unchecked((byte)value); + return unchecked((sbyte)signed.RotateLeft(count)); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..7] is treated as congruent mod 8. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static sbyte RotateRight(this sbyte value, int count) + { + var signed = unchecked((byte)value); + return unchecked((sbyte)signed.RotateRight(count)); + } +} diff --git a/X10D/src/Numerics/UInt16Extensions.cs b/X10D/src/Numerics/UInt16Extensions.cs new file mode 100644 index 0000000..4798deb --- /dev/null +++ b/X10D/src/Numerics/UInt16Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort RotateLeft(this ushort value, int count) + { + return (ushort)((ushort)(value << count) | (ushort)(value >> (16 - count))); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..15] is treated as congruent mod 16. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ushort RotateRight(this ushort value, int count) + { + return (ushort)((ushort)(value >> count) | (ushort)(value << (16 - count))); + } +} diff --git a/X10D/src/Numerics/UInt32Extensions.cs b/X10D/src/Numerics/UInt32Extensions.cs new file mode 100644 index 0000000..d5b619c --- /dev/null +++ b/X10D/src/Numerics/UInt32Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint RotateLeft(this uint value, int count) + { + return BitOperations.RotateLeft(value, count); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..31] is treated as congruent mod 32. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static uint RotateRight(this uint value, int count) + { + return BitOperations.RotateRight(value, count); + } +} diff --git a/X10D/src/Numerics/UInt64Extensions.cs b/X10D/src/Numerics/UInt64Extensions.cs new file mode 100644 index 0000000..709ca93 --- /dev/null +++ b/X10D/src/Numerics/UInt64Extensions.cs @@ -0,0 +1,42 @@ +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace X10D.Numerics; + +/// +/// Numeric-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Rotates the current value left by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong RotateLeft(this ulong value, int count) + { + return BitOperations.RotateLeft(value, count); + } + + /// + /// Rotates the current value right by the specified number of bits. + /// + /// The value to rotate. + /// + /// The number of bits by which to rotate. Any value outside the range [0..63] is treated as congruent mod 64. + /// + /// The rotated value. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static ulong RotateRight(this ulong value, int count) + { + return BitOperations.RotateRight(value, count); + } +} diff --git a/X10D/src/RandomExtensions.cs b/X10D/src/RandomExtensions.cs deleted file mode 100644 index 0008338..0000000 --- a/X10D/src/RandomExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - - /// - /// Extension methods for . - /// - public static class RandomExtensions - { - /// - /// Gets the instance to which other extension methods may refer, when one is - /// needed but not provided. - /// - internal static Random Random { get; } = new Random(); - - /// - /// Returns either or based on 's next - /// generation. - /// - /// The instance. - /// - /// Returns or depending on the return value - /// from . - /// - /// is . - public static bool CoinToss(this Random random) - { - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - return random.Next(2) == 0; - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The instance. - /// The collection from which to draw. - /// Returns a random element of type from . - public static T OneOf(this Random random, params T[] source) - { - return source.ToList().OneOf(random); - } - - /// - /// Returns a random element from using the instance. - /// - /// The collection type. - /// The instance. - /// The collection from which to draw. - /// Returns a random element of type from . - /// - /// or is - /// . - /// - public static T OneOf(this Random random, IList source) - { - if (random is null) - { - throw new ArgumentNullException(nameof(random)); - } - - if (source is null) - { - throw new ArgumentNullException(nameof(source)); - } - - return source[random.Next(source.Count)]; - } - } -} diff --git a/X10D/src/Reflection/MemberInfoExtensions.cs b/X10D/src/Reflection/MemberInfoExtensions.cs new file mode 100644 index 0000000..9fd389e --- /dev/null +++ b/X10D/src/Reflection/MemberInfoExtensions.cs @@ -0,0 +1,125 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace X10D.Reflection; + +/// +/// Extension methods for . +/// +public static class MemberInfoExtensions +{ + /// + /// Returns a value indicating whether or not the current member has been decorated with a specified attribute. + /// + /// The member attributes to check. + /// The attribute type. + /// + /// if the current member has been decorated with a specified attribute, or + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool HasCustomAttribute(this MemberInfo member) + where T : Attribute + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + return member.HasCustomAttribute(typeof(T)); + } + + /// + /// Returns a value indicating whether or not the current member has been decorated with a specified attribute. + /// + /// The member attributes to check. + /// The attribute type. + /// + /// if the current member has been decorated with a specified attribute, or + /// otherwise. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool HasCustomAttribute(this MemberInfo member, Type attribute) + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + if (attribute is null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + if (!attribute.Inherits()) + { + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, ExceptionMessages.TypeDoesNotInheritAttribute, + attribute, typeof(Attribute)), nameof(attribute)); + } + + return member.GetCustomAttribute(attribute) is not null; + } + + /// + /// Retrieves a custom attribute that is decorated by the current member, and projects it into to a new form. + /// + /// The attribute type. + /// The return type of the delegate. + /// The member. + /// A transform function to apply to the attribute. + /// + /// An instance of as provided from . + /// + /// + /// is + /// -or- + /// is . + /// + public static TReturn? SelectFromCustomAttribute(this MemberInfo member, + Func selector) + where TAttribute : Attribute + { + return member.SelectFromCustomAttribute(selector, default); + } + + /// + /// Retrieves a custom attribute that is decorated by the current member, and projects it into to a new form. + /// + /// The attribute type. + /// The return type of the delegate. + /// The member. + /// A transform function to apply to the attribute. + /// The default value to return when the specified attribute is not found. + /// + /// An instance of as provided from . + /// + /// + /// is + /// -or- + /// is . + /// + public static TReturn? SelectFromCustomAttribute(this MemberInfo member, + Func selector, TReturn? defaultValue) + where TAttribute : Attribute + { + if (member is null) + { + throw new ArgumentNullException(nameof(member)); + } + + if (selector is null) + { + throw new ArgumentNullException(nameof(selector)); + } + + return member.GetCustomAttribute() is { } attribute + ? selector(attribute) + : defaultValue; + } +} diff --git a/X10D/src/Reflection/TypeExtensions.cs b/X10D/src/Reflection/TypeExtensions.cs new file mode 100644 index 0000000..301d1d2 --- /dev/null +++ b/X10D/src/Reflection/TypeExtensions.cs @@ -0,0 +1,114 @@ +using System.Diagnostics.Contracts; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace X10D.Reflection; + +/// +/// Extension methods for . +/// +public static class TypeExtensions +{ + /// + /// Returns a value indicating whether the current type implements a specified interface. + /// + /// The type whose interface list to check. + /// The interface type. + /// if the current exists on the type; otherwise, . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Implements(this Type value) + { + return value.Implements(typeof(T)); + } + + /// + /// Returns a value indicating whether the current type implements a specified interface. + /// + /// The type whose interface list to check. + /// The interface type. + /// if the current exists on the type; otherwise, . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Implements(this Type value, Type interfaceType) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (interfaceType is null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (!interfaceType.IsInterface) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotInterface; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, interfaceType); + + throw new ArgumentException(formattedMessage, nameof(interfaceType)); + } + + return Array.IndexOf(value.GetInterfaces(), interfaceType) >= 0; + } + + /// + /// Returns a value indicating whether the current type inherits a specified type. + /// + /// The type whose interface list to check. + /// The base type. + /// + /// if the current type inherits , or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Inherits(this Type value) + where T : class + { + return value.Inherits(typeof(T)); + } + + /// + /// Returns a value indicating whether the current type inherits a specified type. + /// + /// The type whose interface list to check. + /// The base type. + /// + /// if the current type inherits , or + /// otherwise. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool Inherits(this Type value, Type type) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (type is null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!value.IsClass) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotClass; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, value); + + throw new ArgumentException(formattedMessage, nameof(value)); + } + + if (!type.IsClass) + { + string? exceptionMessage = ExceptionMessages.TypeIsNotClass; + string formattedMessage = string.Format(CultureInfo.CurrentCulture, exceptionMessage, type); + + throw new ArgumentException(formattedMessage, nameof(type)); + } + + return value.IsSubclassOf(type); + } +} diff --git a/X10D/src/ReflectionExtensions.cs b/X10D/src/ReflectionExtensions.cs deleted file mode 100644 index ff3cb23..0000000 --- a/X10D/src/ReflectionExtensions.cs +++ /dev/null @@ -1,123 +0,0 @@ -namespace X10D -{ - using System; - using System.ComponentModel; - using System.Reflection; - - /// - /// Extension methods for various reflection types. - /// - public static class ReflectionExtensions - { - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The member. - /// - /// Returns an representing the value stored in this member's - /// . - /// - /// is . - public static object GetDefaultValue(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return default; - } - - return attribute.Value; - } - - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The type to which the value should cast. - /// The member. - /// - /// Returns an instance of representing the value stored in this member's - /// . - /// - /// is . - public static T GetDefaultValue(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - return (T)member.GetDefaultValue(); - } - - /// - /// Gets the value set in this member's annotated , or - /// if none exists. - /// - /// The member. - /// - /// Returns a string representing the value stored in this member's - /// . - /// - /// is . - public static string GetDescription(this MemberInfo member) - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return null; - } - - return attribute.Description; - } - - /// - /// Retrieves a custom attribute of a specified type that is applied to the specified member, and passes it - /// to a selector delegate in order to select one or more the members in the attribute. - /// - /// The attribute type. - /// The return type of the delegate. - /// The member. - /// The selector delegate. - /// - /// Returns an instance of as provided from - /// . - /// - /// - /// is - /// -or- - /// is . - /// - public static TReturn SelectFromCustomAttribute( - this MemberInfo member, - Func selector) - where TAttribute : Attribute - { - if (member is null) - { - throw new ArgumentNullException(nameof(member)); - } - - if (selector is null) - { - throw new ArgumentNullException(nameof(selector)); - } - - if (!(member.GetCustomAttribute() is { } attribute)) - { - return default; - } - - return selector(attribute); - } - } -} diff --git a/X10D/src/SingleExtensions.cs b/X10D/src/SingleExtensions.cs deleted file mode 100644 index 53f6925..0000000 --- a/X10D/src/SingleExtensions.cs +++ /dev/null @@ -1,93 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for . - /// - public static class SingleExtensions - { - /// - /// Clamps a value between a minimum and a maximum value. - /// - /// The value to clamp. - /// The minimum value. - /// The maximum value. - /// - /// Returns if is greater than it, - /// if is less than it, - /// or itself otherwise. - /// - public static float Clamp(this float value, float min, float max) - { - return Math.Min(Math.Max(value, min), max); - } - - /// - /// Converts an angle from degrees to radians. - /// - /// The angle in degrees. - /// Returns in radians. - public static float DegreesToRadians(this float angle) - { - return (float)((double)angle).DegreesToRadians(); - } - - /// - /// Converts the to a []. - /// - /// The number to convert. - /// Returns a []. - public static byte[] GetBytes(this float number) - { - return BitConverter.GetBytes(number); - } - - /// - /// Determines if the is even. - /// - /// The number. - /// - /// Returns if is even, - /// otherwise. - /// - public static bool IsEven(this float number) - { - return ((double)number).IsEven(); - } - - /// - /// Determines if the is odd. - /// - /// The number. - /// - /// Returns if is odd, - /// otherwise. - /// - public static bool IsOdd(this float number) - { - return !number.IsEven(); - } - - /// - /// Converts an angle from radians to degrees. - /// - /// The angle in radians. - /// Returns in degrees. - public static float RadiansToDegrees(this float angle) - { - return (float)((double)angle).RadiansToDegrees(); - } - - /// - /// Rounds to the nearest value. - /// - /// The value to round. - /// The nearest value. - /// Returns the rounded value. - public static float Round(this float v, float nearest = 1) - { - return (float)((double)v).Round(nearest); - } - } -} diff --git a/X10D/src/StreamExtensions.cs b/X10D/src/StreamExtensions.cs deleted file mode 100644 index 521796b..0000000 --- a/X10D/src/StreamExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace X10D -{ - using System; - using System.IO; - using System.Security.Cryptography; - - /// - /// Extension methods for . - /// - public static class StreamExtensions - { - /// - /// Returns the hash of a stream using the specified hashing algorithm. - /// - /// A derived type. - /// The stream whose hash is to be computed. - /// Returns a array representing the hash of the stream. - /// is . - public static byte[] GetHash(this Stream stream) - where T : HashAlgorithm - { - if (stream is null) - { - throw new ArgumentNullException(nameof(stream)); - } - - var create = typeof(T).GetMethod("Create", Array.Empty()); - using var crypt = (T)create?.Invoke(null, null); - return crypt?.ComputeHash(stream); - } - } -} diff --git a/X10D/src/StringExtensions.cs b/X10D/src/StringExtensions.cs deleted file mode 100644 index 93920a1..0000000 --- a/X10D/src/StringExtensions.cs +++ /dev/null @@ -1,409 +0,0 @@ -namespace X10D -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Security; - using System.Text; - - /// - /// Extension methods for . - /// - public static class StringExtensions - { - /// - /// Returns the current string, or if the current string is null or empty. - /// - /// The value to sanitize. - /// or . - public static string? AsNullIfEmpty(this string value) - { - return string.IsNullOrEmpty(value) ? null : value; - } - - /// - /// Returns the current string, or if the current string is null, empty, or consists of only - /// whitespace. - /// - /// The value to sanitize. - /// or . - public static string? AsNullIfWhiteSpace(this string value) - { - return string.IsNullOrWhiteSpace(value) ? null : value; - } - - /// - /// Decodes a base-64 encoded string. - /// - /// The base-64 string to decode. - /// Returns the string in plain text. - public static string Base64Decode(this string data) - { - return Convert.FromBase64String(data).GetString(); - } - - /// - /// Encodes a base-64 encoded string. - /// - /// The plain text string to decode. - /// Returns the string in plain text. - public static string Base64Encode(this string value) - { - return Convert.ToBase64String(value.GetBytes()); - } - - /// - /// Converts this string from one encoding to another. - /// - /// The input string. - /// The input encoding. - /// The output encoding. - /// - /// Returns a new with its data converted to - /// . - /// - /// - /// is - /// - or - - /// is - /// -or - /// is . - /// - public static string ChangeEncoding(this string str, Encoding from, Encoding to) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (from is null) - { - throw new ArgumentNullException(nameof(from)); - } - - if (to is null) - { - throw new ArgumentNullException(nameof(to)); - } - - return str.GetBytes(from).GetString(to); - } - - /// - /// Parses a into an . - /// - /// The type of the . - /// The value to parse. - /// The value corresponding to the . - /// - /// Credit for this method goes to Scott Dorman: - /// (http://geekswithblogs.net/sdorman/Default.aspx). - /// - public static T EnumParse(this string value) - { - return value.EnumParse(false); - } - - /// - /// Parses a into an . - /// - /// The type of the . - /// The value to parse. - /// Whether or not to ignore casing. - /// The value corresponding to the . - /// - /// Credit for this method goes to Scott Dorman: - /// (http://geekswithblogs.net/sdorman/Default.aspx). - /// - public static T EnumParse(this string value, bool ignoreCase) - { - if (value is null) - { - throw new ArgumentNullException(nameof(value)); - } - - value = value.Trim(); - - if (string.IsNullOrWhiteSpace(value)) - { - throw new ArgumentException(Resource.EnumParseEmptyStringException, nameof(value)); - } - - var t = typeof(T); - - if (!t.IsEnum) - { - throw new ArgumentException(Resource.EnumParseNotEnumException); - } - - return (T)Enum.Parse(t, value, ignoreCase); - } - - /// - /// Gets a [] representing the value the with - /// encoding. - /// - /// The string to convert. - /// Returns a []. - public static byte[] GetBytes(this string str) - { - return str.GetBytes(Encoding.UTF8); - } - - /// - /// Gets a [] representing the value the with the provided encoding. - /// - /// The string to convert. - /// The encoding to use. - /// Returns a []. - /// - /// or or both are - /// . - /// - public static byte[] GetBytes(this string str, Encoding encoding) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (encoding is null) - { - throw new ArgumentNullException(nameof(encoding)); - } - - return encoding.GetBytes(str); - } - - /// - /// Determines if all alpha characters in this string are considered lowercase. - /// - /// The input string. - /// - /// Returns if all alpha characters are lowercase, - /// otherwise. - /// - public static bool IsLower(this string str) - { - return str.Where(char.IsLetter).All(char.IsLower); - } - - /// - /// Determines if all alpha characters in this string are considered uppercase. - /// - /// The input string. - /// - /// Returns if all alpha characters are uppercase, - /// otherwise. - /// - public static bool IsUpper(this string str) - { - return str.Where(char.IsLetter).All(char.IsUpper); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// Returns a containing characters. - public static string Random(this string str, int length) - { - return str.Random(length, RandomExtensions.Random); - } - - /// - /// Generates a new random string by filling it with characters found in . - /// - /// The character set. - /// The length of the string to generate. - /// The instance. - /// Returns a containing characters. - /// is . - public static string Random(this string str, int length, Random random) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return str.ToCharArray().Random(length, random); - } - - /// - /// Repeats a string a specified number of times. - /// - /// The string to repeat. - /// The repeat count. - /// - /// Returns a whose value is repeated - /// times. - /// - /// is . - public static string Repeat(this string str, int count) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - var builder = new StringBuilder(str.Length * count); - - for (var i = 0; i < count; i++) - { - builder.Append(str); - } - - return builder.ToString(); - } - - /// - /// Reverses the current string. - /// - /// The string to reverse. - /// A whose characters are that of in reverse order. - public static string Reverse(this string value) - { - return string.Join(string.Empty, Enumerable.Reverse(value)); - } - - /// - /// Shuffles the characters in the string. - /// - /// The string to shuffle. - /// Returns a containing the characters in , rearranged. - public static string Shuffle(this string str) - { - return str.Shuffle(RandomExtensions.Random); - } - - /// - /// Shuffles the characters in the string. - /// - /// The string to shuffle. - /// The instance. - /// Returns a containing the characters in , rearranged. - /// is . - public static string Shuffle(this string str, Random random) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return new string(str.ToCharArray().Shuffle(random).ToArray()); - } - - /// - /// Splits the into chunks that are no greater than in length. - /// - /// The string to split. - /// The maximum length of each string in the returned result. - /// - /// Returns an containing instances which are no - /// greater than in length. - /// - /// is . - public static IEnumerable Split(this string str, int chunkSize) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return SplitInternal(); - - IEnumerable SplitInternal() - { - for (var i = 0; i < str.Length; i += chunkSize) - { - yield return str.Substring(i, Math.Min(chunkSize, str.Length - i)); - } - } - } - - /// - /// Converts a to a . - /// - /// The string to convert. - /// Returns a . - /// is . - public static SecureString ToSecureString(this string str) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - if (string.IsNullOrWhiteSpace(str)) - { - return null; - } - - var result = new SecureString(); - foreach (var c in str) - { - result.AppendChar(c); - } - - return result; - } - - /// - /// Converts a to a . - /// - /// The to convert. - /// Whether or not to use this extension method. - /// Returns a . - /// is . - public static string ToString(this SecureString str, bool extension) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return extension ? new NetworkCredential(string.Empty, str).Password : str.ToString(); - } - - /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// . - /// - /// The input string. - /// Returns an instance of . - /// is . - public static TimeSpan ToTimeSpan(this string str) - { - if (str is null) - { - throw new ArgumentNullException(nameof(str)); - } - - return TimeSpanParser.Parse(str); - } - - /// - /// Returns the current string, or an alternative value if the current string is null or empty, or optionally if the - /// current string consists of only whitespace. - /// - /// The value to check. - /// The alternative value if does not meet the criteria. - /// - /// Optional. If set to , will be returned also if - /// only consists of whitespace. - /// - /// Returns or . - /// is . - public static string WithAlternative(this string? value, string alternative, bool includeWhiteSpace = false) - { - if (alternative is null) - { - throw new ArgumentNullException(nameof(alternative)); - } - - return (includeWhiteSpace ? value?.AsNullIfWhiteSpace() : value?.AsNullIfEmpty()) ?? alternative; - } - } -} diff --git a/X10D/src/StructExtensions.cs b/X10D/src/StructExtensions.cs deleted file mode 100644 index cb9aac7..0000000 --- a/X10D/src/StructExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -namespace X10D -{ - using System; - - /// - /// Extension methods for types. - /// - public static class StructExtensions - { - /// - /// Returns the next value in an using the specified value as a starting point. - /// - /// An . - /// An value. - /// - /// Optional. Whether or not to wrap to the to the start of the enum. Defaults to - /// true. - /// - /// Returns a value. - public static T Next(this T src, bool wrap = true) - where T : struct - { - if (!typeof(T).IsEnum) - { - throw new ArgumentException($"Argument {typeof(T).FullName} is not an Enum"); - } - - var arr = (T[])Enum.GetValues(src.GetType()); - var j = Array.IndexOf(arr, src) + 1; - return arr.Length == j ? arr[wrap ? 0 : j - 1] : arr[j]; - } - - /// - /// Returns the previous value in an using the specified value as a starting point. - /// - /// An . - /// An value. - /// - /// Optional. Whether or not to wrap to the to the end of the enum. Defaults to - /// true. - /// - /// Returns a value. - public static T Previous(this T src, bool wrap = true) - where T : struct - { - if (!typeof(T).IsEnum) - { - throw new ArgumentException($"Argument {typeof(T).FullName} is not an Enum"); - } - - var arr = (T[])Enum.GetValues(src.GetType()); - var j = Array.IndexOf(arr, src) - 1; - return j < 0 ? arr[wrap ? arr.Length - 1 : 0] : arr[j]; - } - } -} diff --git a/X10D/src/Text/CharExtensions.cs b/X10D/src/Text/CharExtensions.cs new file mode 100644 index 0000000..9d59738 --- /dev/null +++ b/X10D/src/Text/CharExtensions.cs @@ -0,0 +1,31 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class CharExtensions +{ + /// + /// Returns a string composed of the current character repeated a specified number of times. + /// + /// The character to repeat. + /// The number of times to repeat. + /// + /// A composed of repeated times. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this char value, int count) + { + return count switch + { + < 0 => throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0), + 0 => string.Empty, + 1 => value.ToString(), + _ => new string(value, count) + }; + } +} diff --git a/X10D/src/Text/Extensions.cs b/X10D/src/Text/Extensions.cs new file mode 100644 index 0000000..574083c --- /dev/null +++ b/X10D/src/Text/Extensions.cs @@ -0,0 +1,25 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text.Json; + +namespace X10D.Text; + +/// +/// Text-related extension methods for all types. +/// +public static class Extensions +{ + /// + /// Returns a JSON string representation of the specified value. + /// + /// The value to convert. + /// The JSON serialization options. + /// The type of the value to convert. + /// A JSON string representing the object. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string ToJson(this T value, JsonSerializerOptions? options = null) + { + return JsonSerializer.Serialize(value, options); + } +} diff --git a/X10D/src/Text/RuneExtensions.cs b/X10D/src/Text/RuneExtensions.cs new file mode 100644 index 0000000..5c0b146 --- /dev/null +++ b/X10D/src/Text/RuneExtensions.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class RuneExtensions +{ + /// + /// Returns a string composed of the current rune repeated a specified number of times. + /// + /// The rune to repeat. + /// The number of times to repeat. + /// + /// A composed of repeated times. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this Rune value, int count) + { + switch (count) + { + case < 0: + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + case 0: + return string.Empty; + case 1: + return value.ToString(); + } + + int utf8SequenceLength = value.Utf8SequenceLength; + Span utf8 = stackalloc byte[utf8SequenceLength]; + value.EncodeToUtf8(utf8); + + Span buffer = stackalloc byte[utf8.Length * count]; + for (var index = 0; index < count; index++) + { + utf8.CopyTo(buffer.Slice(index * utf8.Length, utf8.Length)); + } + + return Encoding.UTF8.GetString(buffer); + } +} diff --git a/X10D/src/Text/StringBuilderReader.cs b/X10D/src/Text/StringBuilderReader.cs new file mode 100644 index 0000000..fe6e969 --- /dev/null +++ b/X10D/src/Text/StringBuilderReader.cs @@ -0,0 +1,221 @@ +using System.Text; + +namespace X10D.Text; + +// NOTE: the overriden async overloads simply wrap the result of their sync counterparts because StringBuilder isn't inherently +// async. calling Task.FromResult (or creating a new ValueTask) is sufficient enough in this case, because there is simply no +// other way to have native async reading of a StringBuilder + +/// +/// Represents a reads from a . +/// +public class StringBuilderReader : TextReader +{ + private readonly StringBuilder _stringBuilder; + private int _index; + + /// + /// Initializes a new instance of the class. + /// + /// The to wrap. + /// is . + public StringBuilderReader(StringBuilder stringBuilder) + { + _stringBuilder = stringBuilder ?? throw new ArgumentNullException(nameof(stringBuilder)); + } + + /// + public override void Close() + { + _index = _stringBuilder.Length; + } + + /// + public override int Peek() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index]; + } + + /// + public override int Read() + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + return _stringBuilder[_index++]; + } + + /// + public override int Read(char[] buffer, int index, int count) + { + if (buffer is null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + if (index < 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (buffer.Length - index < count) + { + throw new ArgumentException(ExceptionMessages.BufferTooSmall, nameof(buffer)); + } + + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = System.Math.Min(_stringBuilder.Length - _index, count); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override int Read(Span buffer) + { + int count = System.Math.Min(buffer.Length, _stringBuilder.Length - _index); + for (var index = 0; index < count; index++) + { + buffer[index] = _stringBuilder[index + _index]; + } + + _index += count; + return count; + } + + /// + public override Task ReadAsync(char[] buffer, int index, int count) + { + return Task.FromResult(Read(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream into a memory block. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the number of + /// characters that have been read, or 0 if at the end of the stream and no data was read. The number will be less than or + /// equal to the buffer length, depending on whether the data is available within the stream. + /// + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(Read(buffer.Span)); + } + + /// + public override int ReadBlock(Span buffer) + { + return Read(buffer); + } + + /// + public override int ReadBlock(char[] buffer, int index, int count) + { + if (_index >= _stringBuilder.Length) + { + return -1; + } + + int length = System.Math.Min(count, _stringBuilder.Length - _index); + _stringBuilder.CopyTo(_index, buffer, index, length); + _index += length; + return length; + } + + /// + public override Task ReadBlockAsync(char[] buffer, int index, int count) + { + return Task.FromResult(ReadBlock(buffer, index, count)); + } + + // except not really 🔽 + /// + /// Asynchronously reads the characters from the current stream and writes the data to a buffer. + /// + /// + /// When this method returns, contains the specified memory block of characters replaced by the characters read from the + /// current source. + /// + /// Ignored. + /// + /// A value task that represents the asynchronous read operation. The value of the type parameter contains the total + /// number of characters read into the buffer. The result value can be less than the number of characters requested if the + /// number of characters currently available is less than the requested number, or it can be 0 (zero) if the end of the + /// stream has been reached. + /// + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) + { + return new ValueTask(ReadBlock(buffer.Span)); + } + + /// + public override string? ReadLine() + { + if (_index >= _stringBuilder.Length) + { + return null; + } + + int start = _index; + while (_index < _stringBuilder.Length && _stringBuilder[_index] != '\n') + { + _index++; + } + + if (_index < _stringBuilder.Length) + { + _index++; + } + + var result = _stringBuilder.ToString(start, _index - start); + + if (result.Length > 0 && (result[^1] == '\n' || result[^1] == '\r')) + { + result = result[..^1]; + } + + return result; + } + + /// + public override Task ReadLineAsync() + { + return Task.FromResult(ReadLine()); + } + + /// + public override string ReadToEnd() + { + var value = _stringBuilder.ToString(_index, _stringBuilder.Length - _index); + _index = _stringBuilder.Length; + return value; + } + + /// + public override Task ReadToEndAsync() + { + return Task.FromResult(ReadToEnd()); + } +} diff --git a/X10D/src/Text/StringExtensions.cs b/X10D/src/Text/StringExtensions.cs new file mode 100644 index 0000000..2f83e3c --- /dev/null +++ b/X10D/src/Text/StringExtensions.cs @@ -0,0 +1,551 @@ +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using X10D.Collections; +using X10D.Core; +using X10D.IO; + +namespace X10D.Text; + +/// +/// Text-related extension methods for . +/// +public static class StringExtensions +{ + /// + /// Normalizes a string which may be either or empty to . + /// + /// The value to normalize. + /// + /// if is or empty; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("value")] + public static string? AsNullIfEmpty(this string? value) + { + return value.WithEmptyAlternative(null); + } + + /// + /// Normalizes a string which may be either , empty, or consisting of only whitespace, to + /// . + /// + /// The value to normalize. + /// + /// if is , empty, or consists of only + /// whitespace; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("value")] + public static string? AsNullIfWhiteSpace(this string? value) + { + return value.WithWhiteSpaceAlternative(null); + } + + /// + /// Converts the specified string, which encodes binary data as base-64 digits, to an equivalent plain text string. + /// + /// The base-64 string to convert. + /// The plain text string representation of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Base64Decode(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return Convert.FromBase64String(value).ToString(Encoding.ASCII); + } + + /// + /// Converts the current string to its equivalent string representation that is encoded with base-64 digits. + /// + /// The plain text string to convert. + /// The string representation, in base 64, of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Base64Encode(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + return Convert.ToBase64String(value.GetBytes(Encoding.ASCII)); + } + + /// + /// Converts this string from one encoding to another. + /// + /// The input string. + /// The input encoding. + /// The output encoding. + /// + /// Returns a new with its data converted to + /// . + /// + /// + /// is + /// - or - + /// is + /// -or + /// is . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string ChangeEncoding(this string value, Encoding sourceEncoding, Encoding destinationEncoding) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (sourceEncoding is null) + { + throw new ArgumentNullException(nameof(sourceEncoding)); + } + + if (destinationEncoding is null) + { + throw new ArgumentNullException(nameof(destinationEncoding)); + } + + return value.GetBytes(sourceEncoding).ToString(destinationEncoding); + } + + /// + /// Parses a into an . + /// + /// The type of the . + /// The value to parse. + /// The value corresponding to the . + /// + /// Credit for this method goes to Scott Dorman: + /// (http://geekswithblogs.net/sdorman/Default.aspx). + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T EnumParse(this string value) + where T : struct, Enum + { + return value.EnumParse(false); + } + + /// + /// Parses a into an . + /// + /// The type of the . + /// The value to parse. + /// Whether or not to ignore casing. + /// The value corresponding to the . + /// + /// Credit for this method goes to Scott Dorman: + /// (http://geekswithblogs.net/sdorman/Default.aspx). + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T EnumParse(this string value, bool ignoreCase) + where T : struct, Enum + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + value = value.Trim(); + + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException(Resource.EnumParseEmptyStringException, nameof(value)); + } + + return Enum.Parse(value, ignoreCase); + } + + /// + /// Returns an object from the specified JSON string. + /// + /// The JSON to convert. + /// The JSON serialization options. + /// The type of the value to deserialize. + /// + /// An object constructed from the JSON string, or if deserialization could not be performed. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static T? FromJson(this string value, JsonSerializerOptions? options = null) + { + return JsonSerializer.Deserialize(value, options); + } + + /// + /// Gets a [] representing the value the with + /// encoding. + /// + /// The string to convert. + /// Returns a []. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte[] GetBytes(this string value) + { + return value.GetBytes(Encoding.UTF8); + } + + /// + /// Gets a [] representing the value the with the provided encoding. + /// + /// The string to convert. + /// The encoding to use. + /// Returns a []. + /// + /// or or both are + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static byte[] GetBytes(this string value, Encoding encoding) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (encoding is null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return encoding.GetBytes(value); + } + + /// + /// Determines if all alpha characters in this string are considered lowercase. + /// + /// The input string. + /// + /// if all alpha characters in this string are lowercase; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLower(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + var rune = new Rune(value[index]); + + if (!Rune.IsLetter(rune)) + { + continue; + } + + if (!Rune.IsLower(rune)) + { + return false; + } + } + + return true; + } + + /// + /// Determines whether the current string is considered palindromic; that is, the letters within the string are the + /// same when reversed. + /// + /// The value to check. + /// + /// if is considered a palindromic string; otherwise, + /// . + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsPalindrome(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (string.IsNullOrWhiteSpace(value)) + { + // empty string is not palindromic + return false; + } + + for (int index = 0, endIndex = value.Length - 1; index < value.Length; index++, endIndex--) + { + Rune startRune = new Rune(value[index]); + Rune endRune = new Rune(value[endIndex]); + + if (!Rune.IsLetter(startRune) && !Rune.IsNumber(startRune)) + { + endIndex++; + continue; + } + + if (!Rune.IsLetter(endRune) && !Rune.IsNumber(endRune)) + { + index--; + continue; + } + + if (Rune.ToUpperInvariant(startRune) != Rune.ToUpperInvariant(endRune)) + { + return false; + } + } + + return true; + } + + /// + /// Determines if all alpha characters in this string are considered uppercase. + /// + /// The input string. + /// + /// if all alpha characters in this string are uppercase; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsUpper(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + for (var index = 0; index < value.Length; index++) + { + var rune = new Rune(value[index]); + + if (!Rune.IsLetter(rune)) + { + continue; + } + + if (!Rune.IsUpper(rune)) + { + return false; + } + } + + return true; + } + + /// + /// Repeats a string a specified number of times. + /// + /// The string to repeat. + /// The repeat count. + /// A string containing repeated times. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Repeat(this string value, int count) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + switch (count) + { + case < 0: + throw new ArgumentOutOfRangeException(nameof(count), ExceptionMessages.CountMustBeGreaterThanOrEqualTo0); + case 0: + return string.Empty; + case 1: + return value; + } + + var builder = new StringBuilder(value.Length * count); + + for (var i = 0; i < count; i++) + { + builder.Append(value); + } + + return builder.ToString(); + } + + /// + /// Returns a new string of a specified length by randomly selecting characters from the current string. + /// + /// The pool of characters to use. + /// The length of the new string returned. + /// The supplier. + /// + /// A new string whose length is equal to which contains randomly selected characters from + /// . + /// + /// is . + /// is less than 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Randomize(this string source, int length, Random? random = null) + { + if (source is null) + { + throw new ArgumentNullException(nameof(source)); + } + + if (length < 0) + { + throw new ArgumentOutOfRangeException(nameof(length), ExceptionMessages.LengthGreaterThanOrEqualTo0); + } + + if (length == 0) + { + return string.Empty; + } + + random ??= Random.Shared; + + char[] array = source.ToCharArray(); + var builder = new StringBuilder(length); + + while (builder.Length < length) + { + char next = random.NextFrom(array); + builder.Append(next); + } + + return builder.ToString(); + } + + /// + /// Reverses the current string. + /// + /// The string to reverse. + /// A whose characters are that of in reverse order. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Reverse(this string value) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Length < 2) + { + return value; + } + + Span span = stackalloc char[value.Length]; + + for (var index = 0; index < value.Length; index++) + { + span[index] = value[value.Length - index - 1]; + } + + return new string(span); + } + + /// + /// Shuffles the characters in the string. + /// + /// The string to shuffle. + /// + /// The instance to use for the shuffling. If is specified, + /// is used. + /// + /// A new containing the characters in , rearranged. + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static string Shuffled(this string value, Random? random = null) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + random ??= Random.Shared; + + char[] array = value.ToCharArray(); + array.Shuffle(random); + return new string(array); + } + + /// + /// Splits the into chunks that are no greater than in length. + /// + /// The string to split. + /// The maximum length of each string in the returned result. + /// + /// Returns an containing instances which are no + /// greater than in length. + /// + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static IEnumerable Split(this string value, int chunkSize) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (chunkSize == 0) + { + yield return string.Empty; + yield break; + } + + for (var i = 0; i < value.Length; i += chunkSize) + { + yield return value[i..System.Math.Min(i + chunkSize, value.Length)]; + } + } + + /// + /// Normalizes a string which may be either or empty to a specified alternative. + /// + /// The value to normalize. + /// The alternative string. + /// + /// if is or empty; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("alternative")] + public static string? WithEmptyAlternative(this string? value, string? alternative) + { + return string.IsNullOrEmpty(value) ? alternative : value; + } + + /// + /// Normalizes a string which may be either , empty, or consisting of only whitespace, to a + /// specified alternative. + /// + /// The value to normalize. + /// The alternative string. + /// + /// if is , empty, or consists of only + /// whitespace; otherwise, . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + [return: NotNullIfNotNull("alternative")] + public static string? WithWhiteSpaceAlternative(this string? value, string? alternative) + { + return string.IsNullOrWhiteSpace(value) ? alternative : value; + } +} diff --git a/X10D/src/Time/ByteExtensions.cs b/X10D/src/Time/ByteExtensions.cs new file mode 100644 index 0000000..b15a91d --- /dev/null +++ b/X10D/src/Time/ByteExtensions.cs @@ -0,0 +1,166 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class ByteExtensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this byte value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && value % 100 != 0; // mod 400 not required, byte.MaxValue is 255 anyway + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this byte value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this byte value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this byte value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this byte value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this byte value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this byte value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this byte value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this byte value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this byte value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/DateTimeExtensions.cs b/X10D/src/Time/DateTimeExtensions.cs new file mode 100644 index 0000000..b546722 --- /dev/null +++ b/X10D/src/Time/DateTimeExtensions.cs @@ -0,0 +1,110 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class DateTimeExtensions +{ + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTime value) + { + return value.Age(DateTime.Today); + } + + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTime value, DateTime asOf) + { + return ((DateTimeOffset)value).Age(asOf); + } + + /// + /// A representing the first occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime First(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).First(dayOfWeek).DateTime; + } + + /// + /// A representing the first day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime FirstDayOfMonth(this DateTime value) + { + return ((DateTimeOffset)value).FirstDayOfMonth().DateTime; + } + + /// + /// Returns a value indicating whether the year represented by the current is a leap year. + /// + /// The date whose year to check. + /// + /// if the year represented by is a leap year; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this DateTime value) + { + return DateTime.IsLeapYear(value.Year); + } + + /// + /// A representing the final occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Last(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).Last(dayOfWeek).DateTime; + } + + /// + /// A representing the last day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime LastDayOfMonth(this DateTime value) + { + return ((DateTimeOffset)value).LastDayOfMonth().DateTime; + } + + /// + /// A representing the next occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Next(this DateTime value, DayOfWeek dayOfWeek) + { + return ((DateTimeOffset)value).Next(dayOfWeek).DateTime; + } + + /// + /// Returns the number of milliseconds that have elapsed since 1970-01-01T00:00:00.000Z. + /// + /// The current date. + /// The number of milliseconds that have elapsed since 1970-01-01T00:00:00.000Z. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long ToUnixTimeMilliseconds(this DateTime value) + { + return ((DateTimeOffset)value).ToUnixTimeMilliseconds(); + } + + /// + /// Returns the number of seconds that have elapsed since 1970-01-01T00:00:00.000Z. + /// + /// The current date. + /// The number of seconds that have elapsed since 1970-01-01T00:00:00.000Z. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static long ToUnixTimeSeconds(this DateTime value) + { + return ((DateTimeOffset)value).ToUnixTimeSeconds(); + } +} diff --git a/X10D/src/Time/DateTimeOffsetExtensions.cs b/X10D/src/Time/DateTimeOffsetExtensions.cs new file mode 100644 index 0000000..ca06ec0 --- /dev/null +++ b/X10D/src/Time/DateTimeOffsetExtensions.cs @@ -0,0 +1,137 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class DateTimeOffsetExtensions +{ + /// + /// Returns the rounded-down integer number of years since a given date as of today. + /// + /// The date from which to calculate. + /// The rounded-down integer number of years since as of today. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTimeOffset value) + { + return value.Age(DateTime.Today); + } + + /// + /// Returns the rounded-down integer number of years since a given date as of another specified date. + /// + /// The date from which to calculate. + /// The date at which to stop calculating. + /// + /// The rounded-down integer number of years since as of the date specified by + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static int Age(this DateTimeOffset value, DateTimeOffset asOf) + { + return (int)(((asOf.Date - TimeSpan.FromDays(1) - value.Date).TotalDays + 1) / 365.2425); + } + + /// + /// Gets a date representing the first occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the first occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset First(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + var first = value.FirstDayOfMonth(); + + if (first.DayOfWeek != dayOfWeek) + { + first = first.Next(dayOfWeek); + } + + return first; + } + + /// + /// Gets a date representing the first day of the current month. + /// + /// The current date. + /// A representing the first day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FirstDayOfMonth(this DateTimeOffset value) + { + return value.AddDays(1 - value.Day); + } + + /// + /// Returns a value indicating whether the year represented by the current is a leap year. + /// + /// The date whose year to check. + /// + /// if the year represented by is a leap year; otherwise, + /// . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this DateTimeOffset value) + { + return DateTime.IsLeapYear(value.Year); + } + + /// + /// Gets a date representing the final occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the final occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset Last(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + var last = value.LastDayOfMonth(); + var lastDayOfWeek = last.DayOfWeek; + + int diff = dayOfWeek - lastDayOfWeek; + int offset = diff > 0 ? diff - 7 : diff; + + return last.AddDays(offset); + } + + /// + /// Gets a date representing the last day of the current month. + /// + /// The current date. + /// A representing the last day of the current month. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset LastDayOfMonth(this DateTimeOffset value) + { + int daysInMonth = DateTime.DaysInMonth(value.Year, value.Month); + return new DateTime(value.Year, value.Month, daysInMonth); + } + + /// + /// Gets a date representing the next occurence of a specified day of the week in the current month. + /// + /// The current date. + /// The day of the week. + /// A representing the next occurence of . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset Next(this DateTimeOffset value, DayOfWeek dayOfWeek) + { + int offsetDays = dayOfWeek - value.DayOfWeek; + + if (offsetDays <= 0) + { + offsetDays += 7; + } + + return value.AddDays(offsetDays); + } +} diff --git a/X10D/src/Time/DecimalExtensions.cs b/X10D/src/Time/DecimalExtensions.cs new file mode 100644 index 0000000..880e1a9 --- /dev/null +++ b/X10D/src/Time/DecimalExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class DecimalExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this decimal value) + { + return TimeSpan.FromMilliseconds((double)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this decimal value) + { + return TimeSpan.FromSeconds((double)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this decimal value) + { + return TimeSpan.FromMinutes((double)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this decimal value) + { + return TimeSpan.FromHours((double)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this decimal value) + { + return TimeSpan.FromDays((double)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this decimal value) + { + return TimeSpan.FromDays((double)value * 7); + } +} diff --git a/X10D/src/Time/DoubleExtensions.cs b/X10D/src/Time/DoubleExtensions.cs new file mode 100644 index 0000000..81d6b38 --- /dev/null +++ b/X10D/src/Time/DoubleExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class DoubleExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this double value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this double value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this double value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this double value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this double value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this double value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/HalfExtensions.cs b/X10D/src/Time/HalfExtensions.cs new file mode 100644 index 0000000..396fc68 --- /dev/null +++ b/X10D/src/Time/HalfExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class HalfExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this Half value) + { + return TimeSpan.FromMilliseconds((float)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this Half value) + { + return TimeSpan.FromSeconds((float)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this Half value) + { + return TimeSpan.FromMinutes((float)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this Half value) + { + return TimeSpan.FromHours((float)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this Half value) + { + return TimeSpan.FromDays((float)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this Half value) + { + return TimeSpan.FromDays((float)value * 7); + } +} diff --git a/X10D/src/Time/Int16Extensions.cs b/X10D/src/Time/Int16Extensions.cs new file mode 100644 index 0000000..bfc1605 --- /dev/null +++ b/X10D/src/Time/Int16Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int16Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this short value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this short value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this short value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this short value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this short value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this short value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this short value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this short value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this short value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this short value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/Int32Extensions.cs b/X10D/src/Time/Int32Extensions.cs new file mode 100644 index 0000000..576f84f --- /dev/null +++ b/X10D/src/Time/Int32Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int32Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this int value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this int value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this int value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this int value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this int value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this int value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this int value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this int value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this int value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this int value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/Int64Extensions.cs b/X10D/src/Time/Int64Extensions.cs new file mode 100644 index 0000000..f07969c --- /dev/null +++ b/X10D/src/Time/Int64Extensions.cs @@ -0,0 +1,172 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class Int64Extensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this long value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this long value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this long value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this long value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this long value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this long value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this long value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this long value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this long value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this long value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/SByteExtensions.cs b/X10D/src/Time/SByteExtensions.cs new file mode 100644 index 0000000..606df12 --- /dev/null +++ b/X10D/src/Time/SByteExtensions.cs @@ -0,0 +1,173 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using X10D.Math; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class SByteExtensions +{ + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this sbyte value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + if (value < 0) + { + value++; + } + + return value.Mod(4) == 0 && value.Mod(100) != 0; // mod 400 not required, sbyte.MaxValue is 127 anyway + } + + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this sbyte value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this sbyte value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this sbyte value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this sbyte value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this sbyte value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this sbyte value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this sbyte value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this sbyte value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this sbyte value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/SingleExtensions.cs b/X10D/src/Time/SingleExtensions.cs new file mode 100644 index 0000000..f1d0546 --- /dev/null +++ b/X10D/src/Time/SingleExtensions.cs @@ -0,0 +1,92 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +public static class SingleExtensions +{ + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this float value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this float value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this float value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this float value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this float value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this float value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/StringExtensions.cs b/X10D/src/Time/StringExtensions.cs new file mode 100644 index 0000000..cf22f71 --- /dev/null +++ b/X10D/src/Time/StringExtensions.cs @@ -0,0 +1,72 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class StringExtensions +{ + /// + /// Parses a shorthand time span string (e.g. 3w 2d 1h) and converts it to an instance of . + /// + /// + /// The input string. Floating point is not supported, but range the following units are supported: + /// + /// + /// + /// Suffix + /// Meaning + /// + /// + /// + /// ms + /// Milliseconds + /// + /// + /// s + /// Seconds + /// + /// + /// m + /// Minutes + /// + /// + /// h + /// Hours + /// + /// + /// d + /// Days + /// + /// + /// w + /// Weeks + /// + /// + /// mo + /// Months + /// + /// + /// y + /// Years + /// + /// + /// + /// A new instance of . + /// is . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan ToTimeSpan(this string input) + { + if (input is null) + { + throw new ArgumentNullException(nameof(input)); + } + + return TimeSpanParser.TryParse(input, out TimeSpan result) + ? result + : default; + } +} diff --git a/X10D/src/Time/TimeSpanExtensions.cs b/X10D/src/Time/TimeSpanExtensions.cs new file mode 100644 index 0000000..d9424f1 --- /dev/null +++ b/X10D/src/Time/TimeSpanExtensions.cs @@ -0,0 +1,38 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Extension methods for . +/// +public static class TimeSpanExtensions +{ + /// + /// Returns a that is a specified duration in the past relative to the current time. + /// + /// The whose duration to subtract. + /// + /// A that is a duration of in the past relative to the current time. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime Ago(this TimeSpan value) + { + return DateTime.Now.Subtract(value); + } + + /// + /// Returns a that is a specified duration in the future relative to the current time. + /// + /// The whose duration to add. + /// + /// A that is a duration of in the future relative to the current time. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTime FromNow(this TimeSpan value) + { + return DateTime.Now.Add(value); + } +} diff --git a/X10D/src/Time/TimeSpanParser.cs b/X10D/src/Time/TimeSpanParser.cs new file mode 100644 index 0000000..2839f22 --- /dev/null +++ b/X10D/src/Time/TimeSpanParser.cs @@ -0,0 +1,133 @@ +namespace X10D.Time; + +/// +/// Represents a class which contains a parser which converts into . +/// +public static class TimeSpanParser +{ + /// + /// Attempts to parses a shorthand time span string (e.g. 3w 2d 1h), converting it to an instance of + /// which represents that duration of time. + /// + /// + /// The input string. Floating point is not supported, but range the following units are supported: + /// + /// + /// + /// Suffix + /// Meaning + /// + /// + /// + /// ms + /// Milliseconds + /// + /// + /// s + /// Seconds + /// + /// + /// m + /// Minutes + /// + /// + /// h + /// Hours + /// + /// + /// d + /// Days + /// + /// + /// w + /// Weeks + /// + /// + /// mo + /// Months + /// + /// + /// y + /// Years + /// + /// + /// + /// When this method returns, contains the parsed result. + /// if the parse was successful, otherwise. + /// is . + public static bool TryParse(string value, out TimeSpan result) + { + if (value is null) + { + throw new ArgumentNullException(nameof(value)); + } + + result = TimeSpan.Zero; + var unitValue = 0; + + for (var index = 0; index < value.Length; index++) + { + char current = value[index]; + switch (current) + { + case var digitChar when char.IsDigit(digitChar): + var digit = (int)char.GetNumericValue(digitChar); + unitValue = unitValue * 10 + digit; + break; + + case 'y': + result += TimeSpan.FromDays(unitValue * 365); + unitValue = 0; + break; + + case 'm': + if (index < value.Length - 1 && value[index + 1] == 'o') + { + index++; + result += TimeSpan.FromDays(unitValue * 30); + } + else if (index < value.Length - 1 && value[index + 1] == 's') + { + index++; + result += TimeSpan.FromMilliseconds(unitValue); + } + else + { + result += TimeSpan.FromMinutes(unitValue); + } + + unitValue = 0; + break; + + case 'w': + result += TimeSpan.FromDays(unitValue * 7); + unitValue = 0; + break; + + case 'd': + result += TimeSpan.FromDays(unitValue); + unitValue = 0; + break; + + case 'h': + result += TimeSpan.FromHours(unitValue); + unitValue = 0; + break; + + case 's': + result += TimeSpan.FromSeconds(unitValue); + unitValue = 0; + break; + + case var space when char.IsWhiteSpace(space): + break; + + default: + result = TimeSpan.Zero; + return false; + } + } + + return true; + } +} diff --git a/X10D/src/Time/UInt16Extensions.cs b/X10D/src/Time/UInt16Extensions.cs new file mode 100644 index 0000000..b0ee2c9 --- /dev/null +++ b/X10D/src/Time/UInt16Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt16Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this ushort value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this ushort value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this ushort value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this ushort value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this ushort value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this ushort value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this ushort value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this ushort value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this ushort value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this ushort value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/UInt32Extensions.cs b/X10D/src/Time/UInt32Extensions.cs new file mode 100644 index 0000000..9a4b09f --- /dev/null +++ b/X10D/src/Time/UInt32Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt32Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this uint value) + { + return DateTimeOffset.FromUnixTimeMilliseconds(value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this uint value) + { + return DateTimeOffset.FromUnixTimeSeconds(value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this uint value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this uint value) + { + return TimeSpan.FromTicks(value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this uint value) + { + return TimeSpan.FromMilliseconds(value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this uint value) + { + return TimeSpan.FromSeconds(value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this uint value) + { + return TimeSpan.FromMinutes(value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this uint value) + { + return TimeSpan.FromHours(value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this uint value) + { + return TimeSpan.FromDays(value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this uint value) + { + return TimeSpan.FromDays(value * 7); + } +} diff --git a/X10D/src/Time/UInt64Extensions.cs b/X10D/src/Time/UInt64Extensions.cs new file mode 100644 index 0000000..2dfb049 --- /dev/null +++ b/X10D/src/Time/UInt64Extensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace X10D.Time; + +/// +/// Time-related extension methods for . +/// +[CLSCompliant(false)] +public static class UInt64Extensions +{ + /// + /// Converts a Unix time expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z (January 1, + /// 1970, at 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800,000. + /// -or- + /// is greater than 253,402,300,799,999. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeMilliseconds(this ulong value) + { + return DateTimeOffset.FromUnixTimeMilliseconds((long)value); + } + + /// + /// Converts a Unix time expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z to a + /// value. + /// + /// + /// A Unix time, expressed as the number of seconds that have elapsed since 1970-01-01T00:00:00Z (January 1, 1970, at + /// 12:00 AM UTC). For Unix times before this date, its value is negative. + /// + /// A date and time value that represents the same moment in time as the Unix time. + /// + /// is less than -62,135,596,800. + /// -or- + /// is greater than 253,402,300,799. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static DateTimeOffset FromUnixTimeSeconds(this ulong value) + { + return DateTimeOffset.FromUnixTimeSeconds((long)value); + } + + /// + /// Returns a value indicating whether the current integer, representing a year, is a leap year. + /// + /// The value whose leap year status to check. + /// + /// if refers to a leap year; otherwise, . + /// + /// is 0. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static bool IsLeapYear(this ulong value) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), ExceptionMessages.YearCannotBeZero); + } + + return value % 4 == 0 && (value % 100 != 0 || value % 400 == 0); + } + + /// + /// Returns a that represents this value as the number of ticks. + /// + /// The duration, in ticks. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Ticks(this ulong value) + { + return TimeSpan.FromTicks((long)value); + } + + /// + /// Returns a that represents this value as the number of milliseconds. + /// + /// The duration, in milliseconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Milliseconds(this ulong value) + { + return TimeSpan.FromMilliseconds((long)value); + } + + /// + /// Returns a that represents this value as the number of seconds. + /// + /// The duration, in seconds. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Seconds(this ulong value) + { + return TimeSpan.FromSeconds((long)value); + } + + /// + /// Returns a that represents this value as the number of minutes. + /// + /// The duration, in minutes. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Minutes(this ulong value) + { + return TimeSpan.FromMinutes((long)value); + } + + /// + /// Returns a that represents this value as the number of hours. + /// + /// The duration, in hours. + /// + /// A whose will equal . + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Hours(this ulong value) + { + return TimeSpan.FromHours((long)value); + } + + /// + /// Returns a that represents this value as the number of days. + /// + /// The duration, in days. + /// A whose will equal . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Days(this ulong value) + { + return TimeSpan.FromDays((long)value); + } + + /// + /// Returns a that represents this value as the number of weeks. + /// + /// The duration, in weeks. + /// + /// A whose will equal × 7. + /// + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TimeSpan Weeks(this ulong value) + { + return TimeSpan.FromDays((long)value * 7); + } +} diff --git a/X10D/src/TimeSpanParser.cs b/X10D/src/TimeSpanParser.cs deleted file mode 100644 index 5f5a7aa..0000000 --- a/X10D/src/TimeSpanParser.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace X10D -{ - using System; - using System.Diagnostics; - using System.Text.RegularExpressions; - - /// - /// Represents a class which contains a parser which converts into . - /// - public static class TimeSpanParser - { - /// - /// Parses a shorthand time span string (e.g. 3w 2d 1.5h) and converts it to an instance of - /// . - /// - /// The input string. - /// The format provider. - /// Returns an instance of . - public static TimeSpan Parse(string input, IFormatProvider provider = null) - { - const string realNumberPattern = @"([0-9]*\.[0-9]+|[0-9]+)"; - var pattern = $"^(?:{realNumberPattern} *w)? *" + - $"(?:{realNumberPattern} *d)? *" + - $"(?:{realNumberPattern} *h)? *" + - $"(?:{realNumberPattern} *m)? *" + - $"(?:{realNumberPattern} *s)? *" + - $"(?:{realNumberPattern} *ms)?$"; - - var match = Regex.Match(input, pattern); - double weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0; - - if (match.Groups[1].Success) - { - weeks = double.Parse(match.Groups[1].Value, provider); - } - - if (match.Groups[2].Success) - { - days = double.Parse(match.Groups[2].Value, provider); - } - - if (match.Groups[3].Success) - { - hours = double.Parse(match.Groups[3].Value, provider); - } - - if (match.Groups[4].Success) - { - minutes = double.Parse(match.Groups[4].Value, provider); - } - - if (match.Groups[5].Success) - { - seconds = double.Parse(match.Groups[5].Value, provider); - } - - if (match.Groups[6].Success) - { - milliseconds = double.Parse(match.Groups[6].Value, provider); - } - - Trace.WriteLine($"Input: {input}"); - Trace.WriteLine($"Parsed: {weeks}w {days}d {hours}h {minutes}m {seconds}s {milliseconds}ms"); - - var span = TimeSpan.Zero; - - span += TimeSpan.FromDays(weeks * 7); - span += TimeSpan.FromDays(days); - span += TimeSpan.FromHours(hours); - span += TimeSpan.FromMinutes(minutes); - span += TimeSpan.FromSeconds(seconds); - span += TimeSpan.FromMilliseconds(milliseconds); - - return span; - } - } -} diff --git a/X10D/src/WaitHandleExtensions.cs b/X10D/src/WaitHandleExtensions.cs deleted file mode 100644 index 9febee6..0000000 --- a/X10D/src/WaitHandleExtensions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace X10D -{ - using System.Threading; - using System.Threading.Tasks; - - /// - /// Extension methods for . - /// - public static class WaitHandleExtensions - { - /// - /// Returns a which can be awaited until the current receives a signal. - /// - /// The instance. - /// Returns a task which wraps . - public static Task WaitOneAsync(this WaitHandle handle) - { - return new Task(() => handle.WaitOne()); - } - } -} diff --git a/banner.png b/banner.png new file mode 100644 index 0000000..d9aa957 Binary files /dev/null and b/banner.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..87aef9f --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "6.0.0", + "rollForward": "latestMajor", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/icon.png b/icon.png index c8a172e..77ff5ba 100644 Binary files a/icon.png and b/icon.png differ