Skip to content

Commit 530ecda

Browse files
committed
resolved all the review comments
Signed-off-by: Medha Tiwari <[email protected]>
1 parent 92b7ed2 commit 530ecda

File tree

4 files changed

+107
-47
lines changed

4 files changed

+107
-47
lines changed

src/MongoDB.Bson/IO/BinaryPrimitivesCompat.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Buffers.Binary;
18+
using System.Runtime.InteropServices;
1819

1920
namespace MongoDB.Bson.IO
2021
{
@@ -31,35 +32,79 @@ public static void WriteDoubleLittleEndian(Span<byte> destination, double value)
3132
{
3233
BinaryPrimitives.WriteInt64LittleEndian(destination, BitConverter.DoubleToInt64Bits(value));
3334
}
35+
3436
public static float ReadSingleLittleEndian(ReadOnlySpan<byte> source)
3537
{
38+
#if NET6_0_OR_GREATER
39+
return BinaryPrimitives.ReadSingleLittleEndian(source);
40+
#else
3641
if (source.Length < 4)
3742
{
3843
throw new ArgumentOutOfRangeException(nameof(source), "Source span is too small to contain a float.");
3944
}
4045

46+
// Manually construct a 32-bit integer from 4 bytes in Little Endian order.
47+
// BSON mandates that all multibyte values-including float32-must be encoded
48+
// using Little Endian byte order, regardless of the system architecture.
49+
//
50+
// This method ensures platform-agnostic behavior by explicitly assembling
51+
// the bytes in the correct order, rather than relying on the system's native endianness.
52+
//
53+
// Given a byte sequence [a, b, c, d], representing a float encoded in Little Endian,
54+
// the expression below constructs the 32-bit integer as:
55+
// intValue = a + (b << 8) + (c << 16) + (d << 24)
56+
//
57+
// This preserves the intended bit pattern when converting back to float using
58+
// BitConverter.Int32BitsToSingle.
59+
//
60+
// Example:
61+
// A float value of 1.0f is represented in IEEE-754 binary32 format as:
62+
// [0x00, 0x00, 0x80, 0x3F] (Little Endian)
63+
// On a Big Endian system, naive interpretation would yield an incorrect value,
64+
// but this method assembles the int as:
65+
// 0x00 + (0x00 << 8) + (0x80 << 16) + (0x3F << 24) = 0x3F800000,
66+
// which correctly maps to 1.0f.
67+
//
68+
// This guarantees BSON-compliant serialization across all platforms.
4169
int intValue =
4270
source[0] |
4371
(source[1] << 8) |
4472
(source[2] << 16) |
4573
(source[3] << 24);
4674

47-
return BitConverter.Int32BitsToSingle(intValue);
75+
// This struct emulates BitConverter.Int32BitsToSingle for platforms like net472.
76+
return new FloatIntUnion { IntValue = intValue }.FloatValue;
77+
#endif
4878
}
4979

5080
public static void WriteSingleLittleEndian(Span<byte> destination, float value)
5181
{
82+
#if NET6_0_OR_GREATER
83+
BinaryPrimitives.WriteSingleLittleEndian(destination, value);
84+
#else
5285
if (destination.Length < 4)
5386
{
5487
throw new ArgumentOutOfRangeException(nameof(destination), "Destination span is too small to hold a float.");
5588
}
5689

57-
int intValue = BitConverter.SingleToInt32Bits(value);
90+
// This struct emulates BitConverter.SingleToInt32Bits for platforms like net472.
91+
int intValue = new FloatIntUnion { FloatValue = value }.IntValue;
92+
5893
destination[0] = (byte)(intValue);
5994
destination[1] = (byte)(intValue >> 8);
6095
destination[2] = (byte)(intValue >> 16);
6196
destination[3] = (byte)(intValue >> 24);
97+
#endif
6298
}
6399

100+
// This layout trick allows safely reinterpreting float as int and vice versa.
101+
// It ensures identical memory layout for both fields, used for low-level bit conversion
102+
// in environments like net472 which lack BitConverter.SingleToInt32Bits and its inverse.
103+
[StructLayout(LayoutKind.Explicit)]
104+
private struct FloatIntUnion
105+
{
106+
[FieldOffset(0)] public float FloatValue;
107+
[FieldOffset(0)] public int IntValue;
108+
}
64109
}
65110
}

