diff --git a/csharp/Platform.Data.Doublets.Tests/UnaryNumberToAddressAddOperationConverterTests.cs b/csharp/Platform.Data.Doublets.Tests/UnaryNumberToAddressAddOperationConverterTests.cs new file mode 100644 index 000000000..9d13f0424 --- /dev/null +++ b/csharp/Platform.Data.Doublets.Tests/UnaryNumberToAddressAddOperationConverterTests.cs @@ -0,0 +1,127 @@ +using System; +using System.IO; +using System.Numerics; +using Platform.Data.Doublets.Converters; +using Platform.Data.Doublets.Decorators; +using Platform.Data.Doublets.Memory.United.Generic; +using Platform.Memory; +using Xunit; + +namespace Platform.Data.Doublets.Tests +{ + public static class UnaryNumberToAddressAddOperationConverterTests + { + [Fact] + public static void BasicConversionTest() + { + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + + // Test null/default conversion + var result = converter.Convert(links.Constants.Null); + Assert.Equal(0UL, result); + }); + } + + [Fact] + public static void UnaryOneConversionTest() + { + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + + // Create unary "one" (self-referencing link) + var unaryOne = links.GetOrCreate(links.Constants.Itself, links.Constants.Itself); + var result = converter.Convert(unaryOne); + + Assert.Equal(1UL, result); + }); + } + + [Fact] + public static void PowerOfTwoValidationTest() + { + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + + // Create a more complex unary number structure that would trigger traversal + var unaryOne = links.GetOrCreate(links.Constants.Itself, links.Constants.Itself); + var two = links.GetOrCreate(unaryOne, unaryOne); + + // Create an invalid link structure where source != target + // and target is not a power of two (using an odd number) + var oddTarget = links.GetOrCreate(links.Constants.Itself, two); // Create link 3 + var invalidUnaryNumber = links.GetOrCreate(unaryOne, oddTarget); + + // This should throw an InvalidOperationException due to power-of-two check + Assert.Throws(() => converter.Convert(invalidUnaryNumber)); + }); + } + + [Fact] + public static void NonCachedBehaviorTest() + { + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + + // Verify that converter creates links on-demand + var initialCount = links.Count(); + + // Create unary "one" should create a new link + var unaryOne = links.GetOrCreate(links.Constants.Itself, links.Constants.Itself); + var result = converter.Convert(unaryOne); + + // Verify no additional links were pre-created by the converter itself + var finalCount = links.Count(); + + Assert.Equal(1UL, result); + // The count should only increase by the links we explicitly created + Assert.True(finalCount >= initialCount); + }); + } + + [Fact] + public static void MultipleTypeTest() + { + // Test with different numeric types + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + var result = converter.Convert(links.Constants.Null); + Assert.Equal((byte)0, result); + }); + + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + var result = converter.Convert(links.Constants.Null); + Assert.Equal((ushort)0, result); + }); + + Using(links => + { + var converter = new UnaryNumberToAddressAddOperationConverter(links); + var result = converter.Convert(links.Constants.Null); + Assert.Equal(0U, result); + }); + } + + private static void Using(Action> action) + where TLinkAddress : IUnsignedNumber, + IShiftOperators, + IBitwiseOperators, + IMinMaxValue, + IComparisonOperators + { + var unitedMemoryLinks = new UnitedMemoryLinks(new HeapResizableDirectMemory()); + using (var logFile = File.Open($"converterTest_{typeof(TLinkAddress).Name}.txt", FileMode.Create, FileAccess.Write)) + { + LoggingDecorator decoratedStorage = new(unitedMemoryLinks, logFile); + action(decoratedStorage); + } + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets/Converters/UnaryNumberToAddressAddOperationConverter.cs b/csharp/Platform.Data.Doublets/Converters/UnaryNumberToAddressAddOperationConverter.cs new file mode 100644 index 000000000..4be1bdc95 --- /dev/null +++ b/csharp/Platform.Data.Doublets/Converters/UnaryNumberToAddressAddOperationConverter.cs @@ -0,0 +1,179 @@ +using System; +using System.Numerics; +using System.Runtime.CompilerServices; +using Platform.Delegates; +using Platform.Numbers; +using Platform.Converters; + +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Platform.Data.Doublets.Converters; + +/// +/// +/// Represents a converter that transforms unary numbers to address add operations without caching. +/// This implementation creates links on-demand only, without precreation. +/// +/// +/// +/// +/// The link address type. +/// +/// +public class UnaryNumberToAddressAddOperationConverter : IConverter + where TLinkAddress : IUnsignedNumber, IComparisonOperators +{ + /// + /// + /// The links storage. + /// + /// + /// + protected readonly ILinks _links; + + /// + /// + /// The constants. + /// + /// + /// + protected readonly LinksConstants _constants; + + /// + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// The links storage. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public UnaryNumberToAddressAddOperationConverter(ILinks links) + { + _links = links; + _constants = links.Constants; + } + + /// + /// + /// Converts a unary number to its address representation without using a cache. + /// Creates links on-demand only. + /// + /// + /// + /// + /// The unary number representation to convert. + /// + /// + /// + /// The address representation of the unary number. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TLinkAddress Convert(TLinkAddress unaryNumber) + { + // Handle null/default case + if (unaryNumber == _constants.Null) + { + return TLinkAddress.Zero; + } + + // Create the unary "one" (self-referencing link) on-demand + var unaryOne = _links.GetOrCreate(_constants.Itself, _constants.Itself); + if (unaryNumber == unaryOne) + { + return TLinkAddress.One; + } + + // Get the source and target of the unary number + var source = _links.GetSource(unaryNumber); + var target = _links.GetTarget(unaryNumber); + + // If source equals target, this is a simple power of two case + if (source == target) + { + // Calculate the power of two value on-demand instead of using cache + return CalculatePowerOfTwoValue(unaryNumber, unaryOne); + } + else + { + // More complex case: accumulate result by traversing the structure + var result = CalculatePowerOfTwoValue(source, unaryOne); + + // Traverse the target structure and accumulate values + while (target != unaryOne && target != _constants.Null) + { + // Check if target is a power of two (as mentioned in the issue) + if (!Platform.Numbers.Math.IsPowerOfTwo(ulong.CreateTruncating(target))) + { + throw new InvalidOperationException($"Target {target} is not a power of two."); + } + + source = _links.GetSource(target); + result = result + CalculatePowerOfTwoValue(source, unaryOne); + target = _links.GetTarget(target); + } + + return result; + } + } + + /// + /// + /// Calculates the power of two value for a unary number without using cache. + /// + /// + /// + /// + /// The unary number. + /// + /// + /// + /// The unary "one" value. + /// + /// + /// + /// The power of two value. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TLinkAddress CalculatePowerOfTwoValue(TLinkAddress unary, TLinkAddress unaryOne) + { + if (unary == unaryOne) + { + return TLinkAddress.One; + } + + // Count the depth to determine the power of two + var depth = 0; + var current = unary; + + while (current != unaryOne && current != _constants.Null) + { + var source = _links.GetSource(current); + var target = _links.GetTarget(current); + + if (source == target) // This means it's a power-of-two link + { + depth++; + current = source; + } + else + { + break; + } + } + + // Calculate 2^depth + var result = TLinkAddress.One; + for (int i = 0; i < depth; i++) + { + result += result; // Double the value (multiply by 2) + } + + return result; + } + +} \ No newline at end of file