Skip to content

Commit 0f9128e

Browse files
committed
avoid problem with literal "nan" / "inf" having inconsistent equality behaviour
1 parent b9da673 commit 0f9128e

File tree

2 files changed

+125
-2
lines changed

2 files changed

+125
-2
lines changed

src/StackExchange.Redis/RedisValue.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -986,7 +986,8 @@ internal RedisValue Simplify()
986986
if (Format.TryParseInt64(s, out i64)) return i64;
987987
if (Format.TryParseUInt64(s, out u64)) return u64;
988988
}
989-
if (Format.TryParseDouble(s, out var f64)) return f64;
989+
// note: don't simplify inf/nan, as that causes equality semantic problems
990+
if (Format.TryParseDouble(s, out var f64) && !IsSpecialDouble(f64)) return f64;
990991
break;
991992
case StorageType.Raw:
992993
var b = _memory.Span;
@@ -995,7 +996,8 @@ internal RedisValue Simplify()
995996
if (Format.TryParseInt64(b, out i64)) return i64;
996997
if (Format.TryParseUInt64(b, out u64)) return u64;
997998
}
998-
if (TryParseDouble(b, out f64)) return f64;
999+
// note: don't simplify inf/nan, as that causes equality semantic problems
1000+
if (TryParseDouble(b, out f64) && !IsSpecialDouble(f64)) return f64;
9991001
break;
10001002
case StorageType.Double:
10011003
// is the double actually an integer?
@@ -1006,6 +1008,8 @@ internal RedisValue Simplify()
10061008
return this;
10071009
}
10081010

1011+
private static bool IsSpecialDouble(double d) => double.IsNaN(d) || double.IsInfinity(d);
1012+
10091013
/// <summary>
10101014
/// Convert to a signed <see cref="long"/> if possible.
10111015
/// </summary>

tests/StackExchange.Redis.Tests/RedisValueEquivalencyTests.cs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,125 @@ static void Check(RedisValue known, RedisValue test)
157157
CheckString(double.NaN, "NaN");
158158
}
159159

160+
[Theory]
161+
[InlineData("na")]
162+
[InlineData("nan")]
163+
[InlineData("nans")]
164+
[InlineData("in")]
165+
[InlineData("inf")]
166+
[InlineData("info")]
167+
public void SpecialCaseEqualityRules_String(string value)
168+
{
169+
RedisValue x = value, y = value;
170+
Assert.Equal(x, y);
171+
172+
Assert.True(x.Equals(y));
173+
Assert.True(y.Equals(x));
174+
Assert.True(x == y);
175+
Assert.True(y == x);
176+
Assert.False(x != y);
177+
Assert.False(y != x);
178+
Assert.Equal(x.GetHashCode(), y.GetHashCode());
179+
}
180+
181+
[Theory]
182+
[InlineData("na")]
183+
[InlineData("nan")]
184+
[InlineData("nans")]
185+
[InlineData("in")]
186+
[InlineData("inf")]
187+
[InlineData("info")]
188+
public void SpecialCaseEqualityRules_Bytes(string value)
189+
{
190+
byte[] bytes0 = Encoding.UTF8.GetBytes(value),
191+
bytes1 = Encoding.UTF8.GetBytes(value);
192+
Assert.NotSame(bytes0, bytes1);
193+
RedisValue x = bytes0, y = bytes1;
194+
195+
Assert.True(x.Equals(y));
196+
Assert.True(y.Equals(x));
197+
Assert.True(x == y);
198+
Assert.True(y == x);
199+
Assert.False(x != y);
200+
Assert.False(y != x);
201+
Assert.Equal(x.GetHashCode(), y.GetHashCode());
202+
}
203+
204+
[Theory]
205+
[InlineData("na")]
206+
[InlineData("nan")]
207+
[InlineData("nans")]
208+
[InlineData("in")]
209+
[InlineData("inf")]
210+
[InlineData("info")]
211+
public void SpecialCaseEqualityRules_Hybrid(string value)
212+
{
213+
byte[] bytes = Encoding.UTF8.GetBytes(value);
214+
RedisValue x = bytes, y = value;
215+
216+
Assert.True(x.Equals(y));
217+
Assert.True(y.Equals(x));
218+
Assert.True(x == y);
219+
Assert.True(y == x);
220+
Assert.False(x != y);
221+
Assert.False(y != x);
222+
Assert.Equal(x.GetHashCode(), y.GetHashCode());
223+
}
224+
225+
[Theory]
226+
[InlineData("na", "NA")]
227+
[InlineData("nan", "NAN")]
228+
[InlineData("nans", "NANS")]
229+
[InlineData("in", "IN")]
230+
[InlineData("inf", "INF")]
231+
[InlineData("info", "INFO")]
232+
public void SpecialCaseNonEqualityRules_String(string s, string t)
233+
{
234+
RedisValue x = s, y = t;
235+
Assert.False(x.Equals(y));
236+
Assert.False(y.Equals(x));
237+
Assert.False(x == y);
238+
Assert.False(y == x);
239+
Assert.True(x != y);
240+
Assert.True(y != x);
241+
}
242+
243+
[Theory]
244+
[InlineData("na", "NA")]
245+
[InlineData("nan", "NAN")]
246+
[InlineData("nans", "NANS")]
247+
[InlineData("in", "IN")]
248+
[InlineData("inf", "INF")]
249+
[InlineData("info", "INFO")]
250+
public void SpecialCaseNonEqualityRules_Bytes(string s, string t)
251+
{
252+
RedisValue x = Encoding.UTF8.GetBytes(s), y = Encoding.UTF8.GetBytes(t);
253+
Assert.False(x.Equals(y));
254+
Assert.False(y.Equals(x));
255+
Assert.False(x == y);
256+
Assert.False(y == x);
257+
Assert.True(x != y);
258+
Assert.True(y != x);
259+
}
260+
261+
[Theory]
262+
[InlineData("na", "NA")]
263+
[InlineData("nan", "NAN")]
264+
[InlineData("nans", "NANS")]
265+
[InlineData("in", "IN")]
266+
[InlineData("inf", "INF")]
267+
[InlineData("info", "INFO")]
268+
public void SpecialCaseNonEqualityRules_Hybrid(string s, string t)
269+
{
270+
RedisValue x = s, y = Encoding.UTF8.GetBytes(t);
271+
Assert.False(x.Equals(y));
272+
Assert.False(y.Equals(x));
273+
Assert.False(x == y);
274+
Assert.False(y == x);
275+
Assert.True(x != y);
276+
Assert.True(y != x);
277+
}
278+
160279
private static void CheckString(RedisValue value, string expected)
161280
{
162281
var s = value.ToString();

0 commit comments

Comments
 (0)