11using System ;
22using System . Buffers ;
33using System . Diagnostics ;
4+ using System . Runtime . CompilerServices ;
45using System . Threading . Tasks ;
56using RESPite . Messages ;
67
@@ -15,12 +16,22 @@ public RedisValue ReadRedisValue()
1516 reader . DemandScalar ( ) ;
1617 if ( reader . IsNull ) return RedisValue . Null ;
1718
18- return reader . Prefix switch
19+ switch ( reader . Prefix )
1920 {
20- RespPrefix . Boolean => reader . ReadBoolean ( ) ,
21- RespPrefix . Integer => reader . ReadInt64 ( ) ,
22- _ => reader . ReadByteArray ( ) ,
23- } ;
21+ case RespPrefix . Boolean :
22+ return reader . ReadBoolean ( ) ;
23+ case RespPrefix . Integer :
24+ return reader . ReadInt64 ( ) ;
25+ }
26+
27+ // bulk/simple/verbatim string: for inline scalars, prefer a compact numeric storage kind when
28+ // the text is the *canonical* representation of that number, so every projection (ToString,
29+ // (byte[]), equality, hash) still round-trips byte-for-byte; otherwise keep the raw bytes
30+ if ( reader . TryGetSpan ( out var span ) && TryReadCanonicalNumber ( span , out var number ) )
31+ {
32+ return number ;
33+ }
34+ return reader . ReadByteArray ( ) ;
2435 }
2536
2637 public string DebugReadTruncatedString ( int maxChars )
@@ -184,7 +195,7 @@ public static RespPrefix GetRespPrefix(ReadOnlySpan<byte> frame)
184195 RespPrefix . SimpleError => ResultType . Error ,
185196 RespPrefix . Null => ResultType . Null ,
186197 RespPrefix . VerbatimString => ResultType . VerbatimString ,
187- RespPrefix . Push => ResultType . Push ,
198+ RespPrefix . Push => ResultType . Push ,
188199 _ => throw new ArgumentOutOfRangeException ( nameof ( prefix ) , prefix , null ) ,
189200 } ;
190201 }
@@ -208,4 +219,67 @@ internal bool AnyNull()
208219 public bool IsCompletedSuccessfully => task. Status is TaskStatus. RanToCompletion;
209220 }
210221#endif
222+
223+ private static readonly int MaxCanonicalLength = Math . Max ( Format . MaxInt64TextLen , Format . MaxDoubleTextLen ) ;
224+
225+ // Recognizes the canonical decimal text of an integer or finite double, returning a numeric-backed
226+ // RedisValue only when re-formatting the parsed value reproduces the exact input bytes. This keeps the
227+ // optimization invisible to callers: non-canonical spellings ("01234", "+5", "1.50", "1e3"), oversize
228+ // values, and the special inf/nan tokens all return false and are kept as a byte[] payload by the caller.
229+ private static bool TryReadCanonicalNumber ( ReadOnlySpan < byte > span , out RedisValue value )
230+ {
231+ static bool Failure ( out RedisValue value )
232+ {
233+ value = default ;
234+ return false ;
235+ }
236+ // integer: canonical exactly when the round-trip text length matches (rules out leading zeros, a
237+ // leading '+', "-0", trailing junk, etc.) - so no need to re-emit and compare bytes
238+ if ( span . IsEmpty | span . Length > MaxCanonicalLength ) return Failure ( out value ) ;
239+
240+ // restrict to *just* basic number tokens
241+ foreach ( var b in span )
242+ {
243+ switch ( b )
244+ {
245+ case ( byte ) '0' :
246+ case ( byte ) '1' :
247+ case ( byte ) '2' :
248+ case ( byte ) '3' :
249+ case ( byte ) '4' :
250+ case ( byte ) '5' :
251+ case ( byte ) '6' :
252+ case ( byte ) '7' :
253+ case ( byte ) '8' :
254+ case ( byte ) '9' :
255+ case ( byte ) '.' :
256+ case ( byte ) '-' :
257+ case ( byte ) 'E' :
258+ case ( byte ) 'e' :
259+ break ;
260+ default :
261+ return Failure ( out value ) ;
262+ }
263+ }
264+
265+ if ( Format . TryParseInt64 ( span , out var i64 ) && Format . MeasureInt64 ( i64 ) == span . Length )
266+ {
267+ value = i64 ;
268+ return true ;
269+ }
270+
271+ if ( Format . TryParseDouble ( span , out var dbl ) )
272+ {
273+ if ( dbl == 0.0 ) dbl = Math . Abs ( dbl ) ; // prevent problems with -0 formatting
274+ Span < byte > formatted = stackalloc byte [ Format . MaxDoubleTextLen ] ;
275+ var len = Format . FormatDouble ( dbl , formatted ) ;
276+ if ( formatted . Slice ( 0 , len ) . SequenceEqual ( span ) )
277+ {
278+ value = dbl ;
279+ return true ;
280+ }
281+ }
282+
283+ return Failure ( out value ) ;
284+ }
211285}
0 commit comments