Skip to content

Commit de63866

Browse files
authored
Implement bit manipulation API for BFloat16 (#120602)
Closes #119874. Contains missing API in #119874 (comment). Please confirm whether it needs reiterating through API review.
1 parent 3871ff9 commit de63866

File tree

11 files changed

+713
-377
lines changed

11 files changed

+713
-377
lines changed

src/libraries/System.Memory/ref/System.Memory.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,8 @@ namespace System.Buffers.Binary
574574
{
575575
public static partial class BinaryPrimitives
576576
{
577+
public static System.Numerics.BFloat16 ReadBFloat16BigEndian(System.ReadOnlySpan<byte> source) { throw null; }
578+
public static System.Numerics.BFloat16 ReadBFloat16LittleEndian(System.ReadOnlySpan<byte> source) { throw null; }
577579
public static double ReadDoubleBigEndian(System.ReadOnlySpan<byte> source) { throw null; }
578580
public static double ReadDoubleLittleEndian(System.ReadOnlySpan<byte> source) { throw null; }
579581
public static System.Half ReadHalfBigEndian(System.ReadOnlySpan<byte> source) { throw null; }
@@ -643,6 +645,8 @@ public static void ReverseEndianness(System.ReadOnlySpan<UInt128> source, System
643645
public static void ReverseEndianness(System.ReadOnlySpan<ulong> source, System.Span<ulong> destination) { }
644646
[System.CLSCompliant(false)]
645647
public static void ReverseEndianness(System.ReadOnlySpan<ushort> source, System.Span<ushort> destination) { }
648+
public static bool TryReadBFloat16BigEndian(System.ReadOnlySpan<byte> source, out System.Numerics.BFloat16 value) { throw null; }
649+
public static bool TryReadBFloat16LittleEndian(System.ReadOnlySpan<byte> source, out System.Numerics.BFloat16 value) { throw null; }
646650
public static bool TryReadDoubleBigEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
647651
public static bool TryReadDoubleLittleEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
648652
public static bool TryReadHalfBigEndian(System.ReadOnlySpan<byte> source, out System.Half value) { throw null; }
@@ -679,6 +683,8 @@ public static void ReverseEndianness(System.ReadOnlySpan<ushort> source, System.
679683
public static bool TryReadUIntPtrBigEndian(System.ReadOnlySpan<byte> source, out nuint value) { throw null; }
680684
[System.CLSCompliantAttribute(false)]
681685
public static bool TryReadUIntPtrLittleEndian(System.ReadOnlySpan<byte> source, out nuint value) { throw null; }
686+
public static bool TryWriteBFloat16BigEndian(System.Span<byte> destination, System.Numerics.BFloat16 value) { throw null; }
687+
public static bool TryWriteBFloat16LittleEndian(System.Span<byte> destination, System.Numerics.BFloat16 value) { throw null; }
682688
public static bool TryWriteDoubleBigEndian(System.Span<byte> destination, double value) { throw null; }
683689
public static bool TryWriteDoubleLittleEndian(System.Span<byte> destination, double value) { throw null; }
684690
public static bool TryWriteHalfBigEndian(System.Span<byte> destination, System.Half value) { throw null; }
@@ -715,6 +721,8 @@ public static void ReverseEndianness(System.ReadOnlySpan<ushort> source, System.
715721
public static bool TryWriteUIntPtrBigEndian(System.Span<byte> destination, nuint value) { throw null; }
716722
[System.CLSCompliantAttribute(false)]
717723
public static bool TryWriteUIntPtrLittleEndian(System.Span<byte> destination, nuint value) { throw null; }
724+
public static void WriteBFloat16BigEndian(System.Span<byte> destination, System.Numerics.BFloat16 value) { }
725+
public static void WriteBFloat16LittleEndian(System.Span<byte> destination, System.Numerics.BFloat16 value) { }
718726
public static void WriteDoubleBigEndian(System.Span<byte> destination, double value) { }
719727
public static void WriteDoubleLittleEndian(System.Span<byte> destination, double value) { }
720728
public static void WriteHalfBigEndian(System.Span<byte> destination, System.Half value) { }

src/libraries/System.Memory/tests/Binary/BinaryReaderUnitTests.cs

Lines changed: 89 additions & 48 deletions
Large diffs are not rendered by default.

src/libraries/System.Private.CoreLib/src/System/BitConverter.cs

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,34 @@ public static bool TryWriteBytes(Span<byte> destination, UInt128 value)
308308
return true;
309309
}
310310

311+
/// <summary>
312+
/// Returns the specified <see cref="BFloat16"/> value as an array of bytes.
313+
/// </summary>
314+
/// <param name="value">The number to convert.</param>
315+
/// <returns>An array of bytes with length 2.</returns>
316+
public static unsafe byte[] GetBytes(BFloat16 value)
317+
{
318+
byte[] bytes = new byte[sizeof(BFloat16)];
319+
bool success = TryWriteBytes(bytes, value);
320+
Debug.Assert(success);
321+
return bytes;
322+
}
323+
324+
/// <summary>
325+
/// Converts a <see cref="BFloat16"/> value into a span of bytes.
326+
/// </summary>
327+
/// <param name="destination">When this method returns, the bytes representing the converted <see cref="BFloat16"/> value.</param>
328+
/// <param name="value">The <see cref="BFloat16"/> value to convert.</param>
329+
/// <returns><see langword="true"/> if the conversion was successful; <see langword="false"/> otherwise.</returns>
330+
public static unsafe bool TryWriteBytes(Span<byte> destination, BFloat16 value)
331+
{
332+
if (destination.Length < sizeof(BFloat16))
333+
return false;
334+
335+
Unsafe.WriteUnaligned(ref MemoryMarshal.GetReference(destination), value);
336+
return true;
337+
}
338+
311339
/// <summary>
312340
/// Returns the specified half-precision floating point value as an array of bytes.
313341
/// </summary>
@@ -693,6 +721,31 @@ public static UInt128 ToUInt128(ReadOnlySpan<byte> value)
693721
return Unsafe.ReadUnaligned<UInt128>(ref MemoryMarshal.GetReference(value));
694722
}
695723

724+
/// <summary>
725+
/// Returns a <see cref="BFloat16"/> number converted from two bytes at a specified position in a byte array.
726+
/// </summary>
727+
/// <param name="value">An array of bytes.</param>
728+
/// <param name="startIndex">The starting position within <paramref name="value"/>.</param>
729+
/// <returns>A <see cref="BFloat16"/> number signed integer formed by two bytes beginning at <paramref name="startIndex"/>.</returns>
730+
/// <exception cref="ArgumentException"><paramref name="startIndex"/> equals the length of <paramref name="value"/> minus 1.</exception>
731+
/// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
732+
/// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> is less than zero or greater than the length of <paramref name="value"/> minus 1.</exception>
733+
public static BFloat16 ToBFloat16(byte[] value, int startIndex) => Int16BitsToBFloat16(ToInt16(value, startIndex));
734+
735+
/// <summary>
736+
/// Converts a read-only byte span into a <see cref="BFloat16"/> value.
737+
/// </summary>
738+
/// <param name="value">A read-only span containing the bytes to convert.</param>
739+
/// <returns>A <see cref="BFloat16"/> value representing the converted bytes.</returns>
740+
/// <exception cref="ArgumentOutOfRangeException">The length of <paramref name="value"/> is less than 2.</exception>
741+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
742+
public static unsafe BFloat16 ToBFloat16(ReadOnlySpan<byte> value)
743+
{
744+
if (value.Length < sizeof(BFloat16))
745+
ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value);
746+
return Unsafe.ReadUnaligned<BFloat16>(ref MemoryMarshal.GetReference(value));
747+
}
748+
696749
/// <summary>
697750
/// Returns a half-precision floating point number converted from two bytes at a specified position in a byte array.
698751
/// </summary>
@@ -948,9 +1001,21 @@ public static bool ToBoolean(ReadOnlySpan<byte> value)
9481001
[MethodImpl(MethodImplOptions.AggressiveInlining)]
9491002
public static Half Int16BitsToHalf(short value) => new Half((ushort)(value));
9501003

951-
internal static short BFloat16BitsToInt16(BFloat16 value) => (short)value._value;
1004+
/// <summary>
1005+
/// Converts the specified <see cref="BFloat16"/> number to a 16-bit signed integer.
1006+
/// </summary>
1007+
/// <param name="value">The number to convert.</param>
1008+
/// <returns>A 16-bit signed integer whose bits are identical to <paramref name="value"/>.</returns>
1009+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1010+
public static short BFloat16ToInt16Bits(BFloat16 value) => (short)value._value;
9521011

953-
internal static BFloat16 Int16BitsToBFloat16(short value) => new BFloat16((ushort)(value));
1012+
/// <summary>
1013+
/// Converts the specified 16-bit signed integer to a <see cref="BFloat16"/> number.
1014+
/// </summary>
1015+
/// <param name="value">The number to convert.</param>
1016+
/// <returns>A <see cref="BFloat16"/> number whose bits are identical to <paramref name="value"/>.</returns>
1017+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1018+
public static BFloat16 Int16BitsToBFloat16(short value) => new BFloat16((ushort)(value));
9541019

9551020
/// <summary>
9561021
/// Converts the specified double-precision floating point number to a 64-bit unsigned integer.
@@ -1006,8 +1071,22 @@ public static bool ToBoolean(ReadOnlySpan<byte> value)
10061071
[MethodImpl(MethodImplOptions.AggressiveInlining)]
10071072
public static Half UInt16BitsToHalf(ushort value) => new Half(value);
10081073

1009-
internal static ushort BFloat16BitsToUInt16(BFloat16 value) => value._value;
1074+
/// <summary>
1075+
/// Converts the specified <see cref="BFloat16"/> number to a 16-bit unsigned integer.
1076+
/// </summary>
1077+
/// <param name="value">The number to convert.</param>
1078+
/// <returns>A 16-bit unsigned integer whose bits are identical to <paramref name="value"/>.</returns>
1079+
[CLSCompliant(false)]
1080+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1081+
public static ushort BFloat16ToUInt16Bits(BFloat16 value) => value._value;
10101082

1011-
internal static BFloat16 UInt16BitsToBFloat16(ushort value) => new BFloat16(value);
1083+
/// <summary>
1084+
/// Converts the specified 16-bit unsigned integer to a <see cref="BFloat16"/> number.
1085+
/// </summary>
1086+
/// <param name="value">The number to convert.</param>
1087+
/// <returns>A <see cref="BFloat16"/> number whose bits are identical to <paramref name="value"/>.</returns>
1088+
[CLSCompliant(false)]
1089+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1090+
public static BFloat16 UInt16BitsToBFloat16(ushort value) => new BFloat16(value);
10121091
}
10131092
}

src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReadBigEndian.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67

@@ -42,6 +43,23 @@ public static Half ReadHalfBigEndian(ReadOnlySpan<byte> source)
4243
MemoryMarshal.Read<Half>(source);
4344
}
4445

