Skip to content

Commit d2bec05

Browse files
committed
2 parents 1157294 + b3a3a3e commit d2bec05

File tree

3 files changed

+121
-5
lines changed

3 files changed

+121
-5
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using System.Globalization;
2+
using System.Text;
3+
using Hexa.NET.Utilities.Text;
4+
5+
namespace Hexa.NET.Utilities.Tests;
6+
7+
public class Utf8FormatterTests
8+
{
9+
[TestCase(1f, 0, "1")]
10+
[TestCase(10f, 0, "10")]
11+
[TestCase(0.5f, 1, "0.5")]
12+
[TestCase(0.75f, 2, "0.75")]
13+
[TestCase(0.125f, 3, "0.125")]
14+
[TestCase(0.0625f, 4, "0.0625")]
15+
[TestCase(0.03125f, 5, "0.03125")]
16+
[TestCase(0.015625f, 6, "0.015625")]
17+
[TestCase(0.0078125f, 7, "0.0078125")]
18+
public unsafe void FormatFloatTest(float value, int digit, string expected)
19+
{
20+
// Arrange
21+
byte* buffer = stackalloc byte[128];
22+
23+
// Act
24+
int len = Utf8Formatter.Format(value, buffer, 128, CultureInfo.InvariantCulture, digit);
25+
ReadOnlySpan<byte> utf8Span = new ReadOnlySpan<byte>(buffer, len);
26+
27+
// Assert
28+
Assert.That(Encoding.UTF8.GetString(utf8Span), Is.EqualTo(expected));
29+
}
30+
31+
[TestCase(1, 0, "1")]
32+
[TestCase(10, 0, "10")]
33+
[TestCase(0.5, 1, "0.5")]
34+
[TestCase(0.75, 2, "0.75")]
35+
[TestCase(0.125, 3, "0.125")]
36+
[TestCase(0.0625, 4, "0.0625")]
37+
[TestCase(0.03125, 5, "0.03125")]
38+
[TestCase(0.015625, 6, "0.015625")]
39+
[TestCase(0.0078125, 7, "0.0078125")]
40+
public unsafe void FormatDoubleTest(double value, int digit, string expected)
41+
{
42+
// Arrange
43+
byte* buffer = stackalloc byte[128];
44+
45+
// Act
46+
int len = Utf8Formatter.Format(value, buffer, 128, CultureInfo.InvariantCulture, digit);
47+
ReadOnlySpan<byte> utf8Span = new ReadOnlySpan<byte>(buffer, len);
48+
49+
// Assert
50+
Assert.That(Encoding.UTF8.GetString(utf8Span), Is.EqualTo(expected));
51+
}
52+
53+
[TestCase(10, 0, ".","10")]
54+
[TestCase(10, 0, "\uFF0C","10")]
55+
[TestCase(0.75f, 2, ".","0.75")]
56+
[TestCase(0.75f, 2, "\uFF0C","0\uFF0C75")]
57+
public unsafe void FormatFloatCultureTest(float value, int digit, string separator, string expected)
58+
{
59+
// Arrange
60+
byte* buffer = stackalloc byte[128];
61+
var culture = new CultureInfo( "", false )
62+
{
63+
NumberFormat =
64+
{
65+
CurrencyDecimalSeparator = separator
66+
}
67+
};
68+
69+
// Act
70+
int len = Utf8Formatter.Format(value, buffer, 128, culture, digit);
71+
ReadOnlySpan<byte> utf8Span = new ReadOnlySpan<byte>(buffer, len);
72+
73+
// Assert
74+
Assert.That(Encoding.UTF8.GetString(utf8Span), Is.EqualTo(expected));
75+
}
76+
77+
[TestCase(10, 0, ".","10")]
78+
[TestCase(10, 0, "\uFF0C","10")]
79+
[TestCase(0.75, 2, ".","0.75")]
80+
[TestCase(0.75, 2, "\uFF0C","0\uFF0C75")]
81+
public unsafe void FormatDoubleCultureTest(double value, int digit, string separator, string expected)
82+
{
83+
// Arrange
84+
byte* buffer = stackalloc byte[128];
85+
var culture = new CultureInfo( "", false )
86+
{
87+
NumberFormat =
88+
{
89+
CurrencyDecimalSeparator = separator
90+
}
91+
};
92+
93+
// Act
94+
int len = Utf8Formatter.Format(value, buffer, 128, culture, digit);
95+
var utf8Span = new ReadOnlySpan<byte>(buffer, len);
96+
97+
// Assert
98+
Assert.That(Encoding.UTF8.GetString(utf8Span), Is.EqualTo(expected));
99+
}
100+
}

