程序员老鬼

月薪2万,在大厂干了10年被裁了,拿了30万赔偿金,五年后HR联系我,问我愿不愿意回去,但要归还30万,我该回去吗?

刚看到个贴子,说有个网友在大厂月薪2万干了10年被裁,拿了30万赔偿。五年后公司又来找,说可以回去,但前提是把当年的30万还回去,他就纠结要不要答应。

Image

网友回复我看了看,有的说“钱不能退,人也别回”,有的觉得“只要给得起现在的价,退点也行”。

我觉得这事吧,先别被情绪带跑。第一,那30万本质是公司当时为裁员付的成本,法律和感情上都没理由原封退回;现在让你退,相当于默认当年那刀可以白砍。第二,就算不谈原则,也得算账:同岗位现在的薪资、发展、稳定性和你手里的机会比一比。

真香就当新工作谈,条件不到位,别被“回锅情怀”绑架。

面试题:发票中的产品金额

想象你在给公司做个开票系统,财务小姐姐丢给你一句话: “我这边有总金额、税率、每个商品的数量单价,你给我把每个商品行的金额算对,保留两位小数,还得保证所有行加起来刚好等于发票总额,不能差一分钱。”

听着挺简单对吧,单价乘数量嘛。真上手你就会发现,坑全在“保留两位小数”和“最后要对得上”这俩词上。

我们简化一下需求,发票上每一行有这些东西:

  • 商品名
  • 数量
  • 单价(不含税)
  • 折扣(可选,比如 9 折)
  • 税率(比如 13%)

要做的事有两步:

  1. 算出每一行「含税金额」,保留两位小数;
  2. 所有行金额加起来,要和发票总金额一模一样。因为四舍五入可能会有几分钱的误差,这就得搞一个“分摊算法”。

所以这是个“计算 + 分摊”的小算发题,用 Java 写比较典型: 避坑要点两个字:BigDecimal。

先不管分摊问题,单行金额其实就三步:

  1. 原始金额 = 单价 × 数量
  2. 折扣后金额 = 原始金额 × (1 - 折扣率)
  3. 含税金额 = 折扣后金额 × (1 + 税率),最后保留两位小数

直接上段 Java 小例子,你感受下写法就行:

import java.math.BigDecimal;
import java.math.RoundingMode;

publicclassInvoiceItem{

private String name;
privateint quantity;
private BigDecimal unitPrice;   // 不含税单价
private BigDecimal discount;    // 0.1 表示打 9 折
private BigDecimal taxRate;     // 0.13 表示 13%

publicInvoiceItem(String name, int quantity,
                       BigDecimal unitPrice,
                       BigDecimal discount,
                       BigDecimal taxRate)
{
this.name = name;
this.quantity = quantity;
this.unitPrice = unitPrice;
this.discount = discount;
this.taxRate = taxRate;
    }

// 计算这一行的含税金额
public BigDecimal calcAmountWithTax(){
        BigDecimal qty = BigDecimal.valueOf(quantity);

// 原始金额 = 单价 * 数量
        BigDecimal raw = unitPrice.multiply(qty);

// 折扣后金额 = 原始金额 * (1 - discount)
        BigDecimal discountFactor = BigDecimal.ONE.subtract(discount);
        BigDecimal afterDiscount = raw.multiply(discountFactor);

// 含税金额 = 折扣后金额 * (1 + taxRate)
        BigDecimal taxFactor = BigDecimal.ONE.add(taxRate);
        BigDecimal withTax = afterDiscount.multiply(taxFactor);

// 保留两位小数,四舍五入
return withTax.setScale(2, RoundingMode.HALF_UP);
    }

public String getName(){
return name;
    }
}

这里有两个小细节你一定要注意一下:

  • 千万别用 double,用 BigDecimal,钱都是这样算的;
  • setScale(2, RoundingMode.HALF_UP),别偷懒用默认构造,不然有时候会直接抛异常。

问题来了,就算每一行你都算对了,两位小数也都对,但所有行加起来,和发票总金额,可能会差个 0.01 或 0.02。

举个极端例子: 有三行商品,理论精确金额是:

  • 33.335
  • 33.335
  • 33.335

四舍五入后每行都是 33.34,那三行加起来就 100.02 但财务要求发票总额就是 100.01 或 100.00,这就不对了。

怎么办?思路很简单:先按正常公式算出每一行,再看差额是多少,最后把差额摊到某一行(或者几行)里去。

最粗暴也最好实现的一种就是: 差额全丢给金额最大的那一行。

我们写个小工具类,把“算行金额”和“分摊误差”都串起来:

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

publicclassInvoiceCalculator{

publicstaticclassLineAmount{
public InvoiceItem item;
public BigDecimal amount; // 已经保留两位小数

publicLineAmount(InvoiceItem item, BigDecimal amount){
this.item = item;
this.amount = amount;
        }
    }

/**
     * 计算每行金额,并按照发票总额做差额分摊
     */

publicstatic List<LineAmount> calcWithAdjust(List<InvoiceItem> items,
                                                  BigDecimal invoiceTotal)
{
        List<LineAmount> result = new ArrayList<>();

// 1. 先算出每一行的金额
        BigDecimal sum = BigDecimal.ZERO;
int maxIndex = 0;
        BigDecimal maxAmount = BigDecimal.ZERO;

for (int i = 0; i < items.size(); i++) {
            InvoiceItem item = items.get(i);
            BigDecimal amount = item.calcAmountWithTax();
            result.add(new LineAmount(item, amount));
            sum = sum.add(amount);

// 记录最大金额的那一行
if (amount.compareTo(maxAmount) > 0) {
                maxAmount = amount;
                maxIndex = i;
            }
        }

// 2. 计算差额 = 发票总额 - 行小计之和
        BigDecimal diff = invoiceTotal.subtract(sum);

// 如果差额非 0,就把差额加到最大金额的那一行
if (diff.compareTo(BigDecimal.ZERO) != 0) {
            LineAmount line = result.get(maxIndex);
            line.amount = line.amount.add(diff)
                    .setScale(2, RoundingMode.HALF_UP);
        }

return result;
    }
}

再来个简单 main,把流程跑通:

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;

publicclassDemo{
publicstaticvoidmain(String[] args){
        InvoiceItem item1 = new InvoiceItem(
"电脑",
1,
new BigDecimal("4999.00"),
                BigDecimal.ZERO,
new BigDecimal("0.13")
        );

        InvoiceItem item2 = new InvoiceItem(
"鼠标",
2,
new BigDecimal("59.90"),
new BigDecimal("0.10"),   // 打 9 折
new BigDecimal("0.13")
        );

        List<InvoiceItem> items = Arrays.asList(item1, item2);

// 假设财务系统那边给的发票总额
        BigDecimal invoiceTotal = new BigDecimal("5200.00");

        List<InvoiceCalculator.LineAmount> list =
                InvoiceCalculator.calcWithAdjust(items, invoiceTotal);

        list.forEach(line -> {
            System.out.println(line.item.getName() + " 行金额 = " + line.amount);
        });
    }
}

实际运行的时候,你会看到每一行金额都很“正常”,并且两行相加刚好是 5200.00。 如果因为四舍五入有 0.01 的误差,就会被我们塞到金额最大的那一行里去,财务看起来也完全能接受。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

最后给大家分享一份不错的副业资料,点击下方公众号,回复关键字: 副业 领,也可以链接我微信:hls404