-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[CBOR] Use System.Half for encoding and decoding half-precision data items #38466
Changes from all commits
4a7db80
e30af0d
9fbeb13
0e8f319
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information.using System; | ||
|
||
using System.Buffers.Binary; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace System.Formats.Cbor | ||
{ | ||
// Temporarily implements missing APIs for System.Half | ||
// Remove class once https://github.com/dotnet/runtime/issues/38288 has been addressed | ||
internal static class HalfHelpers | ||
{ | ||
public const int SizeOfHalf = sizeof(short); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static Half ReadHalfBigEndian(ReadOnlySpan<byte> source) | ||
{ | ||
return BitConverter.IsLittleEndian ? | ||
Int16BitsToHalf(BinaryPrimitives.ReverseEndianness(MemoryMarshal.Read<short>(source))) : | ||
MemoryMarshal.Read<Half>(source); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static void WriteHalfBigEndian(Span<byte> destination, Half value) | ||
{ | ||
if (BitConverter.IsLittleEndian) | ||
{ | ||
short tmp = BinaryPrimitives.ReverseEndianness(HalfToInt16Bits(value)); | ||
MemoryMarshal.Write(destination, ref tmp); | ||
} | ||
else | ||
{ | ||
MemoryMarshal.Write(destination, ref value); | ||
} | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private static unsafe short HalfToInt16Bits(Half value) | ||
{ | ||
return *((short*)&value); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private static unsafe Half Int16BitsToHalf(short value) | ||
{ | ||
return *(Half*)&value; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,13 +3,32 @@ | |
// See the LICENSE file in the project root for more information. | ||
|
||
using System.Buffers.Binary; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace System.Formats.Cbor | ||
{ | ||
public partial class CborWriter | ||
{ | ||
// Implements major type 7 encoding per https://tools.ietf.org/html/rfc7049#section-2.1 | ||
|
||
/// <summary> | ||
/// Writes a half-precision floating point number (major type 7). | ||
/// </summary> | ||
/// <param name="value">The value to write.</param> | ||
/// <exception cref="InvalidOperationException"> | ||
/// Writing a new value exceeds the definite length of the parent data item. -or- | ||
/// The major type of the encoded value is not permitted in the parent data item. -or- | ||
/// The written data is not accepted under the current conformance mode | ||
/// </exception> | ||
internal void WriteHalf(Half value) | ||
{ | ||
EnsureWriteCapacity(1 + HalfHelpers.SizeOfHalf); | ||
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional16BitData)); | ||
HalfHelpers.WriteHalfBigEndian(_buffer.AsSpan(_offset), value); | ||
_offset += HalfHelpers.SizeOfHalf; | ||
AdvanceDataItemCounters(); | ||
} | ||
|
||
/// <summary> | ||
/// Writes a single-precision floating point number (major type 7). | ||
/// </summary> | ||
|
@@ -21,11 +40,15 @@ public partial class CborWriter | |
/// </exception> | ||
public void WriteSingle(float value) | ||
{ | ||
EnsureWriteCapacity(5); | ||
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional32BitData)); | ||
BinaryPrimitives.WriteSingleBigEndian(_buffer.AsSpan(_offset), value); | ||
_offset += 4; | ||
AdvanceDataItemCounters(); | ||
if (!CborConformanceModeHelpers.RequiresPreservingFloatPrecision(ConformanceMode) && | ||
FloatSerializationHelpers.TryConvertSingleToHalf(value, out Half half)) | ||
{ | ||
WriteHalf(half); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
} | ||
else | ||
{ | ||
WriteSingleCore(value); | ||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -39,10 +62,39 @@ public void WriteSingle(float value) | |
/// </exception> | ||
public void WriteDouble(double value) | ||
{ | ||
EnsureWriteCapacity(9); | ||
if (!CborConformanceModeHelpers.RequiresPreservingFloatPrecision(ConformanceMode) && | ||
FloatSerializationHelpers.TryConvertDoubleToSingle(value, out float single)) | ||
{ | ||
if (FloatSerializationHelpers.TryConvertSingleToHalf(single, out Half half)) | ||
{ | ||
WriteHalf(half); | ||
} | ||
else | ||
{ | ||
WriteSingleCore(single); | ||
} | ||
} | ||
else | ||
{ | ||
WriteDoubleCore(value); | ||
} | ||
} | ||
|
||
private void WriteSingleCore(float value) | ||
{ | ||
EnsureWriteCapacity(1 + sizeof(float)); | ||
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional32BitData)); | ||
BinaryPrimitives.WriteSingleBigEndian(_buffer.AsSpan(_offset), value); | ||
_offset += sizeof(float); | ||
AdvanceDataItemCounters(); | ||
} | ||
|
||
private void WriteDoubleCore(double value) | ||
{ | ||
EnsureWriteCapacity(1 + sizeof(double)); | ||
WriteInitialByte(new CborInitialByte(CborMajorType.Simple, CborAdditionalInfo.Additional64BitData)); | ||
BinaryPrimitives.WriteDoubleBigEndian(_buffer.AsSpan(_offset), value); | ||
_offset += 8; | ||
_offset += sizeof(double); | ||
AdvanceDataItemCounters(); | ||
} | ||
|
||
|
@@ -106,5 +158,22 @@ public void WriteSimpleValue(CborSimpleValue value) | |
|
||
AdvanceDataItemCounters(); | ||
} | ||
|
||
private static class FloatSerializationHelpers | ||
{ | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static bool TryConvertDoubleToSingle(double value, out float result) | ||
{ | ||
result = (float)value; | ||
return BitConverter.DoubleToInt64Bits(result) == BitConverter.DoubleToInt64Bits(value); | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public static bool TryConvertSingleToHalf(float value, out Half result) | ||
{ | ||
result = (Half)value; | ||
return BitConverter.SingleToInt32Bits((float)result) == BitConverter.SingleToInt32Bits(value); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just use
sizeof(Half)
inline where needed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compiler complains that it doesn't have a predefined size, so would have to mark as unsafe. Presumably because it's a new type?