猫工具站
帮助中心 猫Blog 关于我们 广告合作 EN

BMI、年龄、日期三台计算器:哪些数字能信,什么时候不能信

计算更新于 2026-09-29

这三台页面都长得像「填两个数、看一个结果」的小工具,但它们真正回答的问题都比标题窄一截:BMI 页回答的是「这个比值四舍五入之后落在哪一档」,年龄页回答的是「从你出生日期那天的 UTC 零点到现在过了多久」,日期页回答的是「两个本地零点之间能塞进多少个整天」。这三个都不是大多数人想问的问题,所以只要靠近边界、靠近午夜、或者你的时区不是页面写代码时假设的那个,差别就会露出来。

下面每一条都是从页面自己的脚本里读出来、再在页面上实际跑一遍确认过的。写出来的数字,就是页面实际显示的数字。

三台机器,三种「现在」

  • BMI 计算器 是无状态的:每敲一下键就重算,什么都不存,和时间没有任何关系。
  • 年龄计算器 在加载时就起了一个每秒执行一次的定时器,一直拿着当前时刻重算,所以你盯着看的时候它自己在变。
  • 日期计算器 里是两个各自独立的标签页——两个日期之间差多久、从某天往前或往后推。除了给输入框填默认值,它们从不读当前时间。

BMI:先四舍五入,再分档

页面先算出比值,把它四舍五入到一位小数,然后才判断落在四档里的哪一档。这个顺序是有后果的。身高 170 厘米时:

体重 真实 BMI 显示 档位
53.32 公斤 18.4498 18.4 偏瘦
53.33 公斤 18.4533 18.5 正常
69.21 公斤 23.9481 23.9 正常
69.22 公斤 23.9516 24.0 超重

差 10 克就换档,而且换得并不成立:18.4533 其实还在 18.5 以下,23.9516 其实还在 24 以下,页面却按「已经越线」处理了。分档用的就是你看到的那个数——脚本是 Math.round(bmi * 10) / 10,再用 toFixed(1) 显示,背后没有藏着更精确的值。所以当读数落在某个分界线的 0.05 以内时,把档位当成「待定」,别当成结论。

BMI:切单位是改写数字,不是换算

点「英制」不会换算你填的东西。它会往两个框里写死一对默认值——67 英寸、143 磅;切回「公制」则写回 170、65。你之前填的数在两次切换里都会被丢掉。

而且这对默认值和公制默认值并不是同一个体型。65 公斤等于 143.3005 磅,170 厘米等于 66.9291 英寸,所以英制默认只是个取整的替身:它算出 22.4,公制默认算出 22.5。两个都落在「正常」,档位上看不出差别,但页面说这两种单位「看到的是同一个体型」,实际差了 0.1。

真要换算,自己把数换算好再填进去——填 66.93 英寸、143.3 磅,页面一样会给出 22.5。

BMI:公式能容忍什么

两个输入框上都写着 min="1",它只约束上下箭头,不约束脚本。脚本读到的值只防了一个「0」:

  • 身高填负数会被平方掉,−170 厘米和 +170 厘米给出完全一样的 22.5、正常。
  • 体重填负数没人拦。−65 公斤会算出一个 −22.5 的 BMI,而它小于 18.5,于是被标成「偏瘦」,色带指针被压在最左端。
  • 没有上限,也没有合理性范围。身高 1 厘米配 65 公斤会返回 650000.0,档位「肥胖」,指针顶在 95%。
  • 留空或填 0 时,大号数字变成破折号,其余什么都不动:档位文字、建议、色带指针都停在最后一次的结果上。这一点页面自己的正文也明说了,比大多数工具页诚实。

指针位置由数值算出后夹在 5%–95% 之间,极端值不会跑出刻度条。另外源码的 HTML 里写死了一个 left: 42%,页面一加载就会被脚本覆盖成 39.5455%(22.5 对应的位置)。

BMI:703 那条捷径,和页面上说过头的两句话

英文版正文写「英制路径与 703 × 磅 ÷ 英寸² 这条捷径在数学上完全等价」。代码里的系数并不是 703,而是 1 ÷ 0.0254² × 0.453592 = 703.0696(每磅每平方英寸)。拿 5 英尺 9 英寸、160 磅来算,页面给 23.6276,703 捷径给 23.6253——差约 0.002。这个差是真实的,小到日常使用不会改变显示的那一位小数,但「完全等价」不是事实。

同一段话还说它的换算系数是「精确定义值(1 英寸 = 0.0254 米,1 磅 = 0.453592 千克)」。英寸那个是精确的;磅不是——定义值是 0.45359237 千克,0.453592 是截短过的。这个截断会让英制结果在小数点后第七位开始偏移,一位小数的显示把它抹掉了,但「精确定义」这句话描述的是代码里没有的东西。

年龄:计数器锚在 UTC 零点,不在你的一天里