46+
/// <summary>
47+
/// Reads a <see cref="BFloat16" /> from the beginning of a read-only span of bytes, as big endian.
48+
/// </summary>
49+
/// <param name="source">The read-only span to read.</param>
50+
/// <returns>The big endian value.</returns>
51+
/// <remarks>Reads exactly 2 bytes from the beginning of the span.</remarks>
52+
/// <exception cref="ArgumentOutOfRangeException">
53+
/// <paramref name="source"/> is too small to contain a <see cref="BFloat16" />.
54+
/// </exception>
55+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
56+
public static BFloat16 ReadBFloat16BigEndian(ReadOnlySpan<byte> source)
57+
{
58+
return BitConverter.IsLittleEndian ?
59+
BitConverter.Int16BitsToBFloat16(ReverseEndianness(MemoryMarshal.Read<short>(source))) :
60+
MemoryMarshal.Read<BFloat16>(source);
61+
}
62+
4563
/// <summary>
4664
/// Reads a <see cref="short" /> from the beginning of a read-only span of bytes, as big endian.
4765
/// </summary>
@@ -278,6 +296,28 @@ public static bool TryReadHalfBigEndian(ReadOnlySpan<byte> source, out Half valu
278296
return MemoryMarshal.TryRead(source, out value);
279297
}
280298

