This is an automated email from the ASF dual-hosted git repository.

paulirwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucenenet.git


The following commit(s) were added to refs/heads/master by this push:
     new 7cfc4c340 Convert Lucene volatile long/double values to Atomic 
counterparts, #1063 (#1064)
7cfc4c340 is described below

commit 7cfc4c340c384bd5af39f42d7591dbe45344fd24
Author: Paul Irwin <[email protected]>
AuthorDate: Sun Dec 15 09:00:09 2024 -0700

    Convert Lucene volatile long/double values to Atomic counterparts, #1063 
(#1064)
    
    * Convert volatile long/double values to Atomic counterparts, #1063
    
    * Lucene.Net.Support.Threading.AtomicDouble: Changed parameter and local 
variable names to reflect AtomicDouble rather than AtomicInt64
    
    * Lucene.Net.Support.Threading.AtomicDouble: Added missing TryFormat 
override
    
    ---------
    
    Co-authored-by: Shad Storhaug <[email protected]>
---
 src/Lucene.Net.Replicator/LocalReplicator.cs       |   8 +-
 .../Lucene.Net.Tests._J-S.csproj                   |   6 +-
 .../Support/Threading/AtomicDoubleTest.cs          | 255 ++++++++++++
 src/Lucene.Net/Index/IndexWriter.cs                |  27 +-
 src/Lucene.Net/Index/IndexWriterConfig.cs          |   4 +-
 src/Lucene.Net/Index/LiveIndexWriterConfig.cs      |  27 +-
 src/Lucene.Net/Index/MergePolicy.cs                |  14 +-
 src/Lucene.Net/Index/SegmentCommitInfo.cs          |  11 +-
 .../Search/ControlledRealTimeReopenThread.cs       |   7 +-
 src/Lucene.Net/Search/TimeLimitingCollector.cs     |  24 +-
 src/Lucene.Net/Store/RateLimiter.cs                |  23 +-
 src/Lucene.Net/Support/Threading/AtomicDouble.cs   | 457 +++++++++++++++++++++
 12 files changed, 799 insertions(+), 64 deletions(-)

diff --git a/src/Lucene.Net.Replicator/LocalReplicator.cs 
b/src/Lucene.Net.Replicator/LocalReplicator.cs
index a7c06b7df..52f30e87a 100644
--- a/src/Lucene.Net.Replicator/LocalReplicator.cs
+++ b/src/Lucene.Net.Replicator/LocalReplicator.cs
@@ -99,13 +99,15 @@ namespace Lucene.Net.Replicator
             public SessionToken Session { get; private set; }
             public RefCountedRevision Revision { get; private set; }
 
-            private long lastAccessTime;
+            // LUCENENET: was volatile long in Lucene, but that is not valid 
in .NET
+            // Instead, we use AtomicInt64 to ensure atomicity.
+            private readonly AtomicInt64 lastAccessTime;
 
             public ReplicationSession(SessionToken session, RefCountedRevision 
revision)
             {
                 Session = session;
                 Revision = revision;
-                lastAccessTime = Stopwatch.GetTimestamp(); // LUCENENET: Use 
the most accurate timer to determine expiration
+                lastAccessTime = new AtomicInt64(Stopwatch.GetTimestamp()); // 
LUCENENET: Use the most accurate timer to determine expiration
             }
 
             public virtual bool IsExpired(long expirationThreshold)
@@ -115,7 +117,7 @@ namespace Lucene.Net.Replicator
 
             public virtual void MarkAccessed()
             {
-                lastAccessTime = Stopwatch.GetTimestamp(); // LUCENENET: Use 
the most accurate timer to determine expiration
+                lastAccessTime.Value = Stopwatch.GetTimestamp(); // LUCENENET: 
Use the most accurate timer to determine expiration
             }
         }
 
diff --git a/src/Lucene.Net.Tests._J-S/Lucene.Net.Tests._J-S.csproj 
b/src/Lucene.Net.Tests._J-S/Lucene.Net.Tests._J-S.csproj
index 22b6666d5..23759b847 100644
--- a/src/Lucene.Net.Tests._J-S/Lucene.Net.Tests._J-S.csproj
+++ b/src/Lucene.Net.Tests._J-S/Lucene.Net.Tests._J-S.csproj
@@ -22,7 +22,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <Import Project="$(SolutionDir)TestTargetFramework.props" />
-  
+
   <PropertyGroup>
     <AssemblyTitle>Lucene.Net.Tests._J-S</AssemblyTitle>
     <RootNamespace>Lucene.Net</RootNamespace>
@@ -80,8 +80,8 @@
   <ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
     <PackageReference 
Include="System.Runtime.InteropServices.RuntimeInformation" 
Version="$(SystemRuntimeInteropServicesRuntimeInformationPackageVersion)" />
   </ItemGroup>
-  
+
   <ItemGroup>
     <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/Lucene.Net.Tests/Support/Threading/AtomicDoubleTest.cs 