出生日期从日期框里取出来是一串 YYYY-MM-DD,而 new Date('2000-01-01') 会把它解析成 UTC 零点,不是你家那天零点。下游所有的总月数、总天数、总小时数,都是从那个瞬间量起的。

可见的后果是这个计数器在 UTC 零点进位,也就是北京时间早上 8 点。以 2000 年 1 月 1 日出生为例:

本地时间 显示的月数 显示的天数
早上 6:30 311 9,496
晚上 20:00 312 9,497

同一个出生日期、同一天,看的时间不同,两个数字都不一样。在本地零点到进位时刻之间,天数比日历差少一天;过了那个点才对上。

对于 UTC 以西的访问者,这个解析还会把存下来的日期整体挪前一天,月初几天会连带影响年数。1996 年 2 月 29 日出生的人,在 2027 年 2 月 28 日打开页面:北京访问显示 30 岁,纽约访问显示 31 岁。页面完全没有时区处理,浏览器给什么它用什么。

年龄:月份按 30.44 天算,两栏数字互相矛盾

年数是用日历字段拼出来的——先减年份,再看月份和日期有没有走到,走不到就借一年——这部分是可靠的。月数不是:它把经过的毫秒数除以 30.44 天再向下取整。

正好 30 岁时,页面显示 359 个月,不是 360。2000 年 1 月 1 日出生、在 2026 年 9 月 29 日打开,页面一边显示 26 岁、一边显示 320 个月——比 26 × 12 多了 8 个月,因为 30.44 只是「有时」偏小,误差方向随跨度变化。要引用的话,只看年数和天数两栏,月数那栏别拿出去说。

年龄:2 月 29 日,和永远不为零的生日倒计时

倒计时用「当前年 + 出生月 + 出生日」拼出下一个生日,然后交给 Date 对象自己去处理溢出。所以在平年,2 月 29 日生日会指向 3 月 1 日:1996 年 2 月 29 日出生的人,在 2026 年 9 月 29 日打开,倒计时指向的是 2027 年 3 月 1 日(页面显示 152 天 14 时)。

生日当天它也读不到零。目标时刻是按本地零点拼的,你看的时候那一瞬间早就过去了,代码于是再加一年:生日是 9 月 29 日的人,在 2026 年 9 月 29 日看到的是「距离下次生日还有 364 天」(北京时区;纽约时区是 363 天)。两个数字都比第二天再看时要多一天。

年龄:清空输入框后,秒表还在跑

页面第一次拿到有效日期时,会装一个每秒执行一次的定时器。清空输入框只做了一件事——把大号数字改成破折号——然后就返回了,没有停掉那个定时器。下一拍它就按上一个生日把年龄写回去,所以破折号存在不到一秒,页面继续从「已经不在框里的出生日期」往下数。

填未来日期走的是另一个分支:大号数字变成「还没出生呢 🍼」就返回了,后面的月/日/时卡片和生日倒计时全都保持上一个有效日期的数字冻在那里,而定时器依然每秒空跑一次。这两种行为都能在几秒内复现:填一个有效日期、清空、等一秒。

日期差:含不含结束日,以及四行数字各算各的

日期差这一栏把两个日期都解析成各自的本地零点,相减、向下取整得到原始天数,再在勾选「包含结束日期」时加一天——而这一项默认就是勾着的。所以同一天对同一天显示 1 天,7 月 1 日到 7 月 3 日勾选是 3 天、不勾是 2 天。这个勾选框恰好改变一天,而页面上没有任何地方提示你「大数字下面挂着个开关」。

下面四行结果用了三种口径:

周数是精确的:总天数除以 7,余数单独写出来。月数和年数是按日历一步步推出来的——不断加一个月直到超过结束日——但它们的余数行是拿总天数减去「月数 × 30.44」「年数 × 365.25」再四舍五入算的。整数按日历、余数按平均长度。

对一个正好是 312 个月、也正好是 26 年的区间:

2000-01-01 → 2026-01-01 显示
天数 9,498 天
周数 1,356 周 余 6 天
月数 312 个月 余 1 天
年数 26 年 余 2 天

余数行和它旁边的整数自相矛盾。四行里能拿日历核对、并且核对得上的,是天数和周数。

因为月数是真从起始日开始走的,它还可能一个月都数不出来:2026 年 1 月 31 日到 3 月 1 日,结果是 0 个月、29 天,因为从 1 月 31 日加一个月会落到 3 月 3 日,已经超过结束日了。

日期差:日期填反,旧数字留在屏幕上

结束日期早于起始日期时,页面不返回负数。它把「起始日期需早于结束日期」写进天数的位置就立刻返回了——没有清空周、月、年三行,它们会保留你上一次算出来的值。实测:先算一遍 26 年跨度,再把两个日期对调,天数是错误提示,旁边的三行还写着 1,356 周 / 312 个月 / 26 年。页面自己的常见问题里说这种情况「周月年三个格子留空」,实际不会。

日期差:夏令时会让天数少一天(中文用户不会遇到)

