一九九九年十二月三十一日,星辰的模拟账少了一分钱。
周会计把算盘重新拨了一遍,仍然少一分。
她不信,又把一百组付款、失败和退款记录逐张翻出来,用铅笔在金额旁打钩。晚上八点,楼下商店已经开始放迎接新世纪的歌曲,办公室里只听见算盘珠子撞击木框的声音。
“电脑汇总是九百八十七元六角三分。”小郭说,“纸账是六角四分。”
“银行模拟回执呢?”
“六角四分。”
“那电脑少一分。”
“也可能你多算一分。”
周会计抬眼看他。小郭立刻把后半句咽回去,拿走一半凭证重算。
今天本来只是封闭试付前的最后一次模拟。他们用一百组虚拟交易覆盖正常付款、金额超限、身份复核、银行受理、失败退回和部分退款。所有钱都停留在纸面和不联网的旧电脑里,目的就是证明三本账可以相互对应。
测试结束后,众人约好去广场看零点倒计时。
林海从家里带来一只饭盒,里面是妻子包的白菜猪肉饺子。饺子挤在一起,皮有些破。他说十点煮,吃完正好出门。苏雨桐买了两袋花生和一串小彩灯,准备挂到窗边。陈维明甚至带来一瓶香槟样子的苹果汽酒,瓶塞上拴着金色塑料绳。
少掉的一分钱把所有计划停在八点。
“会不会是录入错了?”林海问。
“每张单两人录入,差异已经比过。”苏雨桐说。
“打印丢了一位?”
“总数能重复生成。”
“一分钱而已。”陈维明终于忍不住,“一百组里差一分,明年再查。今天所有人都有安排。”
周会计把算盘往桌中间一推:“账上没有‘一分而已’这个科目。”
“我不是说不查。先把结果写成基本一致。”
“差一分就不是一致。”
“真实做生意还有合理损耗。”
“损耗要有去处。”
两个人争起来时,锅里的第一批水正好烧开。林海忘了揭盖,热水扑出来浇灭电炉,屋里腾起一股白汽。众人忙着拔插头、擦桌子,包好的饺子只能继续在饭盒里粘着。
马飞站在打印纸前,没有参与争论。
前世大型工程中,最危险的数字常常不是明显错误,而是“只差一点”。百分之一的推力偏差、几十毫秒的时序漂移、一个被平均值掩盖的异常点,都会因为不值得停下而被带进下一阶段。等它与另一个小偏差相遇,代价就不再小。
支付系统同样如此。一分钱如果没有解释,它可能来自舍入,也可能来自重复扣款、遗漏退款或状态错位。金额小,只能说明这次损失小,不能证明机制安全。
“今晚查完。”他说,“倒计时赶不上就不赶。封闭试付推迟,直到三本账完全相等。”
陈维明看了一眼手表,最终没有再反对。他脱下大衣,卷起袖子:“怎么查?”
他们把一百组交易按十组一叠分开,分别计算付款本金、技术服务费、退款和失败回冲。九点十二分,差异缩小到第六叠;九点四十分,锁定三笔金额三元三角三分的测试交易。
按设计,星辰未来若收技术服务费,将在银行确认交易完成后单独出具服务账单,不从客户货款里暗扣。模拟程序为了测试计费,按百分之一点五计算。单笔三元三角三分乘费率,结果不足一分的部分必须按约定舍入。
问题在于,两处程序舍入方式不同。
交易明细对每笔分别保留到分,再相加;批次汇总先把三笔精确小数相加,最后统一保留到分。两种算法在大多数金额上相同,碰到这三笔时,相差恰好一分。
“那选一种就行。”小郭松了口气,“不是钱丢了。”
“现在是模拟,所以没丢。”苏雨桐说,“真实执行时,如果客户明细按一种扣,财务汇总按另一种记,这一分会真的不知道属于谁。”
“每笔舍入,还是批次舍入?”陈维明问。
周会计说,首先要按合同明确计费单位。对客户逐笔出账,就必须每笔计算并显示,不可以在后台用批次算法制造一个无法对应的差额。更重要的是,程序不应使用会产生隐含小数误差的通用数值表示来存钱。
马飞在白板上写:金额统一用“分”作为最小整数单位。任何比例计算必须写明舍入时点与方向,结果生成独立服务费凭证。货款本金、银行费用和星辰服务费不混在一个数字里。
“如果以后有比分更小的计算呢?”小郭问。
“中间计算可以有更高精度,但进入客户应付前必须按规则落到分,并把舍去部分的处理说清。”
林海则检查另一层:即使计算一致,一笔状态变化也不能只改余额。每次虚拟付款必须同时产生来源和去向,失败则用新的反向记录冲回,不删除原记录。
他在纸上画两个方框。付款人减少八百元,待银行执行增加八百元;银行确认后,待执行减少八百元,收款人应收增加八百元。每一步左右相等,任何一边缺记录,当天就不能结清。
“这叫双分录?”小郭问。
“会计一直这么干。”周会计说,“你们程序现在才肯学。”
十点半,林海重新点着电炉。第一锅饺子已经粘成一团,只能煎。没有平底锅,他在铝饭盒里抹油,把破饺子摊成一张饼。香味慢慢出来,大家围着桌子用筷子夹,谁都分不清自己吃的是皮还是馅。
苏雨桐把小彩灯挂起来。彩灯只有红绿两色,接触不良,每隔几分钟就全灭。马飞用胶布固定插头,它才勉强亮住。
十一点零五分,程序改完。
一百组交易重新运行。第一次在第七十三组停下,因为一笔失败回冲没有关联原指令号;第二次又发现部分退款的状态名称含糊,“已退款”没有区分银行受理和客户账户实际收到。
每修一处,都会露出另一处过去被总数相等掩盖的问题。
十一点四十二分,第三次运行完成。公司财务模拟账、服务履约账、交易状态账全部为九百八十七元六角四分。每一笔借方都能找到贷方,每一次失败都有新的记录指向原始指令,历史没有被覆盖。
周会计仍不肯宣布通过。她随机抽十笔,从纸质授权追到程序记录,再追到模拟银行回执。最后一笔核完时,楼外已经有人提前放烟花。
十一点五十六分。
“通过。”她说。
众人冲下楼,广场肯定赶不上了,只能爬到楼顶。通往屋顶的铁门生锈,林海踹了两脚才打开。寒风迎面扑来,小彩灯来不及拆,他们只带上那瓶苹果汽酒和几只搪瓷杯。
远处的城市灯光被薄雾揉成一片。没有统一倒数,几栋居民楼里传出电视主持人的声音,时间还差十几秒,鞭炮已经从四面八方响起来。
陈维明拔瓶塞,塞子没有飞,只发出一声闷响。他把汽酒倒进杯子,泡沫漫到手上。
“为了什么干杯?”小郭喊。
“为一分钱。”周会计说。
“为二〇〇〇年。”林海说。
苏雨桐望向马飞:“你呢?”
马飞听着零点的钟声。二〇〇〇年,本来是路线图上移动支付预研和火箭技术储备同时展开的一年。它终于到来,却没有未来记忆中的高速网络、智能终端和成熟市场……
“为能对上的每一步。”他说。
杯子碰在一起,搪瓷边缘发出不整齐的响声。苹果汽酒甜得发腻,喝不出香槟的味道。楼下有人认错烟花方向,整栋楼都在笑。
苏雨桐把手伸进口袋,拿出那封曾被房东误拆、后来用胶带粘好的信。她把信封背面空白处撕下一小条,写上“一分钱,查到十一点五十六”,递给周会计当书签。
周会计收下,说下次再差一分,不许拿过新年当借口拖到明年。
众人又笑。
零点过后,生活没有突然改变。公交已经停运,林海要骑车回家,马飞和小郭帮他把车从堆满杂物的楼道抬出来。苏雨桐提着牡丹保温瓶,里面装了剩下的热水。陈维明负责把空汽酒瓶带走,免得第二天被房东说公司在屋顶喝酒。
下楼前,马飞回头看了一眼城市。
新世纪的第一分钟,星辰没有完成一笔真实网络支付,也没有点燃一台火箭发动机。
但它决定了以后每一分钱都必须以整数落账,每一次移动都要同时留下来源与去向,每一个“已退款”都要说清究竟走到了哪里。
一分钱没有让他们发财,也没有让他们损失惨重。
它只是让星辰在真正碰到别人的钱以前,学会了不把无法解释的差额叫作合理误差。