299+
/// <summary>
300+
/// Reads a <see cref="BFloat16" /> from the beginning of a read-only span of bytes, as big endian.
301+
/// </summary>
302+
/// <param name="source">The read-only span of bytes to read.</param>
303+
/// <param name="value">When this method returns, contains the value read out of the read-only span of bytes, as big endian.</param>
304+
/// <returns>
305+
/// <see langword="true" /> if the span is large enough to contain a <see cref="BFloat16" />; otherwise, <see langword="false" />.
306+
/// </returns>
307+
/// <remarks>Reads exactly 2 bytes from the beginning of the span.</remarks>
308+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
309+
public static bool TryReadBFloat16BigEndian(ReadOnlySpan<byte> source, out BFloat16 value)
310+
{
311+
if (BitConverter.IsLittleEndian)
312+
{
313+
bool success = MemoryMarshal.TryRead(source, out short tmp);
314+
value = BitConverter.Int16BitsToBFloat16(ReverseEndianness(tmp));
315+
return success;
316+
}
317+
318+
return MemoryMarshal.TryRead(source, out value);
319+
}
320+
281321
/// <summary>
282322
/// Reads a <see cref="short" /> from the beginning of a read-only span of bytes, as big endian.
283323
/// </summary>

src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.ReadLittleEndian.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67