b/src/Lucene.Net.Tests/Support/Threading/AtomicDoubleTest.cs
new file mode 100644
index 000000000..226b07129
--- /dev/null
+++ b/src/Lucene.Net.Tests/Support/Threading/AtomicDoubleTest.cs
@@ -0,0 +1,255 @@
+using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using System;
+using System.Threading;
+
+namespace Lucene.Net.Threading
+{
+    /*
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements.  See the NOTICE file distributed with
+     * this work for additional information regarding copyright ownership.
+     * The ASF licenses this file to You under the Apache License, Version 2.0
+     * (the "License"); you may not use this file except in compliance with
+     * the License.  You may obtain a copy of the License at
+     *
+     *     http://www.apache.org/licenses/LICENSE-2.0
+     *
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS,
+     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     * See the License for the specific language governing permissions and
+     * limitations under the License.
+     */
+
+    /// <summary>
+    /// This is a modified copy of J2N's TestAtomicInt64,
+    /// modified to test <see cref="AtomicDouble"/>
+    /// </summary>
+    [TestFixture]
+    public class AtomicDoubleTest : LuceneTestCase
+    {
+        private const int LONG_DELAY_MS = 50 * 50;
+
+        /**
+         * fail with message "Unexpected exception"
+         */
+        public void unexpectedException()
+        {
+            fail("Unexpected exception");
+        }
+
+        /**
+         * constructor initializes to given value
+         */
+        [Test]
+        public void TestConstructor()
+        {
+            AtomicDouble ai = new AtomicDouble(1.0d);
+            assertEquals(1.0d, ai);
+        }
+
+        /**
+         * default constructed initializes to zero
+         */
+        [Test]
+        public void TestConstructor2()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            assertEquals(0.0d, ai.Value);
+        }
+
+        /**
+         * get returns the last value set
+         */
+        [Test]
+        public void TestGetSet()
+        {
+            AtomicDouble ai = new AtomicDouble(1);
+            assertEquals(1.0d, ai);
+            ai.Value = 2.0d;
+            assertEquals(2.0d, ai);
+            ai.Value = -3.0d;
+            assertEquals(-3.0d, ai);
+
+        }
+
+        /**
+         * compareAndSet succeeds in changing value if equal to expected else 
fails
+         */
+        [Test]
+        public void TestCompareAndSet()
+        {
+            AtomicDouble ai = new AtomicDouble(1.0d);
+            assertTrue(ai.CompareAndSet(1.0d, 2.0d));
+            assertTrue(ai.CompareAndSet(2.0d, -4.0d));
+            assertEquals(-4.0d, ai.Value);
+            assertFalse(ai.CompareAndSet(-5.0d, 7.0d));
+            assertFalse(7.0d.Equals(ai.Value));
+            assertTrue(ai.CompareAndSet(-4.0d, 7.0d));
+            assertEquals(7.0d, ai.Value);
+        }
+
+        /**
+         * compareAndSet in one thread enables another waiting for value
+         * to succeed
+         */
+        [Test]
+        public void TestCompareAndSetInMultipleThreads()
+        {
+            AtomicDouble ai = new AtomicDouble(1.0d);
+            Thread t = new Thread(() =>
+            {
+                while (!ai.CompareAndSet(2.0d, 3.0d)) Thread.Yield();
+            });
+            try
+            {
+                t.Start();
+                assertTrue(ai.CompareAndSet(1.0d, 2.0d));
+                t.Join(LONG_DELAY_MS);
+                assertFalse(t.IsAlive);
+                assertEquals(ai.Value, 3.0d);
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+        }
+
+        //    /**
+        //     * repeated weakCompareAndSet succeeds in changing value when 
equal
+        //     * to expected
+        //     */
+        //[Test]
+        //    public void TestWeakCompareAndSet()
+        //{
+        //    AtomicDouble ai = new AtomicDouble(1);
+        //    while (!ai.WeakCompareAndSet(1, 2)) ;
+        //    while (!ai.WeakCompareAndSet(2, -4)) ;
+        //    assertEquals(-4, ai.Value);
+        //    while (!ai.WeakCompareAndSet(-4, 7)) ;
+        //    assertEquals(7, ai.Value);
+        //}
+
+        /**
+         * getAndSet returns previous value and sets to given value
+         */
+        [Test]
+        public void TestGetAndSet()
+        {
+            AtomicDouble ai = new AtomicDouble(1.0d);
+            assertEquals(1.0d, ai.GetAndSet(0.0d));
+            assertEquals(0.0d, ai.GetAndSet(-10.0d));
+            assertEquals(-10.0d, ai.GetAndSet(1.0d));
+        }
+
+#if FEATURE_SERIALIZABLE
+        /**
+         * a deserialized serialized atomic holds same value
+         */
+        [Test]
+        public void TestSerialization()
+        {
+            AtomicDouble l = new AtomicDouble();
+
+            try
+            {
+                l.Value = 22.0d;
+                AtomicDouble r = Clone(l);
+                assertEquals(l.Value, r.Value);
+            }
+            catch (Exception /*e*/)
+            {
+                unexpectedException();
+            }
+        }
+#endif
+
+        /**
+         * toString returns current value.
+         */
+        [Test]
+        public void TestToString()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            for (double i = -12.0d; i < 6.0d; i += 1.0d)
+            {
+                ai.Value = i;
+                assertEquals(ai.ToString(), J2N.Numerics.Double.ToString(i));
+            }
+        }
+
+        /**
+         * intValue returns current value.
+         */
+        [Test]
+        public void TestIntValue()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            for (double i = -12.0d; i < 6.0d; ++i)
+            {
+                ai.Value = i;
+                assertEquals((int)i, Convert.ToInt32(ai));
+            }
+        }
+
+
+        /**
+         * longValue returns current value.
+         */
+        [Test]
+        public void TestLongValue()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            for (double i = -12.0d; i < 6.0d; ++i)
+            {
+                ai.Value = i;
+                assertEquals((long)i, Convert.ToInt64(ai));
+            }
+        }
+
+        /**
+         * floatValue returns current value.
+         */
+        [Test]
+        public void TestFloatValue()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            for (double i = -12.0d; i < 6.0d; ++i)
+            {
+                ai.Value = i;
+                assertEquals((float)i, Convert.ToSingle(ai));
+            }
+        }
+
+        /**
+         * doubleValue returns current value.
+         */
+        [Test]
+        public void TestDoubleValue()
+        {
+            AtomicDouble ai = new AtomicDouble();
+            for (double i = -12.0d; i < 6.0d; ++i)
+            {
+                ai.Value = i;
+                assertEquals((double)i, Convert.ToDouble(ai));
+            }
+        }
+
+        /**
+        * doubleValue returns current value.
+        */
+        [Test]
+        public void TestComparisonOperators()
+        {
+            AtomicDouble ai = new AtomicDouble(6.0d);
+            assertTrue(5.0d < ai);
+            assertTrue(9.0d > ai);
+            assertTrue(ai > 4.0d);
+            assertTrue(ai < 7.0d);
+            assertFalse(ai < 6.0d);
+            assertTrue(ai <= 6.0d);
+        }
+    }
+}
diff --git a/src/Lucene.Net/Index/IndexWriter.cs 
b/src/Lucene.Net/Index/IndexWriter.cs
index 1549e82d2..f98cdd709 100644
--- a/src/Lucene.Net/Index/IndexWriter.cs
+++ b/src/Lucene.Net/Index/IndexWriter.cs
@@ -228,13 +228,14 @@ namespace Lucene.Net.Index
         private readonly Directory directory; // where this index resides
         private readonly Analyzer analyzer; // how to analyze text
 
-        private long changeCount; // increments every time a change is 
completed
-        private long lastCommitChangeCount; // last changeCount that was 
committed
+        // LUCENENET specific - since we don't have `volatile long` in .NET, 
we use AtomicInt64
+        private readonly AtomicInt64 changeCount = new AtomicInt64(); // 
increments every time a change is completed
+        private readonly AtomicInt64 lastCommitChangeCount = new 
AtomicInt64(); // last changeCount that was committed
 
         private IList<SegmentCommitInfo> rollbackSegments; // list of 