Hexa.NET.Utilities/Text/Utf8Formatter.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ public static unsafe bool Is<T>(this TypedReference reference, [MaybeNullWhen(fa
4747
#endif
4848

4949
/// <summary>
50-
/// Will be moved to Hexa.NET.Utilities later.
50+
/// Provides high-performance formatting utilities for UTF-8 encoded strings using raw pointers.
51+
/// Designed for scenarios where direct memory manipulation is needed for speed or low-level control.
52+
/// This class operates exclusively on raw pointers and does not perform any managed string allocations.
5153
/// </summary>
5254
public static class Utf8Formatter
5355
{
@@ -327,7 +329,9 @@ public static unsafe int Format(float value, byte* buffer, int bufSize, CultureI
327329
return (int)(buffer - start);
328330
}
329331

332+
byte* beforeSeparator = buffer;
330333
buffer += ConvertUtf16ToUtf8(format.CurrencyDecimalSeparator, buffer, (int)(end - buffer));
334+
byte* afterSeparator = buffer;
331335

332336
digits = digits >= 0 ? digits : 7;
333337

@@ -341,11 +345,16 @@ public static unsafe int Format(float value, byte* buffer, int bufSize, CultureI
341345
if (fraction < 1e-14) break;
342346
}
343347

344-
while (*(buffer - 1) == '0' || *(buffer - 1) == '.')
348+
while (buffer != afterSeparator && *(buffer - 1) == '0')
345349
{
346350
buffer--;
347351
}
348352

353+
if (buffer == afterSeparator)
354+
{
355+
buffer = beforeSeparator;
356+
}
357+
349358
end:
350359
*buffer = 0;
351360
return (int)(buffer - start);
@@ -405,7 +414,9 @@ public static unsafe int Format(double value, byte* buffer, int bufSize, Culture
405414
return (int)(buffer - start);
406415
}
407416

417+
byte* beforeSeparator = buffer;
408418
buffer += ConvertUtf16ToUtf8(format.CurrencyDecimalSeparator, buffer, (int)(end - buffer));
419+
byte* afterSeparator = buffer;
409420

410421
digits = digits >= 0 ? digits : 7;
411422

@@ -419,11 +430,16 @@ public static unsafe int Format(double value, byte* buffer, int bufSize, Culture
419430
if (fraction < 1e-14) break;
420431
}
421432

422-
while (*(buffer - 1) == '0' || *(buffer - 1) == '.')
433+
while (buffer != afterSeparator && *(buffer - 1) == '0')
423434
{
424435
buffer--;
425436
}
426437

438+
if (buffer == afterSeparator)
439+
{
440+
buffer = beforeSeparator;
441+
}
442+
427443
end:
428444
*buffer = 0;
429445
return (int)(buffer - start);
@@ -1860,4 +1876,4 @@ private static unsafe int CountAhead(char** format, char* formatEnd, char target
18601876
return count;
18611877
}
18621878
}
1863-
}
1879+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,4 @@ Contributions are welcome! If you have ideas for new features or improvements, f
8888
8989
## License
9090
91-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
91+
This project is licensed under the MIT License. See the [LICENSE](LICENSE.txt) file for more details.

0 commit comments

Comments
 (0)