@@ -42,6 +43,23 @@ public static Half ReadHalfLittleEndian(ReadOnlySpan<byte> source)
4243
MemoryMarshal.Read<Half>(source);
4344
}
4445

46+
/// <summary>
47+
/// Reads a <see cref="BFloat16" /> from the beginning of a read-only span of bytes, as little endian.
48+
/// </summary>
49+
/// <param name="source">The read-only span to read.</param>
50+
/// <returns>The little endian value.</returns>
51+
/// <remarks>Reads exactly 2 bytes from the beginning of the span.</remarks>
52+
/// <exception cref="ArgumentOutOfRangeException">
53+
/// <paramref name="source"/> is too small to contain a <see cref="Half" />.
54+
/// </exception>
55+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
56+
public static BFloat16 ReadBFloat16LittleEndian(ReadOnlySpan<byte> source)
57+
{
58+
return !BitConverter.IsLittleEndian ?
59+
BitConverter.Int16BitsToBFloat16(ReverseEndianness(MemoryMarshal.Read<short>(source))) :
60+
MemoryMarshal.Read<BFloat16>(source);
61+
}
62+
4563
/// <summary>
4664
/// Reads a <see cref="short" /> from the beginning of a read-only span of bytes, as little endian.
4765
/// </summary>
@@ -278,6 +296,28 @@ public static bool TryReadHalfLittleEndian(ReadOnlySpan<byte> source, out Half v
278296
return MemoryMarshal.TryRead(source, out value);
279297
}
280298

