纯Java Android个税计算器:从引擎设计到模拟器出图
前阵子整理旧工程翻出一个用纯 Java 写的个人所得税计算演示 App。当时做它的原因很简单想验证一下在不引入 Kotlin、不依赖第三方库的前提下一套计算引擎能不能在 Android 原生环境里跑通顺便在模拟器上完成录屏截图把界面效果和计算过程都留档。这个项目定位很纯粹——生成的是计算结果和演示素材纯 Java是为了让刚入门的朋友能直接读懂源码模拟器截图则是给代码仓库准备一份不依赖真机的效果展示。需要先说清楚这份源码只能当学习参考不能把它当成真实申报场景的工具来用任何情况下都别拿它生成的材料去冒充缴税凭证。这个 App 适合三类人看正在学 Java 语法、想知道 Android 原生工程长什么样的初学者需要在简历或作业里放一个功能完整、有截图有源码的小项目的人以及想弄明白累计预扣预缴到底怎么算想找个能跑起来的代码做参照的开发者。下面我按实际开发顺序把从业务逻辑、代码结构到模拟器出图的全过程拆开讲。1. 这个App到底要做什么从生成器到计算演示器的定位先解释一个容易误会的地方标题里写生成器实际做的不是凭空生成一份税单而是把用户输入的月薪、五险一金、专项附加扣除等参数按照现行个税规则一步步计算出每个月的预扣税额并生成一份可滚动的明细结果。计算完成之后App 本身也不负责导出 PDF 之类的东西一切结果都停留在界面上。配合模拟器的录屏和截图就形成了一套完整的输入-计算-展示演示闭环。我把这个项目当成一个纯粹的演示型工程所以很多商业 App 该有的功能都没有没有网络请求、没有数据库、没有登录注册甚至没有第三方依赖。这些缺失恰恰是它能作为学习材料的原因——所有代码都在你能看到的十几个文件里不会出现某个功能其实藏在某个 SDK 里的尴尬。1.1 为什么特意用纯Java现在 Android 开发的主流语言已经变成 Kotlin 了新项目默认模板都是 Kotlin很多教程也直接跳过 Java 讲 Compose。这种情况下选择纯 Java 写一个完整 App反倒显得有点复古。但我的理由很实际第一Java 是 Android 平台最底层的语言之一Activity、Service、BroadcastReceiver 这些组件的 API 本来就是按 Java 设计的。用 Kotlin 写很多概念会被语法糖包裹住用 Java 写你能直接看到生命周期回调长什么样Context 在代码里是怎么传的这些底层事实。第二不引入第三方库能大幅度降低理解成本。这个工程没有 Gson、没有 OkHttp、没有 ButterKnife数据传递靠普通的 Java 对象和方法调用布局文件也只用了系统自带的控件。打开工程每一行代码都能在 JDK 文档或 Android 官方文档里找到出处这对初学者来说是很大的安全感。第三计算引擎是纯 Java 类完全没 import Android 相关的包。这意味着我可以把引擎包单独拷到电脑上在命令行里用 javac 直接编译运行或者写一个 main 方法在 JVM 里测试。很多人喜欢把这种项目叫可脱离 App 运行的算法模块其实说白了就是业务逻辑不要和界面绑死至少在测试的时候你就知道有多省心。下面这张表是我当时对比过的三种技术路线方便你理解我的选择逻辑方案上手门槛依赖情况模拟器出图难度适合场景纯 Java XML 布局低Android 基础教程全覆盖只需系统库和基础兼容库低稳定教学演示、源码参考、小工具Kotlin XML 布局中需先掌握 Kotlin 语法同上低主流商业项目、新功能开发Flutter/React Native较高需理解跨端框架引入整套跨端运行时中环境配置更复杂跨平台业务、UI 高复用团队对你来说如果想要一份打开就能看懂、跑起来就能截图的源码纯 Java 是当前最合适的形态。1.2 功能清单与学习价值这个 Demo 的功能清单不长但每一条都对应一个知识点输入月薪、每月五险一金金额、每月专项附加扣除金额、当年已任职月数四个参数。按累计预扣预缴规则逐月计算累计应纳税所得额、适用税率、速算扣除数、当月应预扣税额、累计已预扣税额。以列表形式展示全年 12 个月的完整计算过程。对非法输入空值、负数、非数字做校验并提示。支持在开发环境的模拟器上运行通过 ADB 命令完成截图和录屏。对应到学习价值上Java 面向对象设计实体类、枚举、工具类、算法逻辑税率分级、累计计算、Android 四大组件里的 Activity 使用、EditText 输入监听、ScrollView 列表、dp 布局适配、ADB 调试命令。一个很小的项目把这么多知识点串在了一起这也是我把它翻出来重新整理的原因之一。2. 个税计算的核心逻辑累计预扣预缴为什么不能简单套月薪×税率说实话在动手之前我也以为个税计算很简单无非就是收入减去扣除再乘个税率。等真正把公式拆开、把税率表一张张对完才发现里面有几个容易翻车的坑。这里必须先讲透业务逻辑因为后面所有 Java 代码都是照着这套规则写的。2.1 公式拆解和参数含义现行个税对工资薪金采用累计预扣预缴法核心公式是这样的累计预扣预缴应纳税所得额 累计收入 - 累计免税收入 - 累计减除费用 - 累计专项扣除 - 累计专项附加扣除 - 累计依法确定的其他扣除本期应预扣预缴税额 累计预扣预缴应纳税所得额 × 预扣率 - 速算扣除数 - 累计减免税额 - 累计已预扣预缴税额逐项说明累计收入从当年 1 月开始到当前月份的工资收入总和。累计减除费用也就是常说的起征点每月 5000 元按当年截至本月的任职受雇月份数累乘。注意这里不是按自然月死算的年中入职的人只算入职后的月份数。累计专项扣除基本养老保险、基本医疗保险、失业保险、住房公积金也就是你工资条里的三险一金个人缴纳部分。累计专项附加扣除子女教育、继续教育、大病医疗、住房贷款利息、住房租金、赡养老人、3 岁以下婴幼儿照护这几类按规定标准逐月累计。预扣率和速算扣除数由累计应纳税所得额决定对应年度综合所得税率表后面我会把完整表格列出来。速算扣除数这个概念值得多说两句。因为个税是超额累进税率——你的应纳税所得额分成若干段每段各自适用不同的税率。比如应纳税所得额 10 万元并不是整 10 万都按 10% 算而是前 3.6 万按 3%剩下的 6.4 万才按 10%。如果每次都要分段乘税率再求和程序写起来很啰嗦。数学家们想了个办法用全额 × 税率 - 一个固定常数一步到位这个常数就是速算扣除数。例如 10 万那档的速算扣除数是 2520就等于前 3.6 万部分多按 10% 计算时比 3% 多出来的部分36000 × (10% - 3%) 2520。这也顺便解释了为什么网上有些按月算税的速算扣除数表看起来不一样——那是月度税率表处理非居民工资薪金、年终奖单独计税用的是另一套基数。做这个 Demo 时很容易把它们混在一起代码里我特意只保留年度综合所得表从根上排除混乱。2.2 税率表与档位边界计算器最核心的数据是这张年度综合所得税率表年应纳税所得额区间预扣率速算扣除数不超过 36000 元3%0超过 36000 元至 144000 元10%2520超过 144000 元至 300000 元20%16920超过 300000 元至 420000 元25%31920超过 420000 元至 660000 元30%52920超过 660000 元至 960000 元35%85920超过 960000 元45%181920注意两个细节一是应纳税所得额不是收入。同样月薪 2 万的人扣除项不同最后落在哪个税档完全不同不能看着收入就拍脑袋说月入 2 万就是 10% 的税。二是临界点。36000、144000、300000 这些数字必须精确处理。比如应纳税所得额恰好是 36000税率应为 3%超过 1 块钱变成 36001就进入 10% 档。以前我用大于等于号判断结果在恰好压线的用例上直接算错。后来写单元测试时固定加了两组数据正好压线和压线多 1 元。2.3 代码落地TaxCalculator引擎设计清楚了规则之后代码反而不难写。我建了一个engine包里面放了三个类TaxLevel.java税率档位枚举把上表写死。CalculateResult.java保存每个月的计算结果。TaxCalculator.java提供核心静态方法。核心方法大概是这个思路public class TaxCalculator { public static final double MONTHLY_DEDUCTION 5000.0; /** * 计算某个月的预扣预缴情况。 * param monthIndex 当前月份从 1 到 12 * param monthlySalary 月薪 * param monthlyInsurance 每月五险一金 * param monthlySpecialDeduction 每月专项附加扣除 * param workedMonths 当年已任职月数 */ public static CalculateResult calculateMonth(int monthIndex, double monthlySalary, double monthlyInsurance, double monthlySpecialDeduction, int workedMonths) { int actualMonths Math.min(monthIndex, workedMonths); double totalIncome monthlySalary * monthIndex; double totalDeduction (MONTHLY_DEDUCTION monthlyInsurance monthlySpecialDeduction) * actualMonths; double taxableIncome totalIncome - totalDeduction; TaxLevel level TaxLevel.getLevel(taxableIncome); double totalTax taxableIncome * level.getRate() - level.getQuickDeduction(); if (totalTax 0) { totalTax 0; } return new CalculateResult(monthIndex, taxableIncome, level.getRate(), level.getQuickDeduction(), totalTax); } /** * 计算某个月本期应补缴的税额。 * 前一个月的累计税额作为已预扣数传入。 */ public static double calculateCurrentMonthTax(CalculateResult current, double previousTotalTax) { return current.getTotalTax() - previousTotalTax; } }TaxLevel的写法要特别注意我用的是有序区间判断而不是单独的 switchpublic enum TaxLevel { LEVEL_1(0, 36000, 0.03, 0), LEVEL_2(36000, 144000, 0.10, 2520), LEVEL_3(144000, 300000, 0.20, 16920), LEVEL_4(300000, 420000, 0.25, 31920), LEVEL_5(420000, 660000, 0.30, 52920), LEVEL_6(660000, 960000, 0.35, 85920), LEVEL_7(960000, Double.MAX_VALUE, 0.45, 181920); private final double lower; private final double upper; private final double rate; private final double quickDeduction; public static TaxLevel getLevel(double taxableIncome) { for (TaxLevel level : values()) { if (taxableIncome level.lower taxableIncome level.upper) { return level; } } return LEVEL_7; } }用这种左开右闭区间的写法配合枚举遍历每一档的边界一目了然。区间上下限直接对应税率表里的超过不超过措辞不容易出现代码和规则互相看不懂的情况。注意36000那档的下界是 0上界是 36000所以刚好等于 36000 时会进入 LEVEL_1符合前面说的边界规则。严格来说区间是 (lower, upper]也就是不包括 lower、包括 upper。3. 纯Java落地实现引擎与UI解耦是重点学会了公式下一步就是把它变成一个能操作的 Android 界面。这一节讲工程组织和界面代码的几个关键点。我的原则始终是引擎包不依赖 AndroidUI 层尽量薄这样代码可读性最好也最容易测试。3.1 工程目录与依赖清单整个工程很简单源码结构是这样的app/ ├── src/main/ │ ├── java/ │ │ └── com/example/taxdemo/ │ │ ├── engine/ │ │ │ ├── TaxCalculator.java │ │ │ ├── TaxLevel.java │ │ │ └── CalculateResult.java │ │ ├── ui/ │ │ │ └── MainActivity.java │ │ ├── util/ │ │ │ └── CommonUtils.java │ │ └── adapter/ │ │ └── ResultListAdapter.java │ └── res/ │ ├── layout/ │ │ └── activity_main.xml │ └── values/ │ ├── strings.xml │ └── colors.xml依赖方面只有系统自带的基础兼容库没有引入任何第三方业务库。你可以打开build.gradle看一眼dependencies里干净得让人感动。这对演示项目的优势很明显克隆下来就能编译不会因为某个依赖版本冲突卡半天。3.2 MainActivity与布局设计要点界面只有一个MainActivity上半部分是四个输入框中间是计算按钮下半部分用ScrollView包住一个动态生成的LinearLayout用来显示 12 个月的计算结果。之所以不用RecyclerView而用ScrollView LinearLayout是因为结果只有 12 条数量固定、结构简单用 RecyclerView 还要写 Adapter 和 Item 布局反而增加学习负担。App 是演示用的代码越直接越好。布局文件里几个容易忽略的细节EditText android:idid/et_salary android:layout_widthmatch_parent android:layout_heightwrap_content android:inputTypenumberDecimal android:hint请输入月薪 / EditText android:idid/et_insurance android:layout_widthmatch_parent android:layout_heightwrap_content android:inputTypenumberDecimal android:hint请输入每月五险一金 / EditText android:idid/et_special android:layout_widthmatch_parent android:layout_heightwrap_content android:inputTypenumberDecimal android:hint请输入每月专项附加扣除 / EditText android:idid/et_worked_months android:layout_widthmatch_parent android:layout_heightwrap_content android:inputTypenumber android:hint当年已任职月数(1-12) /inputType设置成numberDecimal可以让弹出的键盘直接是数字键盘减少用户输入字母的可能。注意当年已任职月数用的是纯number不需要小数。这个字段很容易被忽略但它直接影响累计减除费用的计算如果用户是年中入职不填这个数就会多扣 5000 × 几个月计算结果会差很多。在 AndroidManifest 里还给 Activity 设置了软键盘模式activity android:name.ui.MainActivity android:windowSoftInputModeadjustResize不设置的话键盘弹出来会直接把输入框遮住体验很差录屏的时候也会显得很业余。3.3 从输入到结果的完整调用链核心逻辑写在按钮的点击事件里。这里我用了最原始但最直观的setOnClickListener没有用 ViewBinding因为想让大家看到拿到 View 引用 - 读取输入 - 计算 - 渲染这条完整链路。输入解析部分最容易出错。EditText 拿到的永远是字符串可能是空串、非法数字或负数。我写了一个CommonUtils.safeParse方法统一处理public static double safeParse(EditText editText, double defaultValue) { String text editText.getText().toString().trim(); if (text.isEmpty()) { return defaultValue; } try { double value Double.parseDouble(text); if (value 0) { return defaultValue; } return value; } catch (NumberFormatException e) { return defaultValue; } }这里有个小陷阱Double.parseDouble(0)返回合法的 0.0但有些后续场景要把0和没输入区分开。比如用户不填专项附加扣除应该当作 0 而不是默认值用户填了负数则应该提示非法。所以我还加了isValid辅助方法判断输入框是否合法。计算完成后的渲染逻辑也不复杂LinearLayout resultContainer findViewById(R.id.result_container); resultContainer.removeAllViews(); double previousTotalTax 0.0; for (int month 1; month 12; month) { CalculateResult current TaxCalculator.calculateMonth( month, salary, insurance, special, workedMonths); double monthTax TaxCalculator.calculateCurrentMonthTax(current, previousTotalTax); String line String.format(Locale.CHINA, 第%d个月应纳税所得额 %.2f税率 %.0f%%速算扣除数 %.0f 本月预扣 %.2f累计预扣 %.2f, month, current.getTaxableIncome(), current.getRate() * 100, current.getQuickDeduction(), monthTax, current.getTotalTax()); TextView textView new TextView(this); textView.setText(line); textView.setTextSize(14); textView.setPadding(16, 8, 16, 8); resultContainer.addView(textView); previousTotalTax current.getTotalTax(); }注意我用String.format(Locale.CHINA, ...)带上了 Locale。不在格式化时显式传 Locale在某些语言环境下小数点的显示可能变成逗号中文环境一般没事但这是安卓开发里一个很经典的隐藏 Bug养成随手带上 Locale 的习惯不会错。4. 模拟器里的录屏与截图从调试到出图的全过程写代码只是第一步标题里明确写了模拟器录屏截图说明作者是拿模拟器出效果图的。这节我把完整过程写出来从创建模拟器到导出图片视频每一步的命令和坑都讲清楚。4.1 从创建模拟器到安装App我用开发环境自带的 AVD 管理器创建了一个虚拟机。创建时选的是不带 Play Store 的系统镜像二选一有讲究带 Play Store 的镜像首次启动要登录账号、下载更新既慢又可能在截图时出现各种通知不带商店的原始镜像更干净适合做演示工程。分辨率我用 1080x1920密度 420 左右。这个参数决定了最终截图的清晰度太低图片模糊太高 App 在小字号下可能出现布局挤压。一般演示用途 1080p 就够用了。模拟器启动后把 APK 装进去adb install -r app-debug.apk如果工程还没打包Android Studio 会自动构建 debug 包。安装完成后直接启动adb shell am start -n com.example.taxdemo/.ui.MainActivity用命令行启动 Activity 比手动在模拟器里点图标更可控尤其是准备录屏的时候可以保证每次演示的起始界面完全一致。4.2 三种截图/录屏方式对比我试过三种方式各有适用场景整理成了表格方式命令适用场景坑系统截图工具模拟器窗口上的相机图标临时快速预览会带上模拟器边框和桌面背景不适合放代码仓库ADB 截图adb exec-out screencap -p tax_demo.png截取纯 App 内容清晰度最高电脑终端直接重定向可能损坏图片更稳的是下面这条ADB 截图稳妥版adb shell screencap /sdcard/tax.png再adb pull生产效果图多一条命令但文件百分百完整ADB 录屏adb shell screenrecord --time-limit 15 /sdcard/demo.mp4展示交互流程默认不支持麦克风录音录屏时长默认上限 180 秒我最常用的截图流程是adb shell screencap /sdcard/tax_demo.png adb pull /sdcard/tax_demo.png两条命令先截到模拟器内部存储再拉到电脑本地。之所以不直接用adb exec-out重定向是因为某些电脑终端环境会对二进制输出做二次处理导致 PNG 损坏。既然写博文、放代码仓库都要用图稳定性优先。录屏命令也一并放出来adb shell screenrecord --time-limit 15 --size 720x1280 /sdcard/tax_demo.mp4 adb pull /sdcard/tax_demo.mp4--size 720x1280可以把视频控制在合理大小演示 App 的界面足够清晰传到社区或文档里也不会卡顿。没有加--bit-rate时默认码率可能偏大文件体积会多出不少想要更小可以加--bit-rate 40000004Mbps 对 720p 视频够用。4.3 截图前必须处理的UI细节录屏截图最尴尬的事情不是代码算错而是画面难看。我踩过几个坑现在提前排掉第一个坑是键盘自动弹出。页面一打开第一个输入框自动获得焦点软键盘跟着弹出来把计算按钮都挡住了。解决办法是在根布局上设置android:focusableInTouchModetrue这样页面启动时不会默认让某个 EditText 抢焦点键盘保持关闭截图干净清爽。第二个坑是中文乱码。模拟器默认语言如果是英文中文字符可能出现方框。先把模拟器语言切到中文再重启 App。如果还乱码就检查strings.xml或者代码里是否用了错误的字符编码。演示类项目界面基本都是中文文案这个检查不能省。第三个坑是系统状态栏和通知栏。录屏时会看到模拟器顶部的电池、时间、Wi-Fi 图标如果是测试机还可能有 USB 调试提示画面很乱。我当时给 Activity 设置了一个全屏主题录屏时界面直接铺满视觉上干净不少。第四个坑是文件命名。录屏和截图会生成很多带时间戳的文件我按demo_tax_01.png、demo_tax_02.mp4这种方式管理后面写文档、贴图时不会搞混。另外录屏时手动在模拟器上点输入框、敲数字这些操作都会被记录。如果你不想让光标闪烁、键盘动画等细节显得拖沓可以预先在一个输入框里填好数据录屏时只点一次计算按钮。这样十几秒的视频就能把核心功能讲清楚。5. 结果校验别让计算器算错了还不知道代码能跑了截图也出了但真正让我冷汗直冒的是第一次对照结果时发现算错了。所以专门用一节讲校验这是所有计算类项目最容易轻视的一步。5.1 为什么必须自己校验写计算逻辑的时候我脑子里模拟的用例不够多总觉得差不多。等真把 App 跑起来输入一组数据发现第 7 个月的税额突然从每月 165 跳到 340第一反应是程序 bug后来一个个数字去对税率表才发现这恰恰是累计预扣预缴跳档的正常表现。这个经历说明不要相信自己的第一直觉要用可复现的对照数据来验证代码。我用的方法很笨但很有效先挑几组收入不同的数据用电子表格软件按公式手算一遍全年 12 个月的预扣税再拿 App 跑同样输入逐月比对。5.2 三组实测数据下面是我当时测的三组数据你可以直接复制到 App 里验证情景 A月薪 12000五险一金 1500专项附加扣除 01 月入职。月份累计应纳税所得额适用税率速算扣除数本月预扣税累计预扣税155003%01651656330003%016599073850010%25203401330126600010%25205504080情景 B月薪 25000五险一金 3000专项附加扣除 20001 月入职。月份累计应纳税所得额适用税率速算扣除数本月预扣税累计预扣税1150003%045045034500010%252010801980913500010%25201500109801015000020%169202100130801218000020%16920300019080情景 C月薪 50000五险一金 7000专项附加扣除 50001 月入职。月份累计应纳税所得额适用税率速算扣除数本月预扣税累计预扣税1330003%099099026600010%252030904080516500020%169205400160801033000025%319208100505801239600025%31920825067080用电子表格软件反推的逻辑是全年应纳税所得额直接乘对应税率减速算扣除数应该等于第 12 个月的累计预扣税。比如情景 A年收入 144000 - 减除费用 60000 - 五险一金 18000 6600066000 × 10% - 2520 4080和第 12 个月的累计预扣税完全对上。三组数据全部对上我才敢说引擎逻辑没大问题。5.3 最容易出错的三个隐藏点第一累计减除费用按任职月数而非自然月。开头那个workedMonths输入框就是这样来的。如果 7 月入职到 12 月实际任职 6 个月只能扣 5000 × 6 30000而不是 5000 × 12 60000。很多第一版计算器会在这个地方多扣导致算出来的税偏少。第二税率表别用混。年度综合所得和月度换算用的速算扣除数不一样。如果偷懒在网上搜了一个月税率表直接抄进代码后面怎么验都对不上。这个 Demo 里只用年度综合所得表因为累计预扣预缴本来就该用年度表。第三专项附加扣除的折算。房贷利息每月 1000 元、子女教育每月 2000 元这些是政策标准但用户可能在界面里填的是每年金额。为了演示方便我的输入框是每月金额如果你扩展成每年金额一定要先除以 12 再进计算引擎不然结果直接放大 12 倍。6. 源码文件说明与还能继续做下去的方向最后把源码里的文件逐个说明一遍再给几条扩展思路。很多人拿到源码不知道怎么下手读我按阅读顺序帮你划好优先线。6.1 源码文件清单engine/TaxLevel.java税率档位枚举集中管理税率和速算扣除数。以后政策调整只改这一个文件。engine/TaxCalculator.java核心计算类不依赖任何 Android API可在普通 Java 环境运行。engine/CalculateResult.java计算结果实体类封装每个月的字段。util/CommonUtils.java输入解析和合法性校验工具。ui/MainActivity.java界面入口负责组装数据和渲染结果。res/layout/activity_main.xml界面布局四个输入框加一个按钮加一个结果容器。res/values/strings.xml所有文案统一放这里方便多语言切换。建议阅读顺序先TaxLevel和TaxCalculator弄懂计算规则再CalculateResult看数据怎么包装然后MainActivity看界面怎么调用引擎最后看布局文件把控件和代码对应起来。6.2 可以继续扩展的方向这个项目停在能跑、能截图的演示阶段但它留了不少可以继续做的口子加入年终奖单独计税对比功能。年终奖有两种计税方式单独计税和并入综合所得很多人在月底发奖金时会纠结哪个划算把这套逻辑加进去项目立刻从入门练习变成能实际帮人测算的工具。把专项附加扣除从一个总金额拆成六类分别录入。子女教育、房贷利息、赡养老人各有各的限额拆开录入更贴近真实情况界面也可以改用多选框或分段布局。用Kotlin Jetpack Compose重写 UI 层。引擎包完全不用动这就是当初引擎与 UI 解耦的好处——技术升级时可以保留核心算法不变。做成Java 后端接口。把TaxCalculator直接放进后端工程提供一个查询接口前端传参数、后端返回结果让计算逻辑做到前后端一致。6.3 学习建议初学者拿到这个 Demo不建议从头到尾一行一行背而是先改输入参数观察每月税额的变化规律尤其是跳档那个月前后发生了什么。然后尝试改 UI——比如加一个年度汇总的标题或者把结果改成两列显示。改坏了没关系git 提交在随时可以回退。等到对整套流程有了手感再试着完全不看源码自己从零写一遍税率判断和月份循环。写到36000边界的时候你就知道这样的项目为什么值得亲手做一遍了。最后分享一点个人体会。我现在偶尔还会把TaxCalculator.java单独拷出来改一行输入参数做点税负测算每次打开都提醒我一个看起来很简单的 App真正值钱的地方不在界面多花哨而在边界条件处理得有多稳、代码结构让人多省心。这套源码你可以随便拿去改当作业也好当练习也好保留仅供学习参考这句话就行。能把计算逻辑讲清楚、跑得像样它作为代码参考的使命就算完成了。