Commit f3e9a65d authored by Jon Skeet's avatar Jon Skeet Committed by Jon Skeet

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.
parent 618f06fc
...@@ -84,6 +84,7 @@ csharp_EXTRA_DIST= \ ...@@ -84,6 +84,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs \ csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs \
csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs \ csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.cs \
csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs \ csharp/src/Google.Protobuf.Test/Collections/MapFieldTest.cs \
csharp/src/Google.Protobuf.Test/Collections/ProtobufEqualityComparersTest.cs \
csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs \ csharp/src/Google.Protobuf.Test/Collections/RepeatedFieldTest.cs \
csharp/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs \ csharp/src/Google.Protobuf.Test/Compatibility/PropertyInfoExtensionsTest.cs \
csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs \ csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs \
...@@ -104,6 +105,7 @@ csharp_EXTRA_DIST= \ ...@@ -104,6 +105,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \ csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \
csharp/src/Google.Protobuf.Test/SampleEnum.cs \ csharp/src/Google.Protobuf.Test/SampleEnum.cs \
csharp/src/Google.Protobuf.Test/SampleMessages.cs \ csharp/src/Google.Protobuf.Test/SampleMessages.cs \
csharp/src/Google.Protobuf.Test/SampleNaNs.cs \
csharp/src/Google.Protobuf.Test/TestCornerCases.cs \ csharp/src/Google.Protobuf.Test/TestCornerCases.cs \
csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \ csharp/src/Google.Protobuf.Test/TestProtos/ForeignMessagePartial.cs \
csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \ csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \
...@@ -126,6 +128,7 @@ csharp_EXTRA_DIST= \ ...@@ -126,6 +128,7 @@ csharp_EXTRA_DIST= \
csharp/src/Google.Protobuf/CodedOutputStream.ComputeSize.cs \ csharp/src/Google.Protobuf/CodedOutputStream.ComputeSize.cs \
csharp/src/Google.Protobuf/CodedOutputStream.cs \ csharp/src/Google.Protobuf/CodedOutputStream.cs \
csharp/src/Google.Protobuf/Collections/MapField.cs \ csharp/src/Google.Protobuf/Collections/MapField.cs \
csharp/src/Google.Protobuf/Collections/ProtobufEqualityComparers.cs \
csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs \ csharp/src/Google.Protobuf/Collections/ReadOnlyDictionary.cs \
csharp/src/Google.Protobuf/Collections/RepeatedField.cs \ csharp/src/Google.Protobuf/Collections/RepeatedField.cs \
csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs \ csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.cs \
......
...@@ -540,6 +540,49 @@ namespace Google.Protobuf.Collections ...@@ -540,6 +540,49 @@ namespace Google.Protobuf.Collections
Assert.Throws<ArgumentException>(() => map.ToString()); 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 #if !NET35
[Test] [Test]
public void IDictionaryKeys_Equals_IReadOnlyDictionaryKeys() public void IDictionaryKeys_Equals_IReadOnlyDictionaryKeys()
......
#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]);
}
}
}
}
}
}
...@@ -742,5 +742,18 @@ namespace Google.Protobuf.Collections ...@@ -742,5 +742,18 @@ namespace Google.Protobuf.Collections
var text = list.ToString(); var text = list.ToString();
Assert.AreEqual(text, "[ { \"foo\": 20 } ]", message.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));
}
} }
} }
...@@ -719,5 +719,16 @@ namespace Google.Protobuf ...@@ -719,5 +719,16 @@ namespace Google.Protobuf
JsonFormatter.Default.Format(message, writer); JsonFormatter.Default.Format(message, writer);
Assert.AreEqual("{ \"c\": 31 }", writer.ToString()); 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
#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);
}
}
...@@ -417,5 +417,16 @@ namespace Google.Protobuf.WellKnownTypes ...@@ -417,5 +417,16 @@ namespace Google.Protobuf.WellKnownTypes
TestWellKnownTypes.Descriptor.Fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.Clear(message); TestWellKnownTypes.Descriptor.Fields[TestWellKnownTypes.StringFieldFieldNumber].Accessor.Clear(message);
Assert.IsNull(message.StringField); 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);
}
} }
} }
...@@ -71,9 +71,12 @@ namespace Google.Protobuf.Collections ...@@ -71,9 +71,12 @@ namespace Google.Protobuf.Collections
, IReadOnlyDictionary<TKey, TValue> , IReadOnlyDictionary<TKey, TValue>
#endif #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.) // 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 = 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>>(); private readonly LinkedList<KeyValuePair<TKey, TValue>> list = new LinkedList<KeyValuePair<TKey, TValue>>();
/// <summary> /// <summary>
...@@ -131,11 +134,8 @@ namespace Google.Protobuf.Collections ...@@ -131,11 +134,8 @@ namespace Google.Protobuf.Collections
return map.ContainsKey(key); return map.ContainsKey(key);
} }
private bool ContainsValue(TValue value) private bool ContainsValue(TValue value) =>
{ list.Any(pair => ValueEqualityComparer.Equals(pair.Value, value));
var comparer = EqualityComparer<TValue>.Default;
return list.Any(pair => comparer.Equals(pair.Value, value));
}
/// <summary> /// <summary>
/// Removes the entry identified by the given key from the map. /// Removes the entry identified by the given key from the map.
...@@ -293,8 +293,7 @@ namespace Google.Protobuf.Collections ...@@ -293,8 +293,7 @@ namespace Google.Protobuf.Collections
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item) bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{ {
TValue value; TValue value;
return TryGetValue(item.Key, out value) return TryGetValue(item.Key, out value) && ValueEqualityComparer.Equals(item.Value, value);
&& EqualityComparer<TValue>.Default.Equals(item.Value, value);
} }
/// <summary> /// <summary>
...@@ -363,11 +362,12 @@ namespace Google.Protobuf.Collections ...@@ -363,11 +362,12 @@ namespace Google.Protobuf.Collections
/// </returns> /// </returns>
public override int GetHashCode() public override int GetHashCode()
{ {
var valueComparer = EqualityComparer<TValue>.Default; var keyComparer = KeyEqualityComparer;
var valueComparer = ValueEqualityComparer;
int hash = 0; int hash = 0;
foreach (var pair in list) 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; return hash;
} }
...@@ -394,7 +394,7 @@ namespace Google.Protobuf.Collections ...@@ -394,7 +394,7 @@ namespace Google.Protobuf.Collections
{ {
return false; return false;
} }
var valueComparer = EqualityComparer<TValue>.Default; var valueComparer = ValueEqualityComparer;
foreach (var pair in this) foreach (var pair in this)
{ {
TValue value; TValue value;
......
#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);
}
}
}
...@@ -51,6 +51,7 @@ namespace Google.Protobuf.Collections ...@@ -51,6 +51,7 @@ namespace Google.Protobuf.Collections
, IReadOnlyList<T> , IReadOnlyList<T>
#endif #endif
{ {
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T[] EmptyArray = new T[0]; private static readonly T[] EmptyArray = new T[0];
private const int MinArraySize = 8; private const int MinArraySize = 8;
...@@ -434,7 +435,7 @@ namespace Google.Protobuf.Collections ...@@ -434,7 +435,7 @@ namespace Google.Protobuf.Collections
{ {
return false; return false;
} }
EqualityComparer<T> comparer = EqualityComparer<T>.Default; EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
if (!comparer.Equals(array[i], other.array[i])) if (!comparer.Equals(array[i], other.array[i]))
...@@ -454,7 +455,7 @@ namespace Google.Protobuf.Collections ...@@ -454,7 +455,7 @@ namespace Google.Protobuf.Collections
public int IndexOf(T item) public int IndexOf(T item)
{ {
ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item)); ProtoPreconditions.CheckNotNullUnconstrained(item, nameof(item));
EqualityComparer<T> comparer = EqualityComparer<T>.Default; EqualityComparer<T> comparer = EqualityComparer;
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
if (comparer.Equals(array[i], item)) if (comparer.Equals(array[i], item))
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion #endregion
using Google.Protobuf.Collections;
using Google.Protobuf.Compatibility; using Google.Protobuf.Compatibility;
using Google.Protobuf.WellKnownTypes; using Google.Protobuf.WellKnownTypes;
using System; using System;
...@@ -346,6 +347,7 @@ namespace Google.Protobuf ...@@ -346,6 +347,7 @@ namespace Google.Protobuf
/// </remarks> /// </remarks>
public sealed class FieldCodec<T> public sealed class FieldCodec<T>
{ {
private static readonly EqualityComparer<T> EqualityComparer = ProtobufEqualityComparers.GetEqualityComparer<T>();
private static readonly T DefaultDefault; private static readonly T DefaultDefault;
// Only non-nullable value types support packing. This is the simplest way of detecting that. // Only non-nullable value types support packing. This is the simplest way of detecting that.
private static readonly bool TypeSupportsPacking = default(T) != null; private static readonly bool TypeSupportsPacking = default(T) != null;
...@@ -469,6 +471,6 @@ namespace Google.Protobuf ...@@ -469,6 +471,6 @@ namespace Google.Protobuf
/// </summary> /// </summary>
public int CalculateSizeWithTag(T value) => IsDefault(value) ? 0 : ValueSizeCalculator(value) + tagSize; 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);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment