/** * @name CeL function for physical quantities * @fileoverview 本檔案包含了 value of a quantity/物理量/度量衡(長度、時間、重量等)標準/國際單位/國際單位制詞頭 * 單位換算用的 functions。
* * TODO:
* https://en.wikipedia.org/wiki/Category:Units_of_amount * https://en.wikipedia.org/wiki/Significance_arithmetic * * http://thdl.ntu.edu.tw/tools/ * http://thdl.ntu.edu.tw/weight_measure/transformation8.php
* 中國度量衡/中國古代貨幣 http://thdl.ntu.edu.tw/suzhou/ * * @since * @example * CeL.run('data.quantity',function(){ * // .. * }); * * * @see http://unit.0123456789.tw/ * * @example cm^2, cm2 視作 cm² CeL.assert([(new CeL.quantity('5.4cm')).toString(),'5.4 cm'],''); CeL.assert([(new CeL.quantity('4cm^2')).toString(),'4 cm²'],''); CeL.assert([(new CeL.quantity('4cm2')).toString(),'4 cm²'],''); CeL.assert([(new CeL.quantity('四千五百六十七公尺')).toString(),'4567 m'],''); CeL.assert([(new CeL.quantity('54公尺')).toString(),'54 m'],''); CeL.assert([(new CeL.quantity('5公尺')).multiple(4).toString(),'20 m'],''); CeL.assert([(new CeL.quantity('54cm')).multiple('8.5公尺').toString(),'4.59 m²'],''); CeL.assert([(new CeL.quantity('54cm')).multiple('8.5公尺').toString('繁體中文'),'4.59平方公尺'],''); CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('a').toString(),'5 a'],''); CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('ha').toString(),'0.05 ha'],''); CeL.assert([(new CeL.quantity('500平方公尺')).convert_to('a').toString('繁體中文'),'5 公畝'],''); °C 廿分鐘 三磅 * */ 'use strict'; // -------------------------------------------------------------------------------------------- typeof CeL === 'function' && CeL.run({ // module name name : 'data.quantity', // |data.native. require : 'data.|data.numeral.|data.Convert_Pairs.', // 設定不匯出的子函式。 // no_extend : '*', // 為了方便格式化程式碼,因此將 module 函式主體另外抽出。 code : module_code }); function module_code(library_namespace) { /** * * @param {RegExp}pattern * @param {String}flag * * @returns {RegExp} */ function convert_pattern(pattern, flag) { return new RegExp(pattern.source.replace(/N/, PATTERN_NUMBER_SOURCE) .replace(/S/, SUPERSCRIPT_NUMBER), flag); } // ---------------------------------------------------------------------// // 定義基本常數。 var SUPERSCRIPT_NUMBER = // @ data.native // Number.prototype.to_super.digits, '⁰¹²³⁴⁵⁶⁷⁸⁹', // PATTERN_NUMBER_SOURCE = // /[+\-]?(?:\d+(?:\.\d+)?|\.\d+)(?:E[+\-]?\d+)?/i /[+\-]?(?:\d+(?:\.\d+)?|\.\d+)/i.source, // [ , number, units, 分母 units ] PATTERN_QUANTITY = convert_pattern( // /^\s*(N)\s*([^\/\d][^\/]*)(?:\/(.+))?$/ /^\s*(N)\s*([^\d\s\^].*)$/, 'i'), // kg/(m·s2) and kg·m−1·s−2 are acceptable, but kg/m/s2 is ambiguous // and unacceptable. PATTERN_UNIT = convert_pattern( // [ , unit symbol, exponent, exponent, superscript exponent ] /([^\d\s\^⋅·×()]+)([+\-]?\d|\^\(?([+\-]?\d)\)?|([⁺⁻]?[S]))?/ // , 'gi'), // exponent_to_prefix.pair_Map.get(6) === 'M' exponent_to_prefix = new library_namespace.data.Convert_Pairs( // https://en.wikipedia.org/wiki/Metric_prefix '30=Q,27=R,24=Y,21=Z,18=E,15=P,12=T,9=G,6=M,3=k,2=h,1=da,' // + '-1=d,-2=c,-3=m,-6=µ,-9=n,-12=p,-15=f,-18=a,-21=z,-24=y,-27=r,-30=q', { key_is_number : true }), // 數量級 exponent_of = exponent_to_prefix.clone().reverse({ key_is_number : true }), // prefix_pattern = new RegExp('^\\s*(' + exponent_of.pattern({ get_normal_keys : true }).join('|') + ')([^\\s]+)$'), // prefix name. prefix_name.pair_Map.get('M') === 'mega'. prefix_name = new library_namespace.data.Convert_Pairs( // 'Q=quetta,R=ronna,Y=yotta,Z=zetta,E=exa,P=peta,' // + 'T=tera,G=giga,M=mega,k=kilo,h=hecto,da=deca,' // + 'd=deci,c=centi,m=milli,µ=micro,n=nano,p=pico,' // + 'f=femto,a=atto,z=zepto,y=yocto,r=ronto,q=quecto'); // ---------------------------------------------------------------------// // 初始調整並規範基本常數。 // ---------------------------------------------------------------------// // private 工具函數。 // Prefixes may not be used in combination. function parse_prefix(unit) { library_namespace.debug('parse_prefix(' + unit + ')', 3); var matched = String(unit).match(prefix_pattern); // [ unit symbol, prefix exponent ] return matched ? [ matched[2], exponent_of.pair_Map.get(matched[1]) ] : [ unit, 0 ]; } // 度量衡單位 : 數量級 (prefix exponent / integer powers of 10) // 度量衡單位 : [ 數量級, 單位數量級 (unit power) ] // e.g., m : 0 → m // e.g., m : 3 → km // e.g., m : [ , 2 ] → m^2 // e.g., m : [ 3 , 2 ] → km^2 function multiple_unit(quantity, unit_symbol, prefix_exponent, unit_power) { if (unit_power === '' || isNaN(unit_power)) unit_power = 1; var units = quantity.units; if (!units) quantity.units = units = Object.create(null); if (unit_symbol in units) { var array = units[unit_symbol]; if (!Array.isArray(array)) // assert: !isNaN(units[unit_symbol]) units[unit_symbol] = array = [ array, 1 ]; // 若是 Array,則使用原 Array。 if (prefix_exponent !== array[0]) { // km^3 * cm^-2 // = 10^(3*3 - (-2)*2) m^(3 + -2) // = 10^13 m // = 10 Tm prefix_exponent = array[0] * array[1] + unit_power * (prefix_exponent || 0); array[1] += unit_power; // The "non-three" prefixes (hecto-, deca-, deci-, and // centi-) are however more commonly used for everyday // purposes than in science. if (array[0] = prefix_exponent / 3 / array[1] | 0) prefix_exponent -= 3 * array[0] * array[1]; // assert: Math.abs(prefix_exponent) = 0, 1, 2 // 微調:盡量取到不會 overflow 或 underflow 的 prefix。 if (prefix_exponent < 0) { if (Math.abs(quantity.amount) < 1) array[0]--, prefix_exponent += 3; } else if (prefix_exponent > 0 && Math.abs(quantity.amount) > 1e6 && quantity.amount % 10 === 0) array[0]++, prefix_exponent -= 3; // 把剩餘乘進 quantity.amount。 if (prefix_exponent) quantity.amount *= Math.pow(10, prefix_exponent); array[0] *= 3; } else // e.g., km * km = km^2 array[1] += unit_power; if (!array[1]) delete units[unit_symbol]; else if (array[1] === 1) units[unit_symbol] = array[0]; } else units[unit_symbol] = unit_power !== 1 ? [ prefix_exponent, unit_power ] : prefix_exponent; return units; } function parse_unit(unit, quantity, is_division) { library_namespace.debug('parse_unit(' + unit + ')', 2); if (library_namespace.is_Object(unit)) return unit; var matched = String(unit).match(/([^\/]*)\/([^\/]+)/); if (matched) { return Object.assign(parse_unit(matched[1], quantity), parse_unit( matched[2], quantity, true)); } is_division = is_division ? -1 : 1; // var units = Object.create(null); // reset pattern PATTERN_UNIT.lastIndex = 0; while (matched = PATTERN_UNIT.exec(unit)) { library_namespace.debug('parse_unit: [' + matched + ']', 3); if (matched[4]) matched[2] = matched[4].length > 1 ? (matched[4].charAt(0) === '⁻' ? 1 : -1) * SUPERSCRIPT_NUMBER.indexOf(matched[4].charAt(1)) : SUPERSCRIPT_NUMBER.indexOf(matched[4]); // return [ unit symbol, prefix exponent ] matched[1] = parse_prefix(normalize_name(matched[1])); multiple_unit(quantity, matched[1][0], matched[1][1], matched[3] || matched[2]); } return quantity.units; } function unit_to_String(unit_symbol, prefix_exponent, unit_power) { if (Array.isArray(prefix_exponent) // 處理 [ 數量級, 單位數量級 (unit power) ] && unit_power === undefined) { unit_power = prefix_exponent[1]; prefix_exponent = prefix_exponent[0]; } var unit = (prefix_exponent ? // exponent_to_prefix.pair_Map.get(+prefix_exponent) || ('10^' + prefix_exponent) : '') + unit_symbol; if (unit_power) { if (unit_power < 0) unit += '⁻', unit_power = -unit_power; unit += SUPERSCRIPT_NUMBER[unit_power]; } return unit; } var unit_name_all, unit_name = { // 繁體中文 'cmn-Hant-TW' : { 公里 : 'km', 公尺 : 'm', 公分 : 'cm', 厘米 : 'cm', 公釐 : 'mm', 毫米 : 'mm' }, // https://zh.wikipedia.org/wiki/%E5%B8%82%E5%88%B6 '市制' : { 市尺 : '1/3m' }, // https://zh.wikipedia.org/wiki/%E5%8F%B0%E5%88%B6 '臺制' : { 臺尺 : '10/33m' }, // 英制單位 'imperial' : {}, // default, 公制 '' : { '㎝' : 'cm', '㎠' : 'cm^2', '㎤' : 'cm^3', '㎜' : 'mm', '㎟' : 'mm^2', '㎣' : 'mm^3' } }; // normalize unit name function normalize_name(unit, locale) { var matched = unit.match(/^([平立])方(.+)$/), // prefix_exponent, unit_power; if (matched) { unit_power = matched[1] === '平' ? 2 : 3; unit = matched[2]; } if (!unit_name_all) { // initialization. unit_name_all = Object.create(null); for ( var l in unit_name) Object.assign(unit_name_all, unit_name[l]); } // TODO if (false) if (unit in unit_name_all) return [ unit_name_all[unit], // prefix_exponent, unit_power ]; if (unit in unit_name_all) return unit_name_all[unit]; return unit; } // ---------------------------------------------------------------------// // 定義基本常數。 function Quantity(amount, units, options) { if (!units && isNaN(amount)) { units = String(amount) // 先期處理。 .replace(/^\s*[〇一二三四五六七八九十百千萬億兆]+/, // library_namespace.from_Chinese_numeral); // e.g., new Quantity('2m', null, { type : 'length' }) if (units = units.match(PATTERN_QUANTITY)) { amount = units[1]; units = units[2]; } } // 數值/量值的大小 / number / amount/count of base unit / // numerical value / factor // {Number} or [{Number}積] if (typeof amount === 'string' && !isNaN(amount)) amount = +amount; this.amount = amount; // unit symbol this.units = parse_unit(units, this); if (options) { // 度量衡單位分類 type. e.g., 'length' (長度) if (options.type) this.type = options.type; // 有效數字/the appropriate number of significant // figures if ('significant_figures' in options) this.significant_figures // = options.significant_figures | 0; } } // 數值單位換算。 function convert_to(units) { ; } function multiple(quantity) { if (quantity) if (isNaN(quantity)) { if (quantity.constructor !== Quantity) quantity = new Quantity(quantity); this.amount *= quantity.amount; var units = quantity.units; for ( var unit in units) multiple_unit(this, unit, units[unit]); } else this.amount *= quantity; else this.amount = 0; return this; } // https://en.wikipedia.org/wiki/International_System_of_Units#Unit_symbols_and_the_values_of_quantities // The value of a quantity is written as a number followed // by a space // (representing a multiplication sign) and a unit symbol function to_String(locale) { var result = [], units = this.units; for ( var unit in units) result.push(unit_to_String(unit, units[unit])); // intervening space ' ', dot operator '⋅'. return this.amount + ' ' + result.join('⋅'); } // http://en.wikipedia.org/wiki/SI_base_unit // http://en.wikipedia.org/wiki/International_System_of_Units#Units_and_prefixes // http://en.wikipedia.org/wiki/List_of_thermodynamic_properties // Quantity.type_of('m')==='length'; function type_of(unit) { ; } function name_of(unit, locale) { ; } // Quantity.multiple(Quantity, Quantity); function parse_factor() { ; } // 同一個物理量的兩種不同的單位之間,是靠著轉換因子(conversion // factor)從一個單位轉換到另一個單位。例如,1 in = 2.54 // cm,注意到在這裡「2.54 cm/in」是轉換因子,不具有因次,其數值等於1。 parse_factor('min=60s,hr=60min,a=100m2,Newton=kg*m/s2'); // 常數/constants/factors var constants = { PI : Math.PI, c : 299792458 }; // ---------------------------------------------------------------------// // 應用功能。 // ---------------------------------------------------------------------// // 網頁應用功能。 // ---------------------------------------------------------------------// // export. library_namespace.set_method(Quantity, { type_of : type_of, name_of : name_of, parse_factor : parse_factor, constants : constants }); library_namespace.set_method(Quantity.prototype, { convert_to : convert_to, multiple : multiple }); Quantity.prototype.toString = to_String; return (Quantity// JSDT:_module_ ); }