☰
C++实现十进制数转换为二进制数的数学算法
2026/11/2 15:30:47 网站建设 项目流程

前言


十进制转二进制的数学依据只有一条:任何一个非负整数都可以唯一地表示成 2 的幂的线性组合。把系数从高到低排开,就是二进制位。实现上有两条常见路径:反复除以 2 取余数,或者用位运算逐位取出。前者更贴近数学定义,后者更直接地对应计算机的存储方式。


常见的误解集中在负数上。有人以为-5的二进制就是101前面加个负号,或者用「符号位加绝对值」表示;而 C++ 中整数在大多数平台上采用补码(two's complement),C++20 起标准也明确要求有符号整数使用补码表示。把负整数转换成无符号类型时,标准规定按模 2^N 取余,因此可以得到其补码位模式;但如果直接对负数做%和/,余数的符号是实现定义的,结果不能直接当二进制位用。


本文先讲除 2 取余的数学过程,再给出 C++17 的迭代与递归实现,最后说明负数、定长输出和小数部分的处理边界。


一、除 2 取余的数学过程


以 13 为例:




步骤商余数



13 ÷ 261

6 ÷ 230

3 ÷ 211

1 ÷ 201



余数依次是 1、0、1、1,但它们是从低位到高位产生的,所以结果要倒过来读:1101。验证:1×8 + 1×4 + 0×2 + 1×1 = 13。


这个过程的终止条件是商为 0。对 0 本身要单独处理,否则循环一次也不执行,会得到空串;正确结果是 "0"。


复杂度是 O(log n):每次除以 2,位数约为 log2(n)+1。对 64 位整数,最多 64 次循环,常数很小。


二、迭代实现


最直观的写法是用std::string收集余数,最后反转:


// 适用于 C++17
#include <algorithm>
#include <cstdint>
#include <string>

std::string toBinary(std::uint64_t value) {
if (value == 0) {
return "0";
}
std::string bits;
bits.reserve(64);
while (value != 0) {
bits.push_back(static_cast<char>('0' + (value & 1ULL)));
value >>= 1;
}
std::reverse(bits.begin(), bits.end());
return bits;
}

这里用value & 1ULL代替value % 2,用右移代替除以 2。对无符号类型,两者语义等价,位运算更直接地表达「取最低位」。static_cast<char>('0' + ...)是必要的:'0' + value的运算结果是int,直接push_back会有窄化转换警告,显式转换让意图清楚。


如果要求固定宽度(例如始终输出 32 位),可以从高位向低位填:


// 适用于 C++17
#include <cstdint>
#include <string>

std::string toBinaryFixed(std::uint64_t value, unsigned width) {
if (width == 0 || width > 64) {
return {};
}
std::string bits(width, '0');
for (unsigned i = 0; i < width; ++i) {
const unsigned shift = width - 1 - i;
bits[i] = static_cast<char>('0' + ((value >> shift) & 1ULL));
}
return bits;
}

当width == 64时,第一次移位的位数是 63,对 64 位无符号数是合法的;如果写成1ULL << width就会移位 64 位,那是未定义行为(undefined behavior, UB)。用(value >> shift) & 1不会遇到这个问题,因为shift最大是 63。


三、递归实现


递归版本更短,但要注意递归深度等于位数,对 64 位整数最多 64 层,栈空间足够;如果处理的是任意精度整数,深度可能很大,应改用迭代。


// 适用于 C++17
#include <cstdint>
#include <string>

void appendBinary(std::uint64_t value, std::string& out) {
if (value > 1) {
appendBinary(value >> 1, out);
}
out.push_back(static_cast<char>('0' + (value & 1ULL)));
}

std::string toBinaryRecursive(std::uint64_t value) {
std::string out;
appendBinary(value, out);
return out;
}

递归先处理高位、再追加当前位,所以输出顺序天然正确。value == 0时value > 1为假,直接追加'0',结果就是 "0",不需要额外特判。


四、负数、定长表示与小数部分


C++ 的整数不是数学意义上的整数。对int取负、做除法、取余,都要先明确语义:



  • 有符号整数溢出是 UB,-INT_MIN就是典型例子。

  • 负数取余的结果符号在 C++11 之前是实现定义的,C++11 起规定商向零取整、余数符号与被除数相同,因此-13 % 2 == -1,不能直接映射成二进制位。

  • 有符号负数的右移在 C++20 之前是实现定义的;C++20 起规定为算术右移(补符号位),但为了可移植性,处理位模式时应先转成无符号类型。


把int转成std::uint32_t是标准定义的行为:按模 2^32 取余。因此:


// 适用于 C++17
#include <cstdint>
#include <string>

std::string toBinary32(std::int32_t value) {
const std::uint32_t bits = static_cast<std::uint32_t>(value);
std::string out(32, '0');
for (int i = 31; i >= 0; --i) {
out[static_cast<std::size_t>(31 - i)] =
static_cast<char>('0' + ((bits >> i) & 1U));
}
return out;
}

对-5,static_cast<std::uint32_t>(-5)得到 2^32 - 5,其 32 位位模式是 32 个 1 中最低三位为 011,即补码表示。这个结果是标准保证的,不依赖具体平台;但「32 位」这个宽度是代码写死的,换到 64 位类型就要相应调整。


小数部分


十进制小数转二进制用「乘 2 取整」:把小数部分不断乘 2,取整数位作为下一位,直到小数部分为 0 或达到精度上限。例如 0.625:




步骤乘 2取整剩余小数



11.2510.25

20.500.5

31.010.0



结果是 0.101。很多十进制小数(如 0.1)在二进制中是无限循环的,必须设定截断位数,否则循环不会停止。浮点数还有精度误差,不能用== 0.0判断结束,应使用「剩余部分小于 epsilon」或固定循环次数。


常见坑点



  1. ❌ 对负数直接用% 2取位。


// 适用于 C++17
int n = -5;
while (n) { bits.push_back('0' + n % 2); n /= 2; } // 余数为负,字符错误

✅ 先转成无符号类型,再按位处理。


// 适用于 C++17
std::uint32_t u = static_cast<std::uint32_t>(n);


  1. ❌ 余数按产生顺序直接拼接。


// 适用于 C++17
while (n) { s += char('0' + n % 2); n /= 2; } // 得到的是反序

✅ 追加后反转,或从高位向低位填。


// 适用于 C++17
std::reverse(s.begin(), s.end());


  1. ❌ 忘记处理 0。


// 适用于 C++17
std::string s;
while (n) { /* ... */ } // n == 0 时返回空串

✅ 单独返回 "0"。


// 适用于 C++17
if (n == 0) { return "0"; }


  1. ❌ 用1 << 31取最高位。


// 适用于 C++17
std::uint32_t mask = 1 << 31; // 1 是 int,左移到符号位,UB

✅ 用无符号字面量。


// 适用于 C++17
std::uint32_t mask = 1U << 31;


  1. ❌ 以为char('0' + value)对任意位都成立。


// 适用于 C++17
char c = '0' + 2; // 这里恰好可以,但语义上只适用于 0/1

✅ 明确只处理 0 和 1,或改用查表。


// 适用于 C++17
const char* digits = "01";
char c = digits[value & 1U];


  1. ❌ 对浮点小数用== 0.0判断结束。


// 适用于 C++17
while (fraction != 0.0) { fraction *= 2; /* ... */ } // 0.1 永不结束

✅ 固定精度或使用容差。


// 适用于 C++17
for (int i = 0; i < 32 && fraction > 1e-12; ++i) { /* ... */ }


  1. ❌ 假定有符号负数的二进制是「符号位 + 绝对值」。


// 适用于 C++17
// 认为 -5 是 100...0101

✅ 按补码理解,或先转无符号查看真实位模式。


// 适用于 C++17
std::uint32_t bits = static_cast<std::uint32_t>(-5);


  1. ❌ 用非标准的itoa。


// 适用于 C++17
char buf[33];
itoa(n, buf, 2); // 不是标准函数,部分平台没有

✅ 自己实现,或用std::bitset的to_string(需<bitset>)。


// 适用于 C++17
std::string s = std::bitset<32>(static_cast<std::uint32_t>(n)).to_string();

总结




要点做法



数学依据除 2 取余,余数倒序

迭代取最低位、右移、反转

定长输出从高位到低位填,移位范围 0..width-1

递归先递归高位,再追加当前位

负数先转无符号,按模 2^N 得到补码位模式

小数乘 2 取整,设定精度上限

0单独返回 "0"



十进制转二进制的代码很短,真正需要小心的是类型:有符号与无符号的转换、移位的位数、余数的符号。把「数学整数」和「C++ 整数对象」区分开,负数和小数就不会成为陷阱。




需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询