Flipper Zero JS SDK Math 模块:完整 API 参考与 C 实现剖析
【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware
本文基于仓库文档 js_math.md 及其对应的 C 实现 js_math.c,系统讲解 Flipper Zero 固件中 JS 应用的math模块:如何通过require("math")加载模块、三个数学常数的来源、全部 22 个方法(abs/sin/log/random等)的参数、返回值与示例,并结合源码剖析参数校验、定义域检查、双曲函数的对数实现、基于硬件随机源的random(),以及与标准 JavaScriptMath对象的若干行为差异,帮助开发者在 JS SDK 应用中正确、可预测地编写数学计算代码。
模块加载:require("math")背后发生了什么
math模块不是全局对象,使用前必须先通过require函数加载:
let math = require("math");从源码结构看,这一行背后是一条完整的插件加载链路:
- 全局函数
require由 JS 线程在初始化时注册。js_thread.c 中的JS_FIELD("require", MJS_MK_FN(js_require))将require挂到 mjs 解释器(lib/mjs)的全局对象上,与print、delay、console等全局 API 并列。 require最终调用 js_modules.c 中的js_module_require()。该函数先检查模块是否已加载(重复加载会报错),再在内置模块表中查找;math不在内置模块(内置仅有flipper),因此走外部模块分支:按/ext/apps_data/js_app/plugins/js_<名称>.fal的路径约定(见 js_modules.c 的MODULES_PATH)通过plugin_manager_load_single()加载一个.fal插件。math正是这样一个插件。它在 application.fam 中声明为FlipperAppType.PLUGIN,appid="js_math"、入口点js_math_ep、依赖js_app;入口函数返回的JsModuleDescriptor携带模块名"math"和构造器js_math_create(见 js_math.c)。js_math_create()创建一个 JS 对象,把所有方法与常量写入该对象(js_math.c),并把对象指针写入*object作为返回值——这就是你在脚本里拿到的math对象本身。
补充两点细节:
require时会忽略可选前缀@flipperdevices/fz-sdk/(JS_SDK_VENDOR为flipperdevices,见 js_modules.h),所以require("@flipperdevices/fz-sdk/math")与require("math")等价,便于与桌面端 JS SDK 的包路径风格对齐。- 每个模块只会实例化一次;已加载后再次
require同名模块会收到"module is already installed"错误。
三个常量:PI、E、EPSILON
模块对象上除了方法还有三个只读常量(注册见 js_math.c),其定义直接取自 C 标准库宏(js_math.c):
| 常量 | 含义 | 数值 | 源码来源 |
|---|---|---|---|
math.PI | 圆周率 π | 3.14159265358979323846264338327950288 | M_PI |
math.E | 自然对数的底 e(欧拉数) | 2.71828182845904523536028747135266250 | M_E |
math.EPSILON | 满足1.0 + EPSILON != 1.0的最小浮点增量 | 2.2204460492503131e-16 | DBL_EPSILON(<float.h>) |
这些常量的精度与double类型一致,因为模块内所有数值运算都以double(mjs_get_double)进行。
方法总览
math对象共暴露 22 个方法。以下按功能分组给出参考,参数校验与边界行为均结合 js_math.c 源码核实。
所有单参数函数都遵循同一套前置校验(check_args,js_math.c):实参个数必须与声明一致,且每个实参必须是数字类型,否则通过mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ...)抛出错误并返回undefined。
基础运算
abs(x)
返回x的绝对值。若x为负数(包括 -0)返回-x,否则返回x,结果恒为非负数。
math.abs(-5); // 5max(a, b)/min(a, b)
返回两个数中的较大值 / 较小值。注意与标准 JS 不同,只接受两个参数(源码中check_args(mjs, 2)),不能像Math.max(1,2,3)那样传可变参数。
math.max(10, 20); // 20 math.max(-10, -20); // -10 math.min(10, 20); // 10 math.min(-10, -20); // -20pow(base, exponent)
返回base的exponent次幂,底层直接调用 C 的pow()。
math.pow(7, 2); // 49 math.pow(7, 3); // 343 math.pow(2, 10); // 1024isEqual(a, b, tolerance)
当a与b的差的绝对值在容差tolerance内时返回true,否则false——这是浮点数近似比较的实用工具。
math.isEqual(1.4, 1.6, 0.2); // false math.isEqual(3.556, 3.555, 0.01); // true从源码看(js_math.c),实现是fabs(a - b) <= e,即"小于或等于"容差时判等;文档文字描述的是"小于"。
sign(x)
返回表示符号的数:小于 0 返回 -1,否则返回 1(文档描述)。
math.sign(3); // 1 math.sign(-3); // -1值得注意的一个实现细节:当前源码(js_math.c)的判定是fabs(x) <= EPSILON ? 0 : ±1,即当|x|小到不超过EPSILON(含x = 0)时返回0,而不是文档示例所写的math.sign(0) // 1。也就是说,对"近似零"的输入,当前实现的行为与标准 JSMath.sign(返回 0)反而更接近,但具体以实际运行结果为准,编写依赖符号判定的代码时建议显式处理 0。
random()
返回一个[0, 1)区间内近似均匀分布的伪随机浮点数,可再缩放到自己需要的范围:
let num = math.random();这个方法是整个math模块中唯一不依赖 C 数学库、而是直连硬件的实现(js_math.c):
// double clearly provides more bits for entropy then we pack // 32bit should be enough for now, but fix it maybe const uint32_t random_val = furi_hal_random_get(); double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX;它调用furi_hal_random_get()获取一个 32 位硬件随机值,再除以FURI_HAL_RANDOM_MAX(0xFFFFFFFFU,定义于 furi_hal_random.h)归一化到[0,1)。源码中的注释也坦承当前只用 32 位熵,后续可能改进。
取整与位运算
ceil(x)/floor(x)
ceil返回不小于x的最小整数(向上取整),floor返回不大于x的最大整数(向下取整)。文档给出二者恒等关系:ceil(x) === -floor(-x)、floor(x) === -ceil(-x)。
math.ceil(-7.004); // -7 math.ceil(7.004); // 8 math.floor(-45.95); // -46 math.floor(-0); // -0 math.floor(45.05); // 45 math.floor(45.95); // 45底层直接调用 C 的ceil()/floor()。
trunc(x)
去掉小数部分,返回x的整数部分(向零取整)。
math.trunc(-1.123); // -1 math.trunc(0.123); // 0 math.trunc(13.37); // 13 math.trunc(42.84); // 42实现上(js_math.c)是x < 0 ? ceil(x) : floor(x)。
clz32(x)
返回x的 32 位二进制表示中前导零的位数。
math.clz32(1); // 31 math.clz32(1000); // 22实现(js_math.c)先把参数按int32读取、再转为unsigned int,然后通过右移循环统计有效位数count,最终返回32 - count。因此它适用于位运算、前缀树/哈希等需要判断数值"量级"的场景。
三角函数(角度单位均为弧度)
| 方法 | 功能 | 参数约束 | 示例 |
|---|---|---|---|
sin(x) | x(弧度)的正弦值,结果 ∈ [-1, 1] | 任意实数 | math.sin(math.PI / 2); // 1 |
cos(x) | x(弧度)的余弦值,结果 ∈ [-1, 1] | 任意实数 | math.cos(math.PI); // -1 |
tan无,atan(x) | x的反正切,弧度值 ∈ [-π/2, π/2] | 任意实数 | math.atan(1); // 0.7853981633974483 |
atan2(y, x) | 平面中正 x 轴到点(x, y)射线的角度(弧度),∈ [-π, π] | 任意实数对 | math.atan2(90, 15); // 1.4056476493802699 |
asin(x) | x的反正弦,弧度值 ∈ [-π/2, π/2] | x∈ [-1, 1] | math.asin(0.5); // 0.5235987755982989 |
acos(x) | x的反余弦,弧度值 ∈ [0, π] | x∈ [-1, 1] | math.acos(-1); // 3.141592653589793 |
底层分别直接调用 C 标准库的sin、cos、atan、atan2、asin、acos。atan2的参数顺序是(y, x)(与 C 语言一致),是处理方向、坐标旋转的常用函数。
一个文档与实现的差异点:文档称acos(x)在x超出 [-1, 1] 时返回NaN,而当前源码(js_math.c)会显式做定义域检查,超界时抛出MJS_BAD_ARGS_ERROR("Invalid input value for math.acos")。编写脚本时更应按"超界会报错"来防御。
指数与对数
| 方法 | 功能 | 参数约束 | 示例 |
|---|---|---|---|
exp(x) | 返回e^x | 任意实数 | math.exp(0); // 1;math.exp(1); // 2.718281828459045 |
log(x) | 自然对数ln(x) | x > 0(当前源码对x <= 0直接报错) | math.log(1); // 0;math.log(3); // 1.0986122886681098 |
sqrt(x) | 平方根,返回非负值 | x >= 0(负数会报错,见源码) | math.sqrt(25); // 5 |
cbrt(x) | 立方根 | 任意实数 | math.cbrt(2); // 1.2599210498948732 |
log与sqrt的定义域检查见 js_math.c 与 js_math.c:超界时同样走MJS_BAD_ARGS_ERROR路径,而不是像 C 的log()/sqrt()那样返回NaN。
双曲函数
双曲函数在源码中不是调用 C 库函数,而是用对数/平方根手工推导实现的:
| 方法 | 功能 | 参数约束 | 源码实现 | 示例 |
|---|---|---|---|---|
asinh(x) | 反双曲正弦 | 任意实数 | log(x + sqrt(x² + 1)) | math.asinh(1); // 0.881373587019543 |
acosh(x) | 反双曲余弦 | x >= 1(小于 1 报错) | log(x + sqrt(x² - 1)) | math.acosh(1); // 0 |
atanh(x) | 反双曲正切 | x∈ [-1, 1](越界报错) | 0.5 * ln((1 + x) / (1 - x)) | math.atanh(0.5); // 0.5493061443340548 |
对应实现位于 js_math.c。这种"对数形式"的写法是反双曲函数的标准解析式,好处是不依赖目标平台 C 库是否提供acosh/asinh/atanh,跨工具链行为一致。
与标准 JavaScriptMath的差异清单
在桌面浏览器里习惯Math对象的开发者,移植代码时需要注意这些由源码确认的行为差异:
max/min只接受两个参数:标准 JS 支持可变参数,这里传第三个参数会因实参个数不符而报错。acos/atanh/acosh/log/sqrt的超界处理是抛错而非NaN:C 侧做了显式定义域校验(见前文各节),脚本里应保证输入合法或做好错误处理。sign(0)的返回:文档示例写为 1,但当前源码在|x| <= EPSILON时返回 0,两者描述不一致,建议以实际运行验证为准(js_math.c)。isEqual是 Flipper 特有的 API,标准Math没有;它同时是浮点比较(差值绝对值 ≤ 容差)语义。- 没有
NaN、Infinity等常量,模块只暴露PI、E、EPSILON三个常量。 random()的熵源是硬件随机数,而桌面 JS 是纯软件 PRNG。
适用前提与相关源码索引
- 适用前提:Flipper Zero 固件的 JS 应用运行时(
js_app),当前固件提供的 JS SDK 版本为 1.0(JS_SDK_MAJOR/JS_SDK_MINOR,见 js_modules.h);脚本通过 js_thread.c 启动的独立线程执行。 - 文档与实现的对应关系:
| 内容 | 路径 |
|---|---|
| 本文所依据的 API 文档 | documentation/js/js_math.md |
| 模块 C 实现(全部方法、常量注册) | applications/system/js_app/modules/js_math.c |
插件化模块加载(require实现) | applications/system/js_app/js_modules.c |
全局require注册 | applications/system/js_app/js_thread.c |
js_math插件的构建声明 | applications/system/js_app/application.fam |
硬件随机数接口(random()的熵源) | targets/furi_hal_include/furi_hal_random.h |
| mjs 解释器(JS 引擎本体) | lib/mjs |
掌握以上内容后,你可以在任何 JS SDK 脚本中放心使用require("math")完成三角、对数、取整、位运算与随机数等计算,并清楚每一个方法在固件 C 层的真实行为边界——这正是编写健壮 JS 应用的基础。
【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考