Skip to content

Commit f2a2944

Browse files
committed
feat: implement endian conversion utilities
1 parent 8369803 commit f2a2944

File tree

3 files changed

+189
-17
lines changed

3 files changed

+189
-17
lines changed

src/iceberg/util/endian.h

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -30,46 +30,95 @@ namespace iceberg {
3030

3131
/// \brief Concept for values that can be written in little-endian format.
3232
template <typename T>
33-
concept EndianConvertible = std::is_arithmetic_v<T>;
34-
35-
/// \brief Concept for values that can be written in big-endian format,
36-
template <typename T>
37-
concept BigEndianWritable = std::same_as<T, std::array<uint8_t, 16>>;
33+
concept EndianConvertible = std::is_arithmetic_v<T> && !std::same_as<T, bool>;
3834

3935
/// \brief Convert a value to little-endian format.
4036
template <EndianConvertible T>
41-
T ToLittleEndian(T value) {
42-
if constexpr (std::endian::native != std::endian::little && sizeof(T) > 1) {
43-
return std::byteswap(value);
37+
constexpr T ToLittleEndian(T value) {
38+
if constexpr (std::endian::native == std::endian::little || sizeof(T) <= 1) {
39+
return value;
40+
} else {
41+
if constexpr (std::is_integral_v<T>) {
42+
return std::byteswap(value);
43+
} else if constexpr (std::is_floating_point_v<T>) {
44+
// For floats, use the bit_cast -> byteswap -> bit_cast pattern.
45+
if constexpr (sizeof(T) == sizeof(uint32_t)) { // Typically for float
46+
uint32_t int_representation = std::bit_cast<uint32_t>(value);
47+
int_representation = std::byteswap(int_representation);
48+
return std::bit_cast<T>(int_representation);
49+
} else if constexpr (sizeof(T) == sizeof(uint64_t)) { // Typically for double
50+
uint64_t int_representation = std::bit_cast<uint64_t>(value);
51+
int_representation = std::byteswap(int_representation);
52+
return std::bit_cast<T>(int_representation);
53+
}
54+
}
4455
}
45-
return value;
4656
}
4757

4858
/// \brief Convert a value from little-endian format.
4959
template <EndianConvertible T>
50-
T FromLittleEndian(T value) {
51-
if constexpr (std::endian::native != std::endian::little && sizeof(T) > 1) {
52-
return std::byteswap(value);
60+
constexpr T FromLittleEndian(T value) {
61+
if constexpr (std::endian::native == std::endian::little || sizeof(T) <= 1) {
62+
return value;
63+
} else {
64+
if constexpr (std::is_integral_v<T>) {
65+
return std::byteswap(value);
66+
} else if constexpr (std::is_floating_point_v<T>) {
67+
// For floats, use the bit_cast -> byteswap -> bit_cast pattern.
68+
if constexpr (sizeof(T) == sizeof(uint32_t)) { // Typically for float
69+
uint32_t int_representation = std::bit_cast<uint32_t>(value);
70+
int_representation = std::byteswap(int_representation);
71+
return std::bit_cast<T>(int_representation);
72+
} else if constexpr (sizeof(T) == sizeof(uint64_t)) { // Typically for double
73+
uint64_t int_representation = std::bit_cast<uint64_t>(value);
74+
int_representation = std::byteswap(int_representation);
75+
return std::bit_cast<T>(int_representation);
76+
}
77+
}
5378
}
54-
return value;
5579
}
5680

81+
/// \brief Convert a value to big-endian format.
5782
template <EndianConvertible T>
5883
constexpr T ToBigEndian(T value) {
5984
if constexpr (std::endian::native == std::endian::big || sizeof(T) <= 1) {
6085
return value;
6186
} else {
62-
return std::byteswap(value);
87+
if constexpr (std::is_integral_v<T>) {
88+
return std::byteswap(value);
89+
} else if constexpr (std::is_floating_point_v<T>) {
90+
if constexpr (sizeof(T) == sizeof(uint32_t)) {
91+
uint32_t int_representation = std::bit_cast<uint32_t>(value);
92+
int_representation = std::byteswap(int_representation);
93+
return std::bit_cast<T>(int_representation);
94+
} else if constexpr (sizeof(T) == sizeof(uint64_t)) {
95+
uint64_t int_representation = std::bit_cast<uint64_t>(value);
96+
int_representation = std::byteswap(int_representation);
97+
return std::bit_cast<T>(int_representation);
98+
}
99+
}
63100
}
64101
}
65102

66-
/// \brief Convert a value from big-endian format to native.
103+
/// \brief Convert a value from big-endian format.
67104
template <EndianConvertible T>
68105
constexpr T FromBigEndian(T value) {
69106
if constexpr (std::endian::native == std::endian::big || sizeof(T) <= 1) {
70107
return value;
71108
} else {
72-
return std::byteswap(value);
109+
if constexpr (std::is_integral_v<T>) {
110+
return std::byteswap(value);
111+
} else if constexpr (std::is_floating_point_v<T>) {
112+
if constexpr (sizeof(T) == sizeof(uint32_t)) {
113+
uint32_t int_representation = std::bit_cast<uint32_t>(value);
114+
int_representation = std::byteswap(int_representation);
115+
return std::bit_cast<T>(int_representation);
116+
} else if constexpr (sizeof(T) == sizeof(uint64_t)) {
117+
uint64_t int_representation = std::bit_cast<uint64_t>(value);
118+
int_representation = std::byteswap(int_representation);
119+
return std::bit_cast<T>(int_representation);
120+
}
121+
}
73122
}
74123
}
75124

test/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ add_iceberg_test(util_test
8989
formatter_test.cc
9090
config_test.cc
9191
visit_type_test.cc
92-
string_utils_test.cc)
92+
string_utils_test.cc
93+
endian_test.cc)
9394

9495
if(ICEBERG_BUILD_BUNDLE)
9596
add_iceberg_test(avro_test

test/endian_test.cc

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#include "iceberg/util/endian.h"
21+
22+
#include <array>
23+
#include <cmath>
24+
#include <limits>
25+
26+
#include <gtest/gtest.h>
27+
28+
namespace iceberg {
29+
30+
// test round trip preserves value
31+
TEST(EndianTest, RoundTripPreservesValue) {
32+
EXPECT_EQ(FromLittleEndian(ToLittleEndian<uint16_t>(0x1234)), 0x1234);
33+
EXPECT_EQ(FromBigEndian(ToBigEndian<uint32_t>(0xDEADBEEF)), 0xDEADBEEF);
34+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(std::numeric_limits<uint64_t>::max())),
35+
std::numeric_limits<uint64_t>::max());
36+
EXPECT_EQ(FromBigEndian(ToBigEndian<uint32_t>(0)), 0);
37+
38+
// --- 有符号整数典型值和边界值 ---
39+
EXPECT_EQ(FromBigEndian(ToBigEndian<int16_t>(-1)), -1);
40+
EXPECT_EQ(FromLittleEndian(ToLittleEndian<int32_t>(-0x12345678)), -0x12345678);
41+
EXPECT_EQ(FromBigEndian(ToBigEndian(std::numeric_limits<int64_t>::min())),
42+
std::numeric_limits<int64_t>::min());
43+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(std::numeric_limits<int16_t>::max())),
44+
std::numeric_limits<int16_t>::max());
45+
46+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(3.14f)), 3.14f);
47+
EXPECT_EQ(FromBigEndian(ToBigEndian(2.718281828459045)), 2.718281828459045);
48+
49+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(std::numeric_limits<float>::infinity())),
50+
std::numeric_limits<float>::infinity());
51+
EXPECT_EQ(FromBigEndian(ToBigEndian(-std::numeric_limits<float>::infinity())),
52+
-std::numeric_limits<float>::infinity());
53+
EXPECT_TRUE(std::isnan(
54+
FromLittleEndian(ToLittleEndian(std::numeric_limits<float>::quiet_NaN()))));
55+
EXPECT_EQ(FromBigEndian(ToBigEndian(0.0f)), 0.0f);
56+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(-0.0f)), -0.0f);
57+
58+
EXPECT_EQ(FromBigEndian(ToBigEndian(std::numeric_limits<double>::infinity())),
59+
std::numeric_limits<double>::infinity());
60+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(-std::numeric_limits<double>::infinity())),
61+
-std::numeric_limits<double>::infinity());
62+
EXPECT_TRUE(
63+
std::isnan(FromBigEndian(ToBigEndian(std::numeric_limits<double>::quiet_NaN()))));
64+
EXPECT_EQ(FromLittleEndian(ToLittleEndian(0.0)), 0.0);
65+
EXPECT_EQ(FromBigEndian(ToBigEndian(-0.0)), -0.0);
66+
}
67+
68+
// test constexpr evaluation
69+
TEST(EndianTest, ConstexprEvaluation) {
70+
static_assert(FromBigEndian(ToBigEndian<uint16_t>(0x1234)) == 0x1234);
71+
static_assert(FromLittleEndian(ToLittleEndian<uint32_t>(0x12345678)) == 0x12345678);
72+
static_assert(FromBigEndian(ToBigEndian<int64_t>(-1)) == -1);
73+
74+
static_assert(ToBigEndian<uint8_t>(0xFF) == 0xFF);
75+
static_assert(FromLittleEndian<int8_t>(-1) == -1);
76+
77+
static_assert(FromLittleEndian(ToLittleEndian(3.14f)) == 3.14f);
78+
static_assert(FromBigEndian(ToBigEndian(2.718)) == 2.718);
79+
}
80+
81+
// test platform dependent behavior
82+
TEST(EndianTest, PlatformDependentBehavior) {
83+
uint32_t test_value = 0x12345678;
84+
85+
if constexpr (std::endian::native == std::endian::little) {
86+
EXPECT_EQ(ToLittleEndian(test_value), test_value);
87+
EXPECT_EQ(FromLittleEndian(test_value), test_value);
88+
EXPECT_NE(ToBigEndian(test_value), test_value);
89+
} else if constexpr (std::endian::native == std::endian::big) {
90+
EXPECT_EQ(ToBigEndian(test_value), test_value);
91+
EXPECT_EQ(FromBigEndian(test_value), test_value);
92+
EXPECT_NE(ToLittleEndian(test_value), test_value);
93+
}
94+
95+
EXPECT_EQ(ToLittleEndian<uint8_t>(0xAB), 0xAB);
96+
EXPECT_EQ(ToBigEndian<uint8_t>(0xAB), 0xAB);
97+
}
98+
99+
// test specific byte pattern validation
100+
TEST(EndianTest, SpecificBytePatternValidation) {
101+
uint32_t original_value = 0x12345678;
102+
103+
uint32_t little_endian_val = ToLittleEndian(original_value);
104+
uint32_t big_endian_val = ToBigEndian(original_value);
105+
106+
auto little_bytes = std::bit_cast<std::array<uint8_t, 4>>(little_endian_val);
107+
auto big_bytes = std::bit_cast<std::array<uint8_t, 4>>(big_endian_val);
108+
109+
EXPECT_EQ(little_bytes[0], 0x78);
110+
EXPECT_EQ(little_bytes[1], 0x56);
111+
EXPECT_EQ(little_bytes[2], 0x34);
112+
EXPECT_EQ(little_bytes[3], 0x12);
113+
114+
// 大端模式下,高位字节在前 (Most Significant Byte First)
115+
// 内存布局: 12 34 56 78
116+
EXPECT_EQ(big_bytes[0], 0x12);
117+
EXPECT_EQ(big_bytes[1], 0x34);
118+
EXPECT_EQ(big_bytes[2], 0x56);
119+
EXPECT_EQ(big_bytes[3], 0x78);
120+
}
121+
122+
} // namespace iceberg

0 commit comments

Comments
 (0)