题目重点:MLE 电面明确从字符串实现 round,追问浮点溢出、-.2、2. 和按 100/0.1 取整;本文用十进制字符进位避免 float。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练版输入仅含 ASCII 十进制数字、可选正负号与一个小数点,至少一位数字;p 是 10 的整数幂,如 100、1、0.1。
- 采用 ROUND_HALF_UP,即绝对值恰好落在 .5 时远离零;输出保留 p 指定的小数位,整数取整不加小数点,结果为负零时去掉负号。
- 题目未明确舍入 tie-break 和科学计数法支持;面试现场应先确认。
解题路线
- 先解析 p 得要保留的十进制位指数;分别解析 s 的符号、整数位和小数位,不调用 float,避免超长数字溢出与二进制浮点误差。
- 将数字看成连在一起的十进制字符,计算保留前缀的长度;不够高位时补零,不够低位时也补零,保证任何 p 都可处理。
- 检查第一个丢弃位,≥5 就从保留前缀最右端向左进位,连串 9 要连续变 0,必要时左侧新添 1。
- 按 p 插回小数点或补整数尾零,去掉多余整数前导零;若最终为零清除负号。
- 用 Decimal 的独立十进制 ROUND_HALF_UP 参考实现做随机对照,包含题目给的边界。
手动走一遍
12567 按 100 保留百位以上的 125,下一位 6 触发进位成 126,再补两个零得到 12600。1234.678 按 0.1 保留 12346,下一位 7 触发进位得到 12347,插回小数点为 1234.7。'-.2' 按 0.1 得 -0.2,'2.' 按 0.1 得 2.0。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""ROUND_HALF_UP to a power-of-ten place, without float conversion."""
def _place_exponent(place: str) -> int:
if "." not in place:
if place.startswith("1") and all(char == "0" for char in place[1:]):
return len(place) - 1
raise ValueError("place must be a power of ten")
whole, fraction = place.split(".", 1)
if whole == "1" and fraction and all(char == "0" for char in fraction):
return 0
if whole == "0" and fraction and fraction[-1] == "1" and all(char == "0" for char in fraction[:-1]):
return -len(fraction)
raise ValueError("place must be a power of ten")
def round_decimal_string(value: str, place: str) -> str:
exponent = _place_exponent(place)
if not value:
raise ValueError("empty number")
negative = value[0] == "-"
unsigned = value[1:] if value[0] in "+-" else value
if unsigned.count(".") > 1:
raise ValueError("invalid number")
whole, dot, fraction = unsigned.partition(".")
if not (whole or fraction) or any(not ("0" <= char <= "9") for char in whole + fraction):
raise ValueError("invalid number")
whole = whole or "0"
padding = max(0, exponent + 1 - len(whole))
whole = "0" * padding + whole
digits = whole + fraction
keep_length = len(whole) - exponent
digits += "0" * max(0, keep_length + 1 - len(digits))
retained = list(digits[:keep_length])
if digits[keep_length] >= "5":
index = len(retained) - 1
while index >= 0 and retained[index] == "9":
retained[index] = "0"
index -= 1
if index < 0:
retained.insert(0, "1")
else:
retained[index] = str(int(retained[index]) + 1)
rounded = "".join(retained)
if exponent >= 0:
output = (rounded + "0" * exponent).lstrip("0") or "0"
else:
places = -exponent
rounded = rounded.zfill(places + 1)
output = (rounded[:-places].lstrip("0") or "0") + "." + rounded[-places:]
return "-" + output if negative and any(char in "123456789" for char in output) else output
复杂度
设字符串长度 N、p 表示的位数绝对值 D,解析和进位 O(N+D),需构建保留位与输出 O(N+D) 空间;不把整串转换为固定精度浮点数。
易错点与追问
容易失分的边界
- 直接调用 float('超长十进制'),可能溢出或丢失低位精度。
- 把 '-.2' 或 '2.' 判非法,却没有先核示例。
- 只看下一位 >5,漏掉恰好 5 的 tie-break。
- 连续 9 进位时只改最后一位。
- 把语言默认 bankers rounding 当题目规定的规则。
继续深挖
- 若要求 ties-to-even,应如何判断保留位和后续非零位?
- p 不限于 10 的幂而是 0.25 时,纯字符串算法需如何扩展?
- 带指数记法的输入如何规范化又不爆内存?