segmentInfo we will fallback to if the commit fails
 
         internal volatile SegmentInfos pendingCommit; // set when a commit is 
pending (after prepareCommit() & before commit())
-        internal long pendingCommitChangeCount;
+        internal readonly AtomicInt64 pendingCommitChangeCount = new 
AtomicInt64(); // LUCENENET specific - since we don't have `volatile long` in 
.NET, we use AtomicInt64
 
         private ICollection<string> filesToCommit;
 
@@ -2197,7 +2198,7 @@ namespace Lucene.Net.Index
                 // could close, re-open and re-return the same segment
                 // name that was previously returned which can cause
                 // problems at least with ConcurrentMergeScheduler.
-                changeCount++;
+                _ = changeCount.IncrementAndGet();
                 segmentInfos.Changed();
                 return "_" + 
SegmentInfos.SegmentNumberToString(segmentInfos.Counter++, allowLegacyNames: 
false); // LUCENENET specific - we had this right thru all of the betas, so 
don't change if the legacy feature is enabled
             }
@@ -2821,7 +2822,7 @@ namespace Lucene.Net.Index
                     deleter.Checkpoint(segmentInfos, false);
                     deleter.Refresh();
 
-                    lastCommitChangeCount = changeCount;
+                    lastCommitChangeCount.Value = changeCount.Value;
 
                     deleter.Refresh();
                     deleter.Dispose();
@@ -2957,7 +2958,7 @@ namespace Lucene.Net.Index
                             // Don't bother saving any changes in our 
segmentInfos
                             readerPool.DropAll(false);
                             // Mark that the index has changed
-                            ++changeCount;
+                            _ = changeCount.IncrementAndGet();
                             segmentInfos.Changed();
                             globalFieldNumberMap.Clear();
                             success = true;
@@ -3128,7 +3129,7 @@ namespace Lucene.Net.Index
             UninterruptableMonitor.Enter(this);
             try
             {
-                changeCount++;
+                _ = changeCount.IncrementAndGet();
                 deleter.Checkpoint(segmentInfos, false);
             }
             finally
@@ -3144,7 +3145,7 @@ namespace Lucene.Net.Index
             UninterruptableMonitor.Enter(this);
             try
             {
-                changeCount++;
+                _ = changeCount.IncrementAndGet();
                 segmentInfos.Changed();
             }
             finally
@@ -3935,7 +3936,7 @@ namespace Lucene.Net.Index
                                 // sneak into the commit point:
                                 toCommit = (SegmentInfos)segmentInfos.Clone();
 
-                                pendingCommitChangeCount = changeCount;
+                                pendingCommitChangeCount.Value = 
changeCount.Value;
 
                                 // this protects the segmentInfos we are now 
going
                                 // to commit.  this is important in case, eg, 
while
@@ -4027,7 +4028,7 @@ namespace Lucene.Net.Index
             try
             {
                 segmentInfos.UserData = new Dictionary<string, 
string>(commitUserData);
-                ++changeCount;
+                _ = changeCount.IncrementAndGet();
             }
             finally
             {
@@ -4170,7 +4171,7 @@ namespace Lucene.Net.Index
                             infoStream.Message("IW", "commit: wrote segments 
file \"" + pendingCommit.GetSegmentsFileName() + "\"");
                         }
                         segmentInfos.UpdateGeneration(pendingCommit);
-                        lastCommitChangeCount = pendingCommitChangeCount;
+                        lastCommitChangeCount.Value = 
pendingCommitChangeCount.Value;
                         rollbackSegments = 
pendingCommit.CreateBackupSegmentInfos();
                         // NOTE: don't use this.checkpoint() here, because
                         // we do not want to increment changeCount:
@@ -5144,8 +5145,8 @@ namespace Lucene.Net.Index
                         int delCount = NumDeletedDocs(info);
                         if (Debugging.AssertsEnabled) 
Debugging.Assert(delCount <= info.Info.DocCount);
                         double delRatio = ((double)delCount) / 
info.Info.DocCount;
-                        merge.EstimatedMergeBytes += 
(long)(info.GetSizeInBytes() * (1.0 - delRatio));
-                        merge.totalMergeBytes += info.GetSizeInBytes();
+                        _ = 
merge.estimatedMergeBytes.AddAndGet((long)(info.GetSizeInBytes() * (1.0 - 
delRatio)));
+                        _ = 
merge.totalMergeBytes.AddAndGet(info.GetSizeInBytes());
                     }
                 }
 
diff --git a/src/Lucene.Net/Index/IndexWriterConfig.cs 
b/src/Lucene.Net/Index/IndexWriterConfig.cs
index 7985d4732..8ac9cdbe9 100644
--- a/src/Lucene.Net/Index/IndexWriterConfig.cs
+++ b/src/Lucene.Net/Index/IndexWriterConfig.cs
@@ -293,8 +293,8 @@ namespace Lucene.Net.Index
         // so must declare it new. See: http://stackoverflow.com/q/82437
         new public long WriteLockTimeout
         {
-            get => writeLockTimeout;
-            set => this.writeLockTimeout = value;
+            get => writeLockTimeout.Value;
+            set => writeLockTimeout.Value = value;
         }
 
         /// <summary>
diff --git a/src/Lucene.Net/Index/LiveIndexWriterConfig.cs 
b/src/Lucene.Net/Index/LiveIndexWriterConfig.cs
index 6dd186cbb..f81aa7773 100644
--- a/src/Lucene.Net/Index/LiveIndexWriterConfig.cs
+++ b/src/Lucene.Net/Index/LiveIndexWriterConfig.cs
@@ -1,4 +1,6 @@
-using Lucene.Net.Util;
+using J2N.Threading.Atomic;
+using Lucene.Net.Support.Threading;
+using Lucene.Net.Util;
 using System;
 using System.Text;
 
@@ -40,7 +42,7 @@ namespace Lucene.Net.Index
         private readonly Analyzer analyzer;
 
         private volatile int maxBufferedDocs;
-        private double ramBufferSizeMB;
+        private readonly AtomicDouble ramBufferSizeMB; // LUCENENET specific: 
Changed from volatile double to AtomicDouble
         private volatile int maxBufferedDeleteTerms;
         private volatile int readerTermsIndexDivisor;
         private volatile IndexReaderWarmer mergedSegmentWarmer;
@@ -48,7 +50,7 @@ namespace Lucene.Net.Index
 
         // LUCENENET specific: Volatile fields are not CLS compliant,
         // so we are making them internal. This class cannot be inherited