299+
/// <summary>
300+
/// Reads a <see cref="BFloat16" /> from the beginning of a read-only span of bytes, as little endian.
301+
/// </summary>
302+
/// <param name="source">The read-only span of bytes to read.</param>
303+
/// <param name="value">When this method returns, contains the value read out of the read-only span of bytes, as little endian.</param>
304+
/// <returns>
305+
/// <see langword="true" /> if the span is large enough to contain a <see cref="BFloat16" />; otherwise, <see langword="false" />.
306+
/// </returns>
307+
/// <remarks>Reads exactly 2 bytes from the beginning of the span.</remarks>
308+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
309+
public static bool TryReadBFloat16LittleEndian(ReadOnlySpan<byte> source, out BFloat16 value)
310+
{
311+
if (!BitConverter.IsLittleEndian)
312+
{
313+
bool success = MemoryMarshal.TryRead(source, out short tmp);
314+
value = BitConverter.Int16BitsToBFloat16(ReverseEndianness(tmp));
315+
return success;
316+
}
317+
318+
return MemoryMarshal.TryRead(source, out value);
319+
}
320+
281321
/// <summary>
282322
/// Reads a <see cref="short" /> from the beginning of a read-only span of bytes, as little endian.
283323
/// </summary>

src/libraries/System.Private.CoreLib/src/System/Buffers/Binary/BinaryPrimitives.WriteBigEndian.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Numerics;
45
using System.Runtime.CompilerServices;
56
using System.Runtime.InteropServices;
67

@@ -54,6 +55,29 @@ public static void WriteHalfBigEndian(Span<byte> destination, Half value)
5455
}
5556
}
5657

58+
/// <summary>
59+
/// Writes a <see cref="BFloat16" /> into a span of bytes, as big endian.
60+
/// </summary>
61+
/// <param name="destination">The span of bytes where the value is to be written, as big endian.</param>
62+
/// <param name="value">The value to write into the span of bytes.</param>
63+
/// <remarks>Writes exactly 2 bytes to the beginning of the span.</remarks>
64+
/// <exception cref="ArgumentOutOfRangeException">
65+
/// <paramref name="destination" /> is too small to contain a <see cref="BFloat16" />.
66+
/// </exception>
67+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
68+
public static void WriteBFloat16BigEndian(Span<byte> destination, BFloat16 value)
69+
{
70+
if (BitConverter.IsLittleEndian)
71+
{
72+
short tmp = ReverseEndianness(BitConverter.BFloat16ToInt16Bits(value));
73+
MemoryMarshal.Write(destination, in tmp);
74+
}
75+
else
76+
{
77+
MemoryMarshal.Write(destination, in value);
78+
}
79+
}
80+
5781
/// <summary>
5882
/// Writes a <see cref="short" /> into a span of bytes, as big endian.
5983
/// </summary>
@@ -354,6 +378,27 @@ public static bool TryWriteHalfBigEndian(Span<byte> destination, Half value)
354378
return MemoryMarshal.TryWrite(destination, in value);
355379
}
356380

381+
/// <summary>
382+
/// Writes a <see cref="BFloat16" /> into a span of bytes, as big endian.
383+
/// </summary>
384+
/// <param name="destination">The span of bytes where the value is to be written, as big endian.</param>
385+
/// <param name="value">The value to write into the span of bytes.</param>
386+
/// <returns>
387+
/// <see langword="true" /> if the span is large enough to contain a <see cref="BFloat16" />; otherwise, <see langword="false" />.
388+
/// </returns>
389+
/// <remarks>Writes exactly 2 bytes to the beginning of the span.</remarks>
390+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
391+
public static bool TryWriteBFloat16BigEndian(Span<byte> destination, BFloat16 value)
392+
{
393+
if (BitConverter.IsLittleEndian)
394+
{
395+
short tmp = ReverseEndianness(BitConverter.BFloat16ToInt16Bits(value));
396+
return MemoryMarshal.TryWrite(destination, in tmp);
397+
}
398+
399+
return MemoryMarshal.TryWrite(destination, in value);
400+
}
401+
357402
/// <summary>
358403
/// Writes a <see cref="short" /> into a span of bytes, as big endian.
359404
/// </summary>

0 commit comments

Comments
 (0)