src/MongoDB.Bson/Serialization/BinaryVectorReader.cs

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ public static (TItem[] Items, byte Padding, BinaryVectorDataType VectorDataType)
4848
throw new FormatException("Data length of binary vector of type Float32 must be a multiple of 4 bytes.");
4949
}
5050

51-
var floatArray = BitConverter.IsLittleEndian // We need not to use this condition here, just doing to keep the little endian logic intact
52-
? MemoryMarshal.Cast<byte, float>(vectorDataBytes.Span).ToArray()
53-
: ToFloatArrayBigEndian(vectorDataBytes.Span);
51+
var floatArray = ReadSinglesArrayLittleEndian(vectorDataBytes.Span);
5452
items = (TItem[])(object)floatArray;
5553
break;
5654
case BinaryVectorDataType.Int8:
@@ -119,6 +117,28 @@ TExpectedItem[] AsTypedArrayOrThrow<TExpectedItem>()
119117
return result;
120118
}
121119
}
120+
121+
private static float[] ReadSinglesArrayLittleEndian(ReadOnlySpan<byte> span)
122+
{
123+
if ((span.Length & 3) != 0)
124+
{
125+
throw new FormatException("Data length of binary vector of type Float32 must be a multiple of 4 bytes.");
126+
}
127+
int count = span.Length / 4;
128+
float[] result = new float[count];
129+
if (BitConverter.IsLittleEndian)
130+
{
131+
MemoryMarshal.Cast<byte, float>(span).CopyTo(result);
132+
}
133+
else
134+
{
135+
for (int i = 0; i < count; i++)
136+
{
137+
result[i] = BinaryPrimitivesCompat.ReadSingleLittleEndian(span.Slice(i * 4, 4));
138+
}
139+
}
140+
return result;
141+
}
122142

123143
public static void ValidateItemType<TItem>(BinaryVectorDataType binaryVectorDataType)
124144
{
@@ -143,15 +163,5 @@ private static void ValidateItemTypeForBinaryVector<TItem, TItemExpectedType, TB
143163
throw new NotSupportedException($"Expected {typeof(TItemExpectedType)} for {typeof(TBinaryVectorType)}, but found {typeof(TItem)}.");
144164
}
145165
}
146-
private static float[] ToFloatArrayBigEndian(ReadOnlySpan<byte> span)
147-
{
148-
var count = span.Length / 4;
149-
var result = new float[count];
150-
for (int i = 0; i < count; i++)
151-
{
152-
result[i] = BinaryPrimitivesCompat.ReadSingleLittleEndian(span.Slice(i * 4, 4));
153-
}
154-
return result;
155-
}
156166
}
157167
}