-        // from outside of the assembly anyway, since it has no public 
+        // from outside of the assembly anyway, since it has no public
         // constructors, so protected members are moot.
 
         // modified by IndexWriterConfig
@@ -80,7 +82,8 @@ namespace Lucene.Net.Index
 
         /// <summary>
         /// Timeout when trying to obtain the write lock on init. </summary>
-        internal long writeLockTimeout;
+        // LUCENENET specific - since we don't have `volatile long` in .NET, 
we use AtomicInt64
+        internal readonly AtomicInt64 writeLockTimeout;
 
         /// <summary>
         /// <see cref="DocumentsWriterPerThread.IndexingChain"/> that 
determines how documents are
@@ -139,7 +142,7 @@ namespace Lucene.Net.Index
         {
             this.analyzer = analyzer;
             this.matchVersion = matchVersion;
-            ramBufferSizeMB = IndexWriterConfig.DEFAULT_RAM_BUFFER_SIZE_MB;
+            ramBufferSizeMB = new 
AtomicDouble(IndexWriterConfig.DEFAULT_RAM_BUFFER_SIZE_MB);
             maxBufferedDocs = IndexWriterConfig.DEFAULT_MAX_BUFFERED_DOCS;
             maxBufferedDeleteTerms = 
IndexWriterConfig.DEFAULT_MAX_BUFFERED_DELETE_TERMS;
             readerTermsIndexDivisor = 
IndexWriterConfig.DEFAULT_READER_TERMS_INDEX_DIVISOR;
@@ -151,7 +154,7 @@ namespace Lucene.Net.Index
             openMode = Index.OpenMode.CREATE_OR_APPEND;
             similarity = IndexSearcher.DefaultSimilarity;
             mergeScheduler = new ConcurrentMergeScheduler();
-            writeLockTimeout = IndexWriterConfig.WRITE_LOCK_TIMEOUT;
+            writeLockTimeout = new 
AtomicInt64(IndexWriterConfig.WRITE_LOCK_TIMEOUT);
             indexingChain = DocumentsWriterPerThread.DefaultIndexingChain;
             codec = Codec.Default;
             if (codec is null)
@@ -175,7 +178,7 @@ namespace Lucene.Net.Index
             maxBufferedDeleteTerms = config.MaxBufferedDeleteTerms;
             maxBufferedDocs = config.MaxBufferedDocs;
             mergedSegmentWarmer = config.MergedSegmentWarmer;
-            ramBufferSizeMB = config.RAMBufferSizeMB;
+            ramBufferSizeMB = new AtomicDouble(config.RAMBufferSizeMB);
             readerTermsIndexDivisor = config.ReaderTermsIndexDivisor;
             termIndexInterval = config.TermIndexInterval;
             matchVersion = config.matchVersion;
@@ -185,7 +188,7 @@ namespace Lucene.Net.Index
             openMode = config.OpenMode;
             similarity = config.Similarity;
             mergeScheduler = config.MergeScheduler;
-            writeLockTimeout = config.WriteLockTimeout;
+            writeLockTimeout = new AtomicInt64(config.WriteLockTimeout);
             indexingChain = config.IndexingChain;
             codec = config.Codec;
             infoStream = config.InfoStream;
@@ -239,7 +242,7 @@ namespace Lucene.Net.Index
         /// {
         ///     //customize Lucene41PostingsFormat, passing minBlockSize=50, 
maxBlockSize=100
         ///     private readonly PostingsFormat tweakedPostings = new 
Lucene41PostingsFormat(50, 100);
-        /// 
+        ///
         ///     public override PostingsFormat 
GetPostingsFormatForField(string field)
         ///     {
         ///         if (field.Equals("fieldWithTonsOfTerms", 
StringComparison.Ordinal))
@@ -249,7 +252,7 @@ namespace Lucene.Net.Index
         ///     }
         /// }
         /// ...
-        /// 
+        ///
         /// iwc.Codec = new MyLucene45Codec();
         /// </code>
         /// Note that other implementations may have their own parameters, or 
no parameters at all.
@@ -351,7 +354,7 @@ namespace Lucene.Net.Index
                 {
                     throw new ArgumentException("at least one of 
ramBufferSizeMB and maxBufferedDocs must be enabled");
                 }
-                this.ramBufferSizeMB = value;
+                this.ramBufferSizeMB.Value = value;
             }
         }
 
@@ -587,4 +590,4 @@ namespace Lucene.Net.Index
             return sb.ToString();
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Index/MergePolicy.cs 
b/src/Lucene.Net/Index/MergePolicy.cs
index efacf2e57..325eef7f2 100644
--- a/src/Lucene.Net/Index/MergePolicy.cs
+++ b/src/Lucene.Net/Index/MergePolicy.cs
@@ -1,4 +1,5 @@
-using Lucene.Net.Diagnostics;
+using J2N.Threading.Atomic;
+using Lucene.Net.Diagnostics;
 using Lucene.Net.Support;
 using Lucene.Net.Support.Threading;
 using Lucene.Net.Util;
@@ -128,12 +129,17 @@ namespace Lucene.Net.Index
             }
             private int maxNumSegments = -1;
 
+            // LUCENENET NOTE: original was volatile, using AtomicInt64 
instead.
+            // Also, we expose the value as a `long` property instead of 
exposing
+            // the AtomicInt64 object itself.
+            internal readonly AtomicInt64 estimatedMergeBytes = new 
AtomicInt64();
+
             /// <summary>
             /// Estimated size in bytes of the merged segment. </summary>
-            public long EstimatedMergeBytes { get; internal set; } // used by 
IndexWriter // LUCENENET NOTE: original was volatile, but long cannot be in .NET
+            public long EstimatedMergeBytes => estimatedMergeBytes.Value;
 
             // Sum of sizeInBytes of all SegmentInfos; set by IW.mergeInit
-            internal long totalMergeBytes; // LUCENENET NOTE: original was 
volatile, but long cannot be in .NET
+            internal readonly AtomicInt64 totalMergeBytes = new AtomicInt64(); 
// LUCENENET NOTE: original was volatile, using AtomicInt64 instead
 
             internal IList<SegmentReader> readers; // used by IndexWriter
 
@@ -413,7 +419,7 @@ namespace Lucene.Net.Index
             /// input total size. This is only set once the merge is
             /// initialized by <see cref="IndexWriter"/>.
             /// </summary>
-            public virtual long TotalBytesSize => totalMergeBytes;
+            public virtual long TotalBytesSize => totalMergeBytes.Value;
 
             /// <summary>
             /// Returns the total number of documents that are included with 
