MLE · Phone Coding

Pinterest 手写 Round:字符串小数按指定位数进位

MLE 电面明确从字符串实现 round,追问浮点溢出、-.2、2. 和按 100/0.1 取整;本文用十进制字符进位避免 float。

题目重点:MLE 电面明确从字符串实现 round,追问浮点溢出、-.2、2. 和按 100/0.1 取整;本文用十进制字符进位避免 float。

题意与假设

这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。

  • 训练版输入仅含 ASCII 十进制数字、可选正负号与一个小数点,至少一位数字;p 是 10 的整数幂,如 100、1、0.1。
  • 采用 ROUND_HALF_UP,即绝对值恰好落在 .5 时远离零;输出保留 p 指定的小数位,整数取整不加小数点,结果为负零时去掉负号。
  • 题目未明确舍入 tie-break 和科学计数法支持;面试现场应先确认。

解题路线

  1. 先解析 p 得要保留的十进制位指数;分别解析 s 的符号、整数位和小数位,不调用 float,避免超长数字溢出与二进制浮点误差。
  2. 将数字看成连在一起的十进制字符,计算保留前缀的长度;不够高位时补零,不够低位时也补零,保证任何 p 都可处理。
  3. 检查第一个丢弃位,≥5 就从保留前缀最右端向左进位,连串 9 要连续变 0,必要时左侧新添 1。
  4. 按 p 插回小数点或补整数尾零,去掉多余整数前导零;若最终为零清除负号。
  5. 用 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 时,纯字符串算法需如何扩展?
  • 带指数记法的输入如何规范化又不爆内存?
✓已复制微信号!