Flipper Zero JS SDK Math 模块:完整 API 参考与 C 实现剖析
2026/9/14 1:18:32 网站建设 项目流程

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");

从源码结构看,这一行背后是一条完整的插件加载链路:

  1. 全局函数require由 JS 线程在初始化时注册。js_thread.c 中的JS_FIELD("require", MJS_MK_FN(js_require))require挂到 mjs 解释器(lib/mjs)的全局对象上,与printdelayconsole等全局 API 并列。
  2. 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插件。
  3. math正是这样一个插件。它在 application.fam 中声明为FlipperAppType.PLUGINappid="js_math"、入口点js_math_ep、依赖js_app;入口函数返回的JsModuleDescriptor携带模块名"math"和构造器js_math_create(见 js_math.c)。
  4. js_math_create()创建一个 JS 对象,把所有方法与常量写入该对象(js_math.c),并把对象指针写入*object作为返回值——这就是你在脚本里拿到的math对象本身。

补充两点细节:

  • require时会忽略可选前缀@flipperdevices/fz-sdk/JS_SDK_VENDORflipperdevices,见 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.14159265358979323846264338327950288M_PI
math.E自然对数的底 e(欧拉数)2.71828182845904523536028747135266250M_E
math.EPSILON满足1.0 + EPSILON != 1.0的最小浮点增量2.2204460492503131e-16DBL_EPSILON<float.h>

这些常量的精度与double类型一致,因为模块内所有数值运算都以doublemjs_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); // 5
max(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); // -20
pow(base, exponent)

返回baseexponent次幂,底层直接调用 C 的pow()

math.pow(7, 2); // 49 math.pow(7, 3); // 343 math.pow(2, 10); // 1024
isEqual(a, b, tolerance)

ab的差的绝对值在容差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_MAX0xFFFFFFFFU,定义于 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 标准库的sincosatanatan2asinacosatan2的参数顺序是(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); // 1math.exp(1); // 2.718281828459045
log(x)自然对数ln(x)x > 0(当前源码对x <= 0直接报错)math.log(1); // 0math.log(3); // 1.0986122886681098
sqrt(x)平方根,返回非负值x >= 0(负数会报错,见源码)math.sqrt(25); // 5
cbrt(x)立方根任意实数math.cbrt(2); // 1.2599210498948732

logsqrt的定义域检查见 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对象的开发者,移植代码时需要注意这些由源码确认的行为差异:

  1. max/min只接受两个参数:标准 JS 支持可变参数,这里传第三个参数会因实参个数不符而报错。
  2. acos/atanh/acosh/log/sqrt的超界处理是抛错而非NaN:C 侧做了显式定义域校验(见前文各节),脚本里应保证输入合法或做好错误处理。
  3. sign(0)的返回:文档示例写为 1,但当前源码在|x| <= EPSILON时返回 0,两者描述不一致,建议以实际运行验证为准(js_math.c)。
  4. isEqual是 Flipper 特有的 API,标准Math没有;它同时是浮点比较(差值绝对值 ≤ 容差)语义。
  5. 没有NaNInfinity等常量,模块只暴露PIEEPSILON三个常量。
  6. 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询