aboutsummaryrefslogtreecommitdiffhomepage
path: root/csharp
diff options
context:
space:
mode:
authorGravatar Jon Skeet <jonskeet@google.com>2017-10-25 16:03:23 +0100
committerGravatar Jon Skeet <skeet@pobox.com>2017-12-09 09:49:24 +0000
commitf3e9a65d758d47633a161e526b35dea22bac9a5c (patch)
treea1733195e76c088c2f83093910c92e4d5609d275 /csharp
parent618f06fc9cd7fd872de8977dfe40294cadb4af21 (diff)
Compare floating point values bitwise in C#
This is the manual code part of the Google.Protobuf library, and tests. Some tests will fail until codegen is changed and rerun.
Diffstat (limited to 'csharp')
-rw-r--r--csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs43
-rw-r--r--csharp/src/Google.Protobuf.Test/Collections/ProtobufEqualityComparersTest.cs124
-rw-r--r--csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs13
-rw-r--r--csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs11
-rw-r--r--csharp/src/Google.Protobuf.Test/SampleNaNs.cs53
-rw-r--r--csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs11
-rw-r--r--csharp/src/Google.Protobuf/Collections/MapField.cs22
-rw-r--r--csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs130
-rwxr-xr-xcsharp/src/Google.Protobuf/Collections/RepeatedField.cs5
-rw-r--r--csharp/src/Google.Protobuf/FieldCodec.cs4
10 files changed, 402 insertions, 14 deletions
diff --git a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
index 68b4de45..8791dffc 100644
--- a/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs
@@ -540,6 +540,49 @@ namespace Google.Protobuf.Collections
Assert.Throws<ArgumentException>(() => map.ToString());
}
+ [Test]
+ public void NaNValuesComparedBitwise()
+ {
+ var map1 = new MapField<string, double>
+ {
+ { "x", SampleNaNs.Regular },
+ { "y", SampleNaNs.SignallingFlipped }
+ };
+
+ var map2 = new MapField<string, double>
+ {
+ { "x", SampleNaNs.Regular },
+ { "y", SampleNaNs.PayloadFlipped }
+ };
+
+ var map3 = new MapField<string, double>
+ {
+ { "x", SampleNaNs.Regular },
+ { "y", SampleNaNs.SignallingFlipped }
+ };
+
+ EqualityTester.AssertInequality(map1, map2);
+ EqualityTester.AssertEquality(map1, map3);
+ Assert.True(map1.Values.Contains(SampleNaNs.SignallingFlipped));
+ Assert.False(map2.Values.Contains(SampleNaNs.SignallingFlipped));
+ }
+
+ // This wouldn't usually happen, as protos can't use doubles as map keys,
+ // but let's be consistent.
+ [Test]
+ public void NaNKeysComparedBitwise()
+ {
+ var map = new MapField<double, string>
+ {
+ { SampleNaNs.Regular, "x" },
+ { SampleNaNs.SignallingFlipped, "y" }
+ };
+ Assert.AreEqual("x", map[SampleNaNs.Regular]);
+ Assert.AreEqual("y", map[SampleNaNs.SignallingFlipped]);
+ string ignored;
+ Assert.False(map.TryGetValue(SampleNaNs.PayloadFlipped, out ignored));
+ }
+
#if !NET35
[Test]
public void IDictionaryKeys_Equals_IReadOnlyDictionaryKeys()
diff --git a/csharp/src/Google.Protobuf.Test/Collections/ProtobufEqualityComparersTest.cs b/csharp/src/Google.Protobuf.Test/Collections/ProtobufEqualityComparersTest.cs
new file mode 100644
index 00000000..c76d7ca1
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/Collections/ProtobufEqualityComparersTest.cs
@@ -0,0 +1,124 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2017 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * 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.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// 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
+// OWNER 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.
+#endregion
+
+using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
+using static Google.Protobuf.Collections.ProtobufEqualityComparers;
+
+namespace Google.Protobuf.Collections
+{
+ public class ProtobufEqualityComparersTest
+ {
+ private static readonly double[] doubles =
+ {
+ 0,
+ 1,
+ 1.5,
+ -1.5,
+ double.PositiveInfinity,
+ double.NegativeInfinity,
+ // Three different types of NaN...
+ SampleNaNs.Regular,
+ SampleNaNs.SignallingFlipped,
+ SampleNaNs.PayloadFlipped
+ };
+
+ [Test]
+ public void GetEqualityComparer_Default()
+ {
+ // It's more pain than it's worth to try to parameterize these tests.
+ Assert.AreSame(EqualityComparer<object>.Default, GetEqualityComparer<object>());
+ Assert.AreSame(EqualityComparer<string>.Default, GetEqualityComparer<string>());
+ Assert.AreSame(EqualityComparer<int>.Default, GetEqualityComparer<int>());
+ Assert.AreSame(EqualityComparer<int?>.Default, GetEqualityComparer<int?>());
+ }
+
+ [Test]
+ public void GetEqualityComparer_NotDefault()
+ {
+ // It's more pain than it's worth to try to parameterize these tests.
+ Assert.AreSame(BitwiseDoubleEqualityComparer, GetEqualityComparer<double>());
+ Assert.AreSame(BitwiseSingleEqualityComparer, GetEqualityComparer<float>());
+ Assert.AreSame(BitwiseNullableDoubleEqualityComparer, GetEqualityComparer<double?>());
+ Assert.AreSame(BitwiseNullableSingleEqualityComparer, GetEqualityComparer<float?>());
+ }
+
+ [Test]
+ public void DoubleComparisons()
+ {
+ ValidateEqualityComparer(BitwiseDoubleEqualityComparer, doubles);
+ }
+
+ [Test]
+ public void NullableDoubleComparisons()
+ {
+ ValidateEqualityComparer(BitwiseNullableDoubleEqualityComparer, doubles.Select(d => (double?) d).Concat(new double?[] { null }));
+ }
+
+ [Test]
+ public void SingleComparisons()
+ {
+ ValidateEqualityComparer(BitwiseSingleEqualityComparer, doubles.Select(d => (float) d));
+ }
+
+ [Test]
+ public void NullableSingleComparisons()
+ {
+ ValidateEqualityComparer(BitwiseNullableSingleEqualityComparer, doubles.Select(d => (float?) d).Concat(new float?[] { null }));
+ }
+
+ private static void ValidateEqualityComparer<T>(EqualityComparer<T> comparer, IEnumerable<T> values)
+ {
+ var array = values.ToArray();
+ // Each value should be equal to itself, but not to any other value.
+ for (int i = 0; i < array.Length; i++)
+ {
+ for (int j = 0; j < array.Length; j++)
+ {
+ if (i == j)
+ {
+ Assert.IsTrue(comparer.Equals(array[i], array[j]),
+ "{0} should be equal to itself", array[i], array[j]);
+ }
+ else
+ {
+ Assert.IsFalse(comparer.Equals(array[i], array[j]),
+ "{0} and {1} should not be equal", array[i], array[j]);
+ Assert.AreNotEqual(comparer.GetHashCode(array[i]), comparer.GetHashCode(array[j]),
+ "Hash codes for {0} and {1} should not be equal", array[i], array[j]);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
index 6852f75f..129923b6 100644
--- a/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs
@@ -742,5 +742,18 @@ namespace Google.Protobuf.Collections
var text = list.ToString();
Assert.AreEqual(text, "[ { \"foo\": 20 } ]", message.ToString());
}
+
+ [Test]
+ public void NaNValuesComparedBitwise()
+ {
+ var list1 = new RepeatedField<double> { SampleNaNs.Regular, SampleNaNs.SignallingFlipped };
+ var list2 = new RepeatedField<double> { SampleNaNs.Regular, SampleNaNs.PayloadFlipped };
+ var list3 = new RepeatedField<double> { SampleNaNs.Regular, SampleNaNs.SignallingFlipped };
+
+ EqualityTester.AssertInequality(list1, list2);
+ EqualityTester.AssertEquality(list1, list3);
+ Assert.True(list1.Contains(SampleNaNs.SignallingFlipped));
+ Assert.False(list2.Contains(SampleNaNs.SignallingFlipped));
+ }
}
}
diff --git a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
index 8b153d69..8292d3e7 100644
--- a/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
+++ b/csharp/src/Google.Protobuf.Test/GeneratedMessageTest.cs
@@ -719,5 +719,16 @@ namespace Google.Protobuf
JsonFormatter.Default.Format(message, writer);
Assert.AreEqual("{ \"c\": 31 }", writer.ToString());
}
+
+ [Test]
+ public void NaNComparisons()
+ {
+ var message1 = new TestAllTypes { SingleDouble = SampleNaNs.Regular };
+ var message2 = new TestAllTypes { SingleDouble = SampleNaNs.PayloadFlipped };
+ var message3 = new TestAllTypes { SingleDouble = SampleNaNs.Regular };
+
+ EqualityTester.AssertInequality(message1, message2);
+ EqualityTester.AssertEquality(message1, message3);
+ }
}
} \ No newline at end of file
diff --git a/csharp/src/Google.Protobuf.Test/SampleNaNs.cs b/csharp/src/Google.Protobuf.Test/SampleNaNs.cs
new file mode 100644
index 00000000..08b50191
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Test/SampleNaNs.cs
@@ -0,0 +1,53 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2017 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * 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.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// 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
+// OWNER 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.
+#endregion
+
+using System;
+
+namespace Google.Protobuf
+{
+ /// <summary>
+ /// Samples of different not-a-number values, for testing equality comparisons.
+ /// </summary>
+ public static class SampleNaNs
+ {
+ public static double Regular { get; } = double.NaN;
+
+ // Signalling bit is inverted compared with double.NaN. Doesn't really matter
+ // whether that makes it quiet or signalling - it's different.
+ public static double SignallingFlipped { get; } =
+ BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(double.NaN) ^ -0x8000_0000_0000_0000L);
+
+ // A bit in the middle of the mantissa is flipped; this difference is preserved when casting to float.
+ public static double PayloadFlipped { get; } =
+ BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(double.NaN) ^ 0x1_0000_0000L);
+ }
+}
diff --git a/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs b/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
index 5b7185dc..8ed55744 100644
--- a/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
+++ b/csharp/src/Google.Protobuf.Test/WellKnownTypes/WrappersTest.cs
@@ -417,5 +417,16 @@ namespace Google.Protobuf.WellKnownTypes
TestWellKnownTypes.Descriptor.Fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.Clear(message);
Assert.IsNull(message.StringField);
}
+
+ [Test]
+ public void NaNComparisons()
+ {
+ var message1 = new TestWellKnownTypes { DoubleField = SampleNaNs.Regular };
+ var message2 = new TestWellKnownTypes { DoubleField = SampleNaNs.PayloadFlipped };
+ var message3 = new TestWellKnownTypes { DoubleField = SampleNaNs.Regular };
+
+ EqualityTester.AssertInequality(message1, message2);
+ EqualityTester.AssertEquality(message1, message3);
+ }
}
}
diff --git a/csharp/src/Google.Protobuf/Collections/MapField.cs b/csharp/src/Google.Protobuf/Collections/MapField.cs
index 8dac8e30..dbbcc148 100644
--- a/csharp/src/Google.Protobuf/Collections/MapField.cs
+++ b/csharp/src/Google.Protobuf/Collections/MapField.cs
@@ -71,9 +71,12 @@ namespace Google.Protobuf.Collections
, IReadOnlyDictionary<TKey, TValue>
#endif
{
+ private static readonly EqualityComparer<TValue> ValueEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TValue>();
+ private static readonly EqualityComparer<TKey> KeyEqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<TKey>();
+
// TODO: Don't create the map/list until we have an entry. (Assume many maps will be empty.)
private readonly Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>> map =
- new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>();
+ new Dictionary<TKey, LinkedListNode<KeyValuePair<TKey, TValue>>>(KeyEqualityComparer);
private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
/// <summary>
@@ -131,11 +134,8 @@ namespace Google.Protobuf.Collections
return map.ContainsKey(key);
}
- private bool ContainsValue(TValue value)
- {
- var comparer = EqualityComparer<TValue>.Default;
- return list.Any(pair => comparer.Equals(pair.Value, value));
- }
+ private bool ContainsValue(TValue value) =>
+ list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
/// <summary>
/// Removes the entry identified by the given key from the map.
@@ -293,8 +293,7 @@ namespace Google.Protobuf.Collections
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
TValue value;
- return TryGetValue(item.Key, out value)
- && EqualityComparer<TValue>.Default.Equals(item.Value, value);
+ return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
}
/// <summary>
@@ -363,11 +362,12 @@ namespace Google.Protobuf.Collections
/// </returns>
public override int GetHashCode()
{
- var valueComparer = EqualityComparer<TValue>.Default;
+ var keyComparer = KeyEqualityComparer;
+ var valueComparer = ValueEqualityComparer;
int hash = 0;
foreach (var pair in list)
{
- hash ^= pair.Key.GetHashCode() * 31 + valueComparer.GetHashCode(pair.Value);
+ hash ^= keyComparer.GetHashCode(pair.Key) * 31 + valueComparer.GetHashCode(pair.Value);
}
return hash;
}
@@ -394,7 +394,7 @@ namespace Google.Protobuf.Collections
{
return false;
}
- var valueComparer = EqualityComparer<TValue>.Default;
+ var valueComparer = ValueEqualityComparer;
foreach (var pair in this)
{
TValue value;
diff --git a/csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs b/csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs
new file mode 100644
index 00000000..13ef60fc
--- /dev/null
+++ b/csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs
@@ -0,0 +1,130 @@
+#region Copyright notice and license
+// Protocol Buffers - Google's data interchange format
+// Copyright 2017 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * 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.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// 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
+// OWNER 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.
+#endregion
+
+using System;
+using System.Collections.Generic;
+
+namespace Google.Protobuf.Collections
+{
+ /// <summary>
+ /// Provides a central place to implement equality comparisons, primarily for bitwise float/double equality.
+ /// </summary>
+ public static class ProtobufEqualityComparers
+ {
+ /// <summary>
+ /// Returns an equality comparer for <typeparamref name="T"/> suitable for Protobuf equality comparisons.
+ /// This is usually just the default equality comparer for the type, but floating point numbers are compared
+ /// bitwise.
+ /// </summary>
+ /// <typeparam name="T">The type of equality comparer to return.</typeparam>
+ /// <returns>The equality comparer.</returns>
+ public static EqualityComparer<T> GetEqualityComparer<T>()
+ {
+ return typeof(T) == typeof(double) ? (EqualityComparer<T>) (object) BitwiseDoubleEqualityComparer
+ : typeof(T) == typeof(float) ? (EqualityComparer<T>) (object) BitwiseSingleEqualityComparer
+ : typeof(T) == typeof(double?) ? (EqualityComparer<T>) (object) BitwiseNullableDoubleEqualityComparer
+ : typeof(T) == typeof(float?) ? (EqualityComparer<T>) (object) BitwiseNullableSingleEqualityComparer
+ : EqualityComparer<T>.Default;
+ }
+
+ /// <summary>
+ /// Returns an equality comparer suitable for comparing 64-bit floating point values, by bitwise comparison.
+ /// (NaN values are considered equal, but only when they have the same representation.)
+ /// </summary>
+ public static EqualityComparer<double> BitwiseDoubleEqualityComparer { get; } = new BitwiseDoubleEqualityComparerImpl();
+
+ /// <summary>
+ /// Returns an equality comparer suitable for comparing 32-bit floating point values, by bitwise comparison.
+ /// (NaN values are considered equal, but only when they have the same representation.)
+ /// </summary>
+ public static EqualityComparer<float> BitwiseSingleEqualityComparer { get; } = new BitwiseSingleEqualityComparerImpl();
+
+ /// <summary>
+ /// Returns an equality comparer suitable for comparing nullable 64-bit floating point values, by bitwise comparison.
+ /// (NaN values are considered equal, but only when they have the same representation.)
+ /// </summary>
+ public static EqualityComparer<double?> BitwiseNullableDoubleEqualityComparer { get; } = new BitwiseNullableDoubleEqualityComparerImpl();
+
+ /// <summary>
+ /// Returns an equality comparer suitable for comparing nullable 32-bit floating point values, by bitwise comparison.
+ /// (NaN values are considered equal, but only when they have the same representation.)
+ /// </summary>
+ public static EqualityComparer<float?> BitwiseNullableSingleEqualityComparer { get; } = new BitwiseNullableSingleEqualityComparerImpl();
+
+ private class BitwiseDoubleEqualityComparerImpl : EqualityComparer<double>
+ {
+ public override bool Equals(double x, double y) =>
+ BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
+
+ public override int GetHashCode(double obj) =>
+ BitConverter.DoubleToInt64Bits(obj).GetHashCode();
+ }
+
+ private class BitwiseSingleEqualityComparerImpl : EqualityComparer<float>
+ {
+ // Just promote values to double and use BitConverter.DoubleToInt64Bits,
+ // as there's no BitConverter.SingleToInt32Bits, unfortunately.
+
+ public override bool Equals(float x, float y) =>
+ BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y);
+
+ public override int GetHashCode(float obj) =>
+ BitConverter.DoubleToInt64Bits(obj).GetHashCode();
+ }
+
+ private class BitwiseNullableDoubleEqualityComparerImpl : EqualityComparer<double?>
+ {
+ public override bool Equals(double? x, double? y) =>
+ x == null && y == null ? true
+ : x == null || y == null ? false
+ : BitwiseDoubleEqualityComparer.Equals(x.Value, y.Value);
+
+ // The hash code for null is just a constant which is at least *unlikely* to be used
+ // elsewhere. (Compared with 0, say.)
+ public override int GetHashCode(double? obj) =>
+ obj == null ? 293864 : BitwiseDoubleEqualityComparer.GetHashCode(obj.Value);
+ }
+
+ private class BitwiseNullableSingleEqualityComparerImpl : EqualityComparer<float?>
+ {
+ public override bool Equals(float? x, float? y) =>
+ x == null && y == null ? true
+ : x == null || y == null ? false
+ : BitwiseSingleEqualityComparer.Equals(x.Value, y.Value);
+
+ // The hash code for null is just a constant which is at least *unlikely* to be used
+ // elsewhere. (Compared with 0, say.)
+ public override int GetHashCode(float? obj) =>
+ obj == null ? 293864 : BitwiseSingleEqualityComparer.GetHashCode(obj.Value);
+ }
+ }
+}
diff --git a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
index 6063ff61..c18b63e2 100755
--- a/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
+++ b/csharp/src/Google.Protobuf/Collections/RepeatedField.cs
@@ -51,6 +51,7 @@ namespace Google.Protobuf.Collections
, IReadOnlyList<T>
#endif
{
+ private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T[] EmptyArray = new T[0];
private const int MinArraySize = 8;
@@ -434,7 +435,7 @@ namespace Google.Protobuf.Collections
{
return false;
}
- EqualityComparer<T> comparer = EqualityComparer<T>.Default;
+ EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++)
{
if (!comparer.Equals(array[i], other.array[i]))
@@ -454,7 +455,7 @@ namespace Google.Protobuf.Collections
public int IndexOf(T item)
{
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
- EqualityComparer<T> comparer = EqualityComparer<T>.Default;
+ EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++)
{
if (comparer.Equals(array[i], item))
diff --git a/csharp/src/Google.Protobuf/FieldCodec.cs b/csharp/src/Google.Protobuf/FieldCodec.cs
index c28b47e1..a11f2420 100644
--- a/csharp/src/Google.Protobuf/FieldCodec.cs
+++ b/csharp/src/Google.Protobuf/FieldCodec.cs
@@ -30,6 +30,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
+using Google.Protobuf.Collections;
using Google.Protobuf.Compatibility;
using Google.Protobuf.WellKnownTypes;
using System;
@@ -346,6 +347,7 @@ namespace Google.Protobuf
/// </remarks>
public sealed class FieldCodec<T>
{
+ private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T DefaultDefault;
// Only non-nullable value types support packing. This is the simplest way of detecting that.
private static readonly bool TypeSupportsPacking = default(T) != null;
@@ -469,6 +471,6 @@ namespace Google.Protobuf
/// </summary>
public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize;
- private bool IsDefault(T value) => EqualityComparer<T>.Default.Equals(value, DefaultValue);
+ private bool IsDefault(T value) => EqualityComparer.Equals(value, DefaultValue);
}
}