Skip to content

Commit

Permalink
Add methods to manipulate Half in binary (#40882)
Browse files Browse the repository at this point in the history
* Expose HalfToInt16Bits and Int16BitsToHalf.

* Use HalfToInt16Bits and Int16BitsToHalf in tests.

* Add and expose BinaryPrimitive Half overloads.

* Test BinaryPrimitive Half overloads.

* Add and expose BinaryReader/Writer Half overloads.

* Test BinaryReader/Writer Half overloads.

Co-authored-by: Jan Kotas <jkotas@microsoft.com>
  • Loading branch information
huoyaoyuan and jkotas committed Sep 1, 2020
1 parent 36dc327 commit 393c1b2
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public void BinaryReader_EofReachedEarlyTests_ThrowsException()

// test non-integer numeric types

RunTest(writer => writer.Write((Half)0.1234), reader => reader.ReadHalf());
RunTest(writer => writer.Write((float)0.1234), reader => reader.ReadSingle());
RunTest(writer => writer.Write((double)0.1234), reader => reader.ReadDouble());
RunTest(writer => writer.Write((decimal)0.1234), reader => reader.ReadDecimal());
Expand Down Expand Up @@ -201,6 +202,7 @@ private void ValidateDisposedExceptions(BinaryReader binaryReader)
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadChars(1));
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadDecimal());
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadDouble());
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadHalf());
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadInt16());
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadInt32());
Assert.Throws<ObjectDisposedException>(() => binaryReader.ReadInt64());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ public void BinaryWriter_WriteDoubleTest()
WriteTest(dblArr, (bw, s) => bw.Write(s), (br) => br.ReadDouble());
}

[Fact]
public void BinaryWriter_WriteHalfTest()
{
Half[] hlfArr = new Half[] {
Half.NegativeInfinity, Half.PositiveInfinity, Half.Epsilon, Half.MinValue, Half.MaxValue,
(Half)0.45, (Half)5.55
};

WriteTest(hlfArr, (bw, s) => bw.Write(s), (br) => br.ReadHalf());
}

