From 5fe5462b778797c7ec7a9b8779b5aa25a0d74166 Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Thu, 29 Aug 2024 01:11:10 -0400 Subject: [PATCH] [`fixes`] UInt160 Class (#3422) * Fixed `UInt160` and expanded class * Cleaned up code for `TryParse` * Fixed `TryParse` * Fixed small bug with `TryParse` * Change `UInt160.Zero` to `static readonly` * benchmark UInt160 * Fix benchmark * Fixed bugs and added features for `UInt160` class * Revert and just keep bug fixes * Made @shargon changes * Set `InvariantCultureIgnoreCase` back for `0x` and `0X` --------- Co-authored-by: Shargon Co-authored-by: Jimmy Co-authored-by: Jimmy --- .../Neo.Benchmarks/Benchmarks.UInt160.cs | 156 +++++++++++++++ .../Neo.Benchmarks/Neo.Benchmarks.csproj | 1 + benchmarks/Neo.Benchmarks/OldUInt160.cs | 184 ++++++++++++++++++ benchmarks/Neo.Benchmarks/Program.cs | 3 +- src/Neo/UInt160.cs | 110 ++++++----- tests/Neo.UnitTests/UT_UInt160.cs | 27 ++- 6 files changed, 430 insertions(+), 51 deletions(-) create mode 100644 benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs create mode 100644 benchmarks/Neo.Benchmarks/OldUInt160.cs diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs new file mode 100644 index 0000000000..37fa701ff9 --- /dev/null +++ b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.UInt160.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using BenchmarkDotNet.Attributes; + +namespace Neo.Benchmark; + +public class Benchmarks_UInt160 +{ + static readonly OldUInt160 s_oldUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + static readonly UInt160 s_newUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + + [Benchmark] + public void TestOldUInt160Gernerator1() + { + _ = new OldUInt160(); + } + + [Benchmark] + public void TestOldUInt160Gernerator2() + { + _ = new OldUInt160(new byte[20]); + } + + [Benchmark] + public void TestOldUInt160CompareTo() + { + OldUInt160.Zero.CompareTo(OldUInt160.Zero); + OldUInt160.Zero.CompareTo(s_oldUInt160); + s_oldUInt160.CompareTo(OldUInt160.Zero); + } + + [Benchmark] + public void TestOldUInt160Equals() + { + OldUInt160.Zero.Equals(OldUInt160.Zero); + OldUInt160.Zero.Equals(s_oldUInt160); + s_oldUInt160.Equals(null); + } + + [Benchmark] + public void TestOldUInt160Parse() + { + _ = OldUInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = OldUInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestOldUInt160TryParse() + { + OldUInt160.TryParse(null, out _); + OldUInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + OldUInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + OldUInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOldUInt160OperatorLarger() + { + _ = s_oldUInt160 > OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorLargerAndEqual() + { + _ = s_oldUInt160 >= OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmaller() + { + _ = s_oldUInt160 < OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmallerAndEqual() + { + _ = s_oldUInt160 <= OldUInt160.Zero; + } + + [Benchmark] + public void TestGernerator1() + { + _ = new UInt160(); + } + + [Benchmark] + public void TestGernerator2() + { + _ = new UInt160(new byte[20]); + } + + [Benchmark] + public void TestCompareTo() + { + UInt160.Zero.CompareTo(UInt160.Zero); + UInt160.Zero.CompareTo(s_newUInt160); + s_newUInt160.CompareTo(UInt160.Zero); + } + + [Benchmark] + public void TestEquals() + { + UInt160.Zero.Equals(UInt160.Zero); + UInt160.Zero.Equals(s_newUInt160); + s_newUInt160.Equals(null); + } + + [Benchmark] + public void TestParse() + { + _ = UInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = UInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestTryParse() + { + UInt160.TryParse(null, out _); + UInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + UInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + UInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOperatorLarger() + { + _ = s_newUInt160 > UInt160.Zero; + } + + [Benchmark] + public void TestOperatorLargerAndEqual() + { + _ = s_newUInt160 >= UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmaller() + { + _ = s_newUInt160 < UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmallerAndEqual() + { + _ = s_newUInt160 <= UInt160.Zero; + } +} diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index c4b1a35da9..a59fc6e728 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -5,6 +5,7 @@ net8.0 Neo enable + true diff --git a/benchmarks/Neo.Benchmarks/OldUInt160.cs b/benchmarks/Neo.Benchmarks/OldUInt160.cs new file mode 100644 index 0000000000..965ac985ea --- /dev/null +++ b/benchmarks/Neo.Benchmarks/OldUInt160.cs @@ -0,0 +1,184 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OldUInt160.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Extensions; +using Neo.IO; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Neo +{ + /// + /// Represents a 160-bit unsigned integer. + /// + [StructLayout(LayoutKind.Explicit, Size = 20)] + public class OldUInt160 : IComparable, IEquatable, ISerializable + { + /// + /// The length of values. + /// + public const int Length = 20; + + /// + /// Represents 0. + /// + public static readonly OldUInt160 Zero = new(); + + [FieldOffset(0)] private ulong value1; + [FieldOffset(8)] private ulong value2; + [FieldOffset(16)] private uint value3; + + public int Size => Length; + + /// + /// Initializes a new instance of the class. + /// + public OldUInt160() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The value of the . + public unsafe OldUInt160(ReadOnlySpan value) + { + if (value.Length != Length) throw new FormatException(); + fixed (ulong* p = &value1) + { + Span dst = new(p, Length); + value[..Length].CopyTo(dst); + } + } + + public int CompareTo(OldUInt160 other) + { + int result = value3.CompareTo(other.value3); + if (result != 0) return result; + result = value2.CompareTo(other.value2); + if (result != 0) return result; + return value1.CompareTo(other.value1); + } + + public void Deserialize(ref MemoryReader reader) + { + value1 = reader.ReadUInt64(); + value2 = reader.ReadUInt64(); + value3 = reader.ReadUInt32(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, this)) return true; + return Equals(obj as OldUInt160); + } + + public bool Equals(OldUInt160 other) + { + if (other is null) return false; + return value1 == other.value1 + && value2 == other.value2 + && value3 == other.value3; + } + + public override int GetHashCode() + { + return (int)value1; + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// is not in the correct format. + public static OldUInt160 Parse(string value) + { + if (!TryParse(value, out var result)) throw new FormatException(); + return result; + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(value1); + writer.Write(value2); + writer.Write(value3); + } + + public override string ToString() + { + return "0x" + this.ToArray().ToHexString(reverse: true); + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// if an is successfully parsed; otherwise, . + public static bool TryParse(string s, out OldUInt160 result) + { + if (s == null) + { + result = null; + return false; + } + if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + s = s[2..]; + if (s.Length != Length * 2) + { + result = null; + return false; + } + byte[] data = new byte[Length]; + for (int i = 0; i < Length; i++) + if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) + { + result = null; + return false; + } + result = new OldUInt160(data); + return true; + } + + public static bool operator ==(OldUInt160 left, OldUInt160 right) + { + if (ReferenceEquals(left, right)) return true; + if (left is null || right is null) return false; + return left.Equals(right); + } + + public static bool operator !=(OldUInt160 left, OldUInt160 right) + { + return !(left == right); + } + + public static bool operator >(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) >= 0; + } + + public static bool operator <(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) <= 0; + } + } +} diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index e39ef23ff7..c44b76f839 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -12,4 +12,5 @@ using BenchmarkDotNet.Running; using Neo.Benchmark; -BenchmarkRunner.Run(); +// BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/src/Neo/UInt160.cs b/src/Neo/UInt160.cs index db317ef7e5..da4c920cbe 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.cs @@ -14,6 +14,8 @@ using System; using System.Globalization; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Neo @@ -32,20 +34,18 @@ public class UInt160 : IComparable, IEquatable, ISerializable /// /// Represents 0. /// - public static readonly UInt160 Zero = new(); + public readonly static UInt160 Zero = new(); - [FieldOffset(0)] private ulong value1; - [FieldOffset(8)] private ulong value2; - [FieldOffset(16)] private uint value3; + [FieldOffset(0)] private ulong _value1; + [FieldOffset(8)] private ulong _value2; + [FieldOffset(16)] private uint _value3; public int Size => Length; /// /// Initializes a new instance of the class. /// - public UInt160() - { - } + public UInt160() { } /// /// Initializes a new instance of the class. @@ -53,47 +53,52 @@ public UInt160() /// The value of the . public unsafe UInt160(ReadOnlySpan value) { - if (value.Length != Length) throw new FormatException(); - fixed (ulong* p = &value1) + if (value.Length != Length) + throw new FormatException(); + + fixed (void* p = &_value1) { Span dst = new(p, Length); value[..Length].CopyTo(dst); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(UInt160 other) { - int result = value3.CompareTo(other.value3); + var result = _value3.CompareTo(other._value3); if (result != 0) return result; - result = value2.CompareTo(other.value2); + result = _value2.CompareTo(other._value2); if (result != 0) return result; - return value1.CompareTo(other.value1); + return _value1.CompareTo(other._value1); } public void Deserialize(ref MemoryReader reader) { - value1 = reader.ReadUInt64(); - value2 = reader.ReadUInt64(); - value3 = reader.ReadUInt32(); + _value1 = reader.ReadUInt64(); + _value2 = reader.ReadUInt64(); + _value3 = reader.ReadUInt32(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; return Equals(obj as UInt160); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(UInt160 other) { - if (other is null) return false; - return value1 == other.value1 - && value2 == other.value2 - && value3 == other.value3; + if (other == null) return false; + return _value1 == other._value1 && + _value2 == other._value2 && + _value3 == other._value3; } public override int GetHashCode() { - return (int)value1; + return HashCode.Combine(_value1, _value2, _value3); } /// @@ -110,9 +115,9 @@ public static UInt160 Parse(string value) public void Serialize(BinaryWriter writer) { - writer.Write(value1); - writer.Write(value2); - writer.Write(value3); + writer.Write(_value1); + writer.Write(_value2); + writer.Write(_value3); } public override string ToString() @@ -123,44 +128,61 @@ public override string ToString() /// /// Parses an from the specified . /// - /// An represented by a . + /// An represented by a . /// The parsed . /// if an is successfully parsed; otherwise, . - public static bool TryParse(string s, out UInt160 result) + public static bool TryParse(string str, out UInt160 result) { - if (s == null) + var startIndex = 0; + + result = null; + + if (string.IsNullOrWhiteSpace(str)) return false; + + if (str.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + startIndex = 2; + + if ((str.Length - startIndex) != Length * 2) return false; + + try { - result = null; - return false; + var data = new byte[Length]; + for (var i = 0; i < Length; i++) + { + if (!byte.TryParse(str.AsSpan(i * 2 + startIndex, 2), NumberStyles.HexNumber, null, out data[Length - i - 1])) + return false; + } + result = new(data); + return true; } - if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - s = s[2..]; - if (s.Length != Length * 2) + catch { - result = null; return false; } - byte[] data = new byte[Length]; - for (int i = 0; i < Length; i++) - if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) - { - result = null; - return false; - } - result = new UInt160(data); - return true; + } + + public static implicit operator UInt160(string s) + { + return Parse(s); + } + + public static implicit operator UInt160(byte[] b) + { + return new UInt160(b); } public static bool operator ==(UInt160 left, UInt160 right) { - if (ReferenceEquals(left, right)) return true; - if (left is null || right is null) return false; + if (left is null || right is null) + return Equals(left, right); return left.Equals(right); } public static bool operator !=(UInt160 left, UInt160 right) { - return !(left == right); + if (left is null || right is null) + return !Equals(left, right); + return !left.Equals(right); } public static bool operator >(UInt160 left, UInt160 right) diff --git a/tests/Neo.UnitTests/UT_UInt160.cs b/tests/Neo.UnitTests/UT_UInt160.cs index 4799a80d19..3502c7cf90 100644 --- a/tests/Neo.UnitTests/UT_UInt160.cs +++ b/tests/Neo.UnitTests/UT_UInt160.cs @@ -9,11 +9,10 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -#pragma warning disable CS1718 - using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Security.Cryptography; namespace Neo.UnitTests.IO { @@ -36,8 +35,16 @@ public void TestGernerator1() [TestMethod] public void TestGernerator2() { - UInt160 uInt160 = new UInt160(new byte[20]); + UInt160 uInt160 = new byte[20]; + Assert.IsNotNull(uInt160); + } + + [TestMethod] + public void TestGernerator3() + { + UInt160 uInt160 = "0xff00000000000000000000000000000000000001"; Assert.IsNotNull(uInt160); + Assert.IsTrue(uInt160.ToString() == "0xff00000000000000000000000000000000000001"); } [TestMethod] @@ -57,9 +64,13 @@ public void TestEquals() byte[] temp = new byte[20]; temp[19] = 0x01; UInt160 result = new UInt160(temp); - Assert.AreEqual(true, UInt160.Zero.Equals(UInt160.Zero)); - Assert.AreEqual(false, UInt160.Zero.Equals(result)); - Assert.AreEqual(false, result.Equals(null)); + Assert.IsTrue(UInt160.Zero.Equals(UInt160.Zero)); + Assert.IsFalse(UInt160.Zero.Equals(result)); + Assert.IsFalse(result.Equals(null)); + Assert.IsTrue(UInt160.Zero == UInt160.Zero); + Assert.IsFalse(UInt160.Zero != UInt160.Zero); + Assert.IsTrue(UInt160.Zero == "0x0000000000000000000000000000000000000000"); + Assert.IsFalse(UInt160.Zero == "0x0000000000000000000000000000000000000001"); } [TestMethod] @@ -92,24 +103,28 @@ public void TestTryParse() public void TestOperatorLarger() { Assert.AreEqual(false, UInt160.Zero > UInt160.Zero); + Assert.IsFalse(UInt160.Zero > "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorLargerAndEqual() { Assert.AreEqual(true, UInt160.Zero >= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmaller() { Assert.AreEqual(false, UInt160.Zero < UInt160.Zero); + Assert.IsFalse(UInt160.Zero < "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmallerAndEqual() { Assert.AreEqual(true, UInt160.Zero <= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } } }