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.
This commit is contained in:
parent
618f06fc9c
commit
f3e9a65d75
@ -84,6 +84,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf.Test/CodedInputStreamTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/CodedOutputStreamTest.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/Compatibility/PropertyInfoExtensionsTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/Compatibility/StreamExtensionsTest.cs \
|
||||
@ -104,6 +105,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf.Test/Reflection/TypeRegistryTest.cs \
|
||||
csharp/src/Google.Protobuf.Test/SampleEnum.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/TestProtos/ForeignMessagePartial.cs \
|
||||
csharp/src/Google.Protobuf.Test/TestProtos/MapUnittestProto3.cs \
|
||||
@ -126,6 +128,7 @@ csharp_EXTRA_DIST= \
|
||||
csharp/src/Google.Protobuf/CodedOutputStream.ComputeSize.cs \
|
||||
csharp/src/Google.Protobuf/CodedOutputStream.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/RepeatedField.cs \
|
||||
csharp/src/Google.Protobuf/Compatibility/PropertyInfoExtensions.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()
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
53
csharp/src/Google.Protobuf.Test/SampleNaNs.cs
Normal file
53
csharp/src/Google.Protobuf.Test/SampleNaNs.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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))
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user