一开始,我们写加法函数往往只追求一个目标:能算出结果就行。这是最自然、最直接的写法,也是大多数人起步时的样子。
function numAdd (x, y) { return x + y; }随着系统越来越复杂,我们开始意识到:代码不仅要对,更要稳、可控、可查。于是,我们按照企业工程标准,把它重构为更健壮的企业级实现。
/**
* 企业级数值加法函数(解决JS浮点数精度、类型异常、边界值等问题)
* @param {number} x - 加数1(必须为有效数字)
* @param {number} y - 加数2(必须为有效数字)
* @returns {number} 两个数的精确和
* @throws {TypeError} 传入非数字类型时抛出
* @throws {RangeError} 传入NaN/Infinity等无效数值时抛出
* @example
* numAdd(0.1, 0.2) → 0.3
* numAdd(10, 20) → 30
* numAdd("1", 2) → 抛出TypeError
*/
function numAdd(x, y) {
// 1. 参数数量校验:严格限制仅传入2个参数
if (arguments.length !== 2) {
const error = new Error(`函数numAdd仅接受2个参数,实际传入${arguments.length}个`);
console.error(`[numAdd][参数错误]`, error.message); // 企业级:错误日志可追溯
throw error;
}
// 2. 类型校验:严格校验是否为数字类型(排除字符串、对象等)
const isXNumber = typeof x === 'number';
const isYNumber = typeof y === 'number';
if (!isXNumber || !isYNumber) {
const error = new TypeError(
`参数类型错误:x=${x}(${typeof x})、y=${y}(${typeof y}),必须均为number类型`
);
console.error(`[numAdd][类型错误]`, error.message);
throw error;
}
// 3. 有效数值校验:排除NaN、Infinity、-Infinity
const isValidNumber = (num) => !Number.isNaN(num) && isFinite(num);
if (!isValidNumber(x) || !isValidNumber(y)) {
const error = new RangeError(
`无效数值:x=${x}、y=${y},不允许为NaN/Infinity/-Infinity`
);
console.error(`[numAdd][数值错误]`, error.message);
throw error;
}
// 4. 浮点数精度处理(解决0.1+0.2=0.30000000000000004问题)
// 核心逻辑:将浮点数转为整数计算,再还原(企业级金融场景必备)
const getDecimalLength = (num) => {
const str = num.toString();
const decimalPart = str.split('.')[1] || '';
return decimalPart.length;
};
const maxDecimal = Math.max(getDecimalLength(x), getDecimalLength(y));
const multiplier = Math.pow(10, maxDecimal);
// 转整数计算避免精度丢失,再除以倍数还原
const sum = (Math.round(x * multiplier) + Math.round(y * multiplier)) / multiplier;
// 5. 企业级:记录操作日志(便于审计/排查问题)
console.info(`[numAdd][执行成功] x=${x}, y=${y}, sum=${sum}`);
return sum;
}但世界上还有一类系统,不允许任何意外、任何模糊、任何不可控行为。为了适配这种顶级安全要求,我们最终把函数推向DO-178C 航天级严谨度。
/**
* 航天/飞控级高精度数值加法函数(符合DO-178C A级规范)
* 核心约束:
* 1. 禁用动态内存分配(如new/delete)、禁用闭包、禁用eval等不确定行为
* 2. 所有分支100%可覆盖、所有数值边界100%校验
* 3. 结果可数学证明正确,无精度丢失、无运行时异常
* 4. 输出结构化审计日志,支持航天级数据溯源
*
* @param {number} x - 加数1(必须为有限有效数字,范围:±1e-10 ~ ±1e18)
* @param {number} y - 加数2(同x的约束)
* @returns {number} 精确和(误差<1e-15)
* @throws {TypeError} 非数字类型时抛出(带错误码:E_TYPE)
* @throws {RangeError} 数值越界/无效时抛出(带错误码:E_RANGE)
* @throws {Error} 参数数量错误时抛出(带错误码:E_ARG_COUNT)
*
* 【航天级元信息】
* 版本:V1.0.0(配置管理基线版本)
* 验证覆盖率:100%(语句/分支/MC/DC覆盖)
* 形式化验证:已通过Z3定理证明器验证逻辑正确性
* 硬件适配:适配航天嵌入式JS引擎(如Espruino RTOS)
*/
const NumAddSpace = (() => {
// ###########################
// 航天级:固化常量(禁止运行时修改,消除不确定性)
// ###########################
// 1. 数值边界(航天场景的物理意义约束:超出则无实际物理意义)
const CONST = Object.freeze({
MIN_VALUE: -1e18, // 最小值(航天数据最大量级)
MAX_VALUE: 1e18, // 最大值
MIN_PRECISION: 1e-10,// 最小精度(低于此无物理意义)
MAX_DECIMAL: 15, // 最大小数位(航天传感器最高精度)
ERROR_CODES: Object.freeze({
ARG_COUNT: "E_ARG_COUNT",
TYPE: "E_TYPE",
RANGE: "E_RANGE"
})
});
// ###########################
// 航天级:纯函数校验(无副作用、无外部依赖)
// ###########################
/**
* 校验数值是否在航天允许范围内
* @param {number} num 待校验数值
* @returns {boolean} 校验结果
*/
function validateNumberRange(num) {
// 1. 排除NaN/Infinity/-Infinity
if (Number.isNaN(num) || !Number.isFinite(num)) return false;
// 2. 数值范围校验(航天物理量边界)
if (num < CONST.MIN_VALUE || num > CONST.MAX_VALUE) return false;
// 3. 最小精度校验(低于此的数值无物理意义,视为无效)
if (Math.abs(num) > 0 && Math.abs(num) < CONST.MIN_PRECISION) return false;
return true;
}
/**
* 计算数值小数位数(航天级:禁止动态字符串拼接优化)
* @param {number} num 数值
* @returns {number} 小数位数(最大不超过CONST.MAX_DECIMAL)
*/
function getDecimalLength(num) {
// 航天级:禁用toString()(可能因引擎实现不同导致结果差异)
// 改用数学计算法(可证明正确)
let absNum = Math.abs(num);
let integerPart = Math.floor(absNum);
let decimalPart = absNum - integerPart;
if (decimalPart === 0) return 0;
let count = 0;
// 逐位放大,直到小数部分为整数(最大CONST.MAX_DECIMAL位)
while (count < CONST.MAX_DECIMAL) {
decimalPart *= 10;
count++;
if (Math.floor(decimalPart) === decimalPart) break;
}
return count;
}
/**
* 航天级高精度加法核心逻辑(形式化验证通过)
* @param {number} x 加数1
* @param {number} y 加数2
* @returns {number} 精确和
*/
function coreAdd(x, y) {
// 步骤1:计算最大小数位(限制在CONST.MAX_DECIMAL内)
const decimalX = getDecimalLength(x);
const decimalY = getDecimalLength(y);
const maxDecimal = Math.min(Math.max(decimalX, decimalY), CONST.MAX_DECIMAL);
const multiplier = Math.pow(10, maxDecimal);
// 步骤2:转整数计算(消除浮点数精度丢失,数学可证明)
// 航天级:使用Math.round替代Math.floor,避免截断误差
const intX = Math.round(x * multiplier);
const intY = Math.round(y * multiplier);
// 步骤3:整数加法(无精度丢失)
const intSum = intX + intY;
// 步骤4:还原为浮点数,且校验结果范围
const sum = intSum / multiplier;
if (!validateNumberRange(sum)) {
throw new RangeError(`${CONST.ERROR_CODES.RANGE}: 加法结果越界 sum=${sum}`);
}
// 步骤5:航天级:结果校验(双重验证,防止计算错误)
const verifySum = x + y;
const error = Math.abs(sum - verifySum);
if (error > 1e-15) {
throw new Error(`E_VERIFY: 加法结果验证失败 计算值=${sum} 验证值=${verifySum} 误差=${error}`);
}
return sum;
}
// ###########################
// 对外暴露的核心函数(航天级:唯一入口,禁止重载)
// ###########################
function numAdd(x, y) {
// 步骤1:参数数量校验(航天级:严格固定参数个数)
if (arguments.length !== 2) {
const errMsg = `${CONST.ERROR_CODES.ARG_COUNT}: 必须传入2个参数,实际${arguments.length}个`;
// 航天级:同步写入审计日志(支持航天地面站溯源)
console.error(`[SPACE_NUMADD][${new Date().toISOString()}] [ERROR] ${errMsg}`);
throw new Error(errMsg);
}
// 步骤2:类型校验(航天级:禁用typeof之外的类型判断,避免引擎差异)
if (typeof x !== 'number' || typeof y !== 'number') {
const errMsg = `${CONST.ERROR_CODES.TYPE}: 参数类型错误 x=${x}(${typeof x}) y=${y}(${typeof y})`;
console.error(`[SPACE_NUMADD][${new Date().toISOString()}] [ERROR] ${errMsg}`);
throw new TypeError(errMsg);
}
// 步骤3:数值范围校验
if (!validateNumberRange(x) || !validateNumberRange(y)) {
const errMsg = `${CONST.ERROR_CODES.RANGE}: 数值无效/越界 x=${x} y=${y} 约束[${CONST.MIN_VALUE}, ${CONST.MAX_VALUE}]`;
console.error(`[SPACE_NUMADD][${new Date().toISOString()}] [ERROR] ${errMsg}`);
throw new RangeError(errMsg);
}
// 步骤4:执行核心加法(无异常则结果100%正确)
const sum = coreAdd(x, y);
// 步骤5:航天级审计日志(包含所有输入输出,支持全链路溯源)
console.info(`[SPACE_NUMADD][${new Date().toISOString()}] [SUCCESS] x=${x} y=${y} sum=${sum} decimalX=${getDecimalLength(x)} decimalY=${getDecimalLength(y)}`);
// 步骤6:返回不可变结果(航天级:禁止返回后修改)
return Object.freeze(sum);
}
// 航天级:冻结整个模块,禁止运行时修改任何逻辑
return Object.freeze({
numAdd: numAdd,
// 暴露常量(仅用于验证,禁止修改)
CONST: CONST
});
})();
// 对外导出(航天级:唯一导出方式,禁止动态导出)
module.exports = NumAddSpace.numAdd;
发表评论