[Fact]
public void BinaryWriter_WriteInt16Test()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ private void ValidateDisposedExceptions(BinaryWriter binaryWriter)
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write('a'));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write(new char[] { 'a', 'b' }));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write(5.3));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write((Half)5.3));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write((short)3));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write(33));
Assert.Throws<ObjectDisposedException>(() => binaryWriter.Write((long)42));
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ public static partial class BinaryPrimitives
{
public static double ReadDoubleBigEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static double ReadDoubleLittleEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static System.Half ReadHalfBigEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static System.Half ReadHalfLittleEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static short ReadInt16BigEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static short ReadInt16LittleEndian(System.ReadOnlySpan<byte> source) { throw null; }
public static int ReadInt32BigEndian(System.ReadOnlySpan<byte> source) { throw null; }
Expand Down Expand Up @@ -352,6 +354,8 @@ public static partial class BinaryPrimitives
public static ulong ReverseEndianness(ulong value) { throw null; }
public static bool TryReadDoubleBigEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
public static bool TryReadDoubleLittleEndian(System.ReadOnlySpan<byte> source, out double value) { throw null; }
public static bool TryReadHalfBigEndian(System.ReadOnlySpan<byte> source, out System.Half value) { throw null; }
public static bool TryReadHalfLittleEndian(System.ReadOnlySpan<byte> source, out System.Half value) { throw null; }
public static bool TryReadInt16BigEndian(System.ReadOnlySpan<byte> source, out short value) { throw null; }
public static bool TryReadInt16LittleEndian(System.ReadOnlySpan<byte> source, out short value) { throw null; }
public static bool TryReadInt32BigEndian(System.ReadOnlySpan<byte> source, out int value) { throw null; }
Expand All @@ -374,6 +378,8 @@ public static partial class BinaryPrimitives
public static bool TryReadUInt64LittleEndian(System.ReadOnlySpan<byte> source, out ulong value) { throw null; }
public static bool TryWriteDoubleBigEndian(System.Span<byte> destination, double value) { throw null; }
public static bool TryWriteDoubleLittleEndian(System.Span<byte> destination, double value) { throw null; }
public static bool TryWriteHalfBigEndian(System.Span<byte> destination, System.Half value) { throw null; }
public static bool TryWriteHalfLittleEndian(System.Span<byte> destination, System.Half value) { throw null; }
public static bool TryWriteInt16BigEndian(System.Span<byte> destination, short value) { throw null; }
public static bool TryWriteInt16LittleEndian(System.Span<byte> destination, short value) { throw null; }
public static bool TryWriteInt32BigEndian(System.Span<byte> destination, int value) { throw null; }
Expand All @@ -396,6 +402,8 @@ public static partial class BinaryPrimitives
public static bool TryWriteUInt64LittleEndian(System.Span<byte> destination, ulong value) { throw null; }
public static void WriteDoubleBigEndian(System.Span<byte> destination, double value) { }
public static void WriteDoubleLittleEndian(System.Span<byte> destination, double value) { }
public static void WriteHalfBigEndian(System.Span<byte> destination, System.Half value) { }
public static void WriteHalfLittleEndian(System.Span<byte> destination, System.Half value) { }
public static void WriteInt16BigEndian(System.Span<byte> destination, short value) { }
public static void WriteInt16LittleEndian(System.Span<byte> destination, short value) { }
public static void WriteInt32BigEndian(System.Span<byte> destination, int value) { }
Expand Down
48 changes: 44 additions & 4 deletions src/libraries/System.Memory/tests/Binary/BinaryReaderUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ public void SpanRead()
Assert.True(TryReadInt64LittleEndian(span, out longValue));
Assert.Equal<long>(unchecked((long)0x8877665544332211), longValue);

Half expectedHalf = BitConverter.Int16BitsToHalf(0x1122);
Assert.Equal<Half>(expectedHalf, ReadHalfBigEndian(span));
Assert.True(TryReadHalfBigEndian(span, out Half halfValue));
Assert.Equal<Half>(expectedHalf, halfValue);

expectedHalf = BitConverter.Int16BitsToHalf(0x2211);
Assert.Equal<Half>(expectedHalf, ReadHalfLittleEndian(span));
Assert.True(TryReadHalfLittleEndian(span, out halfValue));
Assert.Equal<Half>(expectedHalf, halfValue);

float expectedFloat = BitConverter.Int32BitsToSingle(0x11223344);
Assert.Equal<float>(expectedFloat, ReadSingleBigEndian(span));
Assert.True(TryReadSingleBigEndian(span, out float floatValue));
Expand Down Expand Up @@ -168,6 +178,16 @@ public void ReadOnlySpanRead()
Assert.True(TryReadInt64LittleEndian(span, out longValue));
Assert.Equal<long>(unchecked((long)0x8877665544332211), longValue);

Half expectedHalf = BitConverter.Int16BitsToHalf(0x1122);
Assert.Equal<Half>(expectedHalf, ReadHalfBigEndian(span));
Assert.True(TryReadHalfBigEndian(span, out Half halfValue));
Assert.Equal<Half>(expectedHalf, halfValue);

expectedHalf = BitConverter.Int16BitsToHalf(0x2211);
Assert.Equal<Half>(expectedHalf, ReadHalfLittleEndian(span));
Assert.True(TryReadHalfLittleEndian(span, out halfValue));
Assert.Equal<Half>(expectedHalf, halfValue);

float expectedFloat = BitConverter.Int32BitsToSingle(0x11223344);
Assert.Equal<float>(expectedFloat, ReadSingleBigEndian(span));
Assert.True(TryReadSingleBigEndian(span, out float floatValue));
Expand Down Expand Up @@ -212,6 +232,8 @@ public void SpanReadFail()
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<ulong>(_span));
Assert.False(MemoryMarshal.TryRead(span, out ulong ulongValue));

TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<Half>(_span));
Assert.False(MemoryMarshal.TryRead(span, out Half halfValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<float>(_span));
Assert.False(MemoryMarshal.TryRead(span, out float floatValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<double>(_span));
Expand Down Expand Up @@ -245,6 +267,8 @@ public void ReadOnlySpanReadFail()
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<ulong>(_span));
Assert.False(MemoryMarshal.TryRead(span, out ulong ulongValue));

TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<Half>(_span));
Assert.False(MemoryMarshal.TryRead(span, out Half halfValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<float>(_span));
Assert.False(MemoryMarshal.TryRead(span, out float floatValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Read<double>(_span));
Expand Down Expand Up @@ -278,6 +302,8 @@ public void SpanWriteAndReadBigEndianHeterogeneousStruct()
WriteUInt64BigEndian(spanBE.Slice(60), s_testStruct.UL1);
WriteSingleBigEndian(spanBE.Slice(68), s_testStruct.F1);
WriteDoubleBigEndian(spanBE.Slice(72), s_testStruct.D1);
WriteHalfBigEndian(spanBE.Slice(80), s_testStruct.H0);
WriteHalfBigEndian(spanBE.Slice(82), s_testStruct.H1);

ReadOnlySpan<byte> readOnlySpanBE = new ReadOnlySpan<byte>(spanBE.ToArray());

Expand All @@ -298,7 +324,9 @@ public void SpanWriteAndReadBigEndianHeterogeneousStruct()
UI1 = ReadUInt32BigEndian(spanBE.Slice(56)),
UL1 = ReadUInt64BigEndian(spanBE.Slice(60)),
F1 = ReadSingleBigEndian(spanBE.Slice(68)),
D1 = ReadDoubleBigEndian(spanBE.Slice(72))
D1 = ReadDoubleBigEndian(spanBE.Slice(72)),
H0 = ReadHalfBigEndian(spanBE.Slice(80)),
H1 = ReadHalfBigEndian(spanBE.Slice(82))
};

var readStructFromReadOnlySpan = new TestStruct
Expand All @@ -318,7 +346,9 @@ public void SpanWriteAndReadBigEndianHeterogeneousStruct()
UI1 = ReadUInt32BigEndian(readOnlySpanBE.Slice(56)),
UL1 = ReadUInt64BigEndian(readOnlySpanBE.Slice(60)),
F1 = ReadSingleBigEndian(readOnlySpanBE.Slice(68)),
D1 = ReadDoubleBigEndian(readOnlySpanBE.Slice(72))
D1 = ReadDoubleBigEndian(readOnlySpanBE.Slice(72)),
H0 = ReadHalfBigEndian(readOnlySpanBE.Slice(80)),
H1 = ReadHalfBigEndian(readOnlySpanBE.Slice(82))
};

Assert.Equal(s_testStruct, readStruct);
Expand Down Expand Up @@ -348,6 +378,8 @@ public void SpanWriteAndReadLittleEndianHeterogeneousStruct()
WriteUInt64LittleEndian(spanLE.Slice(60), s_testStruct.UL1);
WriteSingleLittleEndian(spanLE.Slice(68), s_testStruct.F1);
WriteDoubleLittleEndian(spanLE.Slice(72), s_testStruct.D1);
WriteHalfLittleEndian(spanLE.Slice(80), s_testStruct.H0);
WriteHalfLittleEndian(spanLE.Slice(82), s_testStruct.H1);

ReadOnlySpan<byte> readOnlySpanLE = new ReadOnlySpan<byte>(spanLE.ToArray());

Expand All @@ -368,7 +400,9 @@ public void SpanWriteAndReadLittleEndianHeterogeneousStruct()
UI1 = ReadUInt32LittleEndian(spanLE.Slice(56)),
UL1 = ReadUInt64LittleEndian(spanLE.Slice(60)),
F1 = ReadSingleLittleEndian(spanLE.Slice(68)),
D1 = ReadDoubleLittleEndian(spanLE.Slice(72))
D1 = ReadDoubleLittleEndian(spanLE.Slice(72)),
H0 = ReadHalfLittleEndian(spanLE.Slice(80)),
H1 = ReadHalfLittleEndian(spanLE.Slice(82))
};

var readStructFromReadOnlySpan = new TestStruct
Expand All @@ -388,7 +422,9 @@ public void SpanWriteAndReadLittleEndianHeterogeneousStruct()
UI1 = ReadUInt32LittleEndian(readOnlySpanLE.Slice(56)),
UL1 = ReadUInt64LittleEndian(readOnlySpanLE.Slice(60)),
F1 = ReadSingleLittleEndian(readOnlySpanLE.Slice(68)),
D1 = ReadDoubleLittleEndian(readOnlySpanLE.Slice(72))
D1 = ReadDoubleLittleEndian(readOnlySpanLE.Slice(72)),
H0 = ReadHalfLittleEndian(readOnlySpanLE.Slice(80)),
H1 = ReadHalfLittleEndian(readOnlySpanLE.Slice(82))
};