两个日期都锚在本地零点,所以差值的毫秒数并不总是 86,400,000 的整数倍。在春季把钟往前拨一小时的地区,跨过那一天的「两个零点」之间少了 3600 秒,向下取整就会掉一天:在纽约,2026 年 3 月 1 日到 3 月 9 日——日历上整整八天——两个零点之间只有 191 小时,不勾选「包含结束日期」时页面报 7 天。勾选时(页面默认就是勾着的)它多加的那 1 天恰好把缺口补上,结果显示 8 天。秋季拨回来会多出一小时,取整时被吸收掉、不会把天还给你:2026 年 11 月 1 日到 11 月 9 日相差 193 小时,不勾是 8 天、勾选是 9 天,两个都对。

中国大陆不实行夏令时,从国内访问不会遇到;用美洲或欧洲时区跨春季切换日使用时,要么自己在「不勾选」的结果上加一天,要么保持勾选——它多加的那一天正好落在正确数字上,只是理由不对。

日期差:快捷按钮写的是 UTC 日期

「昨天 / 今天 / 明天」三个按钮取当前时刻、加减一天,然后用 UTC 日期格式化。三个日期框的默认值也是同一条路径填的。所以写进框里的到底是哪一天,取决于 UTC 日期,而不是你的日历:

你打开页面的时间 点「今天」写入
北京时间 00:05 昨天
北京时间 08:00 今天
纽约时间(夏令时)20:00 明天

在北京,这段「点今天得到昨天」的窗口是每天 0 点到 8 点;在纽约是晚 8 点到半夜。页面上没有任何地方提示这几个框里填的并非今天。

日期推算:月份和年份会溢出,不会夹到月末

推算这一栏是直接给月份或年份加上去,然后让 Date 对象自己收敛,结果是超出目标月长度的日期会溢到下一个月:

起点 加 结果
2026 年 1 月 31 日 1 个月 2026 年 3 月 3 日
2024 年 1 月 31 日 1 个月 2024 年 3 月 2 日
2024 年 2 月 29 日 1 年 2025 年 3 月 1 日
2026 年 1 月 31 日 6 个月 2026 年 7 月 31 日

从 1 月 31 日加 6 个月没问题,因为 7 月有 31 天;加 1 个月就不行,因为 2 月没有。这是这门语言日期类型的标准行为,页面既不会夹到月末,也不会提醒你。合同到期日、订阅续费、任何起点带 29、30、31 号的场景,请自己核一遍结果,或者干脆改用「天」。

三台机器怎么配合

  • 只想要一个可以拿去找人聊的筛查比值:用 BMI 页。读档位,别看那一位小数;如果你习惯用磅和英寸,自己换算好再填。
  • 想知道自己活了多少天、离生日还有几天(要精确到天):用年龄页,在你要引用它的当天看,它的天数在一天里的大部分时间是对的,在 UTC 进位之前会少一天。
  • 想算两个日期之间隔多久、或从某天往前推:用日期页。引用天数和周数两行,把月数和年数的余数当粗略值,记住「包含结束日期」默认是勾着的,跨春季时钟切换就自己加一天。

常见问题

为什么体重几乎没变,档位却变了?

因为档位是按四舍五入之后的值判的。落在 18.5、24、28 这三点附近 0.05 以内的读数,标签可能往任何一边倒。

切换单位会丢掉我填的数字吗?

会。两个方向都会往两个框里写死默认值。想保留自己的数,先自己换算再填。

身高或体重填负数会被拦住吗?

不会。身高填负会被平方掉,给出一个看起来正常的结果;体重填负会得到一个负的 BMI,被标成「偏瘦」。

年龄页的月数和天数为什么对不上?

年数按日历字段算,月数按 30.44 天的平均月算,天数从出生日期的 UTC 零点算起。只有年数那一栏是日历口径。

年龄页的天数能信吗?

一天里的大部分时间可以。它在 UTC 零点进位而不是本地零点,所以每天有一段窗口是少一天的,窗口有多长取决于你在哪。

生日倒计时为什么多一天?

目标按本地零点拼出来,你看的时候那一瞬间已经过去,页面就跳到了下一年。生日当天看到的是 364 天或 363 天,不是 0。

日期页算不算结束日?

只在你勾选时算,而它默认是勾着的。同一天对同一天,勾选是 1 天,不勾是 0 天。

八天跨度为什么报 7 天?

你在春季时钟前拨的地区,区间跨过了那一天,而且没有勾选「包含结束日期」。纽约 2026 年 3 月 1 日到 3 月 9 日的两个本地零点之间只有 191 小时——七天零 23 小时——取整就掉了一天。短一天是同一个故事:3 月 2 日到 3 月 9 日日历上是七天,实际只有 167 小时,页面报 6 天。

这些计算会把数据传出去吗?

不会。三台页面都在浏览器本地用 JavaScript 计算,不上传也不保存,刷新即回到默认值。

本指南用到的工具

广告