1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2017 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using System.Collections.Generic; 12 13 namespace Google.Protobuf.Collections 14 { 15 /// <summary> 16 /// Provides a central place to implement equality comparisons, primarily for bitwise float/double equality. 17 /// </summary> 18 public static class ProtobufEqualityComparers 19 { 20 /// <summary> 21 /// Returns an equality comparer for <typeparamref name="T"/> suitable for Protobuf equality comparisons. 22 /// This is usually just the default equality comparer for the type, but floating point numbers are compared 23 /// bitwise. 24 /// </summary> 25 /// <typeparam name="T">The type of equality comparer to return.</typeparam> 26 /// <returns>The equality comparer.</returns> GetEqualityComparer()27 public static EqualityComparer<T> GetEqualityComparer<T>() 28 { 29 return typeof(T) == typeof(double) ? (EqualityComparer<T>) (object) BitwiseDoubleEqualityComparer 30 : typeof(T) == typeof(float) ? (EqualityComparer<T>) (object) BitwiseSingleEqualityComparer 31 : typeof(T) == typeof(double?) ? (EqualityComparer<T>) (object) BitwiseNullableDoubleEqualityComparer 32 : typeof(T) == typeof(float?) ? (EqualityComparer<T>) (object) BitwiseNullableSingleEqualityComparer 33 : EqualityComparer<T>.Default; 34 } 35 36 /// <summary> 37 /// Returns an equality comparer suitable for comparing 64-bit floating point values, by bitwise comparison. 38 /// (NaN values are considered equal, but only when they have the same representation.) 39 /// </summary> 40 public static EqualityComparer<double> BitwiseDoubleEqualityComparer { get; } = new BitwiseDoubleEqualityComparerImpl(); 41 42 /// <summary> 43 /// Returns an equality comparer suitable for comparing 32-bit floating point values, by bitwise comparison. 44 /// (NaN values are considered equal, but only when they have the same representation.) 45 /// </summary> 46 public static EqualityComparer<float> BitwiseSingleEqualityComparer { get; } = new BitwiseSingleEqualityComparerImpl(); 47 48 /// <summary> 49 /// Returns an equality comparer suitable for comparing nullable 64-bit floating point values, by bitwise comparison. 50 /// (NaN values are considered equal, but only when they have the same representation.) 51 /// </summary> 52 public static EqualityComparer<double?> BitwiseNullableDoubleEqualityComparer { get; } = new BitwiseNullableDoubleEqualityComparerImpl(); 53 54 /// <summary> 55 /// Returns an equality comparer suitable for comparing nullable 32-bit floating point values, by bitwise comparison. 56 /// (NaN values are considered equal, but only when they have the same representation.) 57 /// </summary> 58 public static EqualityComparer<float?> BitwiseNullableSingleEqualityComparer { get; } = new BitwiseNullableSingleEqualityComparerImpl(); 59 60 private class BitwiseDoubleEqualityComparerImpl : EqualityComparer<double> 61 { Equals(double x, double y)62 public override bool Equals(double x, double y) => 63 BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y); 64 GetHashCode(double obj)65 public override int GetHashCode(double obj) => 66 BitConverter.DoubleToInt64Bits(obj).GetHashCode(); 67 } 68 69 private class BitwiseSingleEqualityComparerImpl : EqualityComparer<float> 70 { 71 // Just promote values to double and use BitConverter.DoubleToInt64Bits, 72 // as there's no BitConverter.SingleToInt32Bits, unfortunately. 73 Equals(float x, float y)74 public override bool Equals(float x, float y) => 75 BitConverter.DoubleToInt64Bits(x) == BitConverter.DoubleToInt64Bits(y); 76 GetHashCode(float obj)77 public override int GetHashCode(float obj) => 78 BitConverter.DoubleToInt64Bits(obj).GetHashCode(); 79 } 80 81 private class BitwiseNullableDoubleEqualityComparerImpl : EqualityComparer<double?> 82 { Equals(double? x, double? y)83 public override bool Equals(double? x, double? y) => 84 x == null && y == null ? true 85 : x == null || y == null ? false 86 : BitwiseDoubleEqualityComparer.Equals(x.Value, y.Value); 87 88 // The hash code for null is just a constant which is at least *unlikely* to be used 89 // elsewhere. (Compared with 0, say.) GetHashCode(double? obj)90 public override int GetHashCode(double? obj) => 91 obj == null ? 293864 : BitwiseDoubleEqualityComparer.GetHashCode(obj.Value); 92 } 93 94 private class BitwiseNullableSingleEqualityComparerImpl : EqualityComparer<float?> 95 { Equals(float? x, float? y)96 public override bool Equals(float? x, float? y) => 97 x == null && y == null ? true 98 : x == null || y == null ? false 99 : BitwiseSingleEqualityComparer.Equals(x.Value, y.Value); 100 101 // The hash code for null is just a constant which is at least *unlikely* to be used 102 // elsewhere. (Compared with 0, say.) GetHashCode(float? obj)103 public override int GetHashCode(float? obj) => 104 obj == null ? 293864 : BitwiseSingleEqualityComparer.GetHashCode(obj.Value); 105 } 106 } 107 } 108