src/MongoDB.Bson/Serialization/BinaryVectorWriter.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,24 @@ public static byte[] WriteToBytes<TItem>(ReadOnlySpan<TItem> vectorData, BinaryV
4141
switch (binaryVectorDataType)
4242
{
4343
case BinaryVectorDataType.Float32:
44-
int length = vectorData.Length * sizeof(float);
44+
var length = vectorData.Length * sizeof(float);
4545
resultBytes = new byte[2 + length];
4646
resultBytes[0] = (byte)binaryVectorDataType;
4747
resultBytes[1] = padding;
4848

4949
var floatSpan = MemoryMarshal.Cast<TItem, float>(vectorData);
5050
Span<byte> floatOutput = resultBytes.AsSpan(2);
5151

52-
for (int i = 0; i < floatSpan.Length; i++)
52+
if (BitConverter.IsLittleEndian)
5353
{
54-
BinaryPrimitivesCompat.WriteSingleLittleEndian(floatOutput, floatSpan[i]);
55-
floatOutput = floatOutput.Slice(4);
54+
MemoryMarshal.Cast<float, byte>(floatSpan).CopyTo(floatOutput);
55+
}
56+
else
57+
{
58+
for (int i = 0; i < floatSpan.Length; i++)
59+
{
60+
BinaryPrimitivesCompat.WriteSingleLittleEndian(floatOutput.Slice(i * 4, 4), floatSpan[i]);
61+
}
5662
}
5763

5864
return resultBytes;

tests/MongoDB.Bson.Tests/Serialization/Serializers/BinaryVectorSerializerTests.cs

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -365,35 +365,16 @@ private BsonBinaryData SerializeToBinaryData<TCollection>(TCollection collection
365365
private static (T[], byte[] VectorBson) GetTestData<T>(BinaryVectorDataType dataType, int elementsCount, byte bitsPadding)
366366
where T : struct
367367
{
368-
var elementsSpan = new ReadOnlySpan<T>(
369-
Enumerable.Range(0, elementsCount)
370-
.Select(i => Convert.ChangeType(i, typeof(T)).As<T>())
371-
.ToArray());
372-
if (typeof(T) == typeof(float) && dataType == BinaryVectorDataType.Float32)
373-
{
374-
var buffer = new byte[2 + elementsCount * 4]; // 4 bytes per float
375-
buffer[0] = (byte)dataType;
376-
buffer[1] = bitsPadding;
377-
for (int i = 0; i < elementsCount; i++)
378-
{
379-
var floatBytes = BitConverter.GetBytes((float)(object)elementsSpan[i]);
380-
if (!BitConverter.IsLittleEndian)
381-
{
382-
Array.Reverse(floatBytes);
383-
}
384-
Buffer.BlockCopy(floatBytes, 0, buffer, 2 + i * 4, 4);
385-
}
386-
return (elementsSpan.ToArray(), buffer);
387-
}
388-
else if ((typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) && (dataType == BinaryVectorDataType.Int8 || dataType == BinaryVectorDataType.PackedBit))
389-
{
390-
byte[] vectorBsonData = [(byte)dataType, bitsPadding, .. MemoryMarshal.Cast<T, byte>(elementsSpan)];
368+
var elementsSpan = new ReadOnlySpan<T>(
369+
Enumerable.Range(0, elementsCount)
370+
.Select(i => Convert.ChangeType(i, typeof(T)).As<T>())
371+
.ToArray());
372+
var elementsBytesLittleEndian = BitConverter.IsLittleEndian
373+
? MemoryMarshal.Cast<T, byte>(elementsSpan)
374+
: ToLittleEndian(elementsSpan, dataType);
375+
376+
byte[] vectorBsonData = [(byte)dataType, bitsPadding, .. elementsBytesLittleEndian];
391377
return (elementsSpan.ToArray(), vectorBsonData);
392-
}
393-
else
394-
{
395-
throw new NotSupportedException($"Type {typeof(T)} is not supported for data type {dataType}.");
396-
}
397378
}
398379

399380
private static (BinaryVector<T>, byte[] VectorBson) GetTestDataBinaryVector<T>(BinaryVectorDataType dataType, int elementsCount, byte bitsPadding)
@@ -442,5 +423,23 @@ public class BinaryVectorNoAttributeHolder
442423

443424
public BinaryVectorFloat32 ValuesFloat { get; set; }
444425
}
426+
427+
private static byte[] ToLittleEndian<T>(ReadOnlySpan<T> span, BinaryVectorDataType dataType) where T : struct
428+
{
429+
// Types that do NOT need conversion safe on BE
430+
if (dataType == BinaryVectorDataType.Int8 || dataType == BinaryVectorDataType.PackedBit)
431+
{
432+
return MemoryMarshal.Cast<T, byte>(span).ToArray();
433+
}
434+
int elementSize = Marshal.SizeOf<T>();
435+
byte[] result = new byte[span.Length * elementSize];
436+
for (int i = 0; i < span.Length; i++)
437+
{
438+
byte[] bytes = BitConverter.GetBytes((dynamic)span[i]);
439+
Array.Reverse(bytes); // Ensure LE order
440+
Buffer.BlockCopy(bytes, 0, result, i * elementSize, elementSize);
441+
}
442+
return result;
443+
}
445444
}
446445
}

0 commit comments

Comments
 (0)