Assert.Equal(s_testStruct, readStruct);
Expand Down Expand Up @@ -525,6 +561,8 @@ public void ReadingStructFieldByFieldOrReadAndReverseEndianness()
UL1 = ulong.MinValue,
F1 = float.MinValue,
D1 = double.MinValue,
H0 = Half.MaxValue,
H1 = Half.MinValue,
};

[StructLayout(LayoutKind.Sequential)]
Expand All @@ -546,6 +584,8 @@ private struct TestStruct
public ulong UL1;
public float F1;
public double D1;
public Half H0;
public Half H1;
}
}
}
47 changes: 47 additions & 0 deletions src/libraries/System.Memory/tests/Binary/BinaryWriterUnitTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Runtime.InteropServices;
using Xunit;

Expand Down Expand Up @@ -65,6 +66,12 @@ public void SpanWrite()
Assert.True(MemoryMarshal.TryWrite<long>(span, ref longValue));
TestHelpers.Validate<long>(span, longValue);

Half halfValue = BitConverter.Int16BitsToHalf(0x1122);
MemoryMarshal.Write<Half>(span, ref halfValue);
TestHelpers.Validate<Half>(span, halfValue);
Assert.True(MemoryMarshal.TryWrite<Half>(span, ref halfValue));
TestHelpers.Validate<Half>(span, halfValue);

float floatValue = BitConverter.Int32BitsToSingle(0x11223344);
MemoryMarshal.Write<float>(span, ref floatValue);
TestHelpers.Validate<float>(span, floatValue);
Expand Down Expand Up @@ -268,6 +275,43 @@ public void SpanWriteUInt64(ulong value)
Assert.Equal(value, read);
}

// Half cannot be used as constants in InlineData
public static IEnumerable<object[]> SpanWriteHalf_TestData()
{
yield return new object[] { Half.MaxValue };
yield return new object[] { Half.MinValue };
yield return new object[] { Half.Epsilon };
yield return new object[] { Half.PositiveInfinity };
yield return new object[] { Half.NegativeInfinity };
yield return new object[] { Half.NaN };
}

[Theory]
[MemberData(nameof(SpanWriteHalf_TestData))]
public void SpanWriteHalf(Half value)
{
Assert.True(BitConverter.IsLittleEndian);
var span = new Span<byte>(new byte[4]);
WriteHalfBigEndian(span, value);
Half read = ReadHalfBigEndian(span);
Assert.Equal(value, read);

span.Clear();
Assert.True(TryWriteHalfBigEndian(span, value));
read = ReadHalfBigEndian(span);
Assert.Equal(value, read);

span.Clear();
WriteHalfLittleEndian(span, value);
read = ReadHalfLittleEndian(span);
Assert.Equal(value, read);

span.Clear();
Assert.True(TryWriteHalfLittleEndian(span, value));
read = ReadHalfLittleEndian(span);
Assert.Equal(value, read);
}

[Theory]
[InlineData(float.MaxValue)]
[InlineData(float.MinValue)]
Expand Down Expand Up @@ -341,6 +385,7 @@ public void SpanWriteFail()
uint uintValue = 1;
long longValue = 1;
ulong ulongValue = 1;
Half halfValue = (Half)1;
float floatValue = 1;
double doubleValue = 1;

Expand Down Expand Up @@ -378,6 +423,8 @@ public void SpanWriteFail()
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Write<ulong>(_span, ref ulongValue));
Assert.False(MemoryMarshal.TryWrite<ulong>(span, ref ulongValue));

TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Write<Half>(_span, ref halfValue));
Assert.False(MemoryMarshal.TryWrite<Half>(span, ref halfValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Write<float>(_span, ref floatValue));
Assert.False(MemoryMarshal.TryWrite<float>(span, ref floatValue));
TestHelpers.AssertThrows<ArgumentOutOfRangeException, byte>(span, (_span) => MemoryMarshal.Write<double>(_span, ref doubleValue));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,13 +500,13 @@ public static unsafe float Int32BitsToSingle(int value)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe short HalfToInt16Bits(Half value)
public static unsafe short HalfToInt16Bits(Half value)
{
return *((short*)&value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe Half Int16BitsToHalf(short value)
public static unsafe Half Int16BitsToHalf(short value)
{
return *(Half*)&value;
}
Expand Down
Loading

0 comments on commit 393c1b2

Please sign in to comment.