探索C/C++ 进制转换之美:从原理到应用(一)https://developer.aliyun.com/article/1464271
3.5 小数点进制转换
对于小数部分,我们可以使用类似的方法进行进制转换。这里我们以二进制和十进制之间的互相转换为例:
小数:二进制转十进制
例如,将二进制小数 0.1101 转换为十进制小数:
按权展开:(1 × 2^-1) + (1 × 2^-2) + (0 × 2^-3) + (1 × 2^-4) = 0.5 + 0.25 + 0 + 0.0625 = 0.8125。
小数:十进制转二进制
例如,将十进制小数 0.8125 转换为二进制小数:
执行连续乘法,将乘法结果的整数部分一次提取出来,直到其结果为 0 或达到所需的精度:
0.8125 × 2 = 1.625 ...提取 1 0.6250 × 2 = 1.250 ...提取 1 0.2500 × 2 = 0.500 ...提取 0 0.5000 × 2 = 1.000 ...提取 1
将提取的整数部分依次排列:1101;从而得到二进制小数 0.1101。
对于小数部分的八进制、十六进制与其他进制之间的转换,与上述过程类似。可以先将小数转换为二进制形式,然后再将其转换为目标进制。在实际应用中,注意控制所需的精度以避免无限循环小数的出现。
3.6 进制转换综述
下表总结了二进制、八进制、十进制和十六进制之间的数学转换方法与代码转换方法。
转换类型 | 数学方法 | 代码方法(C++) |
二进制 -> 十进制 | 按权展开求和 | 使用 std::bitset 将二进制字符串转换为 unsigned long |
十进制 -> 二进制 | 连续除法求余 | 使用 std::bitset 将整数转换为二进制字符串 |
八进制 -> 十进制 | 按权展开求和 | 使用 std::istringstream 将八进制字符串转换为整数 |
十进制 -> 八进制 | 连续除法求余 | 使用 std::stringstream 将整数转换为八进制字符串 |
十六进制 -> 十进制 | 按权展开求和 | 使用 std::istringstream 将十六进制字符串转换为整数 |
十进制 -> 十六进制 | 连续除法求余 | 使用 std::stringstream 将整数转换为十六进制字符串 |
符号数二进制 -> 十进制 | 补码形式的逆过程求和 | 使用 std::bitset 将补码形式的二进制字符串转换为整数 |
符号数十进制 -> 二进制 | 按补码形式转换 | 使用 std::bitset 将整数转换为补码形式的二进制字符串 |
小数二进制 -> 十进制 | 按权展开求和 | 使用类似按权展开的自定义实现 |
小数十进制 -> 二进制 | 连续乘法求整数部分 | 使用类似连续乘法的自定义实现 |
扩展内容:
- 符号数进制转换:处理负数时需要使用补码表示法。当转换负数的进制时,需先计算绝对值的目标进制表示,然后将其转换成补码形式。
- 小数的进制转换:对于小数部分,使用按权展开求和的方法(二进制 -> 十进制)以及连续乘法求整数部分的方法(十进制 -> 二进制)进行转换。对于小数部分的八进制、十六进制与其他进制之间的转换,可以先将小数转换为二进制形式,然后再将其转换为目标进制。注意控制所需的精度以避免无限循环小数的出现。
3.7 BaseConverter
类的实现
以下是一个 BaseConverter
类的实现,它具有所需的功能。
首先,定义一个结构体 NumberRepresentation
以存储各种进制的数值表示:
#include <string> #include <iostream> #include <sstream> #include <bitset> struct NumberRepresentation { std::string binary; std::string octal; std::string decimal; std::string hexadecimal; };
然后,创建一个进制转换类 BaseConverter
,实现所需的功能:
class BaseConverter { public: NumberRepresentation convert(int number) { NumberRepresentation num_repr; num_repr.binary = int_to_binary(number); num_repr.octal = int_to_octal(number); num_repr.decimal = std::to_string(number); num_repr.hexadecimal = int_to_hex(number); return num_repr; } std::string hex_array_to_string(const uint8_t *hex_array, size_t size) { std::stringstream ss; ss << std::hex; for (size_t i = 0; i < size; ++i) { ss << static_cast<int>(hex_array[i]); } return ss.str(); } // 提供数学转换接口,不使用C++ API int binary_to_decimal_math(const std::string& binary) { int decimal = 0; for (size_t i = 0; i < binary.length(); ++i) { decimal = decimal * 2 + (binary[i] - '0'); } return decimal; } std::string decimal_to_binary_math(int decimal) { std::string binary; while (decimal > 0) { binary = std::to_string(decimal % 2) + binary; decimal /= 2; } return binary; } int octal_to_decimal_math(const std::string& octal) { int decimal = 0; for (size_t i = 0; i < octal.length(); ++i) { decimal = decimal * 8 + (octal[i] - '0'); } return decimal; } std::string decimal_to_octal_math(int decimal) { std::string octal; while (decimal > 0) { octal = std::to_string(decimal % 8) + octal; decimal /= 8; } return octal; } int hexadecimal_to_decimal_math(const std::string& hex) { int decimal = 0; for (size_t i = 0; i < hex.length(); ++i) { char digit = hex[i]; int value; if ('0' <= digit && digit <= '9') { value = digit - '0'; } else if ('A' <= digit && digit <= 'F') { value = digit - 'A' + 10; } else if ('a' <= digit && digit <= 'f') { value = digit - 'a' + 10; } else { throw std::invalid_argument("Invalid hexadecimal character"); } decimal = decimal * 16 + value; } return decimal; } std::string decimal_to_hexadecimal_math(int decimal) { const char* hex_digits = "0123456789ABCDEF"; std::string hex; while (decimal > 0) { hex = hex_digits[decimal % 16] + hex; decimal /= 16; } return hex; } double binary_fraction_to_decimal_math(const std::string& binary_fraction) { double decimal = 0; double mult = 0.5; for (char ch : binary_fraction) { decimal += (ch - '0') * mult; mult *= 0.5; } return decimal; } std::string decimal_fraction_to_binary_math(double decimal_fraction, int precision) { std::string binary_fraction; while (precision > 0 && decimal_fraction > 0) { decimal_fraction *= 2; binary_fraction += (decimal_fraction >= 1) ? '1' : '0'; if (decimal_fraction >= 1) { decimal_fraction -= 1; } --precision; } return binary_fraction; } private: std::string int_to_binary(int number) { std::bitset<32> binary_bitset(number); std::string binary_str = binary_bitset.to_string(); size_t non_zero_pos = binary_str.find_first_not_of('0'); return binary_str.substr(non_zero_pos); } std::string int_to_octal(int number) { std::stringstream ss; ss << std::oct << number; return ss.str(); } std::string int_to_hex(int number) { std::stringstream ss; ss << std::hex << number; return ss.str(); } };
现在,您可以使用这个类进行进制转换和其他相关任务。例如:
int main() { BaseConverter converter; int input_number = 42; NumberRepresentation num_repr = converter.convert(input_number); std::cout << "Binary: " << num_repr.binary << std::endl; std::cout << "Octal: " << num_repr.octal << std::endl; std::cout << "Decimal: " << num_repr.decimal << std::endl; std::cout << "Hexadecimal: " << num_repr.hexadecimal << std::endl; uint8_t hex_array[] = {0x74, 0x65, 0x73, 0x74}; std::string hex_str = converter.hex_array_to_string(hex_array, sizeof(hex_array) / sizeof(uint8_t)); std::cout << "Hexadecimal string: " << hex_str << std::endl; return 0; }
在这个例子中,我们定义了一个结构体 NumberRepresentation
和一个进制转换类 BaseConverter
,类中实现了所需的功能。类的方法可以为整数生成二进制、八进制、十进制和十六进制表示,将十六进制数组转换为字符串,以及提供数学转换接口(这些接口需要自行根据前述数学方法来实现)。
四、C/C++ 高级进制转换应用 (Advanced Base Conversion Applications in C/C++)
4.1 不同进制表示法的读写 (Reading and Writing in Different Base Representations)
在实际应用中,我们可能需要将不同进制的数值表示从文件、网络传输或其他源中读入,并将其转换为整数或浮点数以进行进一步处理。同样地,我们也可能需要将整数或浮点数转换为其他进制的形式,以便进行输出或传输。
读取
从文件或其他源中读取不同进制的表示时,需要了解该表示的格式。例如,二进制前面通常有 “0b” 或 “0B” 前缀,八进制有 “0” 前缀,十六进制有 “0x” 或 “0X” 前缀。读取过程中需忽略这些前缀,并将后续的字符读入字符串或字符数组进行进一步处理。
写入
写入不同进制表示时,通常需要为输出的字符串添加相应的前缀。添加前缀有助于在后续读取时识别不同的数值表示和进制。
4.2 使用位操作符进行进制转换 (Using Bitwise Operators for Base Conversion)
4.2.1 位操作符与进制转换 (Bitwise Operators and Base Conversion)
位操作符允许我们直接操作整数的二进制位。这可以让我们高效地实现二进制与其他进制之间的转换。位操作符包括:按位与(&),按位或(|),按位异或(^),按位非(~),左移(<<),右移(>>)等。
在进制转换过程中,位操作符可以帮助我们直接访问和修改数值的二进制位,从而简化计算过程。例如,对于加法和乘法等基本运算,可以通过按位操作实现而无需真正执行加法和乘法。同时,在需要高效执行的场景中(如网络编程、加密算法等),位操作符在性能上的优势使得它成为实现进制转换等操作的首选。
在实际编程中,结合具体问题和项目需求,我们可以灵活选择是否使用位操作符。选择使用位操作符时需要考虑到代码的可读性和性能。在一些情况下,使用位操作符可以提高代码性能,但可能会降低代码可读性。在这种情况下,我们需要在性能与可读性之间进行权衡。
4.2.2 用位操作符实现的进制转换算法 (Base Conversion Algorithm using Bitwise Operators)
使用位操作符实现的进制转换算法与前述的除法和乘法运算相似。例如,将一个十进制整数转换为二进制字符串时,可以使用右移操作符逐位读取整数的二进制表示:
std::string decimal_to_binary_bitwise(int decimal) { std::string binary; for (int i = 31; i >= 0; --i) { binary += ((decimal >> i) & 1) ? '1' : '0'; } size_t non_zero_pos = binary.find_first_not_of('0'); return binary.substr(non_zero_pos); }
该函数首先创建一个空的二进制字符串。然后,从最高位(第31位)开始,逐位进行以下操作:使用右移操作符将当前位移动到二进制表示的最低位(第0位),然后用按位与操作符 & 和 1 进行按位与运算。这样可以得到当前位的值(0 或 1)。将这个值转换为字符并添加到二进制字符串中。
最后,使用 find_first_not_of
函数找到第一个不是 ‘0’ 的字符的位置,从该位置开始截取子字符串,以去除前导零。这样,我们使用位操作符实现了十进制整数到二进制字符串的转换。在实际应用中,还可以类似地使用位操作符实现其他进制间的转换。
4.2.3 实际应用案例 (Practical Application Cases)
在某些应用场景中,例如网络协议或加密技术等,位操作符特别适用。使用位操作符进行进制转换可以实现高效的算法,提高程序的性能。
案例1:网络协议
许多网络协议(如 IPv4、IPv6 或 TCP/IP 等)在传输数据时需要操作二进制位。在这些场景中,位操作符可以实现高效的算法,达到更好的性能。
例如,IPv4 地址通常表示为 4 个十进制整数(每个范围在 0-255 之间),用英文句点分隔。但在实际中,IPv4 地址是 32 位的二进制整数。使用位操作符可以方便地在二进制和点分十进制之间进行转换。例如,IP 地址子网掩码的计算就需要用到位操作符。
案例2:加密技术
在加密技术中,密钥和明文往往需要进行位级别的操作以产生密文。例如,DES(Data Encryption Standard)加密算法中,在密钥生成和加密过程中大量使用了位操作符。进行置换、选择、异或等操作时,位操作符可以简化实现过程,提高计算速度。
通过这些实际应用案例,可以看到位操作符在进制转换以及其他处理二进制位的场景中具有明显的性能优势。在性能敏感的场合,使用位操作符进行进制转换和其他二进制位操作是一个理想的选择。同时,我们需要权衡代码的可读性和性能,因此在不同的场景下,我们可以根据需要灵活地选择使用位操作符或其他方法。
4.3 利用C++ 标准库中的进制转换功能 (Using Base Conversion Functions in C++ Standard Library)
C++ 标准库提供了一些实用功能,可帮助我们进行进制转换。例如,可以使用 处理二进制字符串,
实现进制间的转换等。比如以下示例:
例1: 使用 bitset 处理二进制字符串
#include <bitset> std::string int_to_binary(int integer) { std::bitset<32> binary_bitset(integer); return binary_bitset.to_string(); }
在这个例子中,我们将整数转换为一个包含 32 位的 bitset
。接着,我们使用 bitset
的 to_string()
函数将其转换为二进制字符串。需要注意的是,bitset::to_string()
生成的字符串可以包含前导零,根据需求可以选择进一步处理结果。
例2: 使用 stringstream 实现进制间的转换
#include <sstream> #include <iomanip> int hex_to_decimal(const std::string& hexadecimal) { int decimal; std::stringstream ss(hexadecimal); ss >> std::hex >> decimal; return decimal; }
在此示例中,我们将十六进制字符串转换为十进制整数。首先,我们创建一个 stringstream
对象并将十六进制字符串输入其中。然后使用 std::hex
修饰符指定我们将从 stringstream 读取十六进制整数。最后,通过将 stringstream 对象转换为整数,实现进制间的转换。
在实际项目中,可以根据性能需求和代码可读性来决定是使用位操作符实现进制转换,还是使用 C++ 标准库中提供的功能。在很多情况下,使用 C++ 标准库的功能具有良好的代码可读性,且性能足够满足需求。
五、进制转换相关面试题
本章将提供一些针对进制转换的面试题。这些题目主要分为以下几个部分:
5.1 基本进制转换题目
这部分面试题主要测试对进制转换基本概念的理解和基本编程能力。
面试题1:将给定的十进制整数转换为二进制字符串
问题描述:给定一个非负整数 n,要求将其转换为二进制字符串。
要求:
- 写一个函数
string decimal_to_binary(int n)
,输入为非负整数 n,输出为二进制字符串。
示例:
输入: 2 输出: "10" 输入: 10 输出: "1010"
面试题2:将给定的十进制整数转换为十六进制字符串
问题描述:给定一个整数 n,要求将其转换为十六进制字符串。注意:负整数应当用补码表示。
要求:
- 写一个函数
string decimal_to_hex(int n)
,输入为整数 n,输出为十六进制字符串。
示例:
输入: 26 输出: "1A" 输入: -1 输出: "FFFFFFFF"
面试题3:将给定的二进制字符串转换为十进制整数
问题描述:给定一个二进制字符串 s,要求将其转换为十进制整数。
要求:
- 写一个函数
int binary_to_decimal(const string &s)
,输入为二进制字符串 s,输出为十进制整数。
示例:
输入: "11010" 输出: 26 输入: "1010" 输出: 10
探索C/C++ 进制转换之美:从原理到应用(三)https://developer.aliyun.com/article/1464273