this merge.
diff --git a/src/Lucene.Net/Index/SegmentCommitInfo.cs 
b/src/Lucene.Net/Index/SegmentCommitInfo.cs
index 5ebf8ae02..374c10811 100644
--- a/src/Lucene.Net/Index/SegmentCommitInfo.cs
+++ b/src/Lucene.Net/Index/SegmentCommitInfo.cs
@@ -1,4 +1,5 @@
 using J2N.Collections.Generic.Extensions;
+using J2N.Threading.Atomic;
 using Lucene.Net.Support;
 using System;
 using System.Collections.Generic;
@@ -58,7 +59,7 @@ namespace Lucene.Net.Index
         // Track the per-generation updates files
         private readonly IDictionary<long, ISet<string>> genUpdatesFiles = new 
Dictionary<long, ISet<string>>();
 
-        private long sizeInBytes = -1; // LUCENENET NOTE: This was volatile in 
the original, but long cannot be volatile in .NET
+        private readonly AtomicInt64 sizeInBytes = new AtomicInt64(-1L); // 
LUCENENET NOTE: This was volatile in the original, using AtomicInt64 instead
 
         /// <summary>
         /// Sole constructor.
@@ -115,7 +116,7 @@ namespace Lucene.Net.Index
         {
             delGen = nextWriteDelGen;
             nextWriteDelGen = delGen + 1;
-            sizeInBytes = -1;
+            sizeInBytes.Value = -1;
         }
 
         /// <summary>
@@ -134,7 +135,7 @@ namespace Lucene.Net.Index
         {
             fieldInfosGen = nextWriteFieldInfosGen;
             nextWriteFieldInfosGen = fieldInfosGen + 1;
-            sizeInBytes = -1;
+            sizeInBytes.Value = -1;
         }
 
         /// <summary>
@@ -161,7 +162,7 @@ namespace Lucene.Net.Index
                 {
                     sum += Info.Dir.FileLength(fileName);
                 }
-                sizeInBytes = sum;
+                sizeInBytes.Value = sum;
             }
 
             return sizeInBytes;
@@ -198,7 +199,7 @@ namespace Lucene.Net.Index
         internal void SetBufferedDeletesGen(long value)
         {
             bufferedDeletesGen = value;
-            sizeInBytes = -1;
+            sizeInBytes.Value = -1;
         }
 
         /// <summary>
diff --git a/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs 
b/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
index 8c3e854bc..95a07df8b 100644
--- a/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
+++ b/src/Lucene.Net/Search/ControlledRealTimeReopenThread.cs
@@ -50,8 +50,13 @@ namespace Lucene.Net.Search
         private readonly long targetMinStaleNS;
         private readonly TrackingIndexWriter writer;
         private volatile bool finish;
+
+        // LUCENENET note: these fields were originally volatile in Lucene,
+        // but since access to them is always done under a lock or with 
Interlocked,
+        // no need to make these AtomicInt64.
         private long waitingGen;
         private long searchingGen;
+
         private readonly AtomicInt64 refreshStartGen = new AtomicInt64();
         private readonly AtomicBoolean isDisposed = new AtomicBoolean(false);
 
@@ -334,4 +339,4 @@ namespace Lucene.Net.Search
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Search/TimeLimitingCollector.cs 
b/src/Lucene.Net/Search/TimeLimitingCollector.cs
index 8a3993d20..b116435a7 100644
--- a/src/Lucene.Net/Search/TimeLimitingCollector.cs
+++ b/src/Lucene.Net/Search/TimeLimitingCollector.cs
@@ -1,4 +1,5 @@
 using J2N.Threading;
+using J2N.Threading.Atomic;
 using Lucene.Net.Support.Threading;
 using System;
 
@@ -43,7 +44,7 @@ namespace Lucene.Net.Search
     {
         /// <summary>
         /// Thrown when elapsed search time exceeds allowed search time. 
</summary>
-        // LUCENENET: It is no longer good practice to use binary 
serialization. 
+        // LUCENENET: It is no longer good practice to use binary 
serialization.
         // See: 
https://github.com/dotnet/corefx/issues/23584#issuecomment-325724568
 #if FEATURE_SERIALIZABLE_EXCEPTIONS
         [Serializable]
@@ -150,7 +151,7 @@ namespace Lucene.Net.Search
         ///     collector.SetBaseline(baseline);
         ///     indexSearcher.Search(query, collector);
         /// </code>
-        /// </para> 
+        /// </para>
         /// </summary>
         /// <seealso cref="SetBaseline()"/>
         public virtual void SetBaseline(long clockTime)
@@ -173,7 +174,7 @@ namespace Lucene.Net.Search
         /// A non greedy collector, upon a timeout, would throw a <see 
cref="TimeExceededException"/>
         /// without allowing the wrapped collector to collect current doc. A 
greedy one would
         /// first allow the wrapped hit collector to collect current doc and 
only then
-        /// throw a <see cref="TimeExceededException"/>. 
+        /// throw a <see cref="TimeExceededException"/>.
         /// </summary>
         public virtual bool IsGreedy
         {
@@ -213,7 +214,7 @@ namespace Lucene.Net.Search
                 SetBaseline();
             }
         }
-        
+
         public virtual void SetScorer(Scorer scorer)
         {
             collector.SetScorer(scorer);
@@ -239,7 +240,7 @@ namespace Lucene.Net.Search
         /// the global <see cref="TimerThread"/> has never been accessed 
before. The thread
         /// returned from this method is started on creation and will be alive 
unless
         /// you stop the <see cref="TimerThread"/> via <see 
cref="TimerThread.StopTimer()"/>.
-        /// </para> 
+        /// </para>
         /// @lucene.experimental
         /// </summary>
         /// <returns> the global TimerThreads <seealso cref="Counter"/> 
</returns>
@@ -291,18 +292,19 @@ namespace Lucene.Net.Search
             //   afford losing a tick or two.
             //
             // See section 17 of the Java Language Specification for details.
-            private readonly long time = 0;
+            // LUCENENET NOTE: despite the explanation above, this value is 
never modified.
+            private const long time = 0;
 
             private volatile bool stop = false;
-            private long resolution;
+            private readonly AtomicInt64 resolution; // LUCENENET: was 
volatile long, using AtomicInt64 instead
             internal readonly Counter counter;
 
             public TimerThread(long resolution, Counter counter)
                 : base(THREAD_NAME)
             {
-                this.resolution = resolution;
+                this.resolution = new AtomicInt64(resolution);
                 this.counter = counter;
-                this.IsBackground = (true);
+                this.IsBackground = true;
             }
 
             public TimerThread(Counter counter)
@@ -319,7 +321,7 @@ namespace Lucene.Net.Search
 
                     try
                     {
-                        
Thread.Sleep(TimeSpan.FromMilliseconds(Interlocked.Read(ref resolution)));
+                        
Thread.Sleep(TimeSpan.FromMilliseconds(resolution.Value));
                     }
                     catch (Exception ie) when (ie.IsInterruptedException())
                     {
@@ -346,7 +348,7 @@ namespace Lucene.Net.Search
             public long Resolution
             {
                 get => resolution;
-                set => this.resolution = Math.Max(value, 5); // 5 milliseconds 
is about the minimum reasonable time for a Object.wait(long) call.
+                set => this.resolution.Value = Math.Max(value, 5); // 5 
milliseconds is about the minimum reasonable time for a Object.wait(long) call.
             }
         }
     }
