@@ -13,6 +13,7 @@ namespace Garnet.common
13
13
public static unsafe class NumUtils
14
14
{
15
15
public const int MaximumFormatInt64Length = 20 ; // 19 + sign (i.e. -9223372036854775808)
16
+ public const int MaximumFormatDoubleLength = 310 ; // (i.e. -1.7976931348623157E+308)
16
17
17
18
/// <summary>
18
19
/// Convert long number into sequence of ASCII bytes
@@ -75,6 +76,81 @@ public static unsafe void LongToBytes(long value, int length, ref byte* result)
75
76
result += length ;
76
77
}
77
78
79
+ /// <summary>
80
+ /// Convert double number into sequence of ASCII bytes
81
+ /// </summary>
82
+ /// <param name="value">Value to convert</param>
83
+ /// <param name="dest">Span Byte</param>
84
+ /// <returns>Length of number in result</returns>
85
+ public static int DoubleToSpanByte ( double value , Span < byte > dest )
86
+ {
87
+ int totalLen = NumOfCharInDouble( value , out var integerDigits , out var signSize , out var fractionalDigits ) ;
88
+ bool isNegative = value < 0 ;
89
+ if ( totalLen > dest . Length )
90
+ return 0 ;
91
+ fixed ( byte * ptr = dest )
92
+ {
93
+ byte * curr = ptr ;
94
+ DoubleToBytes ( value , integerDigits , fractionalDigits , ref curr ) ;
95
+ }
96
+
97
+ return totalLen ;
98
+ }
99
+
100
+ /// <summary>
101
+ /// Convert double number into sequence of ASCII bytes
102
+ /// </summary>
103
+ /// <param name="value">Value to convert</param>
104
+ /// <param name="integerDigits">Number of digits in the integer part of the double value</param>
105
+ /// <param name="fractionalDigits">Number of digits in the fractional part of the double value</param>
106
+ /// <param name="result">Byte pointer, will be updated to point after the written number</param>
107
+ public static unsafe void DoubleToBytes( double value , int integerDigits , int fractionalDigits , ref byte * result )
108
+ {
109
+ Debug . Assert ( ! double . IsNaN ( value ) && ! double . IsInfinity( value) , "Cannot convert NaN or Infinity to bytes." ) ;
110
+
111
+ if ( value == 0 )
112
+ {
113
+ * result ++ = ( byte ) '0' ;
114
+ return ;
115
+ }
116
+
117
+ bool isNegative = value < 0 ;
118
+ if ( isNegative )
119
+ {
120
+ * result ++ = ( byte ) '- ';
121
+ value = - value ;
122
+ }
123
+
124
+ result += integerDigits ;
125
+ var integerPart = Math. Truncate( value ) ;
126
+ double fractionalPart = fractionalDigits > 0 ? Math . Round ( value - integerPart , fractionalDigits ) : 0 ;
127
+
128
+ // Convert integer part
129
+ do
130
+ {
131
+ * -- result = ( byte ) ( ( byte ) '0' + ( integerPart % 10 ) ) ;
132
+ integerPart /= 10 ;
133
+ } while ( integerPart >= 1 ) ;
134
+ result += integerDigits ;
135
+
136
+ if ( fractionalDigits > 0 )
137
+ {
138
+ // Add decimal point
139
+ * result ++ = ( byte ) '.' ;
140
+
141
+ // Convert fractional part
142
+ for ( int i = 0 ; i < fractionalDigits ; i++ )
143
+ {
144
+ fractionalPart *= 10 ;
145
+ int digit = ( int ) fractionalPart;
146
+ * result ++ = ( byte ) ( ( byte ) '0' + digit ) ;
147
+ fractionalPart = Math . Round ( fractionalPart - digit , fractionalDigits - i - 1 ) ;
148
+ }
149
+
150
+ result-- ; // Move back to the last digit
151
+ }
152
+ }
153
+
78
154
/// <summary>
79
155
/// Convert sequence of ASCII bytes into long number
80
156
/// </summary>
@@ -142,6 +218,45 @@ public static bool TryBytesToLong(int length, byte* source, out long result)
142
218
return true;
143
219
}
144
220
221
+ /// <summary>
222
+ /// Convert sequence of ASCII bytes into double number
223
+ /// </summary>
224
+ /// <param name="source">Source bytes</param>
225
+ /// <param name="result">Double value extracted from sequence</param>
226
+ /// <returns>True if sequence contains only numeric digits, otherwise false</returns>
227
+ public static bool TryBytesToDouble ( ReadOnlySpan < byte > source , out double result )
228
+ {
229
+ fixed ( byte * ptr = source )
230
+ return TryBytesToDouble( source. Length, ptr , out result ) ;
231
+ }
232
+
233
+ /// <summary>
234
+ /// Convert sequence of ASCII bytes into double number
235
+ /// </summary>
236
+ /// <param name="length">Length of number</param>
237
+ /// <param name="source">Source bytes</param>
238
+ /// <param name="result">Double value extracted from sequence</param>
239
+ /// <returns>True if sequence contains only numeric digits, otherwise false</returns>
240
+ public static bool TryBytesToDouble( int length , byte * source , out double result)
241
+ {
242
+ var fNeg = * source == '-' ;
243
+ var beg = fNeg ? source + 1 : source ;
244
+ var len = fNeg ? length - 1 : length ;
245
+ result = 0 ;
246
+
247
+ // Do not allow leading zeros
248
+ if ( len > 1 && * beg == '0' && * ( beg + 1 ) != '.' )
249
+ return false ;
250
+
251
+ // Parse number and check consumed bytes to avoid alphanumeric strings
252
+ if ( ! TryParse ( new ReadOnlySpan < byte > ( beg , len ) , out result ) )
253
+ return false ;
254
+
255
+ // Negate if parsed value has a leading negative sign
256
+ result = fNeg ? - result : result;
257
+ return true;
258
+ }
259
+
145
260
/// <summary>
146
261
/// Convert sequence of ASCII bytes into ulong number
147
262
/// </summary>
@@ -370,6 +485,38 @@ public static int NumDigitsInLong(long v, ref bool fNeg)
370
485
return 19 ;
371
486
}
372
487
488
+ /// <summary>
489
+ /// Return number of digits in given double number incluing the decimal part and `.` character
490
+ /// </summary>
491
+ /// <param name="v">Double value</param>
492
+ /// <returns>Number of digits in the integer part of the double value</returns>
493
+ public static int NumOfCharInDouble( double v , out int integerDigits , out byte signSize , out int fractionalDigits )
494
+ {
495
+ if ( v == 0 )
496
+ {
497
+ integerDigits = 1 ;
498
+ signSize = 0 ;
499
+ fractionalDigits = 0 ;
500
+ return 1 ;
501
+ }
502
+
503
+ Debug. Assert ( ! double . IsNaN ( v ) && ! double . IsInfinity ( v ) ) ;
504
+
505
+ signSize = ( byte ) ( v < 0 ? 1 : 0 ) ; // Add sign if the number is negative
506
+ v = Math. Abs ( v ) ;
507
+ integerDigits = ( int ) Math . Log10 ( v ) + 1 ;
508
+
509
+ fractionalDigits = 0 ; // Max of 15 significant digits
510
+ while ( fractionalDigits <= 14 && Math . Abs ( v - Math . Round ( v , fractionalDigits ) ) > 2 * Double . Epsilon ) // 2 * Double.Epsilon is used to handle floating point errors
511
+ {
512
+ fractionalDigits++ ;
513
+ }
514
+
515
+ var dotSize = fractionalDigits != 0 ? 1 : 0 ; // Add decimal point if there are significant digits
516
+
517
+ return signSize + integerDigits + dotSize + fractionalDigits ;
518
+ }
519
+
373
520
/// <inheritdoc cref="Utf8Parser.TryParse(ReadOnlySpan{byte}, out int, out int, char)"/>
374
521
public static bool TryParse ( ReadOnlySpan < byte > source , out int value )
375
522
{
0 commit comments