├── .gitignore ├── NuGetIcon.png ├── Lombiq.Arithmetics.Tests ├── xunit.runner.json ├── CompatibilityAssert.cs ├── Lombiq.Arithmetics.Tests.csproj ├── UnumTests │ ├── UnumHelperTests.cs │ ├── UnumEnvironmentTests.cs │ └── UnumTests.cs ├── PositTests │ ├── QuireTests.cs │ ├── PositTests.cs │ └── Posit32Tests.cs └── BitMaskTests.cs ├── .github └── workflows │ ├── validate-nuget-publish.yml │ ├── publish-nuget.yml │ ├── validate-pull-request.yml │ └── create-jira-issues-for-community-activities.yml ├── Unum ├── UnumException.cs ├── UnumConfiguration.cs ├── UnumHelper.cs ├── UnumEnvironment.cs └── Unum.cs ├── Posit ├── PositEnvironment.cs ├── Quire.cs └── Posit.cs ├── License.md ├── Lombiq.Arithmetics.csproj ├── Readme.md └── BitMask └── BitMask.cs /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | *.user 4 | -------------------------------------------------------------------------------- /NuGetIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lombiq/Arithmetics/HEAD/NuGetIcon.png -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", 3 | "parallelizeAssembly": true, 4 | "parallelizeTestCollections": true 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/validate-nuget-publish.yml: -------------------------------------------------------------------------------- 1 | name: Validate NuGet Publish 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - dev 8 | 9 | jobs: 10 | validate-nuget-publish: 11 | name: Validate NuGet Publish 12 | uses: Lombiq/GitHub-Actions/.github/workflows/validate-nuget-publish.yml@dev 13 | -------------------------------------------------------------------------------- /.github/workflows/publish-nuget.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NuGet 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | publish-nuget: 10 | name: Publish to NuGet 11 | uses: Lombiq/GitHub-Actions/.github/workflows/publish-nuget.yml@dev 12 | secrets: 13 | API_KEY: ${{ secrets.DEFAULT_NUGET_PUBLISH_API_KEY }} 14 | -------------------------------------------------------------------------------- /.github/workflows/validate-pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Validate Pull Request 2 | 3 | on: 4 | pull_request_target: 5 | 6 | jobs: 7 | validate-pull-request: 8 | name : Validate Pull Request 9 | uses: Lombiq/GitHub-Actions/.github/workflows/validate-submodule-pull-request.yml@dev 10 | with: 11 | repository: Lombiq/Hastlayer-SDK 12 | -------------------------------------------------------------------------------- /Unum/UnumException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace Lombiq.Arithmetics; 5 | 6 | [Serializable] 7 | public class UnumException : Exception 8 | { 9 | public UnumException(string message) 10 | : base(message) { } 11 | 12 | public UnumException(string message, Exception innerException) 13 | : base(message, innerException) { } 14 | 15 | public UnumException() { } 16 | 17 | protected UnumException(SerializationInfo info, StreamingContext context) 18 | : base(info, context) 19 | { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/CompatibilityAssert.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Sdk; 2 | 3 | namespace Lombiq.Arithmetics.Tests; 4 | 5 | public static class CompatibilityAssert 6 | { 7 | public static void AreEqual(T actual, T expected) => Xunit.Assert.Equal(expected, actual); 8 | 9 | public static void AreEqual(uint actual, int expected) => Xunit.Assert.Equal((uint)expected, actual); 10 | 11 | public static void AreEqual(int actual, uint expected) => Xunit.Assert.Equal(expected, (uint)actual); 12 | 13 | public static void AreEqual(T actual, T expected, string userMessage) 14 | { 15 | try 16 | { 17 | Xunit.Assert.Equal(expected, actual); 18 | } 19 | catch (EqualException) 20 | { 21 | Xunit.Assert.True(false, userMessage); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/create-jira-issues-for-community-activities.yml: -------------------------------------------------------------------------------- 1 | name: Create Jira issues for community activities 2 | 3 | on: 4 | discussion: 5 | types: [created] 6 | issues: 7 | types: [opened] 8 | pull_request_target: 9 | types: [opened] 10 | 11 | jobs: 12 | create-jira-issues-for-community-activities: 13 | name: Create Jira issues for community activities 14 | uses: Lombiq/GitHub-Actions/.github/workflows/create-jira-issues-for-community-activities.yml@dev 15 | secrets: 16 | JIRA_BASE_URL: ${{ secrets.DEFAULT_JIRA_BASE_URL }} 17 | JIRA_USER_EMAIL: ${{ secrets.DEFAULT_JIRA_USER_EMAIL }} 18 | JIRA_API_TOKEN: ${{ secrets.DEFAULT_JIRA_API_TOKEN }} 19 | JIRA_PROJECT_KEY: HAST 20 | DISCUSSION_JIRA_ISSUE_DESCRIPTION: ${{ secrets.DEFAULT_DISCUSSION_JIRA_ISSUE_DESCRIPTION }} 21 | ISSUE_JIRA_ISSUE_DESCRIPTION: ${{ secrets.DEFAULT_ISSUE_JIRA_ISSUE_DESCRIPTION }} 22 | PULL_REQUEST_JIRA_ISSUE_DESCRIPTION: ${{ secrets.DEFAULT_PULL_REQUEST_JIRA_ISSUE_DESCRIPTION }} 23 | with: 24 | issue-component: Lombiq.Arithmetics 25 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/Lombiq.Arithmetics.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | Library 5 | 6 | 7 | 8 | 9 | 10 | 11 | PreserveNewest 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Posit/PositEnvironment.cs: -------------------------------------------------------------------------------- 1 | namespace Lombiq.Arithmetics; 2 | 3 | public class PositEnvironment 4 | { 5 | public byte MaximumExponentSize { get; } 6 | 7 | public ushort Size { get; } 8 | 9 | public uint Useed { get; } 10 | 11 | public ushort FirstRegimeBitIndex { get; } 12 | 13 | public BitMask SignBitMask { get; } 14 | 15 | public BitMask FirstRegimeBitBitMask { get; } 16 | 17 | public BitMask EmptyBitMask { get; } 18 | 19 | public BitMask MaxValueBitMask { get; } 20 | 21 | public BitMask MinValueBitMask { get; } 22 | 23 | public BitMask NaNBitMask { get; } 24 | 25 | public uint QuireSize { get; } 26 | 27 | public PositEnvironment(byte size, byte maximumExponentSize) 28 | { 29 | Size = size; 30 | MaximumExponentSize = maximumExponentSize; 31 | 32 | Useed = 1U << (1 << MaximumExponentSize); 33 | SignBitMask = new BitMask(Size).SetOne((ushort)(Size - 1)); 34 | FirstRegimeBitIndex = (ushort)(Size - 2); 35 | FirstRegimeBitBitMask = new BitMask(Size).SetOne(FirstRegimeBitIndex); 36 | EmptyBitMask = new BitMask(Size); 37 | MaxValueBitMask = new BitMask(Size, allOne: true) >> 1; 38 | MinValueBitMask = SignBitMask + 1; 39 | NaNBitMask = SignBitMask; 40 | QuireSize = new BitMask((uint)(((Size - 2) * (1 << MaximumExponentSize)) + 5), size).FindMostSignificantOnePosition(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Unum/UnumConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace Lombiq.Arithmetics; 2 | 3 | public class UnumConfiguration 4 | { 5 | /// 6 | /// Gets the number of bits in the exponent. 7 | /// 8 | public byte ExponentSize { get; } 9 | 10 | /// 11 | /// Gets the number of bits in the fraction. 12 | /// 13 | public byte FractionSize { get; } 14 | 15 | public UnumConfiguration(byte exponentSize, byte fractionSize) 16 | { 17 | ExponentSize = exponentSize; 18 | FractionSize = fractionSize; 19 | } 20 | 21 | public static UnumConfiguration FromIeeeConfiguration(IeeeConfiguration configuration) => 22 | configuration switch 23 | { 24 | IeeeConfiguration.HalfPrecision => new UnumConfiguration(5, 10), 25 | IeeeConfiguration.SinglePrecision => new UnumConfiguration(8, 23), 26 | IeeeConfiguration.DoublePrecision => new UnumConfiguration(11, 52), 27 | IeeeConfiguration.ExtendedPrecision => new UnumConfiguration(15, 64), 28 | IeeeConfiguration.QuadPrecision => new UnumConfiguration(15, 112), 29 | _ => new UnumConfiguration(0, 0), 30 | }; 31 | } 32 | 33 | public enum IeeeConfiguration 34 | { 35 | HalfPrecision, // 16-bit. 36 | SinglePrecision, // 32-bit. 37 | DoublePrecision, // 64-bit. 38 | ExtendedPrecision, // 80-bit (Intel x87). 39 | QuadPrecision, // 128-bit. 40 | } 41 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Copyright © 2017, [Lombiq Technologies Ltd.](https://lombiq.com) 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 8 | 9 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | 11 | - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | Library 5 | true 6 | Lombiq Technologies 7 | Copyright © 2017, Lombiq Technologies Ltd. 8 | Lombiq Arithmetics 9 | Next-generation arithmetic implementations, improved floating point number types for .NET, written in C#. Use unum and posit for better answers with fewer bits. See the project website for detailed documentation. 10 | NuGetIcon.png 11 | Lombiq;Hastlayer;FPGA;HardwareAcceleration;Performance;Arithmetic;FloatingPoint;Posit;Unum 12 | https://github.com/Lombiq/Arithmetics 13 | https://github.com/Lombiq/Arithmetics 14 | License.md 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Lombiq Arithmetics 2 | 3 | [![Lombiq.Arithmetics NuGet](https://img.shields.io/nuget/v/Lombiq.Arithmetics?label=Lombiq.Arithmetics)](https://www.nuget.org/packages/Lombiq.Arithmetics/) 4 | 5 | ## About 6 | 7 | [Next-generation arithmetic](https://posithub.org/) implementations, improved floating point number types for .NET, written in C#. Includes the following number types: 8 | 9 | - Posit: a drop-in replacement for standard `float`s and `double`s that provides more accurate results with fewer bits. Can be used in any .NET software that needs better accuracy than `double`s. For more info see the "[Beating Floating Point at its Own Game: Posit Arithmetic](http://www.johngustafson.net/pdfs/BeatingFloatingPoint.pdf)" white paper. 10 | - Unum: similar to posit but with a more complex implementation, a prior idea; just a basic proof of concept. For more info see the [compilation of unum resources](](http://www.johngustafson.net/unums.html) on Dr. John Gustafson's website. 11 | 12 | This project was developed as part of [Hastlayer](https://hastlayer.com/), the .NET HLS tool that converts .NET programs into equivalent logic hardware implementations. Both the posit and unum implementation can be automatically converted into FPGA-implemented logic hardware. 13 | 14 | We've written a detailed whitepaper about our posit implementation and its results. You can download the whitepaper for free by visiting the link found on our [Next Generation Arithmetic with Hastlayer page](https://hastlayer.com/arithmetics). 15 | 16 | This project is developed by [Lombiq Technologies Ltd](https://lombiq.com/). Commercial-grade support is available through Lombiq. 17 | 18 | ## About unum 19 | 20 | Unum is a new number format invented by Dr. John L. Gustafson that can be used to store any number with exact precision (or known error). It can be used to achieve better range and accuracy than IEEE floating point formats while eliminating the algebraic errors that the IEEE floats are prone to. 21 | 22 | For more about its advantages see: [http://ubiquity.acm.org/article.cfm?id=2913029](http://ubiquity.acm.org/article.cfm?id=2913029). 23 | 24 | ## Contributing and support 25 | 26 | Bug reports, feature requests, comments, questions, code contributions, and love letters are warmly welcome, please do so via GitHub issues and pull requests. Please adhere to our [open-source guidelines](https://lombiq.com/open-source-guidelines) while doing so. 27 | 28 | This project is developed by [Lombiq Technologies](https://lombiq.com/). Commercial-grade support is available through Lombiq. 29 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/UnumTests/UnumHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 4 | 5 | namespace Lombiq.Arithmetics.Tests; 6 | 7 | public class UnumHelperTests 8 | { 9 | private readonly UnumEnvironment _warlpiriEnvironment; 10 | private readonly UnumEnvironment _environment_2_2; 11 | private readonly UnumEnvironment _environment_2_3; 12 | private readonly UnumEnvironment _environment_2_4; 13 | private readonly UnumEnvironment _environment_3_2; 14 | private readonly UnumEnvironment _environment_4_8; 15 | 16 | public UnumHelperTests() 17 | { 18 | _warlpiriEnvironment = UnumEnvironment.FromStandardEnvironment(StandardEnvironment.Warlpiri); 19 | _environment_2_2 = new UnumEnvironment(2, 2); 20 | _environment_2_3 = new UnumEnvironment(2, 3); 21 | _environment_2_4 = new UnumEnvironment(2, 4); 22 | _environment_3_2 = new UnumEnvironment(3, 2); 23 | _environment_4_8 = new UnumEnvironment(4, 8); 24 | } 25 | 26 | [Fact] 27 | public void BitsRequiredByLargestExpressablePositiveIntegerIsCorrect() 28 | { 29 | Assert.AreEqual(UnumHelper.BitsRequiredByLargestExpressablePositiveInteger(_warlpiriEnvironment), 2); 30 | Assert.AreEqual(UnumHelper.BitsRequiredByLargestExpressablePositiveInteger(_environment_2_2), 9); 31 | Assert.AreEqual(UnumHelper.BitsRequiredByLargestExpressablePositiveInteger(_environment_2_3), 9); 32 | Assert.AreEqual(UnumHelper.BitsRequiredByLargestExpressablePositiveInteger(_environment_2_4), 9); 33 | Assert.AreEqual(UnumHelper.BitsRequiredByLargestExpressablePositiveInteger(_environment_3_2), 129); 34 | } 35 | 36 | [Fact] 37 | public void LargestExpressablePositiveIntegerIsCorrect() 38 | { 39 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_environment_4_8), _environment_4_8.EmptyBitMask); 40 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_environment_3_2), _environment_3_2.EmptyBitMask); 41 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_warlpiriEnvironment), new BitMask(2, _warlpiriEnvironment.Size)); 42 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_environment_2_2), new BitMask(480, _environment_2_2.Size)); 43 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_environment_2_3), new BitMask(510, _environment_2_3.Size)); 44 | Assert.AreEqual(UnumHelper.LargestExpressablePositiveInteger(_environment_2_4), new BitMask(511, _environment_2_4.Size)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/PositTests/QuireTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 4 | 5 | namespace Lombiq.Arithmetics.Tests; 6 | 7 | public class QuireTests 8 | { 9 | [Fact] 10 | public void QuireBitShiftLeftIsCorrect() 11 | { 12 | Assert.AreEqual( 13 | new Quire(new ulong[] { 0x80000000 }).Segments, 14 | (new Quire(new ulong[] { 1 }) << 31).Segments); 15 | Assert.AreEqual( 16 | new Quire(new ulong[] { 0 }).Segments, 17 | (new Quire(new ulong[] { 6 }) << -1).Segments); 18 | Assert.AreEqual( 19 | new Quire(new ulong[] { 0, 0x_8000_0000_0000_0000 }).Segments, 20 | (new Quire(new ulong[] { 1, 0 }) << 127).Segments); 21 | Assert.AreEqual( 22 | new Quire(new ulong[] { 1 }).Segments, 23 | (new Quire(new ulong[] { 0x00000001 }) << 64).Segments); 24 | Assert.AreEqual( 25 | new Quire(new ulong[] { 0x80000000, 0x00000000 }).Segments, 26 | (new Quire(new ulong[] { 0x00800000, 0x00000000 }) << 8).Segments); 27 | Assert.AreEqual( 28 | new Quire(new ulong[] { 0x200000000 }).Segments, 29 | (new Quire(new ulong[] { 0x00000001 }) << -31).Segments); 30 | } 31 | 32 | [Fact] 33 | public void QuireBitShiftRightIsCorrect() 34 | { 35 | Assert.AreEqual( 36 | new Quire(new ulong[] { 0x00800000, 0x00000000 }).Segments, 37 | (new Quire(new ulong[] { 0x80000000, 0x00000000 }) >> 8).Segments); 38 | Assert.AreEqual( 39 | new Quire(new ulong[] { 1 }).Segments, 40 | (new Quire(new ulong[] { 0x80000000 }) >> 31).Segments); 41 | Assert.AreEqual( 42 | new Quire(new ulong[] { 1, 0 }).Segments, 43 | (new Quire(new ulong[] { 0, 0x_8000_0000_0000_0000 }) >> 127).Segments); 44 | Assert.AreEqual( 45 | new Quire(new ulong[] { 1_152_921_504_606_846_992, 0 }).Segments, 46 | (new Quire(new ulong[] { 0x_0000_0000_0000_0100, 0x_0000_0000_0000_0001 }) >> 4).Segments); 47 | Assert.AreEqual( 48 | new Quire(new[] { 0x_8000_0000_0000_0000 }).Segments, 49 | (new Quire(new[] { 0x_8000_0000_0000_0000 }) >> 64).Segments); 50 | Assert.AreEqual( 51 | new Quire(new ulong[] { 0x_4000_0000_0000_0000 }).Segments, 52 | (new Quire(new[] { 0x_8000_0000_0000_0000 }) >> -63).Segments); 53 | } 54 | 55 | [Fact] 56 | public void QuireAdditionIsCorrect() 57 | { 58 | Assert.AreEqual( 59 | new Quire(new ulong[] { 5 }).Segments, 60 | (new Quire(new ulong[] { 4 }) + new Quire(new ulong[] { 1 })).Segments); 61 | Assert.AreEqual( 62 | new Quire(new ulong[] { 0, 2 }).Segments, 63 | (new Quire(new ulong[] { ulong.MaxValue, 1 }) + new Quire(new ulong[] { 1, 0 })).Segments); 64 | Assert.AreEqual( 65 | new Quire(new ulong[] { 2, 0, 0, 1, 2 }).Segments, 66 | (new Quire(new ulong[] { 1, 0, 0, 0, 1 }) + new Quire(new ulong[] { 1, 0, 0, 1, 1 })).Segments); 67 | } 68 | 69 | [Fact] 70 | public void QuireToIntegerAdditionIsCorrect() => 71 | Assert.AreEqual( 72 | new Quire(new ulong[] { 5 }).Segments, 73 | (new Quire(new ulong[] { 4 }) + 1).Segments); 74 | 75 | [Fact] 76 | public void QuireSubtractionIsCorrect() 77 | { 78 | Assert.AreEqual( 79 | new Quire(new ulong[] { 4 }).Segments, 80 | (new Quire(new ulong[] { 5 }) - new Quire(new ulong[] { 1 })).Segments); 81 | Assert.AreEqual( 82 | new Quire(new ulong[] { 1, 0, 0, 0, 1 }).Segments, 83 | (new Quire(new ulong[] { 2, 0, 0, 1, 2 }) - new Quire(new ulong[] { 1, 0, 0, 1, 1 })).Segments); 84 | Assert.AreEqual( 85 | new Quire(new ulong[] { ulong.MaxValue, 1 }).Segments, 86 | (new Quire(new ulong[] { 0, 2 }) - new Quire(new ulong[] { 1, 0 })).Segments); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Unum/UnumHelper.cs: -------------------------------------------------------------------------------- 1 | namespace Lombiq.Arithmetics; 2 | 3 | public static class UnumHelper 4 | { 5 | /// 6 | /// Calculates the maximum number of bits to describe the size of the given segment, e.g. "eSizeSize" for "eSize", 7 | /// which is essentially the binary logarithm of the segment size. 8 | /// 9 | /// The size of the unum segment (fraction or exponent). 10 | /// The maximum size of the unum segment size. 11 | public static byte SegmentSizeToSegmentSizeSize(ushort segmentSize) 12 | { 13 | if (segmentSize is 0 or 1) return 0; 14 | 15 | segmentSize--; 16 | 17 | byte position = 15; // Position of the most significant 1-bit. 18 | while ((segmentSize >> position) == 0) { position--; } 19 | position++; 20 | 21 | return position; 22 | } 23 | 24 | /// 25 | /// Calculates the maximum number of bits of a segment given its "segment size size", which is essentially 2 to the 26 | /// power of "segmentSizeSize". 27 | /// 28 | /// The size of the segment size. 29 | /// The maximum number of bits of a segment. 30 | public static ushort SegmentSizeSizeToSegmentSize(byte segmentSizeSize) => 31 | (ushort)(1 << segmentSizeSize); 32 | 33 | /// 34 | /// Calculates whether a unum with the given configuration of exponent and fraction size can fit into the given 35 | /// number of bits. 36 | /// 37 | /// The maximum size of the exponent. 38 | /// The maximum size of the fraction. 39 | /// The maximum size allowed for the unum. 40 | /// 41 | /// Whether the number of bits required to store the unum with the given configuration fits the desired maximum 42 | /// size. 43 | /// 44 | public static bool ConfigurationFitsSize(byte eSize, ushort fSize, ushort maximumSize) => 45 | ConfigurationRequiredMaximumBits(eSize, fSize) <= maximumSize; 46 | 47 | /// 48 | /// Calculates the maximum necessary number of bits that a unum with the given configuration requires. 49 | /// 50 | /// The maximum size of the exponent. 51 | /// The maximum size of the fraction. 52 | /// The maximum number of bits for the given configuration. 53 | public static ushort ConfigurationRequiredMaximumBits(byte eSize, ushort fSize) => 54 | // Sign bit + exponent size + fraction size + uncertainty bit + exponent size size + fraction size size. 55 | (ushort)(1 + eSize + fSize + 56 | 1 + SegmentSizeToSegmentSizeSize(eSize) + SegmentSizeToSegmentSizeSize(fSize)); 57 | 58 | /// 59 | /// Calculates the maximum number of bits required for the given unum environment. 60 | /// 61 | /// The size of the maximum size of the exponent. 62 | /// The size of the maximum size of the fraction. 63 | /// The maximum size allowed for the unum. 64 | /// Whether the unum of the given environment fits the desired number of bits. 65 | public static bool EnvironmentFitsSize(byte eSizeSize, byte fSizeSize, ushort maximumSize) => 66 | EnvironmentRequiredMaximumBits(eSizeSize, fSizeSize) <= maximumSize; 67 | 68 | /// 69 | /// Calculates the maximum necessary number of bits that a unum with the given environment requires. 70 | /// 71 | /// The size of the maximum size of the exponent. 72 | /// The size of the maximum size of the fraction. 73 | /// The maximum number of bits for the given environment. 74 | public static ushort EnvironmentRequiredMaximumBits(byte eSizeSize, byte fSizeSize) => 75 | // Sign bit + exponent size + fraction size + uncertainty bit + exponent size size + fraction size size. 76 | (ushort)(1 + SegmentSizeSizeToSegmentSize(eSizeSize) + SegmentSizeSizeToSegmentSize(fSizeSize) + 77 | 1 + eSizeSize + fSizeSize); 78 | 79 | public static int BitsRequiredByLargestExpressablePositiveInteger(UnumEnvironment environment) => 80 | (1 << (environment.ExponentSizeMax - 1)) + 1; 81 | 82 | /// 83 | /// Calculates the biggest expressible integer in the given environment in an integer-like notation. Returns an 84 | /// empty BitMask if the calculated number would be too big to fit in a BitMask of the size of the environment. 85 | /// 86 | /// The environment thats Largest Expressible Integer needs to be calculated. 87 | /// 88 | /// The biggest expressible integer in the given environment if it fits in a BitMask the size of the environment, an 89 | /// empty BitMask otherwise. 90 | /// 91 | public static BitMask LargestExpressablePositiveInteger(UnumEnvironment environment) 92 | { 93 | if (BitsRequiredByLargestExpressablePositiveInteger(environment) > 94 | environment.EmptyBitMask.SegmentCount * 32) return environment.EmptyBitMask; 95 | 96 | return (environment.EmptyBitMask.SetOne(environment.FractionSizeMax) - 1) << 97 | ((1 << (environment.ExponentSizeMax - 1)) - environment.FractionSizeMax + 1); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Posit/Quire.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | 5 | namespace Lombiq.Arithmetics; 6 | 7 | [SuppressMessage( 8 | "Major Code Smell", 9 | "S4035:Classes implementing \"IEquatable\" should be sealed", 10 | Justification = "False positive, it actually implements IEqualityComparer.")] 11 | public class Quire : IEqualityComparer 12 | { 13 | private const ulong SegmentMaskWithLeadingOne = 0x_8000_0000_0000_0000; 14 | private const ulong SegmentMaskWithClosingOne = 1; 15 | 16 | public ushort Size { get; } 17 | public ushort SegmentCount { get; } 18 | 19 | [SuppressMessage( 20 | "Performance", 21 | "CA1819:Properties should not return arrays", 22 | Justification = "Not currently posisble due to IArraySizeHolder limitations.")] 23 | // See: https://github.com/Lombiq/Hastlayer-SDK/issues/63 24 | public ulong[] Segments { get; } 25 | 26 | public Quire(ushort size) 27 | { 28 | var partialSegment = size % 64; 29 | SegmentCount = (ushort)((size >> 6) + (partialSegment == 0 ? 0 : 1)); 30 | Size = size; 31 | Segments = new ulong[SegmentCount]; 32 | for (int i = 0; i < SegmentCount; i++) 33 | Segments[i] = 0; 34 | } 35 | 36 | public Quire(ulong[] segments, ushort size = 0) 37 | { 38 | SegmentCount = (ushort)segments.Length; 39 | Size = size; 40 | if (size > SegmentCount << 6) 41 | { 42 | SegmentCount = (ushort)((size >> 6) + (size % 32 == 0 ? 0 : 1)); 43 | } 44 | 45 | Segments = new ulong[SegmentCount]; 46 | 47 | Array.Copy(segments, Segments, segments.Length); 48 | for (int i = segments.Length; i < SegmentCount; i++) 49 | Segments[i] = 0; 50 | } 51 | 52 | public Quire(uint firstSegment, ushort size) 53 | { 54 | Size = size; 55 | SegmentCount = (ushort)((size >> 6) + (size % 32 == 0 ? 0 : 1)); 56 | Segments = new ulong[SegmentCount]; 57 | Segments[0] = firstSegment; 58 | for (int i = 1; i < SegmentCount; i++) 59 | Segments[i] = 0; 60 | } 61 | 62 | public static Quire operator +(Quire left, Quire right) 63 | { 64 | if (left.SegmentCount == 0 || right.SegmentCount == 0) return left; 65 | var result = new ulong[left.SegmentCount]; 66 | bool carry = false; 67 | ushort segmentPosition = 0, position = 0; 68 | 69 | for (ushort i = 0; i < left.SegmentCount << 6; i++) 70 | { 71 | bool leftBit = ((left.Segments[segmentPosition] >> position) & 1) == 1; 72 | bool rightBit = ((right.Segments[segmentPosition] >> position) & 1) == 1; 73 | byte buffer = (byte)((leftBit ? 1 : 0) + (rightBit ? 1 : 0) + (carry ? 1 : 0)); 74 | 75 | if ((buffer & 1) == 1) result[segmentPosition] += 1UL << position; 76 | carry = buffer >> 1 == 1; 77 | 78 | position++; 79 | if (position >> 6 == 1) 80 | { 81 | position = 0; 82 | segmentPosition++; 83 | } 84 | } 85 | 86 | return new Quire(result); 87 | } 88 | 89 | public static Quire operator +(Quire left, uint right) => left + new Quire(right, (ushort)(left.SegmentCount << 6)); 90 | 91 | public static Quire operator -(Quire left, Quire right) 92 | { 93 | if (left.SegmentCount == 0 || right.SegmentCount == 0) return left; 94 | 95 | var result = new ulong[left.SegmentCount]; 96 | bool carry = false; 97 | ushort segmentPosition = 0, position = 0; 98 | 99 | for (ushort i = 0; i < left.SegmentCount << 6; i++) 100 | { 101 | bool leftBit = ((left.Segments[segmentPosition] >> position) & 1) == 1; 102 | bool rightBit = ((right.Segments[segmentPosition] >> position) & 1) == 1; 103 | 104 | byte buffer = (byte)(2 + (leftBit ? 1 : 0) - (rightBit ? 1 : 0) - (carry ? 1 : 0)); 105 | 106 | if ((buffer & 1) == 1) result[segmentPosition] += 1UL << position; 107 | carry = buffer >> 1 == 0; 108 | 109 | position++; 110 | if (position >> 6 == 1) 111 | { 112 | position = 0; 113 | segmentPosition++; 114 | } 115 | } 116 | 117 | return new Quire(result); 118 | } 119 | 120 | public static Quire operator ~(Quire q) 121 | { 122 | for (ushort i = 0; i < q.SegmentCount; i++) 123 | { 124 | q.Segments[i] = ~q.Segments[i]; 125 | } 126 | 127 | return q; 128 | } 129 | 130 | public static bool operator ==(Quire left, Quire right) 131 | { 132 | if (left.SegmentCount != right.SegmentCount) return false; 133 | 134 | for (ushort i = 0; i < left.SegmentCount; i++) 135 | { 136 | if (left.Segments[i] != right.Segments[i]) return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | public static bool operator !=(Quire left, Quire right) => !(left == right); 143 | 144 | public static Quire operator >>(Quire left, int right) 145 | { 146 | right &= (1 << (left.SegmentCount * 6)) - 1; 147 | 148 | var segments = new ulong[left.SegmentCount]; 149 | Array.Copy(left.Segments, segments, left.Segments.Length); 150 | 151 | for (ushort i = 0; i < right; i++) 152 | { 153 | bool carryOld = false; 154 | 155 | for (ushort j = 1; j <= segments.Length; j++) 156 | { 157 | ushort currentIndex = (ushort)(segments.Length - j); 158 | bool carryNew = (segments[currentIndex] & 1) == 1; 159 | segments[currentIndex] >>= 1; 160 | if (carryOld) segments[currentIndex] |= SegmentMaskWithLeadingOne; 161 | carryOld = carryNew; 162 | } 163 | } 164 | 165 | return new Quire(segments); 166 | } 167 | 168 | public static Quire operator <<(Quire left, int right) 169 | { 170 | right &= (1 << (left.SegmentCount * 6)) - 1; 171 | 172 | var segments = new ulong[left.SegmentCount]; 173 | Array.Copy(left.Segments, segments, left.Segments.Length); 174 | 175 | for (ushort i = 0; i < right; i++) 176 | { 177 | bool carryOld = false; 178 | 179 | for (ushort j = 0; j < segments.Length; j++) 180 | { 181 | bool carryNew = (segments[j] & SegmentMaskWithLeadingOne) == SegmentMaskWithLeadingOne; 182 | segments[j] <<= 1; 183 | if (carryOld) segments[j] |= SegmentMaskWithClosingOne; 184 | carryOld = carryNew; 185 | } 186 | } 187 | 188 | return new Quire(segments); 189 | } 190 | 191 | public static explicit operator ulong(Quire x) => x.Segments[0]; 192 | 193 | public static explicit operator uint(Quire x) => (uint)x.Segments[0]; 194 | 195 | protected bool Equals(Quire other) => this == other; 196 | 197 | public bool Equals(Quire x, Quire y) => x == y; 198 | 199 | public override bool Equals(object obj) => obj is Quire other && this == other; 200 | 201 | public int GetHashCode(Quire obj) => obj.GetHashCode(); 202 | 203 | public override int GetHashCode() 204 | { 205 | unchecked 206 | { 207 | var hashCode = Segments != null ? Segments.GetHashCode() : 0; 208 | hashCode = (hashCode * 397) ^ Size.GetHashCode(); 209 | hashCode = (hashCode * 397) ^ SegmentCount.GetHashCode(); 210 | return hashCode; 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /Unum/UnumEnvironment.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Lombiq.Arithmetics; 4 | 5 | public class UnumEnvironment 6 | { 7 | #region Unum structure 8 | 9 | /// 10 | /// Gets the number of bits allocated to store the maximum number of bits in the exponent field of a unum. 11 | /// 12 | public byte ExponentSizeSize { get; } // "esizesize" 13 | 14 | /// 15 | /// Gets the number of bits allocated to store the maximum number of bits in the fraction field of a unum. 16 | /// 17 | public byte FractionSizeSize { get; } // "fsizesize" 18 | 19 | /// 20 | /// Gets the maximum number of bits usable to store the exponent. 21 | /// 22 | public byte ExponentSizeMax { get; } // "esizemax" 23 | 24 | /// 25 | /// Gets the maximum number of bits usable to store the fraction. 26 | /// 27 | public ushort FractionSizeMax { get; } // "fsizemax" 28 | 29 | /// 30 | /// Gets the number of bits that are used for storing the utag. 31 | /// 32 | public byte UnumTagSize { get; } // "utagsize" 33 | 34 | /// 35 | /// Gets the maximum number of bits used by the environment. 36 | /// 37 | public ushort Size { get; } // "maxubits" 38 | 39 | #endregion Unum structure 40 | 41 | #region Unum masks 42 | 43 | /// 44 | /// Gets an empty BitMask the size of the environment. 45 | /// 46 | public BitMask EmptyBitMask { get; } 47 | 48 | /// 49 | /// Gets a BitMask for picking out the UncertainityBit. 50 | /// 51 | public BitMask UncertaintyBitMask { get; } // "ubitmask" 52 | 53 | /// 54 | /// Gets a BitMask for picking out the ExponentSize. 55 | /// 56 | public BitMask ExponentSizeMask { get; } // "esizemask" 57 | 58 | /// 59 | /// Gets a BitMask for picking out the FractionSize. 60 | /// 61 | public BitMask FractionSizeMask { get; } // "fsizemask" 62 | 63 | /// 64 | /// Gets a BitMask for picking out the ExponentSize and FractionSize. 65 | /// 66 | public BitMask ExponentAndFractionSizeMask { get; } // "efsizemask" 67 | 68 | /// 69 | /// Gets a BitMask for picking out the utag. 70 | /// 71 | public BitMask UnumTagMask { get; } // "utagmask" 72 | 73 | /// 74 | /// Gets a BitMask for picking out the SignBit. 75 | /// 76 | public BitMask SignBitMask { get; } // "signbigu" 77 | 78 | #endregion Unum masks 79 | 80 | #region Unum special values 81 | 82 | /// 83 | /// Gets a BitMask for the Unit in the Last Place or Unit of Least Precision. 84 | /// 85 | [SuppressMessage( 86 | "Minor Code Smell", 87 | "S100:Methods and properties should be named in PascalCase", 88 | Justification = "This is an acronym.")] 89 | public BitMask ULP { get; } 90 | 91 | /// 92 | /// Gets the positive infinity for the given unum environment. 93 | /// 94 | public BitMask PositiveInfinity { get; } // "posinfu" 95 | 96 | /// 97 | /// Gets the negative infinity for the given unum environment. 98 | /// 99 | public BitMask NegativeInfinity { get; } // "neginfu" 100 | 101 | /// 102 | /// Gets a BitMask for the notation of a quiet NaN value in the environment. 103 | /// 104 | public BitMask QuietNotANumber { get; } // "qNaNu" 105 | 106 | /// 107 | /// Gets a BitMask for the notation of a signaling NaN value in the environment. 108 | /// 109 | public BitMask SignalingNotANumber { get; } // "sNaNu" 110 | 111 | /// 112 | /// Gets the largest magnitude positive real number. One ULP less than infinity. 113 | /// 114 | public BitMask LargestPositive { get; } // "maxrealu" 115 | 116 | /// 117 | /// Gets the smallest magnitude positive real number. One ULP more than 0. 118 | /// 119 | public BitMask SmallestPositive { get; } // "smallsubnormalu" 120 | 121 | /// 122 | /// Gets the largest magnitude negative real number. One ULP more than negative infinity. 123 | /// 124 | public BitMask LargestNegative { get; } // "negbigu" 125 | 126 | /// 127 | /// Gets a BitMask for the largest magnitude negative unum in the environment. 128 | /// 129 | public BitMask MinRealU { get; } // "minrealu" 130 | 131 | #endregion Unum special values 132 | 133 | public UnumEnvironment(byte exponentSizeSize, byte fractionSizeSize) 134 | { 135 | // Initializing structure. 136 | ExponentSizeSize = exponentSizeSize; 137 | FractionSizeSize = fractionSizeSize; 138 | 139 | ExponentSizeMax = (byte)UnumHelper.SegmentSizeSizeToSegmentSize(ExponentSizeSize); 140 | FractionSizeMax = UnumHelper.SegmentSizeSizeToSegmentSize(FractionSizeSize); 141 | 142 | UnumTagSize = (byte)(1 + ExponentSizeSize + FractionSizeSize); 143 | Size = (ushort)(1 + ExponentSizeMax + FractionSizeMax + UnumTagSize); 144 | 145 | // Initializing masks. 146 | EmptyBitMask = new BitMask(Size); 147 | 148 | UncertaintyBitMask = new BitMask(Size).SetOne((ushort)(UnumTagSize - 1)); 149 | 150 | FractionSizeMask = new BitMask(Size).SetOne(FractionSizeSize) - 1; 151 | 152 | ExponentSizeMask = UncertaintyBitMask - 1 - FractionSizeMask; 153 | 154 | ExponentAndFractionSizeMask = ExponentSizeMask | FractionSizeMask; 155 | UnumTagMask = UncertaintyBitMask + ExponentAndFractionSizeMask; 156 | 157 | SignBitMask = new BitMask(Size).SetOne((ushort)(Size - 1)); 158 | 159 | // Initializing environment. 160 | ULP = new BitMask(Size).SetOne(UnumTagSize); 161 | 162 | PositiveInfinity = new BitMask(Size).SetOne((ushort)(Size - 1)) - 1 - UncertaintyBitMask; 163 | 164 | NegativeInfinity = new BitMask(Size).SetOne((ushort)(Size - 1)) + PositiveInfinity; 165 | 166 | LargestPositive = PositiveInfinity - ULP; 167 | SmallestPositive = ExponentAndFractionSizeMask + ULP; 168 | 169 | LargestNegative = NegativeInfinity - ULP; 170 | 171 | MinRealU = LargestPositive + (1U << (Size - 1)); 172 | 173 | QuietNotANumber = PositiveInfinity + UncertaintyBitMask; 174 | SignalingNotANumber = NegativeInfinity + UncertaintyBitMask; 175 | } 176 | 177 | public override string ToString() => 178 | $"{nameof(UnumEnvironment)}(Exponent:{ExponentSizeSize};Fraction:{FractionSizeSize})"; 179 | 180 | public static UnumEnvironment FromConfigurationValues(byte eSize, ushort fSize) => 181 | new(UnumHelper.SegmentSizeToSegmentSizeSize(eSize), UnumHelper.SegmentSizeToSegmentSizeSize(fSize)); 182 | 183 | public static UnumEnvironment FromConfiguration(UnumConfiguration configuration) => 184 | FromConfigurationValues(configuration.ExponentSize, configuration.FractionSize); 185 | 186 | public static UnumEnvironment FromStandardEnvironment(StandardEnvironment environment) => 187 | environment switch 188 | { 189 | StandardEnvironment.Warlpiri => 190 | new UnumEnvironment(0, 0), 191 | StandardEnvironment.HalfPrecisionLike => 192 | FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.HalfPrecision)), 193 | StandardEnvironment.SinglePrecisionLike => 194 | FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.SinglePrecision)), 195 | StandardEnvironment.DoublePrecisionLike => 196 | FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.DoublePrecision)), 197 | StandardEnvironment.ExtendedPrecisionLike => 198 | FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.ExtendedPrecision)), 199 | StandardEnvironment.QuadPrecisionLike => 200 | FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.QuadPrecision)), 201 | _ => FromConfiguration(UnumConfiguration.FromIeeeConfiguration(IeeeConfiguration.SinglePrecision)), 202 | }; 203 | 204 | public static UnumEnvironment GetDefaultEnvironment() => FromStandardEnvironment(StandardEnvironment.SinglePrecisionLike); 205 | } 206 | 207 | public enum StandardEnvironment 208 | { 209 | Warlpiri, // 4-bit. 210 | HalfPrecisionLike, // 33-bit. 211 | SinglePrecisionLike, // 50-bit. 212 | DoublePrecisionLike, // 92-bit. 213 | ExtendedPrecisionLike, // 92-bit. 214 | QuadPrecisionLike, // 157-bit. 215 | } 216 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/BitMaskTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System; 3 | using Xunit; 4 | 5 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 6 | 7 | namespace Lombiq.Arithmetics.Tests; 8 | 9 | public class BitMaskTests 10 | { 11 | [Fact] 12 | public void BitMaskSegmentCountIsCorrectlyCalculatedFromSize() 13 | { 14 | var sizesAndSegmentCounts = new[] 15 | { 16 | Tuple.Create(new BitMask(0), 0U), 17 | Tuple.Create(new BitMask(31), 1U), 18 | Tuple.Create(new BitMask(32), 1U), 19 | Tuple.Create(new BitMask(33), 2U), 20 | Tuple.Create(new BitMask(1023), 32U), 21 | Tuple.Create(new BitMask(1024), 32U), 22 | Tuple.Create(new BitMask(1025), 33U), 23 | }; 24 | 25 | foreach (var item in sizesAndSegmentCounts) item.Item2.ShouldBe(item.Item1.SegmentCount, $"Size: {item.Item1.Size}"); 26 | } 27 | 28 | [Fact] 29 | public void BitMaskSizeIsCorrectlySetWithSegments() 30 | { 31 | var sizesAndSegmentCounts = new[] 32 | { 33 | Tuple.Create(new BitMask(Array.Empty()), 0U), 34 | Tuple.Create(new BitMask(new uint[] { 1 }), 32U), 35 | Tuple.Create(new BitMask(new uint[] { 2, 2 }), 64U), 36 | Tuple.Create(new BitMask(new uint[] { 3, 3, 3 }), 96U), 37 | Tuple.Create(new BitMask(new uint[] { 4, 4, 4, 4 }), 128U), 38 | Tuple.Create(new BitMask(new uint[] { 0 }, 222), 222U), 39 | }; 40 | 41 | foreach (var item in sizesAndSegmentCounts) item.Item2.ShouldBe(item.Item1.Size, $"Mask: {item.Item1}"); 42 | } 43 | 44 | [Fact] 45 | public void BitMaskSetOneIsCorrect() 46 | { 47 | Assert.AreEqual(1, new BitMask(new uint[] { 0 }).SetOne(0).Segments[0]); 48 | Assert.AreEqual(1 << 5, new BitMask(32).SetOne(5).Segments[0]); 49 | Assert.AreEqual(0xFFFF, new BitMask(new uint[] { 0xFFFF }).SetOne(5).Segments[0]); 50 | Assert.AreEqual(0xFFFF + (1 << 30), new BitMask(new uint[] { 0xFFFF }).SetOne(30).Segments[0]); 51 | Assert.AreEqual((uint)(1 << 30) << 1, new BitMask(new uint[] { 0 }).SetOne(31).Segments[0]); 52 | Assert.AreEqual(uint.MaxValue, new BitMask(new[] { 0xFFFFFFFE }).SetOne(0).Segments[0]); 53 | Assert.AreEqual(uint.MaxValue, new BitMask(new uint[] { 0x7FFFFFFF }).SetOne(31).Segments[0]); 54 | Assert.AreEqual(new BitMask(new uint[] { 0, 0, 0xFFFF }), new BitMask(new uint[] { 0, 0, 0xFFFF }).SetOne(79)); 55 | Assert.AreEqual(new BitMask(new uint[] { 0, 0, 0x1FFFF }), new BitMask(new uint[] { 0, 0, 0xFFFF }).SetOne(80)); 56 | } 57 | 58 | [Fact] 59 | public void BitMaskSetZeroIsCorrect() 60 | { 61 | Assert.AreEqual(0, new BitMask(new uint[] { 1 }).SetZero(0).Segments[0]); 62 | Assert.AreEqual(0x7FFF, new BitMask(new uint[] { 0xFFFF }).SetZero(15).Segments[0]); 63 | } 64 | 65 | [Fact] 66 | public void BitMaskConstructorCorrectlyCopiesBitMask() 67 | { 68 | var masks = new[] 69 | { 70 | new BitMask(new uint[] { 0x42, 0x42 }), new BitMask(new uint[] { 0x88, 0x88, 0x88 }), 71 | }; 72 | 73 | foreach (var mask in masks) mask.ShouldBe(new BitMask(mask)); 74 | } 75 | 76 | [Fact] 77 | public void BitMaskIntegerAdditionIsCorrect() 78 | { 79 | Assert.AreEqual(1, (new BitMask(new uint[] { 0 }) + 1).Segments[0]); 80 | Assert.AreEqual(0x1FFFE, (new BitMask(new uint[] { 0xFFFF }) + 0xFFFF).Segments[0]); 81 | Assert.AreEqual(0xFFFFFFFF, (new BitMask(new[] { 0xFFFFFFFE }) + 1).Segments[0]); 82 | Assert.AreEqual(0xFFFFFFFF, (new BitMask(new[] { 0xEFFFFFFF }) + 0x10000000).Segments[0]); 83 | Assert.AreEqual(0, (new BitMask(new[] { 0xFFFFFFFF }) + 1).Segments[0]); 84 | Assert.AreEqual(1, (new BitMask(new[] { 0xFFFFFFFF }) + 2).Segments[0]); 85 | Assert.AreEqual(new BitMask(new uint[] { 0, 0, 1 }), new BitMask(new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0 }) + 1); 86 | } 87 | 88 | [Fact] 89 | public void BitMaskIntegerSubtractionIsCorrect() 90 | { 91 | Assert.AreEqual(0, (new BitMask(new uint[] { 1 }) - 1).Segments[0]); 92 | Assert.AreEqual(0, (new BitMask(new[] { 0xFFFFFFFF }) - 0xFFFFFFFF).Segments[0]); 93 | Assert.AreEqual(1, (new BitMask(new[] { 0xFFFFFFFF }) - 0xFFFFFFFE).Segments[0]); 94 | Assert.AreEqual(0xFFFFFFFF, (new BitMask(new uint[] { 0 }) - 1).Segments[0]); 95 | Assert.AreEqual(0xFFFFFFFE, (new BitMask(new uint[] { 0 }) - 2).Segments[0]); 96 | Assert.AreEqual(0xEFFFFFFF, (new BitMask(new[] { 0xFFFFFFFF }) - 0x10000000).Segments[0]); 97 | Assert.AreEqual(0xFFFFFF, (new BitMask(new uint[] { 0x017FFFFF }) - 0x800000).Segments[0]); 98 | Assert.AreEqual(new BitMask(new uint[] { 0xFFFFFFFF, 0 }, 33), new BitMask(new uint[] { 0x7FFFFFFF, 1 }, 33) - 0x80000000); 99 | } 100 | 101 | [Fact] 102 | public void BitMaskAdditionIsCorrect() 103 | { 104 | new BitMask(new[] { 0xFFFFFFFF }).ShouldBe( 105 | new BitMask(new uint[] { 0x55555555 }) + new BitMask(new[] { 0xAAAAAAAA })); 106 | new BitMask(new uint[] { 0xFFFFFFFE, 1 }).ShouldBe( 107 | new BitMask(new uint[] { 0xFFFFFFFF, 0 }) + new BitMask(new uint[] { 0xFFFFFFFF, 0 })); 108 | new BitMask(new uint[] { 0xFFFFFFFE, 0xFFFFFFFF, 1 }).ShouldBe( 109 | new BitMask(new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0 }) + new BitMask(new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0 })); 110 | } 111 | 112 | [Fact] 113 | public void BitMaskSubtractionIsCorrect() 114 | { 115 | new BitMask(new[] { 0xAAAAAAAA }).ShouldBe( 116 | new BitMask(new[] { 0xFFFFFFFF }) - new BitMask(new uint[] { 0x55555555 })); 117 | new BitMask(new uint[] { 0xFFFFFFFF, 0 }).ShouldBe( 118 | new BitMask(new uint[] { 0xFFFFFFFE, 1 }) - new BitMask(new uint[] { 0xFFFFFFFF, 0 })); 119 | new BitMask(new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0 }).ShouldBe( 120 | new BitMask(new uint[] { 0xFFFFFFFE, 0xFFFFFFFF, 1 }) - new BitMask(new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0 })); 121 | } 122 | 123 | [Fact] 124 | public void BitMaskBitShiftLeftIsCorrect() 125 | { 126 | new BitMask(new[] { 0x80000000 }).ShouldBe( 127 | new BitMask(new uint[] { 1 }) << 31); 128 | new BitMask(new uint[] { 0x00000003 }).ShouldBe( 129 | new BitMask(new uint[] { 6 }) << -1); 130 | new BitMask(new uint[] { 0x00000000, 0x80000000 }).ShouldBe( 131 | new BitMask(new uint[] { 1, 0 }) << 63); 132 | new BitMask(new uint[] { 0 }).ShouldBe( 133 | new BitMask(new uint[] { 0x00000001 }) << 32); 134 | new BitMask(new uint[] { 0x80000000, 0x00000000 }).ShouldBe( 135 | new BitMask(new uint[] { 0x00800000, 0x00000000 }) << 8); 136 | new BitMask(new uint[] { 1 }).ShouldBe( 137 | new BitMask(new[] { 0x80000000 }) << -31); 138 | (new BitMask(new uint[] { 1, 0 }) << 63).ShouldBe( 139 | new BitMask(new uint[] { 0x00000000, 0x80000000 })); 140 | } 141 | 142 | [Fact] 143 | public void BitMaskBitShiftRightIsCorrect() 144 | { 145 | new BitMask(new uint[] { 0x00800000, 0x00000000 }).ShouldBe( 146 | new BitMask(new uint[] { 0x80000000, 0x00000000 }) >> 8); 147 | new BitMask(new uint[] { 1 }).ShouldBe( 148 | new BitMask(new[] { 0x80000000 }) >> 31); 149 | new BitMask(new uint[] { 1, 0 }).ShouldBe( 150 | new BitMask(new uint[] { 0x00000000, 0x80000000 }) >> 63); 151 | new BitMask(new uint[] { 0x10000010, 0x00000000 }).ShouldBe( 152 | new BitMask(new uint[] { 0x00000100, 0x00000001 }) >> 4); 153 | new BitMask(new uint[] { 0 }).ShouldBe( 154 | new BitMask(new[] { 0x80000000 }) >> 32); 155 | new BitMask(new[] { 0x80000000 }).ShouldBe( 156 | new BitMask(new uint[] { 1 }) >> -31); 157 | } 158 | 159 | [Fact] 160 | public void FindMostSignificantOneIsCorrect() 161 | { 162 | Assert.AreEqual(0, new BitMask(new uint[] { 0x00000000, 0x00000000 }).FindMostSignificantOnePosition()); 163 | Assert.AreEqual(1, new BitMask(new uint[] { 0x00000001, 0x00000000 }).FindMostSignificantOnePosition()); 164 | Assert.AreEqual(2, new BitMask(new uint[] { 0x00000002, 0x00000000 }).FindMostSignificantOnePosition()); 165 | Assert.AreEqual(33, new BitMask(new uint[] { 0x00000002, 0x00000001 }).FindMostSignificantOnePosition()); 166 | } 167 | 168 | [Fact] 169 | public void FindLeastSignificantOneIsCorrect() 170 | { 171 | Assert.AreEqual(0, new BitMask(new uint[] { 0x00000000, 0x00000000 }).FindLeastSignificantOnePosition()); 172 | Assert.AreEqual(1, new BitMask(new uint[] { 0x00000001, 0x00000000 }).FindLeastSignificantOnePosition()); 173 | Assert.AreEqual(2, new BitMask(new uint[] { 0x00000002, 0x00000000 }).FindLeastSignificantOnePosition()); 174 | Assert.AreEqual(2, new BitMask(new uint[] { 0x00000002, 0x00000001 }).FindLeastSignificantOnePosition()); 175 | Assert.AreEqual(33, new BitMask(new uint[] { 0x00000000, 0x00000001 }).FindLeastSignificantOnePosition()); 176 | } 177 | 178 | [Fact] 179 | public void ShiftToRightEndIsCorrect() 180 | { 181 | new BitMask(new uint[] { 0x00000000, 0x00000000 }) 182 | .ShouldBe(new BitMask(new uint[] { 0x00000000, 0x00000000 }).ShiftOutLeastSignificantZeros()); 183 | new BitMask(new uint[] { 0x00000001, 0x00000000 }).ShiftOutLeastSignificantZeros() 184 | .ShouldBe(new BitMask(new uint[] { 0x00000001, 0x00000000 }).ShiftOutLeastSignificantZeros()); 185 | new BitMask(new uint[] { 0x00000001, 0x00000000 }).ShiftOutLeastSignificantZeros() 186 | .ShouldBe(new BitMask(new uint[] { 0x00000002, 0x00000000 }).ShiftOutLeastSignificantZeros()); 187 | new BitMask(new uint[] { 0x00000001, 0x00000000 }).ShiftOutLeastSignificantZeros() 188 | .ShouldBe(new BitMask(new uint[] { 0x00000000, 0x00000001 }).ShiftOutLeastSignificantZeros()); 189 | new BitMask(new uint[] { 0x00001001, 0x00000000 }).ShiftOutLeastSignificantZeros() 190 | .ShouldBe(new BitMask(new uint[] { 0x10010000, 0x00000000 }).ShiftOutLeastSignificantZeros()); 191 | new BitMask(new uint[] { 0x00001001, 0x00000000 }).ShiftOutLeastSignificantZeros() 192 | .ShouldBe(new BitMask(new uint[] { 0x00000000, 0x10010000 }).ShiftOutLeastSignificantZeros()); 193 | } 194 | 195 | [Fact] 196 | public void GetTwosComplementIsCorrect() 197 | { 198 | new BitMask(new uint[] { 0x00000001 }, 5).GetTwosComplement(5).ShouldBe(new BitMask(new uint[] { 0x1F })); 199 | new BitMask(new uint[] { 0x0000022C }, 12).GetTwosComplement(12).ShouldBe(new BitMask(new uint[] { 0x00000DD4 })); 200 | } 201 | 202 | [Fact] 203 | public void LengthOfRunOfBitsIsCorrect() 204 | { 205 | new BitMask(new uint[] { 0x00000001 }).LengthOfRunOfBits(32).ShouldBe((ushort)31); 206 | new BitMask(new uint[] { 0x30000000 }).LengthOfRunOfBits(32).ShouldBe((ushort)2); 207 | new BitMask(new[] { 0x80000000 }).LengthOfRunOfBits(32).ShouldBe((ushort)1); 208 | new BitMask(new uint[] { 0x00000000 }).LengthOfRunOfBits(32).ShouldBe((ushort)32); 209 | new BitMask(new uint[] { 0x00000013 }).LengthOfRunOfBits(5).ShouldBe((ushort)1); 210 | new BitMask(new uint[] { 17 }).LengthOfRunOfBits(5).ShouldBe((ushort)1); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/UnumTests/UnumEnvironmentTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 4 | 5 | namespace Lombiq.Arithmetics.Tests; 6 | 7 | public class UnumEnvironmentTests 8 | { 9 | private readonly UnumEnvironment _warlpiriEnvironment; 10 | private readonly UnumEnvironment _environment_3_2; 11 | private readonly UnumEnvironment _environment_3_4; 12 | private readonly Unum _unum_3_2; 13 | private readonly Unum _unum_3_4; 14 | 15 | public UnumEnvironmentTests() 16 | { 17 | _warlpiriEnvironment = UnumEnvironment.FromStandardEnvironment(StandardEnvironment.Warlpiri); 18 | _environment_3_2 = new UnumEnvironment(3, 2); 19 | _environment_3_4 = new UnumEnvironment(3, 4); 20 | _unum_3_2 = new Unum(_environment_3_2); 21 | _unum_3_4 = new Unum(_environment_3_4); 22 | } 23 | 24 | [Fact] 25 | public void WarlpiriUnumEnvironmentIsCorrect() 26 | { 27 | Assert.AreEqual(new BitMask(4), _warlpiriEnvironment.EmptyBitMask); 28 | Assert.AreEqual(_warlpiriEnvironment.EmptyBitMask, _warlpiriEnvironment.ExponentSizeMask); 29 | Assert.AreEqual(1, _warlpiriEnvironment.ExponentSizeMax); 30 | Assert.AreEqual(0, _warlpiriEnvironment.ExponentSizeSize); 31 | Assert.AreEqual(_warlpiriEnvironment.EmptyBitMask, _warlpiriEnvironment.FractionSizeMask); 32 | Assert.AreEqual(1, _warlpiriEnvironment.FractionSizeMax); 33 | Assert.AreEqual(0, _warlpiriEnvironment.FractionSizeSize); 34 | Assert.AreEqual(_warlpiriEnvironment.EmptyBitMask, _warlpiriEnvironment.ExponentAndFractionSizeMask); 35 | Assert.AreEqual(4, _warlpiriEnvironment.Size); 36 | 37 | Assert.AreEqual(12, _warlpiriEnvironment.LargestNegative.Lowest32Bits); // 1100 38 | Assert.AreEqual(4, _warlpiriEnvironment.LargestPositive.Lowest32Bits); // 0100 39 | Assert.AreEqual(14, _warlpiriEnvironment.NegativeInfinity.Lowest32Bits); // 1110 40 | Assert.AreEqual(6, _warlpiriEnvironment.PositiveInfinity.Lowest32Bits); // 0110 41 | Assert.AreEqual(7, _warlpiriEnvironment.QuietNotANumber.Lowest32Bits); // 0111 42 | Assert.AreEqual(15, _warlpiriEnvironment.SignalingNotANumber.Lowest32Bits); // 1111 43 | Assert.AreEqual(8, _warlpiriEnvironment.SignBitMask.Lowest32Bits); // 1000 44 | Assert.AreEqual(2, _warlpiriEnvironment.SmallestPositive.Lowest32Bits); // 0010 45 | Assert.AreEqual(2, _warlpiriEnvironment.ULP.Lowest32Bits); // 0010 46 | Assert.AreEqual(1, _warlpiriEnvironment.UncertaintyBitMask.Lowest32Bits); // 0001 47 | Assert.AreEqual(1, _warlpiriEnvironment.UnumTagMask.Lowest32Bits); // 0001 48 | Assert.AreEqual(1, _warlpiriEnvironment.UnumTagSize); 49 | } 50 | 51 | [Fact] 52 | public void UnumExponentSizeSizeIsCorrect() 53 | { 54 | Assert.AreEqual(3, _unum_3_2.ExponentSizeSize); 55 | Assert.AreEqual(3, _unum_3_4.ExponentSizeSize); 56 | } 57 | 58 | [Fact] 59 | public void UnumFractionSizeSizeIsCorrect() 60 | { 61 | Assert.AreEqual(2, _unum_3_2.FractionSizeSize); 62 | Assert.AreEqual(4, _unum_3_4.FractionSizeSize); 63 | } 64 | 65 | [Fact] 66 | public void UnumTagSizeIsCorrect() 67 | { 68 | Assert.AreEqual(6, _unum_3_2.UnumTagSize); 69 | Assert.AreEqual(8, _unum_3_4.UnumTagSize); 70 | } 71 | 72 | [Fact] 73 | public void UnumSizeIsCorrect() 74 | { 75 | Assert.AreEqual(19, _unum_3_2.Size); 76 | Assert.AreEqual(33, _unum_3_4.Size); 77 | } 78 | 79 | [Fact] 80 | public void UnumUncertaintyBitMaskIsCorrect() 81 | { 82 | // 0 0000 0000 0000 1 000 00 83 | Assert.AreEqual( 84 | new BitMask(new uint[] { 0x20 }, _unum_3_2.Size), 85 | _unum_3_2.UncertaintyBitMask, 86 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.UncertaintyBitMask))); 87 | 88 | // 0 0000 0000 0000 0000 0000 0000 1 000 0000 89 | Assert.AreEqual( 90 | new BitMask(new uint[] { 0x80, 0 }, _unum_3_4.Size), 91 | _unum_3_4.UncertaintyBitMask, 92 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.UncertaintyBitMask))); 93 | } 94 | 95 | [Fact] 96 | public void UnumExponentSizeMaskIsCorrect() 97 | { 98 | // 0 0000 0000 0000 0 111 00 99 | Assert.AreEqual( 100 | new BitMask(new uint[] { 0x1C }, _unum_3_2.Size), 101 | _unum_3_2.ExponentSizeMask, 102 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.ExponentSizeMask))); 103 | 104 | // 0 0000 0000 0000 0000 0000 0000 0 111 0000 105 | Assert.AreEqual( 106 | new BitMask(new uint[] { 0x70, 0 }, _unum_3_4.Size), 107 | _unum_3_4.ExponentSizeMask, 108 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.ExponentSizeMask))); 109 | } 110 | 111 | [Fact] 112 | public void UnumFractionSizeMaskIsCorrect() 113 | { 114 | // 0 0000 0000 0000 0 000 11 115 | Assert.AreEqual( 116 | new BitMask(new uint[] { 3 }, _unum_3_2.Size), 117 | _unum_3_2.FractionSizeMask, 118 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.FractionSizeMask))); 119 | 120 | // 0 0000 0000 0000 0000 0000 0000 0 000 1111 121 | Assert.AreEqual( 122 | new BitMask(new uint[] { 0xF, 0 }, _unum_3_4.Size), 123 | _unum_3_4.FractionSizeMask, 124 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.FractionSizeMask))); 125 | } 126 | 127 | [Fact] 128 | public void UnumExponentAndFractionSizeMaskIsCorrect() 129 | { 130 | // 0 0000 0000 0000 0 111 11 131 | Assert.AreEqual( 132 | new BitMask(new uint[] { 0x1F }, _unum_3_2.Size), 133 | _unum_3_2.ExponentAndFractionSizeMask, 134 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.ExponentAndFractionSizeMask))); 135 | 136 | // 0 0000 0000 0000 0000 0000 0000 0 111 1111 137 | Assert.AreEqual( 138 | new BitMask(new uint[] { 0x7F, 0 }, _unum_3_4.Size), 139 | _unum_3_4.ExponentAndFractionSizeMask, 140 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.ExponentAndFractionSizeMask))); 141 | } 142 | 143 | [Fact] 144 | public void UnumTagMaskIsCorrect() 145 | { 146 | // 0 0000 0000 0000 1 111 11 147 | Assert.AreEqual( 148 | new BitMask(new uint[] { 0x3F }, _unum_3_2.Size), 149 | _unum_3_2.UnumTagMask, 150 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.UnumTagMask))); 151 | 152 | // 0 0000 0000 0000 0000 0000 0000 1 111 1111 153 | Assert.AreEqual( 154 | new BitMask(new uint[] { 0xFF, 0 }, _unum_3_4.Size), 155 | _unum_3_4.UnumTagMask, 156 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.UnumTagMask))); 157 | } 158 | 159 | [Fact] 160 | public void UnumSignBitMaskIsCorrect() 161 | { 162 | // 1 0000 0000 0000 0 000 00 163 | Assert.AreEqual( 164 | new BitMask(new uint[] { 0x40000 }, _unum_3_2.Size), 165 | _unum_3_2.SignBitMask, 166 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.SignBitMask))); 167 | 168 | // 1 0000 0000 0000 0000 0000 0000 0 000 0000 169 | Assert.AreEqual( 170 | new BitMask(new uint[] { 0, 1 }, _unum_3_4.Size), 171 | _unum_3_4.SignBitMask, 172 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.SignBitMask))); 173 | } 174 | 175 | [Fact] 176 | public void UnumPositiveInfinityIsCorrect() 177 | { 178 | // 0 1111 1111 1111 0 111 11 179 | Assert.AreEqual( 180 | new BitMask(new uint[] { 0x3FFDF }, _unum_3_2.Size), 181 | _unum_3_2.PositiveInfinity, 182 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.PositiveInfinity))); 183 | 184 | // 0 1111 1111 1111 1111 1111 1111 0 111 1111 185 | Assert.AreEqual( 186 | new BitMask(new uint[] { 0xFFFFFF7F, 0 }, _unum_3_4.Size), 187 | _unum_3_4.PositiveInfinity, 188 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.PositiveInfinity))); 189 | } 190 | 191 | [Fact] 192 | public void UnumNegativeInfinityIsCorrect() 193 | { 194 | // 1 1111 1111 1111 0 111 11 195 | Assert.AreEqual( 196 | new BitMask(new uint[] { 0x7FFDF }, _unum_3_2.Size), 197 | _unum_3_2.NegativeInfinity, 198 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.NegativeInfinity))); 199 | 200 | // 1 1111 1111 1111 1111 1111 1111 0 111 1111 201 | Assert.AreEqual( 202 | new BitMask(new uint[] { 0xFFFFFF7F, 1 }, _unum_3_4.Size), 203 | _unum_3_4.NegativeInfinity, 204 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.NegativeInfinity))); 205 | } 206 | 207 | [Fact] 208 | public void UnumQuietNotANumberIsCorrect() 209 | { 210 | // 0 1111 1111 1111 1 111 11 211 | Assert.AreEqual( 212 | new BitMask(new uint[] { 0x3FFFF }, _unum_3_2.Size), 213 | _unum_3_2.QuietNotANumber, 214 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.QuietNotANumber))); 215 | 216 | // 0 1111 1111 1111 1111 1111 1111 1 111 1111 217 | Assert.AreEqual( 218 | new BitMask(new uint[] { 0xFFFFFFFF, 0 }, _unum_3_4.Size), 219 | _unum_3_4.QuietNotANumber, 220 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.QuietNotANumber))); 221 | } 222 | 223 | [Fact] 224 | public void UnumSignalingNotANumberIsCorrect() 225 | { 226 | // 1 1111 1111 1111 1 111 11 227 | Assert.AreEqual( 228 | new BitMask(new uint[] { 0x7FFFF }, _unum_3_2.Size), 229 | _unum_3_2.SignalingNotANumber, 230 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.SignalingNotANumber))); 231 | 232 | // 1 1111 1111 1111 1111 1111 1111 1 111 1111 233 | Assert.AreEqual( 234 | new BitMask(new uint[] { 0xFFFFFFFF, 1 }, _unum_3_4.Size), 235 | _unum_3_4.SignalingNotANumber, 236 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.SignalingNotANumber))); 237 | } 238 | 239 | [Fact] 240 | public void UnumLargestPositiveIsCorrect() 241 | { 242 | // 0 1111 1111 1110 0 111 11 243 | Assert.AreEqual( 244 | new BitMask(new uint[] { 0x3FF9F }, _unum_3_2.Size), 245 | _unum_3_2.LargestPositive, 246 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.LargestPositive))); 247 | 248 | // 0 1111 1111 1111 1111 1111 1110 0 111 1111 249 | Assert.AreEqual( 250 | new BitMask(new uint[] { 0xFFFFFE7F, 0 }, _unum_3_4.Size), 251 | _unum_3_4.LargestPositive, 252 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.LargestPositive))); 253 | } 254 | 255 | [Fact] 256 | public void UnumSmallestPositiveIsCorrect() 257 | { 258 | // 0 0000 0000 0001 0 111 11 259 | Assert.AreEqual( 260 | new BitMask(new uint[] { 0x5F }, _unum_3_2.Size), 261 | _unum_3_2.SmallestPositive, 262 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.SmallestPositive))); 263 | 264 | // 0 0000 0000 0000 0000 0000 0001 0 111 1111 265 | Assert.AreEqual( 266 | new BitMask(new uint[] { 0x17F, 0 }, _unum_3_4.Size), 267 | _unum_3_4.SmallestPositive, 268 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.SmallestPositive))); 269 | } 270 | 271 | [Fact] 272 | public void UnumLargestNegativeIsCorrect() 273 | { 274 | // 1 1111 1111 1110 0 111 11 275 | Assert.AreEqual( 276 | new BitMask(new uint[] { 0x7FF9F }, _unum_3_2.Size), 277 | _unum_3_2.LargestNegative, 278 | TestFailureMessageBuilder(_unum_3_2, nameof(_unum_3_2.LargestNegative))); 279 | 280 | // 1 1111 1111 1111 1111 1111 1110 0 111 1111 281 | Assert.AreEqual( 282 | new BitMask(new uint[] { 0xFFFFFE7F, 1 }, _unum_3_4.Size), 283 | _unum_3_4.LargestNegative, 284 | TestFailureMessageBuilder(_unum_3_4, nameof(_unum_3_4.LargestNegative))); 285 | } 286 | 287 | private static string TestFailureMessageBuilder(Unum unum, string propertyName) => 288 | $"Testing the \"{propertyName}\" property of the Unum ({unum.ExponentSizeSize}, {unum.FractionSizeSize}) environment failed."; 289 | } 290 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/PositTests/PositTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System; 3 | using Xunit; 4 | 5 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 6 | 7 | namespace Lombiq.Arithmetics.Tests; 8 | 9 | public class PositTests 10 | { 11 | private readonly PositEnvironment _environment_6_1; 12 | private readonly PositEnvironment _environment_6_2; 13 | private readonly PositEnvironment _environment_6_3; 14 | private readonly PositEnvironment _environment_8_2; 15 | private readonly PositEnvironment _environment_12_2; 16 | private readonly PositEnvironment _environment_16_3; 17 | private readonly PositEnvironment _environment_32_3; 18 | private readonly PositEnvironment _environment_32_2; 19 | 20 | public PositTests() 21 | { 22 | _environment_6_1 = new PositEnvironment(6, 1); 23 | _environment_6_2 = new PositEnvironment(6, 2); 24 | _environment_6_3 = new PositEnvironment(6, 3); 25 | _environment_8_2 = new PositEnvironment(8, 2); 26 | _environment_12_2 = new PositEnvironment(12, 2); 27 | _environment_16_3 = new PositEnvironment(16, 3); 28 | _environment_32_3 = new PositEnvironment(32, 3); 29 | _environment_32_2 = new PositEnvironment(32, 2); 30 | } 31 | 32 | [Fact] 33 | public void EncodeRegimeBitsIsCorrect() 34 | { 35 | new Posit(_environment_8_2).EncodeRegimeBits(0).ShouldBe(new BitMask(0x40, _environment_8_2.Size)); 36 | 37 | new Posit(_environment_6_3).EncodeRegimeBits(-3).ShouldBe(new BitMask(0x2, _environment_6_3.Size)); 38 | 39 | new Posit(_environment_6_3).EncodeRegimeBits(3).ShouldBe(new BitMask(0x1E, _environment_6_3.Size)); 40 | 41 | new Posit(_environment_6_3).EncodeRegimeBits(2).ShouldBe(new BitMask(0x1C, _environment_6_3.Size)); 42 | 43 | new Posit(_environment_8_2).EncodeRegimeBits(1).ShouldBe(new BitMask(0x60, _environment_6_3.Size)); 44 | 45 | new Posit(_environment_8_2).EncodeRegimeBits(3).ShouldBe(new BitMask(0x78, _environment_8_2.Size)); 46 | 47 | new Posit(_environment_8_2).EncodeRegimeBits(6).ShouldBe(new BitMask(0x7F, _environment_8_2.Size)); 48 | } 49 | 50 | [Fact] 51 | public void PositIsCorrectlyConstructedFromUint() 52 | { 53 | new Posit(_environment_6_3, 0U).PositBits.ShouldBe(new BitMask(0x0, _environment_6_3.Size)); 54 | 55 | new Posit(_environment_6_3, 2).PositBits.ShouldBe(new BitMask(17, _environment_6_3.Size)); 56 | 57 | new Posit(_environment_6_3, 8U).PositBits.ShouldBe(new BitMask(0x13, _environment_6_3.Size)); 58 | 59 | new Posit(_environment_6_3, 16384U).PositBits.ShouldBe(new BitMask(0x1B, _environment_6_3.Size)); 60 | 61 | new Posit(_environment_6_3, 1_048_576U).PositBits.ShouldBe(new BitMask(0x1D, _environment_6_3.Size)); 62 | 63 | new Posit(_environment_8_2, 13U).PositBits.ShouldBe(new BitMask(0x5D, _environment_8_2.Size)); 64 | 65 | new Posit(_environment_32_2, 17U).PositBits.ShouldBe(new BitMask(0x60400000, _environment_32_2.Size)); 66 | 67 | new Posit(_environment_12_2, 172U).PositBits.ShouldBe(new BitMask(0x6D6, _environment_12_2.Size)); 68 | 69 | new Posit(_environment_12_2, 173U).PositBits.ShouldBe(new BitMask(0x6D6, _environment_12_2.Size)); 70 | 71 | new Posit(_environment_16_3, 48U).PositBits.ShouldBe(new BitMask(22016, _environment_16_3.Size)); 72 | 73 | new Posit(_environment_16_3, 13200U).PositBits.ShouldBe(new BitMask(27449, _environment_16_3.Size)); 74 | 75 | new Posit(_environment_16_3, 500U).PositBits.ShouldBe(new BitMask(25064, _environment_16_3.Size)); 76 | 77 | new Posit(_environment_32_3, 1U).PositBits.ShouldBe(new BitMask(0x40000000, _environment_32_3.Size)); 78 | 79 | // examples of Posit rounding 80 | new Posit(_environment_8_2, 90U).PositBits.ShouldBe(new BitMask(0x6A, _environment_12_2.Size)); 81 | new Posit(_environment_8_2, 82U).PositBits.ShouldBe(new BitMask(0x69, _environment_12_2.Size)); 82 | 83 | // Numbers out of range don't get rounded up infinity. They get rounded to the biggest representable finite 84 | // value (MaxValue). 85 | new Posit(_environment_6_1, 500U).PositBits.ShouldBe(_environment_6_1.MaxValueBitMask); 86 | } 87 | 88 | [Fact] 89 | public void PositIsCorrectlyConstructedFromInt() 90 | { 91 | new Posit(_environment_6_3, 8).PositBits.ShouldBe(new BitMask(0x13, _environment_6_3.Size)); 92 | 93 | new Posit(_environment_6_3, 16384).PositBits.ShouldBe(new BitMask(0x1B, _environment_6_3.Size)); 94 | 95 | new Posit(_environment_8_2, 13).PositBits.ShouldBe(new BitMask(0x5D, _environment_8_2.Size)); 96 | 97 | new Posit(_environment_6_3, -8).PositBits.ShouldBe(new BitMask(0x2D, _environment_6_3.Size)); 98 | 99 | new Posit(_environment_8_2, -13).PositBits.ShouldBe(new BitMask(0xA3, _environment_8_2.Size)); 100 | 101 | new Posit(_environment_32_3, -1).PositBits.ShouldBe(new BitMask(0xC0000000, _environment_32_3.Size)); 102 | 103 | new Posit(_environment_6_3, -16384).PositBits.ShouldBe(new BitMask(0x25, _environment_6_3.Size)); 104 | 105 | new Posit(_environment_16_3, -500).PositBits.ShouldBe(new BitMask(40472, _environment_16_3.Size)); 106 | } 107 | 108 | [Fact] 109 | public void PositToIntIsCorrect() 110 | { 111 | var posit8 = new Posit(_environment_6_3, 8); 112 | Assert.AreEqual((int)posit8, 8); 113 | 114 | var posit16384 = new Posit(_environment_6_3, 16384); 115 | Assert.AreEqual((int)posit16384, 16384); 116 | 117 | var posit1_32_3 = new Posit(_environment_32_3, 1); 118 | Assert.AreEqual((int)posit1_32_3, 1); 119 | } 120 | 121 | [Fact] 122 | public void ExponentSizeIsCorrect() 123 | { 124 | var posit16384 = new Posit(_environment_6_3, 16384); 125 | Assert.AreEqual(posit16384.ExponentSize(), 2); 126 | 127 | var posit2 = new Posit(_environment_6_3, 2); 128 | Assert.AreEqual(posit2.ExponentSize(), 3); 129 | 130 | var posit3 = new Posit(_environment_6_1, 3); 131 | Assert.AreEqual(posit3.ExponentSize(), 1); 132 | 133 | var posit3_6_2 = new Posit(_environment_6_2, 3); 134 | Assert.AreEqual(posit3_6_2.ExponentSize(), 2); 135 | 136 | var posit_negative13 = new Posit(_environment_8_2, -13); 137 | Assert.AreEqual(posit_negative13.ExponentSize(), 2); 138 | } 139 | 140 | [Fact] 141 | public void GetExponentValueIsCorrect() 142 | { 143 | var posit16384 = new Posit(_environment_6_3, 16384); 144 | Assert.AreEqual(posit16384.GetExponentValue(), 6); 145 | 146 | var posit2 = new Posit(_environment_6_3, 2); 147 | Assert.AreEqual(posit2.GetExponentValue(), 1); 148 | 149 | var posit3 = new Posit(_environment_6_1, 3); 150 | Assert.AreEqual(posit3.GetExponentValue(), 1); 151 | 152 | var posit3_6_2 = new Posit(_environment_6_2, 3); 153 | Assert.AreEqual(posit3_6_2.GetExponentValue(), 1); 154 | 155 | var posit_negative13 = new Posit(_environment_8_2, -13); 156 | Assert.AreEqual(posit_negative13.GetExponentValue(), 3); 157 | 158 | var posit13248 = new Posit(_environment_16_3, 13248); 159 | Assert.AreEqual(posit13248.GetExponentValue(), 5); 160 | } 161 | 162 | [Fact] 163 | public void FractionSizeIsCorrect() 164 | { 165 | var posit16384 = new Posit(_environment_6_3, 16384); 166 | Assert.AreEqual(posit16384.FractionSize(), 0); 167 | 168 | var posit0 = new Posit(_environment_6_3, 0); 169 | Assert.AreEqual(posit0.FractionSize(), 0); 170 | 171 | var posit2 = new Posit(_environment_6_3, 2); 172 | Assert.AreEqual(posit2.FractionSize(), 0); 173 | 174 | var posit3 = new Posit(_environment_6_1, 3); 175 | Assert.AreEqual(posit3.FractionSize(), 2); 176 | 177 | var posit_negative13 = new Posit(_environment_8_2, -13); 178 | Assert.AreEqual(posit_negative13.FractionSize(), 3); 179 | 180 | var posit13248 = new Posit(_environment_16_3, 13248); 181 | Assert.AreEqual(posit13248.FractionSize(), 9); 182 | } 183 | 184 | [Fact] 185 | public void FractionWithHiddenBitIsCorrect() 186 | { 187 | var posit16384 = new Posit(_environment_6_3, 16384); 188 | posit16384.FractionWithHiddenBit().ShouldBe(new BitMask(1, _environment_6_3.Size)); 189 | 190 | var posit0 = new Posit(_environment_6_1, 0); 191 | posit0.FractionWithHiddenBit().ShouldBe(new BitMask(1, _environment_6_1.Size)); 192 | 193 | var posit3 = new Posit(_environment_6_1, 3); 194 | posit3.FractionWithHiddenBit().ShouldBe(new BitMask(6, _environment_6_1.Size)); 195 | 196 | var posit_negative13 = new Posit(_environment_8_2, -13); 197 | posit_negative13.FractionWithHiddenBit().ShouldBe(new BitMask(0xD, _environment_6_1.Size)); 198 | } 199 | 200 | [Fact] 201 | public void GetRegimeKValueIsCorrect() 202 | { 203 | new Posit(_environment_6_3, 8).GetRegimeKValue().ShouldBe(0); 204 | 205 | new Posit(_environment_6_3, 16384).GetRegimeKValue().ShouldBe(1); 206 | 207 | new Posit(_environment_6_3, 0).GetRegimeKValue().ShouldBe(-5); 208 | 209 | new Posit(_environment_8_2, 13).GetRegimeKValue().ShouldBe(0); 210 | 211 | new Posit(_environment_6_3, -8).GetRegimeKValue().ShouldBe(0); 212 | 213 | new Posit(_environment_8_2, -13).GetRegimeKValue().ShouldBe(0); 214 | 215 | new Posit(_environment_6_3, -16384).GetRegimeKValue().ShouldBe(1); 216 | } 217 | 218 | [Fact] 219 | public void CalculateScaleFactorIsCorrect() 220 | { 221 | new Posit(_environment_16_3, 13200).CalculateScaleFactor().ShouldBe(13); 222 | new Posit(_environment_16_3, 48).CalculateScaleFactor().ShouldBe(5); 223 | new Posit(_environment_16_3, 13248).CalculateScaleFactor().ShouldBe(13); 224 | new Posit(_environment_16_3, 1).CalculateScaleFactor().ShouldBe(0); 225 | new Posit(_environment_16_3, 2).CalculateScaleFactor().ShouldBe(1); 226 | } 227 | 228 | [Fact] 229 | public void AdditionIsCorrect() 230 | { 231 | var posit0 = new Posit(_environment_6_3, 0); 232 | var posit = posit0 + 1; 233 | posit.PositBits.ShouldBe(new Posit(_environment_6_3, 1).PositBits); 234 | 235 | var posit_negative_1 = new Posit(_environment_6_3, -1); 236 | var posit_negative_2 = posit_negative_1 + posit_negative_1; 237 | 238 | posit_negative_2.PositBits.ShouldBe(new Posit(_environment_6_3, -2).PositBits); 239 | 240 | posit_negative_1 -= 1; 241 | posit_negative_1.PositBits.ShouldBe(new Posit(_environment_6_3, -2).PositBits); 242 | 243 | posit_negative_2 += 1; 244 | posit_negative_2.PositBits.ShouldBe(new Posit(_environment_6_3, -1).PositBits); 245 | 246 | var posit1 = new Posit(_environment_6_3, 1); 247 | var posit2 = posit1 + 1; 248 | posit2.PositBits.ShouldBe(new Posit(_environment_6_3, 2).PositBits); 249 | 250 | var isPosit0 = posit1 - 1; 251 | isPosit0.PositBits.ShouldBe(posit0.PositBits); 252 | 253 | var posit3 = new Posit(_environment_6_2, 3); 254 | var posit6 = posit3 + posit3; 255 | posit6.PositBits.ShouldBe(new Posit(_environment_6_2, 6).PositBits); 256 | 257 | var posit1_16_3 = new Posit(_environment_16_3, 1); 258 | var posit2_16_3 = new Posit(_environment_16_3, 2); 259 | var posit3_16_3 = posit2_16_3 + posit1_16_3; 260 | posit3_16_3.PositBits.ShouldBe(new Posit(_environment_16_3, 3).PositBits); 261 | 262 | var posit4_16_3 = new Posit(_environment_16_3, 3) + posit1_16_3; 263 | posit4_16_3.PositBits.ShouldBe(new Posit(_environment_16_3, 4).PositBits); 264 | 265 | // This will be OK, once the quire will be used. 266 | //// var posit66K_32_3 = new Posit.Posit(_environment_32_3, 66000); 267 | //// var posit66K1_32_3 = posit66K_32_3 + 1; 268 | //// posit66K1_32_3.PositBits.ShouldBe(new Posit.Posit(_environment_32_3, 66001).PositBits); 269 | 270 | var posit48 = new Posit(_environment_16_3, 48); 271 | var posit13200 = new Posit(_environment_16_3, 13200); 272 | var posit13248 = posit48 + posit13200; 273 | posit13248.PositBits.ShouldBe(new Posit(_environment_16_3, 13248).PositBits); 274 | 275 | var posit1_32_2 = new Posit(_environment_32_2, 1); 276 | var posit2_32_2 = posit1_32_2 + posit1_32_2; 277 | posit2_32_2.PositBits.ShouldBe(new Posit(_environment_32_2, 2).PositBits); 278 | 279 | var otherPosit13248 = posit13200 + posit48; 280 | otherPosit13248.PositBits.ShouldBe(new Posit(_environment_16_3, 13248).PositBits); 281 | } 282 | 283 | [Fact] 284 | public void AdditionIsCorrectForPositives() 285 | { 286 | var posit1 = new Posit(_environment_32_3, 1); 287 | 288 | for (var i = 1; i < 10000; i++) 289 | { 290 | posit1 += 1; 291 | } 292 | 293 | posit1.PositBits.ShouldBe(new Posit(_environment_32_3, 10000).PositBits); 294 | 295 | var posit1_32_2 = new Posit(_environment_32_2, 1); 296 | 297 | for (var i = 1; i < 10000; i++) 298 | { 299 | posit1_32_2 += 1; 300 | } 301 | 302 | posit1_32_2.PositBits.ShouldBe(new Posit(_environment_32_2, 10000).PositBits); 303 | } 304 | 305 | [Fact] 306 | public void AdditionIsCorrectForNegatives() 307 | { 308 | var posit1 = new Posit(_environment_16_3, -500); 309 | 310 | for (var i = 1; i <= 1000; i++) 311 | { 312 | posit1 += 1; 313 | } 314 | 315 | for (var j = 1; j <= 500; j++) 316 | { 317 | posit1 -= 1; 318 | } 319 | 320 | posit1.PositBits.ShouldBe(new Posit(_environment_16_3, 0).PositBits); 321 | 322 | var positA = new Posit(_environment_32_3, 1); 323 | Console.WriteLine((int)positA); 324 | var positB = positA; 325 | Console.WriteLine((int)positB); 326 | 327 | for (var i = 1; i < 100000; i++) 328 | { 329 | positA += positB; 330 | } 331 | 332 | var result = (int)positA; 333 | Assert.AreEqual(result, 100000); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /Posit/Posit.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace Lombiq.Arithmetics; 4 | 5 | // signbit regime exponent(?) fraction(?) 6 | public readonly struct Posit : System.IEquatable 7 | { 8 | private readonly PositEnvironment _environment; 9 | public BitMask PositBits { get; } 10 | 11 | #region Posit structure 12 | 13 | public byte MaximumExponentSize => _environment.MaximumExponentSize; 14 | public ushort Size => _environment.Size; 15 | public uint Useed => _environment.Useed; 16 | public ushort FirstRegimeBitIndex => _environment.FirstRegimeBitIndex; 17 | 18 | #endregion Posit structure 19 | 20 | #region Posit Masks 21 | 22 | public BitMask SignBitMask => _environment.SignBitMask; 23 | 24 | public BitMask FirstRegimeBitBitMask => _environment.FirstRegimeBitBitMask; 25 | 26 | public BitMask EmptyBitMask => _environment.EmptyBitMask; 27 | 28 | public BitMask MaxValueBitMask => _environment.MaxValueBitMask; 29 | 30 | public BitMask MinValueBitMask => _environment.MinValueBitMask; 31 | 32 | public BitMask NaNBitMask => _environment.NaNBitMask; 33 | 34 | #endregion Posit Masks 35 | 36 | #region Posit constructors 37 | 38 | public Posit(PositEnvironment environment) 39 | { 40 | _environment = environment; 41 | 42 | PositBits = new BitMask(_environment.Size); 43 | } 44 | 45 | public Posit(PositEnvironment environment, BitMask bits) 46 | { 47 | _environment = environment; 48 | 49 | PositBits = BitMask.FromImmutableArray(bits.Segments, _environment.Size); 50 | } 51 | 52 | public Posit(PositEnvironment environment, uint value) 53 | { 54 | _environment = environment; 55 | 56 | PositBits = new BitMask(value, _environment.Size); 57 | if (value == 0) return; 58 | 59 | var exponentValue = (uint)PositBits.FindMostSignificantOnePosition() - 1; 60 | 61 | ushort kValue = 0; 62 | while (exponentValue >= 1 << environment.MaximumExponentSize && kValue < _environment.Size - 1) 63 | { 64 | exponentValue -= 1U << environment.MaximumExponentSize; 65 | kValue++; 66 | } 67 | 68 | PositBits = AssemblePositBitsWithRounding(signBit: false, kValue, new BitMask(exponentValue, 32), PositBits); 69 | } 70 | 71 | public Posit(PositEnvironment environment, int value) 72 | { 73 | _environment = environment; 74 | PositBits = value >= 0 ? new Posit(environment, (uint)value).PositBits : 75 | new Posit(environment, (uint)-value).PositBits.GetTwosComplement(_environment.Size); 76 | } 77 | 78 | #endregion Posit constructors 79 | 80 | #region Posit numeric states 81 | 82 | public bool IsPositive() => (PositBits & SignBitMask) == EmptyBitMask; 83 | 84 | public bool IsNaN() => PositBits == NaNBitMask; 85 | 86 | public bool IsZero() => PositBits == EmptyBitMask; 87 | 88 | #endregion Posit numeric states 89 | 90 | #region Methods to handle parts of the Posit 91 | 92 | public BitMask EncodeRegimeBits(int regimeKValue) 93 | { 94 | BitMask regimeBits; 95 | if (regimeKValue > 0) 96 | { 97 | regimeBits = (new BitMask(1, _environment.Size) << (regimeKValue + 1)) - 1; 98 | regimeBits <<= _environment.Size - regimeBits.FindMostSignificantOnePosition() - 1; 99 | } 100 | else 101 | { 102 | regimeBits = _environment.FirstRegimeBitBitMask << regimeKValue; 103 | } 104 | 105 | return regimeBits; 106 | } 107 | 108 | private BitMask AssemblePositBitsWithRounding(bool signBit, int regimeKValue, BitMask exponentBits, BitMask fractionBits) 109 | { 110 | // Calculating the regime. 111 | var wholePosit = EncodeRegimeBits(regimeKValue); 112 | 113 | // Attaching the exponent. 114 | var regimeLength = wholePosit.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)); 115 | 116 | var exponentShiftedLeftBy = Size - (regimeLength + 2) - MaximumExponentSize; 117 | wholePosit += exponentBits << exponentShiftedLeftBy; 118 | 119 | // Calculating rounding. 120 | if (exponentShiftedLeftBy < 0) 121 | { 122 | CaclulateRounding(exponentShiftedLeftBy, ref exponentBits, ref wholePosit); 123 | return !signBit ? wholePosit : wholePosit.GetTwosComplement(_environment.Size); 124 | } 125 | 126 | var fractionMostSignificantOneIndex = fractionBits.FindMostSignificantOnePosition() - 1; 127 | 128 | // Hiding the hidden bit. (It is always one.) 129 | fractionBits = fractionBits.SetZero((ushort)fractionMostSignificantOneIndex); 130 | 131 | var fractionShiftedLeftBy = _environment.Size - 2 - fractionMostSignificantOneIndex - regimeLength - 132 | _environment.MaximumExponentSize; 133 | // Attaching the fraction. 134 | wholePosit += fractionBits << fractionShiftedLeftBy; 135 | // Calculating rounding. 136 | if (fractionShiftedLeftBy < 0) 137 | { 138 | CaclulateRounding(fractionShiftedLeftBy, ref fractionBits, ref wholePosit); 139 | } 140 | 141 | return !signBit ? wholePosit : wholePosit.GetTwosComplement(_environment.Size); 142 | } 143 | 144 | public int GetRegimeKValue() 145 | { 146 | var bits = IsPositive() ? PositBits : PositBits.GetTwosComplement(Size); 147 | return (bits & FirstRegimeBitBitMask) == EmptyBitMask 148 | ? -bits.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)) 149 | : bits.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)) - 1; 150 | } 151 | 152 | public int CalculateScaleFactor() 153 | { 154 | if (GetRegimeKValue() == -(Size - 1)) return 0; 155 | return (int)((GetRegimeKValue() * (1 << MaximumExponentSize)) + GetExponentValue()); 156 | } 157 | 158 | public uint ExponentSize() 159 | { 160 | var bits = IsPositive() ? PositBits : PositBits.GetTwosComplement(Size); 161 | return Size - (bits.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)) + 2) > MaximumExponentSize 162 | ? MaximumExponentSize : (uint)(Size - (bits.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)) + 2)); 163 | } 164 | 165 | public uint GetExponentValue() 166 | { 167 | var exponentMask = IsPositive() ? PositBits : PositBits.GetTwosComplement(Size); 168 | exponentMask = (exponentMask >> (int)FractionSize()) 169 | << (int)((PositBits.SegmentCount * 32) - ExponentSize()) 170 | >> ((PositBits.SegmentCount * 32) - MaximumExponentSize); 171 | return exponentMask.Lowest32Bits; 172 | } 173 | 174 | public uint FractionSize() 175 | { 176 | var bits = IsPositive() ? PositBits : PositBits.GetTwosComplement(Size); 177 | var fractionSize = Size - (bits.LengthOfRunOfBits((ushort)(FirstRegimeBitIndex + 1)) + 2 + MaximumExponentSize); 178 | return fractionSize > 0 ? (uint)fractionSize : 0; 179 | } 180 | 181 | #endregion Methods to handle parts of the Posit 182 | 183 | #region Helper methods for operations and conversions 184 | 185 | public BitMask FractionWithHiddenBit() 186 | { 187 | var bits = IsPositive() ? PositBits : PositBits.GetTwosComplement(Size); 188 | var result = bits << (int)((PositBits.SegmentCount * 32) - FractionSize()) 189 | >> (int)((PositBits.SegmentCount * 32) - FractionSize()); 190 | return result.SetOne((ushort)FractionSize()); 191 | } 192 | 193 | public static int CalculateScaleFactor(int regimeKValue, uint exponentValue, byte maximumExponentSize) => 194 | (int)((regimeKValue * (1 << maximumExponentSize)) + exponentValue); 195 | 196 | private static void CaclulateRounding(int shiftedLeftBy, ref BitMask bits, ref BitMask wholePosit) 197 | { 198 | bits <<= bits.Size + shiftedLeftBy; 199 | if (bits >= new BitMask(bits.Size).SetOne((ushort)(bits.Size - 1))) 200 | { 201 | if (bits == new BitMask(bits.Size).SetOne((ushort)(bits.Size - 1))) 202 | { 203 | wholePosit += wholePosit.Lowest32Bits & 1; 204 | } 205 | else 206 | { 207 | wholePosit += 1; 208 | } 209 | } 210 | } 211 | 212 | #endregion Helper methods for operations and conversions 213 | 214 | #region operators 215 | 216 | [SuppressMessage( 217 | "Critical Code Smell", 218 | "S3776:Cognitive Complexity of methods should not be too high", 219 | Justification = "It's really not that bad.")] 220 | public static Posit operator +(Posit left, Posit right) 221 | { 222 | var leftIsPositive = left.IsPositive(); 223 | var rightIsPositive = right.IsPositive(); 224 | var resultSignBit = (left.PositBits + right.PositBits).FindMostSignificantOnePosition() < left.PositBits.Size 225 | ? !leftIsPositive 226 | : !rightIsPositive; 227 | 228 | var signBitsMatch = leftIsPositive == rightIsPositive; 229 | 230 | int leftRegimeKValue; 231 | uint leftExponentValue; 232 | int rightRegimeKValue; 233 | uint rightExponentValue; 234 | 235 | if (!leftIsPositive) 236 | { 237 | var negatedLeft = -left; 238 | leftRegimeKValue = negatedLeft.GetRegimeKValue(); 239 | leftExponentValue = negatedLeft.GetExponentValue(); 240 | } 241 | else 242 | { 243 | leftRegimeKValue = left.GetRegimeKValue(); 244 | leftExponentValue = left.GetExponentValue(); 245 | } 246 | 247 | if (!rightIsPositive) 248 | { 249 | var negatedRight = -right; 250 | rightRegimeKValue = negatedRight.GetRegimeKValue(); 251 | rightExponentValue = negatedRight.GetExponentValue(); 252 | } 253 | else 254 | { 255 | rightRegimeKValue = right.GetRegimeKValue(); 256 | rightExponentValue = right.GetExponentValue(); 257 | } 258 | 259 | // Handling special cases first. 260 | if (leftRegimeKValue == -(left.Size - 1)) 261 | { 262 | return leftIsPositive ? right : left; 263 | } 264 | 265 | if (rightRegimeKValue == -(right.Size - 1)) 266 | { 267 | return rightIsPositive ? left : right; 268 | } 269 | 270 | var resultFractionBits = new BitMask(left._environment.Size); // Later on the quire will be used here. 271 | 272 | var scaleFactorDifference = CalculateScaleFactor(leftRegimeKValue, leftExponentValue, left.MaximumExponentSize) 273 | - CalculateScaleFactor(rightRegimeKValue, rightExponentValue, right.MaximumExponentSize); 274 | 275 | var scaleFactor = 276 | scaleFactorDifference >= 0 277 | ? CalculateScaleFactor(leftRegimeKValue, leftExponentValue, left.MaximumExponentSize) 278 | : CalculateScaleFactor(rightRegimeKValue, rightExponentValue, right.MaximumExponentSize); 279 | 280 | if (scaleFactorDifference == 0) 281 | { 282 | if (signBitsMatch) 283 | { 284 | resultFractionBits += left.FractionWithHiddenBit() + right.FractionWithHiddenBit(); 285 | } 286 | else if (left.FractionWithHiddenBit() >= right.FractionWithHiddenBit()) 287 | { 288 | resultFractionBits += left.FractionWithHiddenBit() - right.FractionWithHiddenBit(); 289 | } 290 | else 291 | { 292 | resultFractionBits += right.FractionWithHiddenBit() - left.FractionWithHiddenBit(); 293 | } 294 | 295 | scaleFactor += resultFractionBits.FindMostSignificantOnePosition() - 296 | left.FractionWithHiddenBit().FindMostSignificantOnePosition(); 297 | } 298 | else if (scaleFactorDifference > 0) 299 | { 300 | // The scale factor of the left Posit is bigger. 301 | var fractionSizeDifference = (int)(left.FractionSize() - right.FractionSize()); 302 | resultFractionBits += left.FractionWithHiddenBit(); 303 | var biggerPositMovedToLeft = left.Size - 1 - left.FractionWithHiddenBit().FindMostSignificantOnePosition(); 304 | resultFractionBits <<= biggerPositMovedToLeft; 305 | 306 | var difference = right.FractionWithHiddenBit() << 307 | (biggerPositMovedToLeft - scaleFactorDifference + fractionSizeDifference); 308 | resultFractionBits = signBitsMatch 309 | ? resultFractionBits + difference 310 | : resultFractionBits - difference; 311 | 312 | scaleFactor += resultFractionBits.FindMostSignificantOnePosition() - (left.Size - 1); 313 | } 314 | else 315 | { 316 | // The scale factor of the right Posit is bigger. 317 | var fractionSizeDifference = (int)(right.FractionSize() - left.FractionSize()); 318 | resultFractionBits += right.FractionWithHiddenBit(); 319 | var biggerPositMovedToLeft = right.Size - 1 - right.FractionWithHiddenBit().FindMostSignificantOnePosition(); 320 | resultFractionBits <<= biggerPositMovedToLeft; 321 | 322 | if (signBitsMatch) 323 | { 324 | resultFractionBits += left.FractionWithHiddenBit() << 325 | (biggerPositMovedToLeft + scaleFactorDifference + fractionSizeDifference); 326 | } 327 | else 328 | { 329 | resultFractionBits -= left.FractionWithHiddenBit() << 330 | (biggerPositMovedToLeft + scaleFactorDifference + fractionSizeDifference); 331 | } 332 | 333 | scaleFactor += resultFractionBits.FindMostSignificantOnePosition() - (right.Size - 1); 334 | } 335 | 336 | if (resultFractionBits.FindMostSignificantOnePosition() == 0) return new Posit(left._environment, left.EmptyBitMask); 337 | 338 | var resultRegimeKValue = scaleFactor / (1 << left.MaximumExponentSize); 339 | var resultExponentBits = new BitMask((uint)(scaleFactor % (1 << left.MaximumExponentSize)), left._environment.Size); 340 | 341 | return new Posit( 342 | left._environment, 343 | left.AssemblePositBitsWithRounding(resultSignBit, resultRegimeKValue, resultExponentBits, resultFractionBits)); 344 | } 345 | 346 | public static Posit operator +(Posit left, int right) => left + new Posit(left._environment, right); 347 | 348 | public static Posit operator -(Posit left, Posit right) => left + (-right); 349 | 350 | public static Posit operator -(Posit left, int right) => left - new Posit(left._environment, right); 351 | 352 | public static Posit operator -(Posit x) 353 | { 354 | if (x.IsNaN() || x.IsZero()) return new Posit(x._environment, x.PositBits); 355 | return new Posit(x._environment, x.PositBits.GetTwosComplement(x.Size)); 356 | } 357 | 358 | public static bool operator ==(Posit left, Posit right) => left.PositBits == right.PositBits; 359 | 360 | public static bool operator >(Posit left, Posit right) 361 | { 362 | if (!left.IsPositive()) left = -left; 363 | if (!right.IsPositive()) right = -right; 364 | return (left.PositBits + right.PositBits).FindMostSignificantOnePosition() > left.PositBits.Size; 365 | } 366 | 367 | public static bool operator <(Posit left, Posit right) => !(left.PositBits > right.PositBits); 368 | 369 | public static bool operator !=(Posit left, Posit right) => !(left == right); 370 | 371 | public static explicit operator int(Posit x) 372 | { 373 | uint result; 374 | 375 | // The posit fits into the range 376 | if ((x.GetRegimeKValue() * (1 << x.MaximumExponentSize)) + x.GetExponentValue() + 1 < 31) 377 | { 378 | result = (x.FractionWithHiddenBit() << ( 379 | (int)((x.GetRegimeKValue() * (1 << x.MaximumExponentSize)) + x.GetExponentValue()) - 380 | x.FractionWithHiddenBit().FindMostSignificantOnePosition() + 381 | 1)) 382 | .Lowest32Bits; 383 | } 384 | else 385 | { 386 | return x.IsPositive() ? int.MaxValue : int.MinValue; 387 | } 388 | 389 | return x.IsPositive() ? (int)result : (int)-result; 390 | } 391 | 392 | public override bool Equals(object obj) => obj is Posit other && this == other; 393 | 394 | public bool Equals(Posit other) => this == other; 395 | 396 | public override int GetHashCode() 397 | { 398 | unchecked 399 | { 400 | return ((_environment != null ? _environment.GetHashCode() : 0) * 397) ^ PositBits.GetHashCode(); 401 | } 402 | } 403 | 404 | #endregion operators 405 | } 406 | -------------------------------------------------------------------------------- /BitMask/BitMask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Immutable; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace Lombiq.Arithmetics; 6 | 7 | [StructLayout(LayoutKind.Auto)] 8 | public struct BitMask : IEquatable 9 | { 10 | private const uint SegmentMaskWithLeadingOne = 0x80000000; // 1000 0000 0000 0000 0000 0000 0000 0000 11 | private const uint SegmentMaskWithClosingOne = 1; // 0000 0000 0000 0000 0000 0000 0000 0001 12 | public ushort Size { get; } 13 | public ushort SegmentCount { get; } 14 | public ImmutableArray Segments { get; } 15 | 16 | public uint Lowest32Bits => Segments[0]; 17 | 18 | #region Constructors 19 | 20 | public BitMask(uint segment, ushort size) 21 | { 22 | Size = size; 23 | SegmentCount = (ushort)((size >> 5) + (size % 32 == 0 ? 0 : 1)); 24 | 25 | /* Creating a new, temporary array once that will be used to initialize the ImmutableArray, 26 | * so the "extension" items (i.e. the 0-value items on top of the original segments) aren't added 27 | * using ImmutableArray.Add, which would instantiate a new array for each addition. */ 28 | var extendedSegments = new uint[SegmentCount]; 29 | extendedSegments[0] = segment; 30 | Segments = ImmutableArray.CreateRange(extendedSegments); 31 | } 32 | 33 | public BitMask(uint[] segments, ushort size = 0) 34 | { 35 | var segmentBits = (ushort)(segments.Length << 5); 36 | 37 | Size = size < segmentBits ? segmentBits : size; 38 | SegmentCount = (ushort)segments.Length; 39 | if (size > segmentBits) SegmentCount = (ushort)((size >> 5) + (size % 32 == 0 ? 0 : 1)); 40 | 41 | if (SegmentCount > segments.Length) 42 | { 43 | /* Creating a new, temporary array once that will be used to initialize the ImmutableArray, 44 | * so the "extension" items (i.e. the 0-value items on top of the original segments) aren't added 45 | * using ImmutableArray.Add, which would instantiate a new array for each addition. */ 46 | var extendedSegments = new uint[SegmentCount]; 47 | Array.Copy(segments, extendedSegments, segments.Length); 48 | Segments = ImmutableArray.CreateRange(extendedSegments); 49 | } 50 | else 51 | { 52 | Segments = ImmutableArray.CreateRange(segments); 53 | } 54 | } 55 | 56 | public BitMask(ushort size, bool allOne = false) 57 | { 58 | var partialSegment = size % 32; 59 | SegmentCount = (ushort)((size >> 5) + (partialSegment == 0 ? 0 : 1)); 60 | Size = size; 61 | 62 | // Creating a temporary array, so the items aren't added using ImmutableArray.Add, because that instantiates a 63 | // new array for each execution. 64 | var segments = new uint[SegmentCount]; 65 | 66 | if (allOne) 67 | { 68 | ushort i = 0; 69 | for (; i < SegmentCount - 1; i++) segments[i] = uint.MaxValue; 70 | 71 | // The last segment is special in a way that it might not be necessary to have all 1 bits. 72 | segments[i] = partialSegment > 0 ? (uint)(1 << partialSegment) - 1 : uint.MaxValue; 73 | } 74 | 75 | Segments = ImmutableArray.CreateRange(segments); 76 | } 77 | 78 | public BitMask(BitMask source) 79 | { 80 | Size = source.Size; 81 | SegmentCount = source.SegmentCount; 82 | Segments = source.Segments; 83 | } 84 | 85 | #endregion Constructors 86 | 87 | #region Static factories 88 | 89 | public static BitMask FromImmutableArray(ImmutableArray segments, ushort size = 0) 90 | { 91 | var intermediarySegments = new uint[segments.Length]; 92 | segments.CopyTo(intermediarySegments); 93 | 94 | return new BitMask(intermediarySegments, size); 95 | } 96 | 97 | #endregion Static factories 98 | 99 | #region BitMask manipulation functions 100 | 101 | /// 102 | /// Returns a new BitMask, where the given index is set to one. 103 | /// 104 | /// The index of the bit to set. 105 | /// A BitMask where the given bit is set to one. 106 | public BitMask SetOne(ushort index) 107 | { 108 | if (index > SegmentCount * 32) return new BitMask(this); 109 | 110 | var bitPosition = index % 32; 111 | var segmentPosition = index >> 5; 112 | 113 | if ((Segments[segmentPosition] >> bitPosition) % 2 == 0) 114 | return FromImmutableArray(Segments.SetItem(segmentPosition, Segments[segmentPosition] | (uint)(1 << bitPosition)), Size); 115 | 116 | return new BitMask(this); 117 | } 118 | 119 | /// 120 | /// Returns a new BitMask, where the given bit is set to zero. 121 | /// 122 | /// The index of the bit to set to zero. 123 | /// A BitMask where the given bit is set to zero. 124 | public BitMask SetZero(ushort index) 125 | { 126 | if (index > SegmentCount * 32) return new BitMask(this); 127 | 128 | var bitPosition = index % 32; 129 | var segmentPosition = index >> 5; 130 | 131 | if ((Segments[segmentPosition] >> bitPosition) % 2 == 1) 132 | return FromImmutableArray(Segments.SetItem(segmentPosition, Segments[segmentPosition] & ~(1U << bitPosition)), Size); 133 | 134 | return new BitMask(this); 135 | } 136 | 137 | /// 138 | /// Shifts the BitMask to the right by the number of trailing zeros. 139 | /// 140 | /// A BitMask where the trailing zeros are shifted out to the right. 141 | public BitMask ShiftOutLeastSignificantZeros() 142 | { 143 | var leastSignificantOnePosition = FindLeastSignificantOnePosition(); 144 | var mask = new BitMask(this); 145 | if (leastSignificantOnePosition == 0) return mask; 146 | 147 | return mask >> (leastSignificantOnePosition - 1); 148 | } 149 | 150 | /// 151 | /// Sets the segment on the given index to the segment given as an argument. 152 | /// 153 | /// The index of the Segment to set. 154 | /// The segment that the BitMask's segment on the given index will be set to. 155 | /// A BitMask where the trailing zeros are shifted out to the right. 156 | public BitMask SetSegment(int index, uint segment) 157 | { 158 | if (index >= SegmentCount) return new BitMask(this); 159 | 160 | return FromImmutableArray(Segments.SetItem(index, segment)); 161 | } 162 | 163 | #endregion BitMask manipulation functions 164 | 165 | #region Operators 166 | 167 | public static bool operator ==(BitMask left, BitMask right) 168 | { 169 | if (left.SegmentCount != right.SegmentCount) return false; 170 | 171 | for (ushort i = 0; i < left.SegmentCount; i++) 172 | if (left.Segments[i] != right.Segments[i]) return false; 173 | 174 | return true; 175 | } 176 | 177 | public static bool operator >(BitMask left, BitMask right) 178 | { 179 | for (ushort i = 1; i <= left.SegmentCount; i++) 180 | if (left.Segments[left.SegmentCount - i] > right.Segments[left.SegmentCount - i]) return true; 181 | 182 | return false; 183 | } 184 | 185 | public static bool operator <(BitMask left, BitMask right) 186 | { 187 | for (ushort i = 1; i <= left.SegmentCount; i++) 188 | if (left.Segments[left.SegmentCount - i] < right.Segments[left.SegmentCount - i]) return true; 189 | 190 | return false; 191 | } 192 | 193 | public static bool operator >=(BitMask left, BitMask right) => !(left < right); 194 | 195 | public static bool operator <=(BitMask left, BitMask right) => !(left > right); 196 | 197 | public static bool operator !=(BitMask left, BitMask right) => !(left == right); 198 | 199 | public static BitMask operator +(BitMask left, uint right) => left + new BitMask(right, left.Size); 200 | 201 | public static BitMask operator -(BitMask left, uint right) => left - new BitMask(right, left.Size); 202 | 203 | /// 204 | /// Bit-by-bit addition of two masks with "ripple-carry". 205 | /// 206 | /// Left operand BitMask. 207 | /// Right operand BitMask. 208 | /// The sum of the masks with respect to the size of the bigger operand. 209 | public static BitMask operator +(BitMask left, BitMask right) 210 | { 211 | if (left.SegmentCount == 0 || right.SegmentCount == 0) return left; 212 | bool carry = false; 213 | byte buffer; 214 | ushort segmentPosition = 0, position = 0; 215 | var segments = new uint[left.SegmentCount]; 216 | 217 | for (ushort i = 0; i < (left.Size > right.Size ? left.Size : right.Size); i++) 218 | { 219 | var leftBit = (left.Segments[segmentPosition] >> position) % 2; 220 | var rightBit = i >= right.Size ? 0 : (right.Segments[segmentPosition] >> position) % 2; 221 | var carryBit = carry ? 1 : 0; 222 | 223 | buffer = (byte)(leftBit + rightBit + carryBit); 224 | 225 | if (buffer % 2 != 0) segments[segmentPosition] += (uint)(1 << position); 226 | carry = buffer >> 1 == 1; 227 | 228 | position++; 229 | if (position >> 5 == 1) 230 | { 231 | position = 0; 232 | segmentPosition++; 233 | } 234 | } 235 | 236 | return new BitMask(segments); 237 | } 238 | 239 | /// 240 | /// Bit-by-bit subtraction of two masks with "ripple-carry". 241 | /// 242 | /// Left operand BitMask. 243 | /// Right operand BitMask. 244 | /// The difference between the masks with respect to the size of the bigger operand. 245 | public static BitMask operator -(BitMask left, BitMask right) 246 | { 247 | if (left.SegmentCount == 0 || right.SegmentCount == 0) return left; 248 | 249 | bool carry = false; 250 | byte buffer; 251 | ushort segmentPosition = 0, position = 0; 252 | var segments = new uint[left.SegmentCount]; 253 | 254 | for (ushort i = 0; i < (left.Size > right.Size ? left.Size : right.Size); i++) 255 | { 256 | var leftBit = (left.Segments[segmentPosition] >> position) % 2; 257 | var rightBit = i >= right.Size ? 0 : (right.Segments[segmentPosition] >> position) % 2; 258 | 259 | buffer = (byte)(2 + leftBit - rightBit - (carry ? 1 : 0)); 260 | 261 | if (buffer % 2 != 0) segments[segmentPosition] += (uint)(1 << position); 262 | carry = buffer >> 1 == 0; 263 | 264 | position++; 265 | if (position >> 5 == 1) 266 | { 267 | position = 0; 268 | segmentPosition++; 269 | } 270 | } 271 | 272 | return new BitMask(segments); 273 | } 274 | 275 | public static BitMask operator ++(BitMask mask) => mask + 1; 276 | 277 | public static BitMask operator --(BitMask mask) => mask - 1; 278 | 279 | public static BitMask operator |(BitMask left, BitMask right) 280 | { 281 | if (left.SegmentCount != right.SegmentCount) return new BitMask(left.Size); 282 | 283 | var segments = new uint[left.SegmentCount]; 284 | 285 | for (ushort i = 0; i < segments.Length; i++) 286 | segments[i] = left.Segments[i] | right.Segments[i]; 287 | 288 | return new BitMask(segments); 289 | } 290 | 291 | public static BitMask operator &(BitMask left, BitMask right) 292 | { 293 | if (left.SegmentCount != right.SegmentCount) return new BitMask(left.Size); 294 | 295 | var segments = new uint[left.SegmentCount]; 296 | 297 | for (ushort i = 0; i < segments.Length; i++) 298 | segments[i] = left.Segments[i] & right.Segments[i]; 299 | 300 | return new BitMask(segments); 301 | } 302 | 303 | public static BitMask operator ^(BitMask left, BitMask right) 304 | { 305 | if (left.SegmentCount != right.SegmentCount) return new BitMask(left.Size); 306 | 307 | var segments = new uint[left.SegmentCount]; 308 | 309 | for (ushort i = 0; i < segments.Length; i++) 310 | segments[i] = left.Segments[i] ^ right.Segments[i]; 311 | 312 | return new BitMask(segments); 313 | } 314 | 315 | public static BitMask operator ~(BitMask input) 316 | { 317 | var segments = new uint[input.SegmentCount]; 318 | 319 | for (ushort i = 0; i < segments.Length; i++) 320 | segments[i] = ~input.Segments[i]; 321 | 322 | return new BitMask(segments); 323 | } 324 | 325 | /// 326 | /// Bit-shifting of a BitMask to the right by an integer. Shifts left if negative value is given. 327 | /// 328 | /// Left operand BitMask to shift. 329 | /// Right operand int tells how many bits to shift by. 330 | /// BitMask of size of left BitMask, shifted left by number of bits given in the right integer. 331 | public static BitMask operator >>(BitMask left, int right) 332 | { 333 | if (right < 0) return left << -right; 334 | 335 | bool carryOld, carryNew; 336 | var segments = new uint[left.SegmentCount]; 337 | left.Segments.CopyTo(segments); 338 | ushort currentIndex; 339 | 340 | for (ushort i = 0; i < right; i++) 341 | { 342 | carryOld = false; 343 | 344 | for (ushort j = 1; j <= segments.Length; j++) 345 | { 346 | currentIndex = (ushort)(segments.Length - j); 347 | carryNew = segments[currentIndex] % 2 == 1; 348 | segments[currentIndex] >>= 1; 349 | if (carryOld) segments[currentIndex] |= SegmentMaskWithLeadingOne; 350 | carryOld = carryNew; 351 | } 352 | } 353 | 354 | return new BitMask(segments); 355 | } 356 | 357 | /// 358 | /// Bit-shifting of a BitMask to the left by an integer. Shifts right if negative value is given. 359 | /// 360 | /// Left operand BitMask. 361 | /// Right operand int tells how many bits to shift by. 362 | /// BitMask of size of left BitMask, shifted right by number of bits given in the right integer. 363 | public static BitMask operator <<(BitMask left, int right) 364 | { 365 | if (right < 0) return left >> -right; 366 | 367 | bool carryOld, carryNew; 368 | var segments = new uint[left.SegmentCount]; 369 | left.Segments.CopyTo(segments); 370 | 371 | for (ushort i = 0; i < right; i++) 372 | { 373 | carryOld = false; 374 | 375 | for (ushort j = 0; j < segments.Length; j++) 376 | { 377 | carryNew = (segments[j] & SegmentMaskWithLeadingOne) == SegmentMaskWithLeadingOne; 378 | segments[j] <<= 1; 379 | if (carryOld) segments[j] |= SegmentMaskWithClosingOne; 380 | carryOld = carryNew; 381 | } 382 | } 383 | 384 | return new BitMask(segments); 385 | } 386 | 387 | #endregion Operators 388 | 389 | #region Helper functions 390 | 391 | /// 392 | /// Finds the most significant 1-bit. 393 | /// 394 | /// Returns the position (not index!) of the most significant 1-bit or zero if there is none. 395 | public ushort FindMostSignificantOnePosition() 396 | { 397 | ushort position = 0; 398 | uint currentSegment; 399 | 400 | // ushort can't be checked against with ">= 0", because that's always true. 401 | for (ushort i = 1; i <= SegmentCount; i++) 402 | { 403 | currentSegment = Segments[SegmentCount - i]; 404 | while (currentSegment != 0) 405 | { 406 | currentSegment >>= 1; 407 | position++; 408 | if (currentSegment == 0) return (ushort)(((SegmentCount - i) * 32) + position); 409 | } 410 | } 411 | 412 | return 0; 413 | } 414 | 415 | public BitMask GetTwosComplement(ushort size) 416 | { 417 | var mask = new BitMask(this); 418 | return ((~mask + 1) << ((SegmentCount * 32) - size)) >> ((SegmentCount * 32) - size); 419 | } 420 | 421 | public ushort LengthOfRunOfBits(ushort startingPosition) 422 | { 423 | ushort length = 1; 424 | var mask = new BitMask(this) << ((SegmentCount * 32) - startingPosition); 425 | var startingBit = mask.Segments[0] >> 31 > 0; 426 | mask <<= 1; 427 | for (var i = 0; i < startingPosition; i++) 428 | { 429 | if ((mask.Segments[0] >> 31 > 0) != startingBit) return length; 430 | mask <<= 1; 431 | length++; 432 | } 433 | 434 | return (length > startingPosition) ? startingPosition : length; 435 | } 436 | 437 | /// 438 | /// Finds the least significant 1-bit. 439 | /// 440 | /// Returns the position (not index!) of the least significant 1-bit or zero if there is none. 441 | public ushort FindLeastSignificantOnePosition() 442 | { 443 | ushort position = 1; 444 | uint currentSegment; 445 | 446 | for (ushort i = 0; i < SegmentCount; i++) 447 | { 448 | currentSegment = Segments[i]; 449 | if (currentSegment == 0) 450 | { 451 | position += 32; 452 | } 453 | else 454 | { 455 | while (currentSegment % 2 == 0) 456 | { 457 | position++; 458 | currentSegment >>= 1; 459 | } 460 | 461 | if (currentSegment % 2 == 1) return position; 462 | } 463 | } 464 | 465 | return 0; 466 | } 467 | 468 | // Array indexer is not supported by Hastlayer yet. 469 | //// public uint this[int i] => Segments[i]; 470 | 471 | #endregion Helper functions 472 | 473 | #region Overrides 474 | 475 | public override bool Equals(object obj) => obj is BitMask other && this == other; 476 | 477 | public bool Equals(BitMask other) => this == other; 478 | 479 | public override int GetHashCode() 480 | { 481 | var segmentHashCodes = new int[SegmentCount]; 482 | for (int i = 0; i < SegmentCount; i++) segmentHashCodes[i] = Segments[i].GetHashCode(); 483 | 484 | return segmentHashCodes.GetHashCode(); 485 | } 486 | 487 | public override string ToString() => string.Join(", ", Segments); 488 | 489 | #endregion Overrides 490 | } 491 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/PositTests/Posit32Tests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using System.Diagnostics; 3 | using System.Globalization; 4 | using Xunit; 5 | 6 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 7 | 8 | namespace Lombiq.Arithmetics.Tests; 9 | 10 | public class Posit32Tests 11 | { 12 | [Fact] 13 | public void EncodeRegimeBitsIsCorrect() 14 | { 15 | Assert.AreEqual(Posit32.EncodeRegimeBits(0), 0x_4000_0000); 16 | Assert.AreEqual(Posit32.EncodeRegimeBits(1), 0x_6000_0000); 17 | Assert.AreEqual(Posit32.EncodeRegimeBits(2), 0x_7000_0000); 18 | Assert.AreEqual(Posit32.EncodeRegimeBits(-3), 0x_0800_0000); 19 | Assert.AreEqual(Posit32.EncodeRegimeBits(-30), 0x_0000_0001); 20 | Assert.AreEqual(Posit32.EncodeRegimeBits(30), 0x_7FFF_FFFF); 21 | } 22 | 23 | [Fact] 24 | public void Posit32IsCorrectlyConstructedFromInt() 25 | { 26 | Assert.AreEqual(new Posit32(0).PositBits, 0x_0000_0000); 27 | 28 | Assert.AreEqual(new Posit32(1).PositBits, 0x_4000_0000); 29 | 30 | Assert.AreEqual(new Posit32(-1).PositBits, 0x_C000_0000); 31 | 32 | Assert.AreEqual(new Posit32(2).PositBits, 0x_4800_0000); 33 | 34 | Assert.AreEqual(new Posit32(13).PositBits, 0x_5D00_0000); 35 | 36 | Assert.AreEqual(new Posit32(17).PositBits, 0x_6040_0000); 37 | 38 | Assert.AreEqual(new Posit32(500).PositBits, 0x_71E8_0000); 39 | 40 | Assert.AreEqual(new Posit32(100).PositBits, 0b_01101010_01000000_00000000_00000000); 41 | 42 | Assert.AreEqual(new Posit32(-500).PositBits, 0x_8E18_0000); 43 | 44 | Assert.AreEqual(new Posit32(-499).PositBits, 0x_8E1A_0000); 45 | 46 | Assert.AreEqual(new Posit32(int.MaxValue).PositBits, 0b_01111111_10110000_00000000_00000000); 47 | 48 | Assert.AreEqual(new Posit32(int.MinValue).PositBits, 0b_10000000_01010000_00000000_00000000); 49 | 50 | Assert.AreEqual(new Posit32(int.MaxValue - 1).PositBits, 0b_01111111_10110000_00000000_00000000); 51 | } 52 | 53 | [Fact] 54 | public void Posit32AdditionIsCorrect() 55 | { 56 | var posit16 = new Posit32(16); 57 | var posit17 = posit16 + 1; 58 | posit17.PositBits.ShouldBe(new Posit32(17).PositBits); 59 | 60 | var posit1 = new Posit32(1); 61 | var posit0 = posit1 - 1; 62 | posit0.PositBits.ShouldBe(new Posit32(0).PositBits); 63 | var positNegative1 = posit0 - 1; 64 | positNegative1.PositBits.ShouldBe(0x_C000_0000); 65 | 66 | var positNegative500 = new Posit32(-500); 67 | var positNegative499 = positNegative500 + 1; 68 | positNegative499.PositBits.ShouldBe(new Posit32(-499).PositBits); 69 | 70 | var positNegative2 = positNegative1 - 1; 71 | positNegative2.PositBits.ShouldBe(new Posit32(-2).PositBits); 72 | 73 | var posit3 = new Posit32(100.0125F); 74 | var posit4 = posit3 - 100; 75 | 76 | posit4.PositBits.ShouldBe(new Posit32(0b_00010110_01100110_00000000_00000000, fromBitMask: true).PositBits); 77 | 78 | (new Posit32(500) + new Posit32(-500)).PositBits.ShouldBe(new Posit32(0).PositBits); 79 | (new Posit32(99_988) + new Posit32(-88_999)).PositBits.ShouldBe(new Posit32(10_989).PositBits); 80 | (new Posit32(0.75F) + new Posit32(0.75F)).PositBits.ShouldBe(new Posit32(1.5F).PositBits); 81 | (new Posit32(4F) + new Posit32(-3.75F)).PositBits.ShouldBe(new Posit32(0.25F).PositBits); 82 | } 83 | 84 | [Fact] 85 | public void Posit32AdditionIsCorrectForPositives() 86 | { 87 | var posit1 = new Posit32(1); 88 | 89 | for (var i = 1; i < 50_000; i++) 90 | { 91 | posit1 += 1; 92 | } 93 | 94 | posit1.PositBits.ShouldBe(new Posit32(50_000).PositBits); 95 | } 96 | 97 | [Fact] 98 | public void Posit32LengthOfRunOfBitsIsCorrect() 99 | { 100 | Assert.AreEqual(Posit32.LengthOfRunOfBits(1, 31), 30); 101 | Assert.AreEqual(Posit32.LengthOfRunOfBits(0x_6000_0000, 31), 2); 102 | Assert.AreEqual(Posit32.LengthOfRunOfBits(0b_00010000_10001111_01010011_11000101, 31), 2); 103 | } 104 | 105 | [Fact] 106 | public void Posit32AdditionIsCorrectForNegatives() 107 | { 108 | var posit1 = new Posit32(-500); 109 | 110 | for (var i = 1; i <= 500; i++) 111 | { 112 | posit1 += 1; 113 | } 114 | 115 | for (var j = 1; j <= 500; j++) 116 | { 117 | posit1 -= 1; 118 | } 119 | 120 | posit1.PositBits.ShouldBe(new Posit32(-500).PositBits); 121 | } 122 | 123 | [Fact] 124 | public void Posit32MultiplicationIsCorrect() 125 | { 126 | var posit1 = new Posit32(1); 127 | posit1 *= 5; 128 | posit1.PositBits.ShouldBe(new Posit32(5).PositBits); 129 | 130 | var posit2 = new Posit32(2); 131 | posit2 *= new Posit32(0.25F); 132 | posit2.PositBits.ShouldBe(new Posit32(0.5F).PositBits); 133 | 134 | var posit3 = new Posit32(0.1F); 135 | posit3 *= new Posit32(0.01F); 136 | posit3.PositBits.ShouldBe(new Posit32(0b_00001100_00001100_01001001_10111010, fromBitMask: true).PositBits); 137 | 138 | var posit55 = new Posit32(int.MaxValue - 1); 139 | posit55 *= new Posit32(0.25F); 140 | posit55.PositBits.ShouldBe(new Posit32((int.MaxValue - 1) / 4).PositBits); 141 | 142 | posit55 *= new Posit32(0); 143 | posit55.PositBits.ShouldBe(new Posit32(0).PositBits); 144 | 145 | var positReal1 = new Posit32(0b_01000000_00000000_00110100_01101110, fromBitMask: true); 146 | var positReal2 = new Posit32(0b_01000000_00000000_00110100_01101110, fromBitMask: true); 147 | var pr3 = positReal1 * positReal2; 148 | 149 | Assert.AreEqual(pr3.PositBits, 0b_01000000_00000000_01101000_11011101); 150 | } 151 | 152 | [Fact] 153 | public void Posit32DivisionIsCorrect() 154 | { 155 | var posit6 = new Posit32(6); 156 | posit6 /= 4; 157 | posit6.PositBits.ShouldBe(new Posit32(1.5F).PositBits); 158 | 159 | var posit2 = new Posit32(2); 160 | posit2 /= 4; 161 | posit2.PositBits.ShouldBe(new Posit32(0.5F).PositBits); 162 | 163 | var posit55 = new Posit32(int.MaxValue - 1); 164 | posit55 /= new Posit32(4); 165 | posit55.PositBits.ShouldBe(new Posit32((int.MaxValue - 1) / 4).PositBits); 166 | 167 | posit55 /= new Posit32(0); 168 | posit55.PositBits.ShouldBe(new Posit32(Posit32.NaNBitMask, fromBitMask: true).PositBits); 169 | 170 | var posit12345 = new Posit32(12_345); 171 | posit12345 /= 100; 172 | posit12345.PositBits.ShouldBe(new Posit32(0b_01101011_10110111_00110011_00110011, fromBitMask: true).PositBits); 173 | 174 | var positBig = new Posit32(5_000_000); 175 | positBig /= 1_000_000; 176 | positBig.PositBits.ShouldBe(new Posit32(5).PositBits); 177 | 178 | var positBig2 = new Posit32(50_000_000); 179 | positBig2 /= 50_000_000; 180 | positBig2.PositBits.ShouldBe(new Posit32(1).PositBits); 181 | 182 | var positSmall = new Posit32(0.02F); 183 | positSmall /= new Posit32(0.05F); 184 | positSmall.PositBits.ShouldBe(new Posit32(0b_00110100_11001100_11001100_11000101, fromBitMask: true).PositBits); 185 | 186 | var positSmall2 = new Posit32(0.1F); 187 | 188 | positSmall2 /= 100; 189 | positSmall2.PositBits.ShouldBe(new Posit32(0b_00001100_00001100_01001001_10111011, fromBitMask: true).PositBits); 190 | } 191 | 192 | [Fact] 193 | public void Posit32ToIntIsCorrect() 194 | { 195 | var posit0 = new Posit32(0); 196 | Assert.AreEqual((int)posit0, 0); 197 | 198 | var posit1 = new Posit32(1); 199 | Assert.AreEqual((int)posit1, 1); 200 | 201 | var posit8 = new Posit32(8); 202 | Assert.AreEqual((int)posit8, 8); 203 | 204 | var posit16384 = new Posit32(16_384); 205 | Assert.AreEqual((int)posit16384, 16_384); 206 | 207 | var positNegative13 = new Posit32(-13); 208 | Assert.AreEqual((int)positNegative13, -13); 209 | 210 | var positIntMaxValue = new Posit32(int.MaxValue); 211 | Assert.AreEqual((int)positIntMaxValue, int.MaxValue); 212 | var positCloseToIntMaxValue = new Posit32(2_147_481_600); 213 | Assert.AreEqual((int)positCloseToIntMaxValue, 2_147_481_600); 214 | } 215 | 216 | [Fact] 217 | public void Posit32IsCorrectlyConstructedFromFloat() 218 | { 219 | Assert.AreEqual(new Posit32(0F).PositBits, 0x_0000_0000); 220 | Assert.AreEqual(new Posit32(-0F).PositBits, 0x_0000_0000); 221 | Assert.AreEqual(new Posit32(0.75F).PositBits, 0b_00111100_00000000_00000000_00000000); 222 | 223 | Assert.AreEqual(new Posit32(0.0500000007450580596923828125F).PositBits, 0b_00011110_01100110_01100110_01101000); 224 | Assert.AreEqual(new Posit32(-0.00179999996908009052276611328125F).PositBits, 0b_11110010_01010000_01001000_00011000); 225 | 226 | Assert.AreEqual(new Posit32(-134.75F).PositBits, 0x_93CA_0000); 227 | Assert.AreEqual(new Posit32(100000.5F).PositBits, 0b_01111100_01000011_01010000_01000000); 228 | Assert.AreEqual(new Posit32(-2_000_000.5F).PositBits, 0b_10000001_11000101_11101101_11111110); 229 | 230 | Assert.AreEqual( 231 | new Posit32( 232 | 1.065291755432698054096667486857660145523165660824704316367306233814815641380846500396728515625E-38F).PositBits, 233 | 0b_00000000_00000000_00000000_00000001); 234 | Assert.AreEqual(new Posit32(2.7647944E+38F).PositBits, 0b_01111111_11111111_11111111_11111111); 235 | } 236 | 237 | [Fact] 238 | public void Posit32IsCorrectlyConstructedFromDouble() 239 | { 240 | Assert.AreEqual(new Posit32(0D).PositBits, 0x_0000_0000); 241 | Assert.AreEqual(new Posit32(-0D).PositBits, 0x_0000_0000); 242 | Assert.AreEqual(new Posit32(0.75).PositBits, 0b_00111100_00000000_00000000_00000000); 243 | 244 | Assert.AreEqual(new Posit32(0.0500000007450580596923828125).PositBits, 0b_00011110_01100110_01100110_01101000); 245 | Assert.AreEqual(new Posit32(-0.00179999996908009052276611328125).PositBits, 0b_11110010_01010000_01001000_00011000); 246 | 247 | Assert.AreEqual(new Posit32(-134.75).PositBits, 0b_10010011_11001010_00000000_00000000); 248 | Assert.AreEqual(new Posit32(100000.5).PositBits, 0b_01111100_01000011_01010000_01000000); 249 | Assert.AreEqual(new Posit32(-2_000_000.5).PositBits, 0b_10000001_11000101_11101101_11111110); 250 | 251 | Assert.AreEqual( 252 | new Posit32( 253 | 1.065291755432698054096667486857660145523165660824704316367306233814815641380846500396728515625E-38).PositBits, 254 | 0b_00000000_00000000_00000000_00000001); 255 | Assert.AreEqual(new Posit32(2.7647944E+38).PositBits, 0b_01111111_11111111_11111111_11111111); 256 | } 257 | 258 | [Fact] 259 | public void Posit32ToFloatIsCorrect() 260 | { 261 | var posit1 = new Posit32(1); 262 | Assert.AreEqual((float)posit1, 1); 263 | 264 | var positNegative1234 = new Posit32(-1_234); 265 | Assert.AreEqual((float)positNegative1234, -1_234); 266 | 267 | var posit3 = new Posit32(0.75F); 268 | Assert.AreEqual((float)posit3, 0.75); 269 | 270 | var posit4 = new Posit32(-134.75F); 271 | Assert.AreEqual((float)posit4, -134.75); 272 | 273 | var posit5 = new Posit32(100000.5F); 274 | Assert.AreEqual((float)posit5, 100000.5); 275 | 276 | var posit6 = new Posit32(-2_000_000.5F); 277 | Assert.AreEqual((float)posit6, -2_000_000.5); 278 | 279 | var posit7 = new Posit32(-0.00179999996908009052276611328125F); 280 | Assert.AreEqual((float)posit7, -0.00179999996908009052276611328125); 281 | 282 | var posit8 = new Posit32(0.0500000007450580596923828125F); 283 | Assert.AreEqual((float)posit8, 0.0500000007450580596923828125); 284 | 285 | var posit11 = new Posit32(0.002F); 286 | Assert.AreEqual((float)posit11, 0.002F); 287 | 288 | var posit9 = new Posit32(0.005F); 289 | Assert.AreEqual((float)posit9, 0.005F); 290 | 291 | var posit10 = new Posit32(0.1F); 292 | Assert.AreEqual((float)posit10, 0.1F); 293 | 294 | var posit12 = new Posit32(0.707106781F); 295 | Assert.AreEqual((float)posit12, 0.707106781F); 296 | } 297 | 298 | [Fact] 299 | public void Posit32ToDoubleIsCorrect() 300 | { 301 | var posit1 = new Posit32(1); 302 | Assert.AreEqual((double)posit1, 1); 303 | 304 | var positNegative1234 = new Posit32(-1_234); 305 | Assert.AreEqual((double)positNegative1234, -1_234); 306 | 307 | var posit3 = new Posit32(0.75); 308 | Assert.AreEqual((double)posit3, 0.75); 309 | 310 | var posit4 = new Posit32(-134.75); 311 | Assert.AreEqual((double)posit4, -134.75); 312 | 313 | var posit5 = new Posit32(100000.5); 314 | Assert.AreEqual((double)posit5, 100000.5); 315 | 316 | var posit6 = new Posit32(-2_000_000.5); 317 | Assert.AreEqual((float)posit6, -2_000_000.5); 318 | 319 | var posit7 = new Posit32(-0.00179999996908009052276611328125); 320 | Assert.AreEqual((double)posit7, -0.00179999996908009052276611328125); 321 | 322 | var posit8 = new Posit32(0.0500000007450580596923828125); 323 | Assert.AreEqual((double)posit8, 0.0500000007450580596923828125); 324 | 325 | var posit11 = new Posit32(0.002); 326 | Assert.AreEqual((double)posit11, 0.001999999978579581); 327 | 328 | var posit9 = new Posit32(0.005); 329 | Assert.AreEqual((double)posit9, 0.005000000004656613); 330 | 331 | var posit10 = new Posit32(0.1); 332 | Assert.AreEqual((double)posit10, 0.10000000009313226); 333 | 334 | var posit12 = new Posit32(0.707106781); 335 | Assert.AreEqual((double)posit12, 0.7071067802608013); 336 | } 337 | 338 | [Fact] 339 | public void Posit32ToQuireIsCorrect() 340 | { 341 | var posit1 = new Posit32(1); 342 | Assert.AreEqual(((Quire)posit1).Segments, (new Quire(new ulong[] { 1 }, 512) << 240).Segments); 343 | 344 | var positNegative1 = new Posit32(-1); 345 | Assert.AreEqual( 346 | ((Quire)positNegative1).Segments, 347 | new Quire( 348 | new ulong[] { 0, 0, 0, 0x_FFFF_0000_0000_0000, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue }, 512) 349 | .Segments); 350 | 351 | var positNegative3 = new Posit32(-3); 352 | Assert.AreEqual( 353 | ((Quire)positNegative3).Segments, 354 | new Quire( 355 | new ulong[] { 0, 0, 0, 0x_FFFD_0000_0000_0000, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue, ulong.MaxValue }, 512) 356 | .Segments); 357 | 358 | var positMax = new Posit32(0x_7FFF_FFFF, fromBitMask: true); 359 | Assert.AreEqual(((Quire)positMax).Segments, (new Quire(new ulong[] { 1 }, 512) << 360).Segments); 360 | 361 | var positNaN = new Posit32(Posit32.NaNBitMask, fromBitMask: true); 362 | var quireNaN = (Quire)positNaN; 363 | var quireNaNFromMask = new Quire(new ulong[] { 1 }, 512) << 511; 364 | 365 | Assert.AreEqual(quireNaN.Segments, quireNaNFromMask.Segments); 366 | } 367 | 368 | [Fact] 369 | public void Posit32FusedSumIsCorrect() 370 | { 371 | var positArray = new Posit32[3]; 372 | positArray[0] = new Posit32(1); 373 | positArray[1] = new Posit32(16_777_216); 374 | positArray[2] = new Posit32(4); 375 | Assert.AreEqual(Posit32.FusedSum(positArray).PositBits, new Posit32(16_777_224).PositBits); 376 | 377 | positArray[2] = new Posit32(Posit32.NaNBitMask, fromBitMask: true); 378 | Assert.AreEqual(Posit32.FusedSum(positArray).PositBits, positArray[2].PositBits); 379 | } 380 | 381 | [Fact] 382 | public void Posit32MultiplyIntoQuireIsCorrect() 383 | { 384 | var posit1 = new Posit32(3); 385 | var posit2 = new Posit32(4); 386 | var posit3 = new Posit32(-1); 387 | 388 | Assert.AreEqual(new Posit32(Posit32.MultiplyIntoQuire(posit1, posit2)).PositBits, new Posit32(12).PositBits); 389 | 390 | Assert.AreEqual(new Posit32(Posit32.MultiplyIntoQuire(posit1, posit3)).PositBits, new Posit32(-3).PositBits); 391 | } 392 | 393 | [Fact] 394 | public void Posit32FusedDotProductIsCorrect() 395 | { 396 | var positArray1 = new Posit32[3]; 397 | var positArray2 = new Posit32[3]; 398 | positArray1[0] = new Posit32(1); 399 | positArray1[1] = new Posit32(2); 400 | positArray1[2] = new Posit32(3); 401 | 402 | positArray2[0] = new Posit32(1); 403 | positArray2[1] = new Posit32(2); 404 | positArray2[2] = new Posit32(4); 405 | Assert.AreEqual(Posit32.FusedDotProduct(positArray1, positArray2).PositBits, new Posit32(17).PositBits); 406 | 407 | var positArray3 = new Posit32[3]; 408 | positArray3[0] = new Posit32(-1); 409 | positArray3[1] = new Posit32(2); 410 | positArray3[2] = new Posit32(-100); 411 | Assert.AreEqual(Posit32.FusedDotProduct(positArray1, positArray3).PositBits, new Posit32(-297).PositBits); 412 | } 413 | 414 | [Fact] 415 | public void Posit32FusedMultiplyAddIsCorrect() 416 | { 417 | var posit1 = new Posit32(300); 418 | var posit2 = new Posit32(0.5F); 419 | var posit3 = new Posit32(-1); 420 | 421 | Assert.AreEqual(Posit32.FusedMultiplyAdd(posit1, posit2, posit3).PositBits, new Posit32(149).PositBits); 422 | Assert.AreEqual(Posit32.FusedMultiplyAdd(posit1, posit3, posit2).PositBits, new Posit32(-299.5F).PositBits); 423 | } 424 | 425 | [Fact] 426 | public void Posit32FusedAddMultiplyIsCorrect() 427 | { 428 | var posit1 = new Posit32(0.75F); 429 | var posit2 = new Posit32(0.5F); 430 | var posit3 = new Posit32(-2); 431 | 432 | Assert.AreEqual(Posit32.FusedAddMultiply(posit1, posit2, posit3).PositBits, new Posit32(-2.5F).PositBits); 433 | Assert.AreEqual(Posit32.FusedAddMultiply(posit2, posit3, posit1).PositBits, new Posit32(-1.125F).PositBits); 434 | } 435 | 436 | [Fact] 437 | public void Posit32FusedMultiplyMultiplySubtractIsCorrect() 438 | { 439 | var posit1 = new Posit32(0.75F); 440 | var posit2 = new Posit32(0.5F); 441 | var posit3 = new Posit32(-2); 442 | var posit4 = new Posit32(125.125F); 443 | 444 | Assert.AreEqual( 445 | Posit32.FusedMultiplyMultiplySubtract(posit1, posit2, posit3, posit4).PositBits, 446 | new Posit32(250.625F).PositBits); 447 | Assert.AreEqual( 448 | Posit32.FusedMultiplyMultiplySubtract(posit2, posit3, posit1, posit4).PositBits, 449 | new Posit32(-94.84375F).PositBits); 450 | } 451 | 452 | [Fact] 453 | public void Posit32SquareRootIsCorrect() 454 | { 455 | var positNaN = new Posit32(Posit32.NaNBitMask, fromBitMask: true); 456 | Posit32.Sqrt(positNaN).PositBits.ShouldBe(new Posit32(Posit32.NaNBitMask, fromBitMask: true).PositBits); 457 | 458 | var positZero = new Posit32(0); 459 | Posit32.Sqrt(positZero).PositBits.ShouldBe(new Posit32(0).PositBits); 460 | 461 | var positOne = new Posit32(1); 462 | Posit32.Sqrt(positOne).PositBits.ShouldBe(new Posit32(1).PositBits); 463 | 464 | var positNegative = new Posit32(-1); 465 | Posit32.Sqrt(positNegative).PositBits.ShouldBe(new Posit32(Posit32.NaNBitMask, fromBitMask: true).PositBits); 466 | 467 | var posit4 = new Posit32(4); 468 | Posit32.Sqrt(posit4).PositBits.ShouldBe(new Posit32(2).PositBits); 469 | 470 | var posit9 = new Posit32(9); 471 | Posit32.Sqrt(posit9).PositBits.ShouldBe(new Posit32(3).PositBits); 472 | 473 | var posit625 = new Posit32(625); 474 | Posit32.Sqrt(posit625).PositBits.ShouldBe(new Posit32(25).PositBits); 475 | 476 | var positSmallerThanOne1 = new Posit32(0.5F); 477 | Debug.WriteLine(((float)Posit32.Sqrt(positSmallerThanOne1)).ToString("0.0000000000", CultureInfo.InvariantCulture)); 478 | Posit32.Sqrt(positSmallerThanOne1).PositBits.ShouldBe(new Posit32(0b_00111011_01010000_01001111_00110011, fromBitMask: true).PositBits); 479 | 480 | var positBig = new Posit32(1_004_004); 481 | Posit32.Sqrt(positBig).PositBits.ShouldBe(new Posit32(1_002).PositBits); 482 | } 483 | 484 | [Fact] 485 | public void Posit32ToStringIsCorrect() 486 | { 487 | var posit1 = new Posit32(0.75F); 488 | var posit2 = new Posit32(-200_000); 489 | var posit3 = new Posit32(125.12545F); 490 | var posit4 = new Posit32(0.999); 491 | 492 | posit1.ToString(CultureInfo.InvariantCulture).ShouldBe("0.75"); 493 | posit2.ToString(CultureInfo.InvariantCulture).ShouldBe("-200000"); 494 | posit3.ToString("0.############", CultureInfo.InvariantCulture).ShouldBe("125.125450134277"); 495 | posit4.ToString("0.###############", CultureInfo.InvariantCulture).ShouldBe("0.998999997973442"); 496 | } 497 | } 498 | -------------------------------------------------------------------------------- /Lombiq.Arithmetics.Tests/UnumTests/UnumTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | using Assert = Lombiq.Arithmetics.Tests.CompatibilityAssert; 4 | 5 | namespace Lombiq.Arithmetics.Tests; 6 | 7 | public class UnumTests 8 | { 9 | private readonly UnumEnvironment _warlpiriEnvironment; 10 | private readonly UnumEnvironment _environment_2_2; 11 | private readonly UnumEnvironment _environment_2_3; 12 | private readonly UnumEnvironment _environment_2_4; 13 | private readonly UnumEnvironment _environment_3_2; 14 | private readonly UnumEnvironment _environment_3_4; 15 | private readonly UnumEnvironment _environment_3_5; 16 | private readonly UnumEnvironment _environment_4_3; 17 | private readonly UnumEnvironment _environment_4_8; 18 | 19 | public UnumTests() 20 | { 21 | _warlpiriEnvironment = UnumEnvironment.FromStandardEnvironment(StandardEnvironment.Warlpiri); 22 | _environment_2_2 = new UnumEnvironment(2, 2); 23 | _environment_2_3 = new UnumEnvironment(2, 3); 24 | _environment_2_4 = new UnumEnvironment(2, 4); 25 | _environment_3_2 = new UnumEnvironment(3, 2); 26 | _environment_3_4 = new UnumEnvironment(3, 4); 27 | _environment_3_5 = new UnumEnvironment(3, 5); 28 | _environment_4_3 = new UnumEnvironment(4, 3); 29 | _environment_4_8 = new UnumEnvironment(4, 8); 30 | } 31 | 32 | [Fact] 33 | public void WarlpiriUnumValuesAndCalculationsAreCorrect() 34 | { 35 | var unumNegative2 = new Unum(_warlpiriEnvironment, -2); 36 | Assert.AreEqual(-2, (int)unumNegative2); 37 | 38 | var unumNegative1 = new Unum(_warlpiriEnvironment, -1); 39 | Assert.AreEqual(-1, (int)unumNegative1); 40 | 41 | var unumNegative0 = new Unum(_warlpiriEnvironment, new BitMask(new uint[] { 8 }, _warlpiriEnvironment.Size)); 42 | Assert.AreEqual(0, (int)unumNegative0); 43 | 44 | var unum0 = new Unum(_warlpiriEnvironment, 0); 45 | Assert.AreEqual(0, (int)unum0); 46 | 47 | var unum1 = new Unum(_warlpiriEnvironment, 1); 48 | Assert.AreEqual(1, (int)unum1); 49 | 50 | var unum2 = new Unum(_warlpiriEnvironment, 2); 51 | Assert.AreEqual(2, (int)unum2); 52 | 53 | Assert.AreEqual(unumNegative0, unumNegative2 + unum2); 54 | 55 | Assert.AreEqual(unumNegative0, unumNegative1 + unum1); 56 | 57 | Assert.AreEqual(unum0, unum2 - unum2); 58 | 59 | Assert.AreEqual(unum0, unum1 - unum1); 60 | 61 | Assert.AreEqual(unum2, unum1 + unum1); 62 | 63 | Assert.AreEqual(unumNegative2, unumNegative1 + unumNegative1); 64 | 65 | Assert.AreEqual(unumNegative1, unumNegative2 + unum1); 66 | 67 | Assert.AreEqual(unum1, unum2 + unumNegative1); 68 | 69 | Assert.AreEqual(unumNegative0, unumNegative1 - unumNegative1); 70 | 71 | Assert.AreEqual(unumNegative0, unumNegative2 - unumNegative2); 72 | 73 | Assert.AreEqual(unum1, unumNegative1 - unumNegative2); 74 | 75 | Assert.AreEqual(unum1, unum0 - unumNegative1); 76 | 77 | Assert.AreEqual(unum1, unumNegative0 - unumNegative1); 78 | 79 | Assert.AreEqual(unum2, unum0 - unumNegative2); 80 | 81 | Assert.AreEqual(unum2, unumNegative0 - unumNegative2); 82 | } 83 | 84 | [Fact] 85 | public void UnumIsCorrectlyConstructedFromUintArray() 86 | { 87 | var unum0 = new Unum(_environment_4_8, new uint[] { 0 }); 88 | Assert.AreEqual(unum0.IsZero(), expected: true); 89 | 90 | var unumMinus1 = new Unum(_environment_4_8, new uint[] { 1 }, negative: true); 91 | var bitMaskMinus1 = new BitMask(new uint[] { 0x2000, 0, 0, 0, 0, 0, 0, 0, 0x20000000 }, _environment_4_8.Size); 92 | Assert.AreEqual(unumMinus1.UnumBits, bitMaskMinus1); 93 | 94 | var unum10 = new Unum(_environment_2_2, new uint[] { 10 }); 95 | var bitMask10 = new BitMask(new uint[] { 0x329 }, _environment_2_2.Size); 96 | Assert.AreEqual(unum10.UnumBits, bitMask10); 97 | 98 | var unum500000 = new Unum(_environment_4_8, new uint[] { 500000 }); // 0xC7A1250C9 99 | var bitMask500000 = new BitMask(new[] { 0xC7A1250C }, _environment_4_8.Size); 100 | Assert.AreEqual(unum500000.UnumBits, bitMask500000); 101 | 102 | var unumBig = new Unum(_environment_4_8, new uint[] { 594_967_295 }); 103 | var bitMaskBig = new BitMask(new uint[] { 0xCF5FE51C, 0xF06E }, _environment_4_8.Size); 104 | Assert.AreEqual(unumBig.UnumBits, bitMaskBig); 105 | 106 | var minValue = new uint[8]; 107 | for (var i = 0; i < 8; i++) minValue[i] = uint.MaxValue; 108 | minValue[7] >>= 1; 109 | var unumMin = new Unum(_environment_4_8, minValue, negative: true); // This is negative. 110 | var bitMaskMinValue = new BitMask( 111 | new uint[] 112 | { 113 | 0xFFFFE8FD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 114 | 0xFFFFFFFF, 0xFFFFFFFF, 0x200FEFFF, 115 | }, 116 | _environment_4_8.Size); 117 | Assert.AreEqual(unumMin.UnumBits, bitMaskMinValue); 118 | 119 | var maxValue = new uint[8]; 120 | for (int i = 0; i < 8; i++) maxValue[i] = uint.MaxValue; 121 | maxValue[7] >>= 1; 122 | 123 | var bitMaskMaxValue = new BitMask( 124 | new uint[] 125 | { 126 | 0xFFFFE8FD, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 127 | 0xFFFFFFFF, 0xFFFFFFFF, 0xFEFFF, 128 | }, 129 | _environment_4_8.Size); 130 | var unumMax = new Unum(_environment_4_8, maxValue); 131 | 132 | Assert.AreEqual(unumMax.IsPositive(), expected: true); 133 | Assert.AreEqual(unumMax.Size, _environment_4_8.Size); 134 | Assert.AreEqual(unumMax.FractionSizeWithHiddenBit(), 255); 135 | Assert.AreEqual(unumMax.ExponentValueWithBias(), 254); 136 | Assert.AreEqual(unumMax.FractionWithHiddenBit(), new BitMask(maxValue, _environment_4_8.Size)); 137 | Assert.AreEqual(unumMax.UnumBits, bitMaskMaxValue); 138 | 139 | var tooBigUnum_warlpiri = new Unum(_warlpiriEnvironment, 3); 140 | var tooBigBitMask_warlpiri = _warlpiriEnvironment.LargestPositive | _warlpiriEnvironment.UncertaintyBitMask; 141 | Assert.AreEqual(tooBigUnum_warlpiri.UnumBits, tooBigBitMask_warlpiri); 142 | 143 | var tooBigNegativeUnum_warlpiri = new Unum(_warlpiriEnvironment, -3); 144 | var tooBigNegativeBitMask_warlpiri = _warlpiriEnvironment.LargestNegative | _warlpiriEnvironment.UncertaintyBitMask; 145 | Assert.AreEqual(tooBigNegativeUnum_warlpiri.UnumBits, tooBigNegativeBitMask_warlpiri); 146 | 147 | var maxValue_2_2 = new Unum(_environment_2_2, 480); 148 | var maxBitMask_2_2 = new BitMask(new uint[] { 0xFEE }, _environment_2_2.Size); 149 | Assert.AreEqual(maxValue_2_2.UnumBits, maxBitMask_2_2); 150 | 151 | var minValue_2_2 = new Unum(_environment_2_2, -480); 152 | var bitMaskMinValue_2_2 = new BitMask(new uint[] { 0x2FEE }, _environment_2_2.Size); 153 | Assert.AreEqual(minValue_2_2.UnumBits, bitMaskMinValue_2_2); 154 | 155 | var tooBigUnum_2_2 = new Unum(_environment_2_2, 481); 156 | var tooBigBitMask_2_2 = _environment_2_2.LargestPositive | _environment_2_2.UncertaintyBitMask; 157 | Assert.AreEqual(tooBigUnum_2_2.UnumBits, tooBigBitMask_2_2); 158 | 159 | var tooBigNegativeUnum_2_2 = new Unum(_environment_2_2, -481); 160 | var tooBigNegativeBitMask_2_2 = _environment_2_2.LargestNegative | _environment_2_2.UncertaintyBitMask; 161 | Assert.AreEqual(tooBigNegativeUnum_2_2.UnumBits, tooBigNegativeBitMask_2_2); 162 | 163 | var maxValue_2_3 = new Unum(_environment_2_3, 510); 164 | var maxBitMask_2_3 = new BitMask(new uint[] { 0x1FFDE }, _environment_2_3.Size); 165 | Assert.AreEqual(maxValue_2_3.UnumBits, maxBitMask_2_3); 166 | 167 | var minValue_2_3 = new Unum(_environment_2_3, -510); 168 | var bitMaskMinValue_2_3 = new BitMask(new uint[] { 0x5FFDE }, _environment_2_3.Size); 169 | Assert.AreEqual(minValue_2_3.UnumBits, bitMaskMinValue_2_3); 170 | 171 | var tooBigUnum_2_3 = new Unum(_environment_2_3, 511); 172 | var tooBigBitMask_2_3 = _environment_2_3.LargestPositive | _environment_2_3.UncertaintyBitMask; 173 | Assert.AreEqual(tooBigUnum_2_3.UnumBits, tooBigBitMask_2_3); 174 | 175 | var tooBigNegativeUnum_2_3 = new Unum(_environment_2_3, -511); 176 | var tooBigNegativeBitMask_2_3 = _environment_2_3.LargestNegative | _environment_2_3.UncertaintyBitMask; 177 | Assert.AreEqual(tooBigNegativeUnum_2_3.UnumBits, tooBigNegativeBitMask_2_3); 178 | 179 | // Testing in an environment where the biggest representable value isn't an integer. 180 | var maxValue_2_4 = new Unum(_environment_2_4, 511); 181 | var maxBitMask_2_4 = new BitMask(new uint[] { 0x7FFB7 }, _environment_2_4.Size); 182 | Assert.AreEqual(maxValue_2_4.UnumBits, maxBitMask_2_4); 183 | 184 | var minValue_2_4 = new Unum(_environment_2_4, -511); 185 | var bitMaskMinValue_2_4 = new BitMask(new uint[] { 0x807FFB7 }, _environment_2_4.Size); 186 | Assert.AreEqual(minValue_2_4.UnumBits, bitMaskMinValue_2_4); 187 | 188 | var tooBigUnum_2_4 = new Unum(_environment_2_4, 512); 189 | var tooBigBitMask_2_4 = _environment_2_4.LargestPositive | _environment_2_4.UncertaintyBitMask; 190 | Assert.AreEqual(tooBigUnum_2_4.UnumBits, tooBigBitMask_2_4); 191 | 192 | var tooBigNegativeUnum_2_4 = new Unum(_environment_2_4, -512); 193 | var tooBigNegativeBitMask_2_4 = _environment_2_4.LargestNegative | _environment_2_4.UncertaintyBitMask; 194 | Assert.AreEqual(tooBigNegativeUnum_2_4.UnumBits, tooBigNegativeBitMask_2_4); 195 | } 196 | 197 | [Fact] 198 | public void FractionToUintArrayIsCorrect() 199 | { 200 | var unumZero = new Unum(_environment_4_8, new uint[] { 0 }); 201 | Assert.AreEqual(unumZero.FractionToUintArray(), new uint[] { 0, 0, 0, 0, 0, 0, 0, 0, 0 }); 202 | 203 | var unum1 = new Unum(_environment_4_8, new uint[] { 1 }); 204 | Assert.AreEqual(unum1.FractionToUintArray(), new uint[] { 1, 0, 0, 0, 0, 0, 0, 0, 0 }); 205 | 206 | var unum500000 = new Unum(_environment_4_8, new uint[] { 500000 }); 207 | Assert.AreEqual(unum500000.FractionToUintArray(), new uint[] { 500000, 0, 0, 0, 0, 0, 0, 0, 0 }); 208 | 209 | var unumBig = new Unum(_environment_4_8, new uint[] { 594_967_295 }); 210 | Assert.AreEqual(unumBig.FractionToUintArray(), new uint[] { 594_967_295, 0, 0, 0, 0, 0, 0, 0, 0 }); 211 | 212 | var maxValue = new uint[8]; 213 | for (var i = 0; i < 8; i++) 214 | { 215 | maxValue[i] = uint.MaxValue; 216 | } 217 | 218 | maxValue[7] >>= 1; 219 | var unumMax = new Unum(_environment_4_8, maxValue); 220 | Assert.AreEqual( 221 | unumMax.FractionToUintArray(), 222 | new uint[] { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, 0 }); 223 | } 224 | 225 | [Fact] 226 | public void UnumIsCorrectlyConstructedFromInt() 227 | { 228 | var unum0 = new Unum(_environment_3_4, 0); 229 | var bitMask0 = new BitMask(new uint[] { 0 }, _environment_3_4.Size); 230 | Assert.AreEqual(unum0.UnumBits, bitMask0); 231 | 232 | var unum1 = new Unum(_environment_3_4, 1); 233 | var bitMask1 = new BitMask(new uint[] { 0x100 }, _environment_3_4.Size); 234 | Assert.AreEqual(unum1.UnumBits, bitMask1); 235 | 236 | var unum30 = new Unum(_environment_3_4, 30); 237 | var bitMask30 = new BitMask(new uint[] { 0x3F22 }, _environment_3_4.Size); 238 | Assert.AreEqual(unum30.UnumBits, bitMask30); 239 | 240 | var unum1000 = new Unum(_environment_3_4, 1000); 241 | var bitMask1000 = new BitMask(new uint[] { 0x63D45 }, _environment_3_4.Size); 242 | Assert.AreEqual(unum1000.UnumBits, bitMask1000); 243 | 244 | var unum5000 = new Unum(_environment_3_4, 5000); 245 | var bitMask5000 = new BitMask(new uint[] { 0x367148 }, _environment_3_4.Size); 246 | Assert.AreEqual(unum5000.UnumBits, bitMask5000); 247 | 248 | var unum6000 = new Unum(_environment_3_4, 6000); 249 | var bitMask6000 = new BitMask(new uint[] { 0x1B7747 }, _environment_3_4.Size); 250 | Assert.AreEqual(unum6000.UnumBits, bitMask6000); 251 | 252 | var unumNegative30 = new Unum(_environment_3_4, -30); 253 | var bitMaskMinus30 = new BitMask(new uint[] { 0x3F22, 1 }, _environment_3_4.Size); 254 | Assert.AreEqual(unumNegative30.UnumBits, bitMaskMinus30); 255 | 256 | var unumNegative1000 = new Unum(_environment_3_4, -1000); 257 | var bitMaskMinus1000 = new BitMask(new uint[] { 0x63D45, 1 }, _environment_3_4.Size); 258 | Assert.AreEqual(unumNegative1000.UnumBits, bitMaskMinus1000); 259 | } 260 | 261 | [Fact] 262 | public void UnumIsCorrectlyConstructedFromUInt() 263 | { 264 | var unum0 = new Unum(_environment_3_4, 0U); 265 | var bitMask0 = new BitMask(new uint[] { 0 }, _environment_3_4.Size); 266 | Assert.AreEqual(bitMask0, unum0.UnumBits); 267 | 268 | var unum1 = new Unum(_environment_3_4, 1U); 269 | var bitMask1 = new BitMask(new uint[] { 0x100 }, _environment_3_4.Size); 270 | Assert.AreEqual(bitMask1, unum1.UnumBits); 271 | 272 | var unum2 = new Unum(_environment_3_4, 2U); 273 | var bitMask2 = new BitMask(new uint[] { 0x200 }, _environment_3_4.Size); 274 | Assert.AreEqual(bitMask2, unum2.UnumBits); 275 | 276 | var unum4 = new Unum(_environment_3_4, 4U); 277 | var bitMask4 = new BitMask(new uint[] { 0x610 }, _environment_3_4.Size); 278 | Assert.AreEqual(bitMask4, unum4.UnumBits); 279 | 280 | var unum8 = new Unum(_environment_3_4, 8U); 281 | var bitMask8 = new BitMask(new uint[] { 0xC20 }, _environment_3_4.Size); 282 | Assert.AreEqual(bitMask8, unum8.UnumBits); 283 | 284 | var unum10 = new Unum(_environment_2_2, 10U); 285 | var bitMask10 = new BitMask(new uint[] { 0x329 }, _environment_2_2.Size); 286 | Assert.AreEqual(bitMask10, unum10.UnumBits); 287 | 288 | var unum13 = new Unum(_environment_3_4, 13U); 289 | var bitMask13 = new BitMask(new uint[] { 0x3522 }, _environment_3_4.Size); 290 | Assert.AreEqual(bitMask13, unum13.UnumBits); 291 | 292 | var unum30 = new Unum(_environment_3_4, 30U); 293 | var bitMask30 = new BitMask(new uint[] { 0x3F22 }, _environment_3_4.Size); 294 | Assert.AreEqual(bitMask30, unum30.UnumBits); 295 | 296 | var unum1000 = new Unum(_environment_3_4, 1000U); 297 | var bitMask1000 = new BitMask(new uint[] { 0x63D45 }, _environment_3_4.Size); 298 | Assert.AreEqual(bitMask1000, unum1000.UnumBits); 299 | 300 | var unum5000 = new Unum(_environment_3_4, 5000U); 301 | var bitMask5000 = new BitMask(new uint[] { 0x367148 }, _environment_3_4.Size); 302 | Assert.AreEqual(bitMask5000, unum5000.UnumBits); 303 | 304 | var unum6000 = new Unum(_environment_3_4, 6000U); 305 | var bitMask6000 = new BitMask(new uint[] { 0x1B7747 }, _environment_3_4.Size); 306 | Assert.AreEqual(bitMask6000, unum6000.UnumBits); 307 | } 308 | 309 | [Fact] 310 | public void IsExactIsCorrect() 311 | { 312 | // 0 0000 0000 0000 1 000 00 313 | var bitMask_3_2_uncertain = new BitMask(new uint[] { 0x20 }, 19); 314 | var unum_3_2_uncertain = new Unum(_environment_3_2, bitMask_3_2_uncertain); 315 | Assert.AreEqual(actual: false, unum_3_2_uncertain.IsExact()); 316 | 317 | var bitMask_3_2_certain = new BitMask(19, allOne: false); 318 | var unum_3_2_certain = new Unum(_environment_3_2, bitMask_3_2_certain); 319 | Assert.AreEqual(actual: true, unum_3_2_certain.IsExact()); 320 | 321 | var bitMask_3_4_uncertain = new BitMask(new uint[] { 0x80, 0 }, 33); 322 | var unum_3_4_uncertain = new Unum(_environment_3_4, bitMask_3_4_uncertain); 323 | Assert.AreEqual(actual: false, unum_3_4_uncertain.IsExact()); 324 | 325 | var bitMask_3_4_certain = new BitMask(33, allOne: false); 326 | var unum_3_4_certain = new Unum(_environment_3_4, bitMask_3_4_certain); 327 | Assert.AreEqual(actual: true, unum_3_4_certain.IsExact()); 328 | } 329 | 330 | [Fact] 331 | public void FractionSizeIsCorrect() 332 | { 333 | var bitMask_3_2_allOne = new BitMask(19, allOne: true); 334 | var unum_3_2_allOne = new Unum(_environment_3_2, bitMask_3_2_allOne); 335 | Assert.AreEqual(4, unum_3_2_allOne.FractionSize()); 336 | 337 | var bitMask_3_4_allOne = new BitMask(33, allOne: true); 338 | var unum_3_4_allOne = new Unum(_environment_3_4, bitMask_3_4_allOne); 339 | Assert.AreEqual(16, unum_3_4_allOne.FractionSize()); 340 | } 341 | 342 | [Fact] 343 | public void ExponentSizeIsCorrect() 344 | { 345 | var bitMask_3_2_allOne = new BitMask(19, allOne: true); 346 | var unum_3_2_allOne = new Unum(_environment_3_2, bitMask_3_2_allOne); 347 | Assert.AreEqual(8, unum_3_2_allOne.ExponentSize()); 348 | 349 | var bitMask_3_4_allOne = new BitMask(33, allOne: true); 350 | var unum_3_4_allOne = new Unum(_environment_3_4, bitMask_3_4_allOne); 351 | Assert.AreEqual(8, unum_3_4_allOne.ExponentSize()); 352 | 353 | var bitMask_4_3_allOne = new BitMask(33, allOne: true); 354 | var unum_4_3_allOne = new Unum(_environment_4_3, bitMask_4_3_allOne); 355 | Assert.AreEqual(16, unum_4_3_allOne.ExponentSize()); 356 | } 357 | 358 | [Fact] 359 | public void FractionMaskIsCorrect() 360 | { 361 | // 0 0000 0000 1111 0000 00 362 | var bitMask_3_2_allOne = new BitMask(19, allOne: true); 363 | var unum_3_2_allOne = new Unum(_environment_3_2, bitMask_3_2_allOne); 364 | var bitMask_3_2_FractionMask = new BitMask(new uint[] { 0x3C0 }, 19); 365 | Assert.AreEqual(bitMask_3_2_FractionMask, unum_3_2_allOne.FractionMask()); 366 | 367 | // 0 0000 0000 1111 1111 1111 1111 0000 0000 368 | var bitMask_3_4_allOne = new BitMask(33, allOne: true); 369 | var unum_3_4_allOne = new Unum(_environment_3_4, bitMask_3_4_allOne); 370 | var bitMask_3_4_FractionMask = new BitMask(new uint[] { 0xFFFF00 }, 33); 371 | Assert.AreEqual(bitMask_3_4_FractionMask, unum_3_4_allOne.FractionMask()); 372 | } 373 | 374 | [Fact] 375 | public void ExponentMaskIsCorrect() 376 | { 377 | // 0 1111 1111 0000 0 000 00 378 | var bitMask_3_2_allOne = new BitMask(19, allOne: true); 379 | var unum_3_2_allOne = new Unum(_environment_3_2, bitMask_3_2_allOne); 380 | var bitMask_3_2_ExponentMask = new BitMask(new uint[] { 0x3FC00 }, 19); 381 | Assert.AreEqual(bitMask_3_2_ExponentMask, unum_3_2_allOne.ExponentMask()); 382 | 383 | // 0 1111 1111 0000 0000 0000 0000 0 000 0000 384 | var bitMask_3_4_allOne = new BitMask(33, allOne: true); 385 | var unum_3_4_allOne = new Unum(_environment_3_4, bitMask_3_4_allOne); 386 | var bitMask_3_4_ExponentMask = new BitMask(new[] { 0xFF000000 }, 33); 387 | Assert.AreEqual(bitMask_3_4_ExponentMask, unum_3_4_allOne.ExponentMask()); 388 | } 389 | 390 | [Fact] 391 | public void ExponentValueWithBiasIsCorrect() 392 | { 393 | var bitMask1 = new BitMask(new uint[] { 0xE40 }, 33); 394 | var unum1 = new Unum(_environment_3_4, bitMask1); 395 | Assert.AreEqual(unum1.ExponentValueWithBias(), -8); 396 | 397 | var unumZero = new Unum(_environment_3_4, 0); 398 | Assert.AreEqual(unumZero.ExponentValueWithBias(), 1); 399 | } 400 | 401 | [Fact] 402 | public void FractionWithHiddenBitIsCorrect() 403 | { 404 | var bitMask1 = new BitMask(new uint[] { 0xE40 }, 33); 405 | var unum1 = new Unum(_environment_3_4, bitMask1); 406 | Assert.AreEqual(new BitMask(new uint[] { 2 }, 33), unum1.FractionWithHiddenBit()); 407 | 408 | var bitMask2 = new BitMask(new uint[] { 0x3F22 }, 33); 409 | var unum2 = new Unum(_environment_3_4, bitMask2); 410 | Assert.AreEqual(new BitMask(new uint[] { 0xF }, 33), unum2.FractionWithHiddenBit()); 411 | 412 | var bitMask3 = new BitMask(new uint[] { 0x7E012B }, 33); 413 | var unum3 = new Unum(_environment_3_4, bitMask3); 414 | Assert.AreEqual(new BitMask(new uint[] { 0x1E01 }, 33), unum3.FractionWithHiddenBit()); 415 | } 416 | 417 | [Fact] 418 | public void AddExactUnumsIsCorrect() 419 | { 420 | // First example from The End of Error p. 117. 421 | var bitMask1 = new BitMask(new uint[] { 0xE40 }, 33); 422 | var bitMask2 = new BitMask(new uint[] { 0x3F22 }, 33); 423 | var unumFirst = new Unum(_environment_3_4, bitMask1); 424 | var unumSecond = new Unum(_environment_3_4, bitMask2); 425 | var bitMaskSum = new BitMask(new uint[] { 0x7E012B }, 33); 426 | var unumSum1 = Unum.AddExactUnums(unumFirst, unumSecond); 427 | Assert.AreEqual(unumSum1.UnumBits, bitMaskSum); 428 | 429 | // Addition should be commutative. 430 | var unumSum2 = Unum.AddExactUnums(unumSecond, unumFirst); 431 | Assert.AreEqual(unumSum1.UnumBits, unumSum2.UnumBits); 432 | 433 | var unum0 = new Unum(_environment_3_4, 0); 434 | var unum1 = new Unum(_environment_3_4, 1); 435 | var unum0Plus1 = unum0 + unum1; 436 | var unum31 = new Unum(_environment_3_4, 30) + unum1; 437 | var unum0PlusUnum1 = Unum.AddExactUnums(unum0, unum1); 438 | Assert.AreEqual(unum31.UnumBits, new Unum(_environment_3_4, 31).UnumBits); 439 | Assert.AreEqual(unum1.UnumBits, unum0Plus1.UnumBits); 440 | Assert.AreEqual(unum1.UnumBits, unum0PlusUnum1.UnumBits); 441 | 442 | // Case of inexact result, second example from The End or Error, p. 117. 443 | var bitMask4 = new BitMask(new uint[] { 0x18F400CF }, 33); // 1000.0078125 444 | var unum1000 = new Unum(_environment_3_4, 1000); 445 | var unum6 = Unum.AddExactUnums(unum1000, unumFirst); // 1/256 446 | Assert.AreEqual(unum6.UnumBits, bitMask4); 447 | 448 | var unum5000 = new Unum(_environment_3_4, 5000); 449 | var unum6000 = new Unum(_environment_3_4, 6000); 450 | Assert.AreEqual(Unum.AddExactUnums(unum5000, unum1000).UnumBits, unum6000.UnumBits); 451 | 452 | var unumNegativeThirty = new Unum(_environment_3_4, -30); 453 | var unum30 = new Unum(_environment_3_4, 30); 454 | Assert.AreEqual(Unum.AddExactUnums(unum30, unumNegativeThirty).UnumBits, unum0.UnumBits); 455 | } 456 | 457 | [Fact] 458 | public void AdditionIsCorrectForIntegers() 459 | { 460 | var result = new Unum(_environment_3_5, 0); 461 | var count = 100; 462 | 463 | for (int i = 1; i <= count; i++) result += new Unum(_environment_3_5, i * 1000); 464 | for (int i = 1; i <= count; i++) result -= new Unum(_environment_3_5, i * 1000); 465 | 466 | Assert.AreEqual(result.UnumBits, new Unum(_environment_3_5, 0).UnumBits); 467 | } 468 | 469 | [Fact] 470 | public void SubtractExactUnumsIsCorrect() 471 | { 472 | var bitMask1 = new BitMask(new uint[] { 0x7E012B }, 33); // 30.00390625 473 | var bitMask2 = new BitMask(new uint[] { 0xE40 }, 33); // 0.00390625 474 | var bitMask3 = new BitMask(new uint[] { 0x3F22 }, 33); // 30 475 | 476 | var unum1 = new Unum(_environment_3_4, bitMask1); 477 | var unum2 = new Unum(_environment_3_4, bitMask2); 478 | var unum3 = Unum.SubtractExactUnums(unum1, unum2); 479 | Assert.AreEqual(unum3.UnumBits, bitMask3); 480 | 481 | var unum5000 = new Unum(_environment_3_4, 5000); 482 | var unum6000 = new Unum(_environment_3_4, 6000); 483 | var unum1000 = new Unum(_environment_3_4, 1000); 484 | 485 | var unumRes = Unum.SubtractExactUnums(unum6000, unum5000); 486 | Assert.AreEqual(unumRes.UnumBits, unum1000.UnumBits); 487 | 488 | var unum30 = new Unum(_environment_3_4, 30); 489 | var unumZero = new Unum(_environment_3_4, 0); 490 | Assert.AreEqual(Unum.SubtractExactUnums(unum30, unum30).UnumBits, unumZero.UnumBits); 491 | } 492 | 493 | [Fact] 494 | public void IntToUnumIsCorrect() 495 | { 496 | var unum0 = new Unum(_environment_3_4, 0); 497 | var bitMask0 = new BitMask(new uint[] { 0 }, 33); 498 | Assert.AreEqual(unum0.UnumBits, bitMask0); 499 | 500 | var unum1 = new Unum(_environment_3_4, 1); 501 | Assert.AreEqual(unum1.UnumBits, new BitMask(new uint[] { 0x100 }, 33)); 502 | 503 | var unum2 = new Unum(_environment_3_4, 2); 504 | Assert.AreEqual(unum2.UnumBits, new BitMask(new uint[] { 0x200 }, 33)); 505 | 506 | var unum30 = new Unum(_environment_3_4, 30); 507 | var bitMask30 = new BitMask(new uint[] { 0x3F22 }, 33); 508 | Assert.AreEqual(unum30.UnumBits, bitMask30); 509 | 510 | var unum1000 = new Unum(_environment_3_4, 1000); 511 | var bitMask1000 = new BitMask(new uint[] { 0x63D45 }, 33); 512 | Assert.AreEqual(unum1000.UnumBits, bitMask1000); 513 | 514 | var unum5000 = new Unum(_environment_3_4, 5000); 515 | var bitMask5000 = new BitMask(new uint[] { 0x367148 }, 33); 516 | Assert.AreEqual(unum5000.UnumBits, bitMask5000); 517 | 518 | var bitMask6000 = new BitMask(new uint[] { 0x1B7747 }, 33); 519 | var unum6000 = new Unum(_environment_3_4, 6000); 520 | Assert.AreEqual(unum6000.UnumBits, bitMask6000); 521 | 522 | var unumNegative30 = new Unum(_environment_3_4, -30); 523 | var bitMaskNegative30 = new BitMask(new uint[] { 0x3F22, 1 }, 33); 524 | Assert.AreEqual(unumNegative30.UnumBits, bitMaskNegative30); 525 | 526 | var unumNegative1000 = new Unum(_environment_3_4, -1000); 527 | var bitMaskNegative1000 = new BitMask(new uint[] { 0x63D45, 1 }, 33); 528 | Assert.AreEqual(unumNegative1000.UnumBits, bitMaskNegative1000); 529 | } 530 | 531 | [Fact] 532 | public void UnumToUintIsCorrect() 533 | { 534 | var unum1 = new Unum(_environment_3_4, 1); 535 | var number1 = (uint)unum1; 536 | Assert.AreEqual(number1, 1); 537 | 538 | var unum2 = new Unum(_environment_3_4, 2); 539 | var number2 = (uint)unum2; 540 | Assert.AreEqual(number2, 2); 541 | 542 | var unum30 = new Unum(_environment_3_4, 30); 543 | var number30 = (uint)unum30; 544 | Assert.AreEqual(number30, 30); 545 | 546 | var unum1000 = new Unum(_environment_3_4, 1000); 547 | var number1000 = (uint)unum1000; 548 | Assert.AreEqual(number1000, 1000); 549 | 550 | var unum5000 = new Unum(_environment_3_4, 5000); 551 | var number5000 = (uint)unum5000; 552 | Assert.AreEqual(number5000, 5000); 553 | 554 | var unum6000 = new Unum(_environment_3_4, 6000); 555 | var number6000 = (uint)unum6000; 556 | Assert.AreEqual(number6000, 6000); 557 | } 558 | 559 | [Fact] 560 | public void UnumToIntIsCorrect() 561 | { 562 | var unum1 = new Unum(_environment_3_4, 1); 563 | var one = (int)unum1; 564 | Assert.AreEqual(one, 1); 565 | 566 | var unum30 = new Unum(_environment_3_4, 30); 567 | var number30 = (int)unum30; 568 | Assert.AreEqual(number30, 30); 569 | 570 | var unum1000 = new Unum(_environment_3_4, 1000); 571 | var number1000 = (int)unum1000; 572 | Assert.AreEqual(number1000, 1000); 573 | 574 | var unumNegative30 = new Unum(_environment_3_4, -30); 575 | var numberNegative30 = (int)unumNegative30; 576 | Assert.AreEqual(numberNegative30, -30); 577 | 578 | var unumNegative1000 = new Unum(_environment_3_4, -1000); 579 | var numberNegative1000 = (int)unumNegative1000; 580 | Assert.AreEqual(numberNegative1000, -1000); 581 | } 582 | 583 | [Fact] 584 | public void UnumToFloatIsCorrect() 585 | { 586 | var unum30 = new Unum(_environment_3_4, 30); 587 | var numbe30 = (float)unum30; 588 | Assert.AreEqual(numbe30, 30F); 589 | 590 | var unum1000 = new Unum(_environment_3_4, 1000); 591 | var number1000 = (float)unum1000; 592 | Assert.AreEqual(number1000, 1000); 593 | 594 | var unumNegative30 = new Unum(_environment_3_4, -30); 595 | var numberNegative30 = (float)unumNegative30; 596 | Assert.AreEqual(numberNegative30, -30); 597 | 598 | var unumNegative1000 = new Unum(_environment_3_4, -1000); 599 | var numberNegative1000 = (float)unumNegative1000; 600 | Assert.AreEqual(numberNegative1000, -1000); 601 | } 602 | } 603 | -------------------------------------------------------------------------------- /Unum/Unum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace Lombiq.Arithmetics; 5 | 6 | public readonly struct Unum : IEquatable 7 | { 8 | private readonly UnumEnvironment _environment; 9 | 10 | // Signbit Exponent Fraction Ubit ExponentSize FractionSize 11 | public BitMask UnumBits { get; } 12 | 13 | #region Unum structure 14 | 15 | /// 16 | /// Gets the number of bits allocated to store the maximum number of bits in the exponent field of a unum. 17 | /// 18 | public byte ExponentSizeSize => _environment.ExponentSizeSize; // "esizesize" 19 | 20 | /// 21 | /// Gets the number of bits allocated to store the maximum number of bits in the fraction field of a unum. 22 | /// 23 | public byte FractionSizeSize => _environment.FractionSizeSize; // "fsizesize" 24 | 25 | /// 26 | /// Gets the maximum number of bits usable to store the exponent. 27 | /// 28 | public byte ExponentSizeMax => _environment.ExponentSizeMax; // "esizemax" 29 | 30 | /// 31 | /// Gets the maximum number of bits usable to store the fraction. 32 | /// 33 | public ushort FractionSizeMax => _environment.FractionSizeMax; // "fsizemax" 34 | 35 | /// 36 | /// Gets the number of bits that are used storing the utag. 37 | /// 38 | public byte UnumTagSize => _environment.UnumTagSize; // "utagsize" 39 | 40 | /// 41 | /// Gets the maximum number of bits used by the environment. 42 | /// 43 | public ushort Size => _environment.Size; // "maxubits" 44 | 45 | #endregion Unum structure 46 | 47 | #region Unum masks 48 | 49 | /// 50 | /// Gets a BitMask for picking out the UncertainityBit. 51 | /// 52 | public BitMask UncertaintyBitMask => _environment.UncertaintyBitMask; // "ubitmask" 53 | 54 | /// 55 | /// Gets a BitMask for picking out the ExponentSize. 56 | /// 57 | public BitMask ExponentSizeMask => _environment.ExponentSizeMask; // "esizemask" 58 | 59 | /// 60 | /// Gets a BitMask for picking out the FractionSize. 61 | /// 62 | public BitMask FractionSizeMask => _environment.FractionSizeMask; // "fsizemask" 63 | 64 | /// 65 | /// Gets a BitMask for picking out the ExponentSize and FractionSize. 66 | /// 67 | public BitMask ExponentAndFractionSizeMask => _environment.ExponentAndFractionSizeMask; // "efsizemask" 68 | 69 | /// 70 | /// Gets a BitMask for picking out the utag. 71 | /// 72 | public BitMask UnumTagMask => _environment.UnumTagMask; // "utagmask" 73 | 74 | /// 75 | /// Gets a BitMask for picking out the SignBit. 76 | /// 77 | public BitMask SignBitMask => _environment.SignBitMask; // "signbigu" 78 | 79 | #endregion Unum masks 80 | 81 | #region Unum environment 82 | 83 | /// 84 | /// Gets a BitMask for the Unit in the Last Place or Unit of Least Precision. 85 | /// 86 | [SuppressMessage("Minor Code Smell", "S100:Methods and properties should be named in PascalCase", Justification = "It's an acronym.")] 87 | public BitMask ULP => _environment.ULP; 88 | 89 | /// 90 | /// Gets a BitMask for the unum notation of positive infinity. 91 | /// 92 | public BitMask PositiveInfinity => _environment.PositiveInfinity; // "posinfu" 93 | 94 | /// 95 | /// Gets a BitMask for the unum notation of negative infinity. 96 | /// 97 | public BitMask NegativeInfinity => _environment.NegativeInfinity; // "neginfu" 98 | 99 | /// 100 | /// Gets a BitMask for the unum notation of a quiet NaN value. 101 | /// 102 | public BitMask QuietNotANumber => _environment.QuietNotANumber; // "qNaNu" 103 | 104 | /// 105 | /// Gets a BitMask for the unum notation of a signaling NaN value. 106 | /// 107 | public BitMask SignalingNotANumber => _environment.SignalingNotANumber; // "sNaNu" 108 | 109 | /// 110 | /// Gets a BitMask for the largest expressable finite positive unum in the environment. 111 | /// 112 | public BitMask LargestPositive => _environment.LargestPositive; // "maxrealu" 113 | 114 | /// 115 | /// Gets a BitMask for the smallest expressable positive real unum in the environment. 116 | /// 117 | public BitMask SmallestPositive => _environment.SmallestPositive; // "smallsubnormalu" 118 | 119 | /// 120 | /// Gets a BitMask for the largest expressable finite negative unum in the environment. 121 | /// 122 | public BitMask LargestNegative => _environment.LargestNegative; // "negbigu" 123 | 124 | /// 125 | /// Gets a BitMask for the largest magnitude negative unum in the environment. 126 | /// 127 | public BitMask MinRealU => _environment.MinRealU; // "minrealu" 128 | 129 | #endregion Unum environment 130 | 131 | #region Unum constructors 132 | 133 | public Unum(UnumEnvironment environment) 134 | { 135 | _environment = environment; 136 | 137 | UnumBits = new BitMask(_environment.Size); 138 | } 139 | 140 | public Unum(UnumEnvironment environment, BitMask bits) 141 | { 142 | _environment = environment; 143 | 144 | // To be sure that UnumBits has the same size as the environment. Excess bits will be truncated. 145 | UnumBits = BitMask.FromImmutableArray(bits.Segments, _environment.Size); 146 | } 147 | 148 | /// 149 | /// Initializes a new instance of the struct. Creates a Unum of the given environment initialized 150 | /// with the value of the uint. 151 | /// 152 | /// The Unum environment. 153 | /// The uint value to initialize the new Unum with. 154 | public Unum(UnumEnvironment environment, uint value) 155 | { 156 | _environment = environment; 157 | // Creating an array of the size needed to call the other constructor. This is necessary because in Hastlayer 158 | // only arrays with dimensions defined at compile-time are supported. 159 | var valueArray = new uint[environment.EmptyBitMask.SegmentCount]; 160 | valueArray[0] = value; 161 | 162 | UnumBits = new Unum(environment, valueArray).UnumBits; 163 | } 164 | 165 | /// 166 | /// Initializes a new instance of the struct. Creates a Unum initialized with a value that is 167 | /// defined by the bits in a uint array. 168 | /// 169 | /// The Unum environment. 170 | /// 171 | /// The uint array which defines the Unum's value as an integer. To use with Hastlayer this should be the same size 172 | /// as the BitMasks in the given environment. 173 | /// 174 | /// Defines whether the number is positive or not. 175 | public Unum(UnumEnvironment environment, uint[] value, bool negative = false) 176 | { 177 | _environment = environment; 178 | 179 | UnumBits = new BitMask(value, environment.Size); 180 | if (UnumBits == _environment.EmptyBitMask) return; 181 | 182 | // Handling the case when the number wouldn't fit in the range of the environment. 183 | if (UnumHelper.LargestExpressablePositiveInteger(environment) != environment.EmptyBitMask && 184 | UnumBits > UnumHelper.LargestExpressablePositiveInteger(environment)) 185 | { 186 | UnumBits = negative ? environment.LargestNegative | environment.UncertaintyBitMask 187 | : environment.LargestPositive | environment.UncertaintyBitMask; 188 | return; 189 | } 190 | 191 | var uncertainityBit = false; 192 | 193 | // Putting the actual value in a BitMask. 194 | var exponent = new BitMask(value, Size); 195 | 196 | // The value of the exponent is one less than the number of binary digits in the integer. 197 | var exponentValue = new BitMask((uint)(exponent.FindMostSignificantOnePosition() - 1), Size); 198 | 199 | // Calculating the number of bits needed to represent the value of the exponent. 200 | var exponentSize = exponentValue.FindMostSignificantOnePosition(); 201 | 202 | // If the value of the exponent is not a power of 2, then one more bit is needed to represent the biased value. 203 | if ((exponentValue.Lowest32Bits & (exponentValue.Lowest32Bits - 1)) > 0) exponentSize++; 204 | 205 | // Handling input numbers that don't fit in the range of the given environment. 206 | if (exponentSize > ExponentSizeMax) 207 | { 208 | UnumBits = negative ? (environment.LargestNegative | environment.UncertaintyBitMask) - 1 209 | : (environment.LargestPositive | environment.UncertaintyBitMask) - 1; 210 | return; 211 | } 212 | 213 | // Calculating the bias from the number of bits representing the exponent. 214 | var bias = exponentSize == 0 ? 0 : (1 << (exponentSize - 1)) - 1; 215 | 216 | // Applying the bias to the exponent. 217 | exponent = exponentValue + (uint)bias; 218 | 219 | // Putting the actual value in a BitMask. 220 | var fraction = new BitMask(value, Size); 221 | 222 | // Shifting out the zeros after the least significant 1-bit. 223 | fraction = fraction.ShiftOutLeastSignificantZeros(); 224 | 225 | // Calculating the number of bits needed to represent the fraction. 226 | var fractionSize = fraction.FindMostSignificantOnePosition(); 227 | 228 | /* If there's a hidden bit and it's 1, 229 | * then the most significant 1-bit of the fraction is stored there, 230 | * so we're removing it from the fraction and decreasing fraction size accordingly. */ 231 | if (exponent.Lowest32Bits > 0) 232 | { 233 | fractionSize--; 234 | fraction = fraction.SetZero(fractionSize); 235 | } 236 | 237 | // Handling input numbers that fit in the range, but are too big to represent exactly. 238 | if (fractionSize > FractionSizeMax) 239 | { 240 | fraction >>= FractionSizeMax - fractionSize; 241 | uncertainityBit = true; 242 | } 243 | 244 | UnumBits = AssembleUnumBits( 245 | negative, 246 | exponent, 247 | fraction, 248 | uncertainityBit, 249 | (byte)(exponentSize > 0 ? exponentSize - 1 : 0), 250 | (ushort)(fractionSize > 0 ? fractionSize - 1 : 0)); 251 | } 252 | 253 | public Unum(UnumEnvironment environment, int value) 254 | { 255 | _environment = environment; 256 | 257 | // Creating an array of the size needed to call the other constructor. This is necessary because in Hastlayer 258 | // only arrays with dimensions defined at compile-time are supported. 259 | var valueArray = new uint[environment.EmptyBitMask.SegmentCount]; 260 | 261 | if (value >= 0) 262 | { 263 | valueArray[0] = (uint)value; 264 | UnumBits = new Unum(environment, valueArray).UnumBits; 265 | } 266 | else 267 | { 268 | valueArray[0] = (uint)-value; 269 | UnumBits = new Unum(environment, valueArray, negative: true).UnumBits; 270 | } 271 | } 272 | 273 | #endregion Unum constructors 274 | 275 | #region Methods to set the values of individual Unum structure elements 276 | 277 | /// 278 | /// Assembles the Unum from its pre-computed parts. 279 | /// 280 | /// The SignBit of the Unum. 281 | /// The biased notation of the exponent of the Unum. 282 | /// The fraction of the Unum without the hidden bit. 283 | /// The value of the uncertainity bit (Ubit). 284 | /// 285 | /// The Unum's exponent size, in a notation that is one less than the actual value. 286 | /// 287 | /// 288 | /// The Unum's fraction size, in a notation that is one less than the actual value. 289 | /// 290 | /// The BitMask representing the whole Unum with all the parts set. 291 | private BitMask AssembleUnumBits( 292 | bool signBit, 293 | BitMask exponent, 294 | BitMask fraction, 295 | bool uncertainityBit, 296 | byte exponentSize, 297 | ushort fractionSize) 298 | { 299 | var wholeUnum = new BitMask(exponentSize, Size) << FractionSizeSize; 300 | wholeUnum += fractionSize; 301 | 302 | if (uncertainityBit) wholeUnum += UncertaintyBitMask; 303 | 304 | wholeUnum += fraction << UnumTagSize; 305 | wholeUnum += exponent << (UnumTagSize + fractionSize + 1); 306 | 307 | if (signBit) wholeUnum += SignBitMask; 308 | 309 | return wholeUnum; 310 | } 311 | 312 | /// 313 | /// Sets the SignBit to the given value and leaves everything else as is. 314 | /// 315 | /// The desired SignBit. 316 | /// The BitMask representing the Unum with its SignBit set to the given value. 317 | public Unum SetSignBit(bool signBit) 318 | { 319 | var newUnumBits = signBit ? UnumBits | SignBitMask : UnumBits & (new BitMask(Size, allOne: true) ^ SignBitMask); 320 | return new Unum(_environment, newUnumBits); 321 | } 322 | 323 | /// 324 | /// Sets the Ubit to the given value and leaves everything else as is. 325 | /// 326 | /// The desired UBit. 327 | /// The BitMask representing the Unum with its UBit set to the given value. 328 | public Unum SetUncertainityBit(bool uncertainityBit) 329 | { 330 | var newUnumBits = uncertainityBit ? UnumBits | UncertaintyBitMask : UnumBits & (~UncertaintyBitMask); 331 | return new Unum(_environment, newUnumBits); 332 | } 333 | 334 | /// 335 | /// Changes the exponent to the bitstring given in the input BitMask and leaves everything else as is. 336 | /// 337 | /// The desired exponent in biased notation. 338 | /// The BitMask representing the Unum with its exponent set to the given value. 339 | public Unum SetExponentBits(BitMask exponent) 340 | { 341 | var newUnumBits = (UnumBits & (new BitMask(Size, allOne: true) ^ ExponentMask())) | 342 | (exponent << (FractionSizeSize + ExponentSizeSize + 1 + FractionSize())); 343 | return new Unum(_environment, newUnumBits); 344 | } 345 | 346 | /// 347 | /// Sets the fraction to the given value and leaves everything else as is. 348 | /// 349 | /// The desired fraction without the hidden bit. 350 | /// The BitMask representing the Unum with its fraction set to the given value. 351 | public Unum SetFractionBits(BitMask fraction) 352 | { 353 | var newUnumBits = (UnumBits & (new BitMask(Size, allOne: true) ^ FractionMask())) | 354 | (fraction << (FractionSizeSize + ExponentSizeSize + 1)); 355 | return new Unum(_environment, newUnumBits); 356 | } 357 | 358 | /// 359 | /// Sets the fractionSize to the given value and leaves everything else as is. 360 | /// 361 | /// The desired fractionSize in a notation that is one less than the actual value. 362 | /// The BitMask representing the Unum with its fractionSize set to the given value. 363 | public Unum SetFractionSizeBits(byte fractionSize) 364 | { 365 | var newUnumBits = (UnumBits & (new BitMask(Size, allOne: true) ^ FractionSizeMask)) | 366 | new BitMask(fractionSize, Size); 367 | return new Unum(_environment, newUnumBits); 368 | } 369 | 370 | /// 371 | /// Sets the exponentSize to the given value and leaves everything else as is. 372 | /// 373 | /// The BitMask representing the Unum with its exponentSize set to the given value. 374 | public Unum SetExponentSizeBits(byte exponentSize) 375 | { 376 | var newUnumBits = (UnumBits & (new BitMask(Size, allOne: true) ^ ExponentSizeMask)) | 377 | (new BitMask(exponentSize, Size) << FractionSizeSize); 378 | return new Unum(_environment, newUnumBits); 379 | } 380 | 381 | #endregion Methods to set the values of individual Unum structure elements 382 | 383 | #region Binary data extraction 384 | 385 | /// 386 | /// Copies the actual integer value represented by the Unum into an array of unsigned integers with the most 387 | /// significant bit of the last element functioning as the signbit. 388 | /// 389 | /// 390 | /// An array of unsigned integers that together represent the integer value of the Unum with the most significant 391 | /// bit of the last uint functioning as a signbit. 392 | /// 393 | public uint[] FractionToUintArray() 394 | { 395 | var resultMask = FractionWithHiddenBit() << (ExponentValueWithBias() - FractionSize()); 396 | var result = new uint[resultMask.SegmentCount]; 397 | 398 | for (var i = 0; i < resultMask.SegmentCount; i++) result[i] = resultMask.Segments[i]; 399 | if (!IsPositive()) 400 | { 401 | result[resultMask.SegmentCount - 1] |= 0x80000000; 402 | } 403 | else 404 | { 405 | result[resultMask.SegmentCount - 1] <<= 1; 406 | result[resultMask.SegmentCount - 1] >>= 1; 407 | } 408 | 409 | return result; 410 | } 411 | 412 | #endregion Binary data extraction 413 | 414 | #region Binary data manipulation 415 | 416 | public Unum Negate() 417 | { 418 | var newUnumBits = UnumBits ^ SignBitMask; 419 | return new Unum(_environment, newUnumBits); 420 | } 421 | 422 | #endregion Binary data manipulation 423 | 424 | #region Unum numeric states 425 | 426 | public bool IsExact() => (UnumBits & UncertaintyBitMask) == _environment.EmptyBitMask; 427 | 428 | public bool IsPositive() => (UnumBits & SignBitMask) == _environment.EmptyBitMask; 429 | 430 | // This is needed because there are many valid representations of zero in an Unum environment. 431 | public bool IsZero() => 432 | (UnumBits & UncertaintyBitMask) == _environment.EmptyBitMask && 433 | (UnumBits & FractionMask()) == _environment.EmptyBitMask && 434 | (UnumBits & ExponentMask()) == _environment.EmptyBitMask; 435 | 436 | #endregion Unum numeric states 437 | 438 | #region Methods for Utag independent Masks and values 439 | 440 | public byte ExponentSize() => (byte)(((UnumBits & ExponentSizeMask) >> FractionSizeSize) + 1).Lowest32Bits; 441 | 442 | public ushort FractionSize() => (ushort)((UnumBits & FractionSizeMask) + 1).Lowest32Bits; 443 | 444 | public BitMask FractionMask() 445 | { 446 | var fractionMask = new BitMask(1, Size); 447 | return ((fractionMask << FractionSize()) - 1) << UnumTagSize; 448 | } 449 | 450 | public BitMask ExponentMask() 451 | { 452 | var exponentMask = new BitMask(1, Size); 453 | return ((exponentMask << ExponentSize()) - 1) << (FractionSize() + UnumTagSize); 454 | } 455 | 456 | #endregion Methods for Utag independent Masks and values 457 | 458 | #region Methods for Utag dependent Masks and values 459 | 460 | public BitMask Exponent() => (ExponentMask() & UnumBits) >> (UnumTagSize + FractionSize()); 461 | 462 | public BitMask Fraction() => (FractionMask() & UnumBits) >> UnumTagSize; 463 | 464 | public BitMask FractionWithHiddenBit() => 465 | HiddenBitIsOne() ? Fraction().SetOne(FractionSize()) : Fraction(); 466 | 467 | public ushort FractionSizeWithHiddenBit() => HiddenBitIsOne() ? (ushort)(FractionSize() + 1) : FractionSize(); 468 | 469 | public int Bias() => (1 << (ExponentSize() - 1)) - 1; 470 | 471 | public bool HiddenBitIsOne() => Exponent().Lowest32Bits > 0; 472 | 473 | public int ExponentValueWithBias() => (int)Exponent().Lowest32Bits - Bias() + (HiddenBitIsOne() ? 0 : 1); 474 | 475 | public bool IsNan() => UnumBits == SignalingNotANumber || UnumBits == QuietNotANumber; 476 | 477 | public bool IsPositiveInfinity() => UnumBits == PositiveInfinity; 478 | 479 | public bool IsNegativeInfinity() => UnumBits == NegativeInfinity; 480 | 481 | #endregion Methods for Utag dependent Masks and values 482 | 483 | #region Operations for exact Unums 484 | 485 | [SuppressMessage( 486 | "Critical Code Smell", 487 | "S3776:Cognitive Complexity of methods should not be too high", 488 | Justification = "Not currently posisble due to TypeConverter limitations.")] 489 | public static Unum AddExactUnums(Unum left, Unum right) 490 | { 491 | // Handling special cases first. 492 | if (left.IsNan() || 493 | right.IsNan() || 494 | (left.IsPositiveInfinity() && right.IsNegativeInfinity()) || 495 | (left.IsNegativeInfinity() && right.IsPositiveInfinity())) 496 | { 497 | return new Unum(left._environment, left.QuietNotANumber); 498 | } 499 | 500 | if (left.IsPositiveInfinity() || right.IsPositiveInfinity()) 501 | { 502 | return new Unum(left._environment, left.PositiveInfinity); 503 | } 504 | 505 | if (left.IsNegativeInfinity() || right.IsNegativeInfinity()) 506 | { 507 | return new Unum(left._environment, left.NegativeInfinity); 508 | } 509 | 510 | AddExactUnumsInner( 511 | left, 512 | right, 513 | out var scratchPad, 514 | out var resultExponentValue, 515 | out var smallerBitsMovedToLeft, 516 | out var resultSignBit); 517 | 518 | // Calculating how the addition changed the exponent of the result. 519 | var exponentChange = scratchPad.FindMostSignificantOnePosition() - (left.FractionSizeMax + 1); 520 | var resultExponent = new BitMask(left._environment.Size) + 521 | ExponentValueToExponentBits(resultExponentValue + exponentChange, left.Size); 522 | // Calculating the ExponentSize needed to the excess-k notation of the results Exponent value. 523 | var resultExponentSize = (byte)(ExponentValueToExponentSize(resultExponentValue + exponentChange) - 1); 524 | 525 | var resultUbit = false; 526 | if (smallerBitsMovedToLeft < 0) resultUbit = true; // There are lost digits, so we set the ubit to 1. 527 | // If there are no lost digits, we can shift out the least significant zeros to save space. 528 | else scratchPad = scratchPad.ShiftOutLeastSignificantZeros(); 529 | 530 | ushort resultFractionSize = 0; 531 | 532 | // Calculating the results FractionSize. 533 | if (scratchPad.FindMostSignificantOnePosition() == 0) 534 | { 535 | // If the Fraction is zero, so is the FractionSize. 536 | resultExponent = scratchPad; // 0 537 | resultExponentSize = 0; // If the Fraction is zero, so is the ExponentSize. 538 | } 539 | else 540 | { 541 | resultFractionSize = (ushort)(scratchPad.FindMostSignificantOnePosition() - 1); 542 | } 543 | 544 | if (resultExponent.FindMostSignificantOnePosition() != 0) 545 | { 546 | // Erase the hidden bit if it is set. 547 | scratchPad = scratchPad.SetZero((ushort)(scratchPad.FindMostSignificantOnePosition() - 1)); 548 | resultFractionSize = (ushort)(resultFractionSize == 0 ? 0 : resultFractionSize - 1); 549 | } 550 | 551 | // This is temporary, for the imitation of float behaviour. Now the Ubit works as a flag for rounded values. 552 | // When Ubounds will be implemented this should be handled in the addition operator. 553 | if (!left.IsExact() || !right.IsExact()) resultUbit = true; 554 | 555 | // Setting the parts of the result Unum to the calculated values. 556 | var resultBitMask = left.AssembleUnumBits( 557 | resultSignBit, 558 | resultExponent, 559 | scratchPad, 560 | resultUbit, 561 | resultExponentSize, 562 | resultFractionSize); 563 | 564 | return new Unum(left._environment, resultBitMask); 565 | } 566 | 567 | private static void AddExactUnumsInner( 568 | Unum left, 569 | Unum right, 570 | out BitMask scratchPad, 571 | out int resultExponentValue, 572 | out int smallerBitsMovedToLeft, 573 | out bool resultSignBit) 574 | { 575 | var exponentValueDifference = left.ExponentValueWithBias() - right.ExponentValueWithBias(); 576 | var signBitsMatch = left.IsPositive() == right.IsPositive(); 577 | int biggerBitsMovedToLeft; 578 | 579 | if (exponentValueDifference > 0) 580 | { 581 | // Left Exponent is bigger. We align the fractions according to their exponent values so the Most 582 | // Significant Bit of the bigger number gets to the leftmost position that the FractionSize allows. This way 583 | // the digits that won't fit automatically get lost. 584 | resultSignBit = !left.IsPositive(); 585 | resultExponentValue = left.ExponentValueWithBias(); 586 | biggerBitsMovedToLeft = left.FractionSizeMax + 1 - (left.FractionSize() + 1); 587 | smallerBitsMovedToLeft = left.FractionSizeMax + 1 - (right.FractionSize() + 1) - exponentValueDifference; 588 | 589 | scratchPad = left.FractionWithHiddenBit() << biggerBitsMovedToLeft; 590 | // Adding the aligned Fractions. 591 | scratchPad = AddAlignedFractions( 592 | scratchPad, 593 | right.FractionWithHiddenBit() << smallerBitsMovedToLeft, 594 | signBitsMatch); 595 | 596 | return; 597 | } 598 | 599 | if (exponentValueDifference < 0) 600 | { 601 | // Right Exponent is bigger. We align the fractions according to their exponent values so the Most 602 | // Significant Bit of the bigger number gets to the leftmost position that the FractionSize allows. This way 603 | // the digits that won't fit automatically get lost. 604 | resultSignBit = !right.IsPositive(); 605 | resultExponentValue = right.ExponentValueWithBias(); 606 | biggerBitsMovedToLeft = left.FractionSizeMax + 1 - (right.FractionSize() + 1); 607 | smallerBitsMovedToLeft = left.FractionSizeMax + 1 - (left.FractionSize() + 1) + exponentValueDifference; 608 | 609 | scratchPad = right.FractionWithHiddenBit() << biggerBitsMovedToLeft; 610 | // Adding the aligned Fractions. 611 | scratchPad = AddAlignedFractions( 612 | scratchPad, 613 | left.FractionWithHiddenBit() << smallerBitsMovedToLeft, 614 | signBitsMatch); 615 | 616 | return; 617 | } 618 | 619 | // Exponents are equal. 620 | resultExponentValue = left.ExponentValueWithBias(); 621 | 622 | // We align the fractions so their Most Significant Bit gets to the leftmost position that the FractionSize 623 | // allows. This way the digits that won't fit automatically get lost. 624 | biggerBitsMovedToLeft = left.FractionSizeMax + 1 - (left.FractionSize() + 1); 625 | smallerBitsMovedToLeft = left.FractionSizeMax + 1 - (right.FractionSize() + 1); 626 | // Adding the aligned Fractions. 627 | scratchPad = AddAlignedFractions( 628 | left.FractionWithHiddenBit() << biggerBitsMovedToLeft, 629 | right.FractionWithHiddenBit() << smallerBitsMovedToLeft, 630 | signBitsMatch); 631 | 632 | if (!signBitsMatch) 633 | { 634 | // False Positive. 635 | #pragma warning disable S3240 // The simplest possible condition syntax should be used 636 | if (left.HiddenBitIsOne() == right.HiddenBitIsOne()) 637 | { 638 | // If the value of the Hidden Bits match we just compare the fractions, and get the Sign of the bigger 639 | // one. 640 | resultSignBit = left.Fraction() >= right.Fraction() 641 | ? !left.IsPositive() // Left Fraction is bigger. 642 | : !right.IsPositive(); // Right Fraction is bigger. 643 | } 644 | else 645 | { 646 | // Otherwise we get the Sign of the number that has a Hidden Bit set. 647 | resultSignBit = left.HiddenBitIsOne() ? !left.IsPositive() : !right.IsPositive(); 648 | } 649 | #pragma warning restore S3240 // The simplest possible condition syntax should be used 650 | } 651 | else 652 | { 653 | resultSignBit = !left.IsPositive(); 654 | } 655 | } 656 | 657 | public static Unum SubtractExactUnums(Unum left, Unum right) => AddExactUnums(left, NegateExactUnum(right)); 658 | 659 | public static Unum NegateExactUnum(Unum input) => input.Negate(); 660 | 661 | public static bool AreEqualExactUnums(Unum left, Unum right) => 662 | (left.IsZero() && right.IsZero()) || left.UnumBits == right.UnumBits; 663 | 664 | #endregion Operations for exact Unums 665 | 666 | #region Helper methods for operations and conversions 667 | 668 | public static BitMask ExponentValueToExponentBits(int value, ushort size) 669 | { 670 | var exponent = new BitMask((uint)((value < 0) ? -value : value), size); 671 | var exponentSize = ExponentValueToExponentSize(value); 672 | exponent += (uint)(1 << (exponentSize - 1)) - 1; // Applying bias 673 | 674 | if (value < 0) 675 | { 676 | exponent -= (uint)(-2 * value); 677 | } 678 | 679 | return exponent; 680 | } 681 | 682 | public static byte ExponentValueToExponentSize(int value) 683 | { 684 | byte size = 1; 685 | 686 | if (value > 0) while (value > 1 << (size - 1)) size++; 687 | else while (-value >= 1 << (size - 1)) size++; 688 | 689 | return size; 690 | } 691 | 692 | public static BitMask AddAlignedFractions(BitMask left, BitMask right, bool signBitsMatch) 693 | { 694 | if (signBitsMatch) return left + right; 695 | 696 | var mask = left > right ? left - right : right - left; 697 | return mask; 698 | } 699 | 700 | #endregion Helper methods for operations and conversions 701 | 702 | #region Operators 703 | 704 | public static Unum operator +(Unum left, Unum right) => AddExactUnums(left, right); 705 | 706 | public static Unum operator -(Unum x) => NegateExactUnum(x); 707 | 708 | public static Unum operator -(Unum left, Unum right) => SubtractExactUnums(left, right); 709 | 710 | public static bool operator ==(Unum left, Unum right) => AreEqualExactUnums(left, right); 711 | 712 | public static bool operator !=(Unum left, Unum right) => !(left == right); 713 | 714 | // Converting from an Unum to int results in information loss, so only allowing it explicitly (with a cast). 715 | public static explicit operator int(Unum x) 716 | { 717 | uint result; 718 | 719 | if ((x.ExponentValueWithBias() + x.FractionSizeWithHiddenBit()) < 31) // The Unum fits into the range. 720 | result = (x.FractionWithHiddenBit() << (x.ExponentValueWithBias() - x.FractionSize())).Lowest32Bits; 721 | else return x.IsPositive() ? int.MaxValue : int.MinValue; // The absolute value of the Unum is too large. 722 | 723 | return x.IsPositive() ? (int)result : -(int)result; 724 | } 725 | 726 | public static explicit operator uint(Unum x) => 727 | (x.FractionWithHiddenBit() << (x.ExponentValueWithBias() - x.FractionSize())).Lowest32Bits; 728 | 729 | // This is not well tested yet. 730 | public static explicit operator float(Unum x) 731 | { 732 | // Handling special cases first. 733 | if (x.IsNan()) return float.NaN; 734 | if (x.IsNegativeInfinity()) return float.NegativeInfinity; 735 | if (x.IsPositiveInfinity()) return float.PositiveInfinity; 736 | if (x.ExponentValueWithBias() > 127) // Exponent is too big for float format. 737 | return x.IsPositive() ? float.PositiveInfinity : float.NegativeInfinity; 738 | if (x.ExponentValueWithBias() < -126) return x.IsPositive() ? 0 : -0; // Exponent is too small for float format. 739 | 740 | var result = (x.Fraction() << (23 - x.FractionSize())).Lowest32Bits; 741 | result |= (uint)(x.ExponentValueWithBias() + 127) << 23; 742 | 743 | return x.IsPositive() ? 744 | BitConverter.ToSingle(BitConverter.GetBytes(result), 0) : 745 | -BitConverter.ToSingle(BitConverter.GetBytes(result), 0); 746 | } 747 | 748 | #endregion Operators 749 | 750 | #region Overrides 751 | 752 | public override bool Equals(object obj) => obj is Unum other && this == other; 753 | 754 | public bool Equals(Unum other) => this == other; 755 | 756 | public override int GetHashCode() 757 | { 758 | unchecked 759 | { 760 | return ((_environment != null ? _environment.GetHashCode() : 0) * 397) ^ UnumBits.GetHashCode(); 761 | } 762 | } 763 | 764 | public override string ToString() => $"{nameof(Unum)}(Bits:{UnumBits};Environment:{_environment})"; 765 | 766 | #endregion Overrides 767 | } 768 | --------------------------------------------------------------------------------