diff --git a/src/Lucene.Net/Store/RateLimiter.cs 
b/src/Lucene.Net/Store/RateLimiter.cs
index 9e972110c..192465e60 100644
--- a/src/Lucene.Net/Store/RateLimiter.cs
+++ b/src/Lucene.Net/Store/RateLimiter.cs
@@ -1,4 +1,5 @@
 using J2N;
+using J2N.Threading.Atomic;
 using Lucene.Net.Support.Threading;
 using System;
 using System.Threading;
@@ -46,7 +47,7 @@ namespace Lucene.Net.Store
         /// rate at or below the target.
         /// <para>
         /// Note: the implementation is thread-safe
-        /// </para> 
+        /// </para>
         /// </summary>
         /// <returns> the pause time in nano seconds </returns>
         public abstract long Pause(long bytes);
@@ -56,9 +57,11 @@ namespace Lucene.Net.Store
         /// </summary>
         public class SimpleRateLimiter : RateLimiter
         {
-            private double mbPerSec;
-            private double nsPerByte;
-            private long lastNS;
+            // LUCENENET: these fields are volatile in Lucene, but that is not
+            // valid in .NET. Instead, we use AtomicInt64/AtomicDouble to 
ensure atomicity.
+            private readonly AtomicDouble mbPerSec = new AtomicDouble();
+            private readonly AtomicDouble nsPerByte = new AtomicDouble();
+            private readonly AtomicInt64 lastNS = new AtomicInt64();
 
             // TODO: we could also allow eg a sub class to dynamically
             // determine the allowed rate, eg if an app wants to
@@ -76,11 +79,11 @@ namespace Lucene.Net.Store
             /// </summary>
             public override void SetMbPerSec(double mbPerSec)
             {
-                this.mbPerSec = mbPerSec;
+                this.mbPerSec.Value = mbPerSec;
                 if (mbPerSec == 0)
-                    nsPerByte = 0;
+                    nsPerByte.Value = 0;
                 else
-                    nsPerByte = 1000000000.0 / (1024 * 1024 * mbPerSec);
+                    nsPerByte.Value = 1000000000.0 / (1024 * 1024 * mbPerSec);
             }
 
             /// <summary>
@@ -106,12 +109,12 @@ namespace Lucene.Net.Store
 
                 // TODO: this is purely instantaneous rate; maybe we
                 // should also offer decayed recent history one?
-                var targetNS = lastNS = lastNS + ((long)(bytes * nsPerByte));
+                var targetNS = /*lastNS =*/ lastNS.AddAndGet((long)(bytes * 
nsPerByte));
                 long startNS;
                 var curNS = startNS = Time.NanoTime() /* ns */;
                 if (lastNS < curNS)
                 {
-                    lastNS = curNS;
+                    lastNS.Value = curNS;
                 }
 
                 // While loop because Thread.sleep doesn't always sleep
@@ -139,4 +142,4 @@ namespace Lucene.Net.Store
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/Lucene.Net/Support/Threading/AtomicDouble.cs 
b/src/Lucene.Net/Support/Threading/AtomicDouble.cs
new file mode 100644
index 000000000..3dce80b21
--- /dev/null
+++ b/src/Lucene.Net/Support/Threading/AtomicDouble.cs
@@ -0,0 +1,457 @@
+#region Copyright 2010 by Apache Harmony, Licensed under the Apache License, 
Version 2.0
+/*  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+#endregion
+
+using J2N;
+using J2N.Numerics;
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+#nullable enable
+
+namespace Lucene.Net.Support.Threading
+{
+    /// <summary>
+    /// A <see cref="double"/> value that may be updated atomically.
+    /// An <see cref="AtomicDouble"/> is used in applications such as 
atomically
+    /// stored and retrieved values, and cannot be used as a replacement
+    /// for a <see cref="System.Double"/>. However, this class does
+    /// implement implicit conversion to <see cref="double"/>, so it can
+    /// be utilized with language features, tools and utilities that deal
+    /// with numerical operations.
+    /// <para/>
+    /// NOTE: This is a modified version of <see 
cref="J2N.Threading.Atomic.AtomicInt64"/> to support <see cref="double"/> 
values.
+    /// It does not have the increment, decrement, and add methods because 
those operations are not atomic
+    /// due to the conversion to/from <see cref="long"/>.
+    /// </summary>
+    /// <remarks>
+    /// Note that this class is set up to mimic <c>double</c> in Java, rather 
than the J2N <see cref="J2N.Numerics.Double"/> class.
+    /// This may cause differences in comparing NaN values.
+    /// </remarks>
+#if FEATURE_SERIALIZABLE
+    [Serializable]
+#endif
+    [DebuggerDisplay("{Value}")]
+    internal class AtomicDouble : Number, IEquatable<AtomicDouble>, 
IEquatable<double>, IFormattable, IConvertible
+    {
+        private long value;
+
+        /// <summary>
+        /// Creates a new <see cref="AtomicDouble"/> with the default initial 
value, <c>0</c>.
+        /// </summary>
+        public AtomicDouble()
+            : this(0d)
+        { }
+
+        /// <summary>
+        /// Creates a new <see cref="AtomicDouble"/> with the given initial 
<paramref name="value"/>.
+        /// </summary>
+        /// <param name="value">The initial value.</param>
+        public AtomicDouble(double value)
+        {
+            this.value = BitConversion.DoubleToRawInt64Bits(value);
+        }
+
+        /// <summary>
+        /// Gets or sets the current value. Note that these operations can be 
done
+        /// implicitly by setting the <see cref="AtomicDouble"/> to a <see 
cref="double"/>.
+        /// <code>
+        /// AtomicDouble aDouble = new AtomicDouble(4.0);
+        /// double x = aDouble;
+        /// </code>
+        /// </summary>
+        /// <remarks>
+        /// Properties are inherently not atomic. Operators such as += and -= 
should not
+        /// be used on <see cref="Value"/> because they perform both a 
separate get and a set operation.
+        /// </remarks>
+        public double Value
+        {
+            get => BitConversion.Int64BitsToDouble(Interlocked.Read(ref 
this.value));
+            set => Interlocked.Exchange(ref this.value, 
BitConversion.DoubleToRawInt64Bits(value));
+        }
+
+        /// <summary>
+        /// Atomically sets to the given value and returns the old value.
+        /// </summary>
+        /// <param name="newValue">The new value.</param>
+        /// <returns>The previous value.</returns>
+        public double GetAndSet(double newValue)
+        {
+            return BitConversion.Int64BitsToDouble(Interlocked.Exchange(ref 
value, BitConversion.DoubleToRawInt64Bits(newValue)));
+        }
+
+        /// <summary>
+        /// Atomically sets the value to the given updated value
+        /// if the current value equals the expected value.
+        /// </summary>
+        /// <param name="expect">The expected value (the comparand).</param>
+        /// <param name="update">The new value that will be set if the current 
value equals the expected value.</param>
+        /// <returns><c>true</c> if successful. A <c>false</c> return value 
indicates that the actual value
+        /// was not equal to the expected value.</returns>
+        public bool CompareAndSet(double expect, double update)
+        {
+            long expectLong = BitConversion.DoubleToRawInt64Bits(expect);
+            long updateLong = BitConversion.DoubleToRawInt64Bits(update);
+            long rc = Interlocked.CompareExchange(ref value, updateLong, 
expectLong);
+            return rc == expectLong;
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="AtomicDouble"/> is 
equal to the current <see cref="AtomicDouble"/>.
+        /// </summary>
+        /// <param name="other">The <see cref="AtomicDouble"/> to compare with 
the current <see cref="AtomicDouble"/>.</param>
+        /// <returns><c>true</c> if <paramref name="other"/> is equal to the 
current <see cref="AtomicDouble"/>; otherwise, <c>false</c>.</returns>
+        public bool Equals(AtomicDouble? other)
+        {
+            if (other is null)
+                return false;
+
+            // NOTE: comparing long values rather than floating point 
comparison
+            return Interlocked.Read(ref value) == Interlocked.Read(ref 
other.value);
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="double"/> is equal to 
the current <see cref="AtomicDouble"/>.
+        /// </summary>
+        /// <param name="other">The <see cref="double"/> to compare with the 
current <see cref="AtomicDouble"/>.</param>
+        /// <returns><c>true</c> if <paramref name="other"/> is equal to the 
current <see cref="AtomicDouble"/>; otherwise, <c>false</c>.</returns>
+        public bool Equals(double other)
+        {
+            // NOTE: comparing long values rather than floating point 
comparison
+            return Interlocked.Read(ref value) == 
BitConversion.DoubleToRawInt64Bits(other);
+        }
+
+        /// <summary>
+        /// Determines whether the specified <see cref="object"/> is equal to 
the current <see cref="AtomicDouble"/>.
+        /// <para/>
+        /// If <paramref name="other"/> is a <see cref="AtomicDouble"/>, the 
comparison is not done atomically.
+        /// </summary>
+        /// <param name="other">The <see cref="object"/> to compare with the 
current <see cref="AtomicDouble"/>.</param>
+        /// <returns><c>true</c> if <paramref name="other"/> is equal to the 
current <see cref="AtomicDouble"/>; otherwise, <c>false</c>.</returns>
+        public override bool Equals(object? other)
+        {
+            if (other is AtomicDouble ad)
+                return Equals(ad);
+            if (other is double d)
+                return Equals(d);
+            return false;
+        }
+
+        /// <summary>
+        /// Returns the hash code for this instance.
+        /// </summary>
+        /// <returns>A 32-bit signed integer hash code.</returns>
+        public override int GetHashCode()
+        {
+            return Value.GetHashCode();
+        }
+
+        /// <summary>
+        /// Converts the numeric value of this instance to its equivalent 
string representation.
+        /// </summary>
+        /// <returns>The string representation of the value of this instance, 
consisting of
+        /// a negative sign if the value is negative,
+        /// and a sequence of digits ranging from 0 to 9 with no leading 
zeroes.</returns>
+        public override string ToString()
+        {
+            return J2N.Numerics.Double.ToString(Value);
+        }
+
+        /// <summary>
+        /// Converts the numeric value of this instance to its equivalent 
string representation,
+        /// using the specified <paramref name="format"/>.
+        /// </summary>
+        /// <param name="format">A standard or custom numeric format 
string.</param>
+        /// <returns>The string representation of the value of this instance 
as specified
+        /// by <paramref name="format"/>.</returns>
+        public override string ToString(string? format)
+        {
+            return J2N.Numerics.Double.ToString(Value, format);
+        }
+
+        /// <summary>
+        /// Converts the numeric value of this instance to its equivalent 
string representation
+        /// using the specified culture-specific format information.
+        /// </summary>
+        /// <param name="provider">An object that supplies culture-specific 
formatting information.</param>
+        /// <returns>The string representation of the value of this instance 
as specified by <paramref name="provider"/>.</returns>
+        public override string ToString(IFormatProvider? provider)
+        {
+            return J2N.Numerics.Double.ToString(Value, provider);
+        }
+
+        /// <summary>
+        /// Converts the numeric value of this instance to its equivalent 
string representation using the
+        /// specified format and culture-specific format information.
+        /// </summary>
+        /// <param name="format">A standard or custom numeric format 
string.</param>
+        /// <param name="provider">An object that supplies culture-specific 
formatting information.</param>
+        /// <returns>The string representation of the value of this instance 
as specified by
+        /// <paramref name="format"/> and <paramref 
name="provider"/>.</returns>
+        public override string ToString(string? format, IFormatProvider? 
provider)
+        {
+            return J2N.Numerics.Double.ToString(Value, format, provider);
+        }
+
+        #region TryFormat
+
+        /// <summary>
+        /// Tries to format the value of the current double instance into the 
provided span of characters.
+        /// </summary>
+        /// <param name="destination">The span in which to write this 
instance's value formatted as a span of characters.</param>
+        /// <param name="charsWritten">When this method returns, contains the 
number of characters that were written in
+        /// <paramref name="destination"/>.</param>
+        /// <param name="format">A span containing the characters that 
represent a standard or custom format string that
+        /// defines the acceptable format for <paramref 
name="destination"/>.</param>
+        /// <param name="provider">An optional object that supplies 
culture-specific formatting information for
+        /// <paramref name="destination"/>.</param>
+        /// <returns><c>true</c> if the formatting was successful; otherwise, 
<c>false</c>.</returns>
+        public override bool TryFormat(Span<char> destination, out int 
charsWritten, ReadOnlySpan<char> format = default, IFormatProvider? provider = 
null)
+        {
+            return J2N.Numerics.Double.TryFormat(Value, destination, out 
charsWritten, format, provider);
+        }
+
+        #endregion
+
+        #region IConvertible Members
+
+        /// <inheritdoc/>
+        public override byte ToByte()
+        {
+            return (byte)Value;
+        }
+
+        /// <inheritdoc/>
+        public override sbyte ToSByte()
+        {
+            return (sbyte)Value;
+        }
+
+        /// <inheritdoc/>
+        public override double ToDouble()
+        {
+            return Value;
+        }
+
+        /// <inheritdoc/>
+        public override float ToSingle()
+        {
+            return (float)Value;
+        }
+
+        /// <inheritdoc/>
+        public override int ToInt32()
+        {
+            return (int)Value;
+        }
+
+        /// <inheritdoc/>
+        public override long ToInt64()
+        {
+            return (long)Value;
+        }
+
+        /// <inheritdoc/>
+        public override short ToInt16()
+        {
+            return (short)Value;
+        }
+
+        /// <summary>
+        /// Returns the <see cref="TypeCode"/> for value type <see 
cref="int"/>.
+        /// </summary>
+        /// <returns></returns>
+        public TypeCode GetTypeCode() => ((IConvertible)Value).GetTypeCode();
+
+        bool IConvertible.ToBoolean(IFormatProvider? provider) => 
Convert.ToBoolean(Value);
+
+        byte IConvertible.ToByte(IFormatProvider? provider) => 
Convert.ToByte(Value);
+
+        char IConvertible.ToChar(IFormatProvider? provider) => 
Convert.ToChar(Value);
+
+        DateTime IConvertible.ToDateTime(IFormatProvider? provider) => 
Convert.ToDateTime(Value);
+
+        decimal IConvertible.ToDecimal(IFormatProvider? provider) => 
Convert.ToDecimal(Value);
+
+        double IConvertible.ToDouble(IFormatProvider? provider) => Value;
+
+        short IConvertible.ToInt16(IFormatProvider? provider) => 
Convert.ToInt16(Value);
+
+        int IConvertible.ToInt32(IFormatProvider? provider) => 
Convert.ToInt32(Value);
+
+        long IConvertible.ToInt64(IFormatProvider? provider) => 
Convert.ToInt64(Value);
+
+        sbyte IConvertible.ToSByte(IFormatProvider? provider) => 
Convert.ToSByte(Value);
+
+        float IConvertible.ToSingle(IFormatProvider? provider) => 
Convert.ToSingle(Value);
+
+        object IConvertible.ToType(Type conversionType, IFormatProvider? 
provider) => ((IConvertible)Value).ToType(conversionType, provider);
+
+        ushort IConvertible.ToUInt16(IFormatProvider? provider) => 
Convert.ToUInt16(Value);
+
+        uint IConvertible.ToUInt32(IFormatProvider? provider) => 
Convert.ToUInt32(Value);
+
+        ulong IConvertible.ToUInt64(IFormatProvider? provider) => 
Convert.ToUInt64(Value);
+
+        #endregion IConvertible Members
+
+        #region Operator Overrides
+
+        /// <summary>
+        /// Implicitly converts an <see cref="AtomicDouble"/> to a <see 
cref="double"/>.
+        /// </summary>
+        /// <param name="atomicDouble">The <see cref="AtomicDouble"/> to 
convert.</param>
+        public static implicit operator double(AtomicDouble atomicDouble)
+        {
+            return atomicDouble.Value;
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
equality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are equal; otherwise, 
<c>false</c>.</returns>
+        public static bool operator ==(AtomicDouble? a1, AtomicDouble? a2)
+        {
+            if (a1 is null)
+                return a2 is null;
+            if (a2 is null)
+                return false;
+
+            return a1.Equals(a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
inequality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are not equal; 
otherwise, <c>false</c>.</returns>
+        public static bool operator !=(AtomicDouble? a1, AtomicDouble? a2)
+        {
+            return !(a1 == a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
equality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are equal; otherwise, 
<c>false</c>.</returns>
+        public static bool operator ==(AtomicDouble? a1, double a2)
+        {
+            if (a1 is null)
+                return false;
+
+            return a1.Equals(a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
inequality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are not equal; 
otherwise, <c>false</c>.</returns>
+        public static bool operator !=(AtomicDouble? a1, double a2)
+        {
+            return !(a1 == a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
equality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are equal; otherwise, 
<c>false</c>.</returns>
+        public static bool operator ==(double a1, AtomicDouble? a2)
+        {
+            if (a2 is null)
+                return false;
+
+            return a2.Equals(a1);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
inequality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are not equal; 
otherwise, <c>false</c>.</returns>
+        public static bool operator !=(double a1, AtomicDouble? a2)
+        {
+            return !(a1 == a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
equality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are equal; otherwise, 
<c>false</c>.</returns>
+        public static bool operator ==(AtomicDouble? a1, double? a2)
+        {
+            if (a1 is null)
+                return a2 is null;
+            if (a2 is null)
+                return false;
+
+            return a1.Equals(a2.Value);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
inequality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are not equal; 
otherwise, <c>false</c>.</returns>
+        public static bool operator !=(AtomicDouble? a1, double? a2)
+        {
+            return !(a1 == a2);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
equality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are equal; otherwise, 
<c>false</c>.</returns>
+        public static bool operator ==(double? a1, AtomicDouble? a2)
+        {
+            if (a1 is null)
+                return a2 is null;
+            if (a2 is null)
+                return false;
+
+            return a2.Equals(a1.Value);
+        }
+
+        /// <summary>
+        /// Compares <paramref name="a1"/> and <paramref name="a2"/> for value 
inequality.
+        /// </summary>
+        /// <param name="a1">The first number.</param>
+        /// <param name="a2">The second number.</param>
+        /// <returns><c>true</c> if the given numbers are not equal; 
otherwise, <c>false</c>.</returns>
+        public static bool operator !=(double? a1, AtomicDouble? a2)
+        {
+            return !(a1 == a2);
+        }
+
+        #endregion Operator Overrides
+    }
+}

Reply via email to