Integer, Overflow និងភាពជាក់លាក់
មាតិកា
- លេខរស់នៅក្នុងប្រអប់
- Integer ទំហំថេរ
- Overflow៖ ពេលកម្មវិធីរាប់វិលត្រឡប់
- លេខ JavaScript និងដែនកំណត់ Safe Integer
- BigInt៖ Integer ដែលគ្មានដំបូល
- Floating Point៖ ហេតុអ្វី 0.1 + 0.2 ចម្លែក
- ប្រៀបធៀបលេខទសភាគដោយសុវត្ថិភាព
- លុយគួរស្ថិតក្នុងសេនជា Integer
- ច្បាប់នៃការបង្គត់
- រូបមន្ត៖ Module money
- សាកល្បងដោយខ្លួនឯង
1. លេខរស់នៅក្នុងប្រអប់
នៅលើក្រដាស លេខអាចវែងប៉ុណ្ណាក៏បាន។ សរសេរលេខ 9 ហើយតាមក្រោយដោយលេខ 9 មួយរយទៀត ក្រដាសក៏មិនត្អូញត្អែរដែរ។ កុំព្យូទ័រខុសគ្នា។ រាល់លេខត្រូវរក្សាទុកក្នុងទំហំអង្គចងចាំថេរមួយ ហើយអង្គចងចាំទំហំថេរអាចផ្ទុកបានតែចំនួនតម្លៃខុសៗគ្នាដែលថេរប៉ុណ្ណោះ។
នៅក្នុង លេខ និងប្រព័ន្ធលេខ អ្នកបានឃើញថាលេខត្រូវបានរក្សាទុកជា bit។ ជំពូកនេះសួរសំណួរបន្ទាប់ថា៖ តើអ្វីកើតឡើងពេល bit អស់? ចម្លើយពន្យល់ពីគ្រួសារ bug ដ៏ល្បីមួយ៖ ឧបករណ៍រាប់ដែលស្រាប់តែក្លាយជាអវិជ្ជមាន, ID ដែលប្តូរមួយដោយស្ងាត់ៗ និងកន្ត្រកទិញទំនិញដែលបូកបាន $3.3000000000000003។
2. Integer ទំហំថេរ
ភាសាជាច្រើន (C, Java, Go, Rust និងមូលដ្ឋានទិន្នន័យដូចជា MySQL និង PostgreSQL) ផ្តល់ integer type ដែលមាន ទទឹង (width) ថេរ គឺចំនួន bit ថេរ។ ជាមួយ n bit អ្នកទទួលបានតម្លៃខុសៗគ្នា 2^n (2^n មានន័យថា 2 គុណនឹងខ្លួនឯង nដង)។
| Type | Bit | ដែនគ្មានសញ្ញា (Unsigned) | ដែនមានសញ្ញា (Signed) |
|---|---|---|---|
| byte | 8 | 0 ដល់ 255 | -128 ដល់ 127 |
| short | 16 | 0 ដល់ 65,535 | -32,768 ដល់ 32,767 |
| int | 32 | 0 ដល់ 4,294,967,295 | -2,147,483,648 ដល់ 2,147,483,647 |
| long | 64 | 0 ដល់ ប្រហែល 1.8 x 10^19 | ប្រហែល -9.2 x 10^18 ដល់ 9.2 x 10^18 |
Unsigned មានន័យថា "សូន្យឡើងទៅ"។ Signed មានន័យថា "អាចអវិជ្ជមាន" ហើយវាចំណាយ bit មួយសម្រាប់សញ្ញា ដែលជាមូលហេតុដែលផ្នែកវិជ្ជមានឈប់ត្រឹមប្រហែលពាក់កណ្តាលនៃអតិបរមា unsigned។
ហេតុអ្វីដែន signed 8-bit គឺ -128 ដល់ 127 មិនមែន -127 ដល់ 127? ព្រោះមានគំរូច្បាស់លាស់ 256 ហើយសូន្យយកអស់មួយ។ លេខអវិជ្ជមានបាន 128 គំរូ លេខវិជ្ជមានបាន 127 បូកសូន្យ។
អ្នកនឹងជួបដែនកំណត់ signed 32-bit គឺ 2,147,483,647 ជាប្រចាំ។ វាជា INT លំនាំដើមក្នុងមូលដ្ឋានទិន្នន័យ SQL ជាច្រើន។ ប្រសិនបើ column id របស់តារាងជា INT signed 32-bit ហើយកម្មវិធីរបស់អ្នកបញ្ចូលជួរពីរបីលានក្នុងមួយថ្ងៃ តារាងនឹងពេញក្នុងរយៈពេលប្រហែលបីឆ្នាំ ហើយពេលនោះការបញ្ចូលចាប់ផ្តើមបរាជ័យ។ ដូច្នេះហើយបានជាប្រព័ន្ធធំៗប្រើ BIGINT 64-bit សម្រាប់ ID តាំងពីថ្ងៃដំបូង។
3. Overflow៖ ពេលកម្មវិធីរាប់វិលត្រឡប់
Overflow គឺអ្វីដែលកើតឡើងពេលការគណនាបង្កើតលេខធំពេកសម្រាប់ប្រអប់របស់វា។ ក្នុងភាសាជាច្រើន លទ្ធផល វិលត្រឡប់ (wraps around) ដូចម៉ែត្រគីឡូម៉ែត្ររថយន្តដែលវិលពី 999999 ត្រឡប់ទៅ 000000។
លេខធម្មតារបស់ JavaScript មិនវិលត្រឡប់ទេ (អ្នកនឹងឃើញមូលហេតុក្នុងផ្នែកបន្ទាប់) ប៉ុន្តែវាមាន typed array ដែលដើរដូច integer របស់ C។ វាជាមធ្យោបាយងាយស្រួលដើម្បី មើល overflow។ បង្កើតថតសម្រាប់ការពិសោធន៍នៃជំពូកនេះ និងឯកសារមួយឈ្មោះ overflow.js៖
const counter = new Int32Array([2147483647]); // តម្លៃ signed 32-bit ធំបំផុត
counter[0] = counter[0] + 1;
console.log(counter[0]); // -2147483648
const byte = new Uint8Array([255]); // តម្លៃ unsigned 8-bit ធំបំផុត
byte[0] = byte[0] + 1;
console.log(byte[0]); // 0
const tooBig = new Uint8Array([300]);
console.log(tooBig[0]); // 44
Int32Array រក្សាទុក integer signed 32-bit ហើយ Uint8Array រក្សាទុក integer unsigned 8-bit។ ការបូក 1 ទៅតម្លៃធំបំផុតវិលទៅតម្លៃតូចបំផុត។ 300 ក្លាយជា 44 ព្រោះ 300 - 256 = 44៖ មានតែ 8 bit ក្រោមគេបំផុតប៉ុណ្ណោះដែលនៅរស់។ នោះគឺជាគំនិត "សំណល់បន្ទាប់ពីចែក" ពី នព្វន្ត Modular បេះបិទ។
Bitwise operator របស់ JavaScript ក៏បង្ខំលេខឲ្យទៅជាទម្រង់ signed 32-bit ដែរ ដែលជាមូលហេតុដែលល្បិចនេះលេចឡើងក្នុងកូដចាស់ៗ៖
console.log((2147483647 + 1) | 0); // -2147483648
console.log((2 ** 32 + 5) | 0); // 5
Bug overflow ក្នុងជីវិតពិតមិនមែនគ្រាន់តែជារឿងតូចតាចទេ។ ឧបករណ៍រាប់ 16-bit ដែលវិលត្រឡប់បានបង្កឱ្យមានកំហុសពិន្ទុក្នុងហ្គេម arcade ចាស់ៗ ហើយ timestamp 32-bit "វិនាទីចាប់តាំងពី 1970" នឹងអស់នៅឆ្នាំ 2038 លើប្រព័ន្ធដែលនៅតែរក្សាទុកវាក្នុង integer signed 32-bit។ ដំណោះស្រាយគឺតែងតែជាគំនិតដដែល៖ ប្រើប្រអប់ធំជាងនេះ។
4. លេខ JavaScript និងដែនកំណត់ Safe Integer
ក្នុង JavaScript លេខស្ទើរតែទាំងអស់ជាតម្លៃ floating-point 64-bit ("double" របស់ IEEE 754)។ វាមិនវិលត្រឡប់នៅ 2 ពាន់លានទេ។ ផ្ទុយទៅវិញ វាអាចតំណាងឱ្យ integer បានជាក់លាក់ តែរហូតដល់ដែនកំណត់មួយប៉ុណ្ណោះ ហើយហួសពីនោះវាចាប់ផ្តើមបង្គត់។
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2); // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true
បន្ទាត់ចុងក្រោយគឺគួរឱ្យខ្លាចបំផុត៖ ការគណនាពីរខុសគ្នាផ្តល់ចម្លើយដូចគ្នា។ 9007199254740991 គឺ 2^53 - 1។ ខាងលើវា double ដាច់ពីគ្នាលើសពី 1 ដូច្នេះ integer ខ្លះគ្មានជាតម្លៃទេ ហើយត្រូវបង្គត់ទៅអ្នកជិតខាង។
អ្នកអាចសួរ JavaScript ថា integer នៅតែអាចទុកចិត្តបានឬទេ៖
console.log(Number.isSafeInteger(2 ** 53 - 1)); // true
console.log(Number.isSafeInteger(2 ** 53)); // false
ហេតុអ្វីរឿងនេះសំខាន់ក្នុងការអនុវត្តជាក់ស្តែង? ស្រមៃមើលមូលដ្ឋានទិន្នន័យដែលមាន ID 64-bit ដូចជា 9007199254740993។ client JavaScript អានវាពី JSON ជាលេខ ហើយបោះពុម្ព 9007199254740992។ ឥឡូវ ID ខុសមួយ ហើយចង្អុលទៅកំណត់ត្រាផ្សេង។ API ជាច្រើន (Twitter/X និង Discord គឺជាឧទាហរណ៍ដែលគេស្គាល់ច្បាស់) ត្រឡប់ ID ធំៗជា string ដោយសារមូលហេតុនេះឯង។
Factorial ក៏ជាវិធីងាយមួយទៀតដើម្បីធ្លាក់ចេញពីគែម៖
let f = 1;
for (let i = 2; i <= 18; i++) f *= i;
console.log(f, Number.isSafeInteger(f)); // 6402373705728000 true
f *= 19;
console.log(f, Number.isSafeInteger(f)); // 121645100408832000 false
18! គឺ factorial ធំបំផុតដែលសមដោយសុវត្ថិភាព។ ត្រង់ 19! លទ្ធផលប្រហែលជានៅតែ មើលទៅ សមហេតុផល ប៉ុន្តែលេខខាងចុងលែងអាចទុកចិត្តបានហើយ។
5. BigInt៖ Integer ដែលគ្មានដំបូល
ពេលអ្នកត្រូវការចំនួនគត់ជាក់លាក់ហួសដែនកំណត់សុវត្ថិភាព JavaScript មាន number type ទីពីរ៖ BigInt។ សរសេរ BigInt ដោយបន្ថែម n ក្រោយខ្ទង់ ឬហៅ BigInt(...)។
console.log(9007199254740993); // 9007199254740992 (ខុសមួយ)
console.log(9007199254740993n); // 9007199254740993n (ជាក់លាក់)
console.log(2n ** 64n); // 18446744073709551616n
console.log(typeof 10n); // bigint
BigInt ធំបានតាមអង្គចងចាំអនុញ្ញាត។ ច្បាប់ពីរបីដែលត្រូវចងចាំ៖
- អ្នកមិនអាចលាយ BigInt និងលេខធម្មតាក្នុង expression តែមួយបានទេ។
10n + 5បោះTypeError: Cannot mix BigInt and other types, use explicit conversions។ ត្រូវបំលែងម្ខាងជាមុន៖10n + BigInt(5)។ - ការចែកបោះចោលប្រភាគ ព្រោះ BigInt គ្មានទសភាគ៖
7n / 2nគឺ3n។ Number(big)បំលែងត្រឡប់មកវិញ ប៉ុន្តែអ្នកបាត់ភាពជាក់លាក់ ប្រសិនបើតម្លៃនៅលើដែនកំណត់សុវត្ថិភាព។- BigInt យឺតជាងលេខធម្មតា។ ប្រើវាពេលត្រូវការ ដូចជា ID ធំៗ, factorial ធំៗ ឬ cryptography មិនមែនសម្រាប់រាល់ឧបករណ៍រាប់ក្នុង loop ឡើយ។
នេះជា factorial ដែលធ្វើឱ្យ number type ធម្មតាខូច ធ្វើឱ្យត្រឹមត្រូវ៖
function bigFactorial(n) {
let result = 1n;
for (let i = 2n; i <= BigInt(n); i++) {
result *= i;
}
return result;
}
console.log(bigFactorial(20)); // 2432902008176640000n
console.log(bigFactorial(25)); // 15511210043330985984000000n
25! គឺប្រហែល 1.55 x 10^25 ហួសឆ្ងាយពី Number.MAX_SAFE_INTEGER ហើយគ្រប់ខ្ទង់ត្រឹមត្រូវ។ អ្នកនឹងប្រើវានៅក្នុង ការរាប់៖ Permutation និង Combination ដែល factorial ធំឡើងយ៉ាងលឿន។
6. Floating Point៖ ហេតុអ្វី 0.1 + 0.2 ចម្លែក
Integer គឺជាករណីងាយ។ ទសភាគគឺជាកន្លែងដែលការភ្ញាក់ផ្អើលភាគច្រើនរស់នៅ។ បើក console ហើយវាយ៖
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false
នេះមិនមែនជា bug របស់ JavaScript ទេ។ Python, Java, C និង Go បោះពុម្ពដូចគ្នា។ មូលហេតុគឺកុំព្យូទ័ររក្សាទុកលេខជា binary ហើយប្រភាគទសភាគខ្លះមិនអាចសរសេរឱ្យបានជាក់លាក់ក្នុង binary ទេ។ វាជាមូលហេតុដូចគ្នាដែល 1/3 មិនអាចសរសេរឱ្យបានជាក់លាក់ក្នុងទសភាគ៖ អ្នកទទួលបាន 0.3333... ជារៀងរហូត ហើយនៅចំណុចណាមួយអ្នកត្រូវកាត់វាចោល។
ក្នុង binary 0.5 និង 0.25 ងាយស្រួល (វាគឺ 2^-1 និង 2^-2) ដូច្នេះវាជាក់លាក់។ ប៉ុន្តែ 0.1 ក្លាយជាលំនាំដដែលៗមិនចេះចប់ ហើយត្រូវកាត់ចោល ទុកកំហុសតូចមួយ។ បូកវាបញ្ចូលគ្នាពីរបីដង នោះកំហុសនឹងលេចឡើង៖
console.log(0.5 + 0.25); // 0.75 (ជាក់លាក់)
console.log(0.1 * 3); // 0.30000000000000004
console.log(1.1 + 2.2); // 3.3000000000000003
console.log(4.35 * 100); // 434.99999999999994
let total = 0;
for (let i = 0; i < 10; i++) total += 0.1;
console.log(total); // 0.9999999999999999
console.log(total === 1); // false
កំហុសទាំងនេះតូចណាស់ (ប្រហែល 1 ក្នុងមួយ quadrillion) ហើយល្អគ្រប់គ្រាន់សម្រាប់រូបវិទ្យា, ក្រាហ្វិក ឬមធ្យមភាគ។ ប៉ុន្តែវាមិនល្អសម្រាប់អ្វីដែលត្រូវការ "ស្មើ" ឬ "ជាក់លាក់ដល់សេន" ឡើយ។
7. ប្រៀបធៀបលេខទសភាគដោយសុវត្ថិភាព
ដោយសារលទ្ធផលទសភាគអាចខុសបន្តិចបន្តួច កុំប្រៀបធៀបទសភាគដែលបានគណនាដោយ === ឡើយ។ ត្រូវប្រៀបធៀបដោយប្រើ tolerance តូចមួយជំនួសវិញ។ នេះគឺជា function nearlyEqual ដែលអ្នកបានសរសេរក្នុង លេខ និងប្រព័ន្ធលេខ ហើយនេះជាមូលហេតុដែលវាមាន។
import { nearlyEqual } from "./src/numbers.js";
console.log(0.1 + 0.2 === 0.3); // false
console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true
JavaScript ថែមទាំងផ្តល់គម្លាតតូចបំផុតដែលមានន័យនៅជិត 1 ជា Number.EPSILON៖
console.log(Number.EPSILON); // 2.220446049250313e-16
console.log(Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON); // true
e-16 មានន័យថា "គុណ 10 ស្វ័យគុណ -16" ដូច្នេះ Number.EPSILON គឺប្រហែល 0.0000000000000002។ ប្រើវាសម្រាប់លេខជិត 1 ចំណែកលេខធំជាង ឬតូចជាងឆ្ងាយ tolerance ទាក់ទង (relative tolerance) (អ្វីដែល nearlyEqual គួរធ្វើ) ល្អជាង។
ការបង្ហាញគឺជាបញ្ហាដាច់ដោយឡែក។ toFixed(2) បង្គត់លេខទៅពីរខ្ទង់ទសភាគ ហើយត្រឡប់ string៖
console.log((0.1 + 0.2).toFixed(2)); // "0.30"
នោះល្អសម្រាប់ការបោះពុម្ព ប៉ុន្តែកុំប្រើវាដើម្បីជួសជុលតម្លៃដែលបានរក្សាទុក ហើយដូចផ្នែកបន្ទាប់បង្ហាញ សូម្បីតែ toFixed ក៏មានការភ្ញាក់ផ្អើលដែរ។
8. លុយគួរស្ថិតក្នុងសេនជា Integer
នេះគឺជាកំហុសបុរាណ។ ហាងមួយបូកតម្លៃជាទសភាគ៖
const price = 19.99;
console.log(price * 100); // 1998.9999999999998
console.log(Math.floor(price * 100)); // 1998
បំលែង $19.99 ទៅជាសេនដោយគុណ 100, បោះចោលប្រភាគ ហើយអ្នកបាត់សេនមួយ។ នោះគឺជា bug ខុសមួយសេន។ វាកម្រគ្រប់គ្រាន់ដែលរួចពីការធ្វើតេស្ត ហើយទូទៅគ្រប់គ្រាន់ដែលអ្នកគណនេយ្យនឹងរកឃើញនៅទីបំផុត។
ដំណោះស្រាយគឺជាច្បាប់ដែលស្ទើរតែគ្រប់ប្រព័ន្ធទូទាត់ប្រើ៖
រក្សាទុក និងគណនាលុយជា integer ទាំងមូលនៃឯកតាតូចបំផុត។ បំលែងទៅជា decimal string តែពេលបង្ហាញប៉ុណ្ណោះ។
សម្រាប់ដុល្លារអាមេរិក ឯកតាតូចបំផុតគឺសេន ដូច្នេះ $19.99 គឺ integer 1999។ នព្វន្ត integer គឺជាក់លាក់ (រហូតដល់ដែនកំណត់សុវត្ថិភាព ដែលជាប្រហែល 90 ទ្រីលានដុល្លារជាសេន ច្រើនល្មមសម្រាប់ហាងមួយ)៖
const itemCents = 1999;
const quantity = 3;
const subtotal = itemCents * quantity;
console.log(subtotal); // 5997
const vat = Math.round((subtotal * 10) / 100); // អាករ VAT 10%
console.log(vat); // 600
console.log(subtotal + vat); // 6597
សូមកត់សម្គាល់ថា subtotal * 10 កើតឡើងមុនដោយប្រើ integer ហើយមានតែការចែកចុងក្រោយទេដែលបង្កើតប្រភាគ ដែលយើងបង្គត់តែម្តង ដោយចេតនា។
ចុះរៀលវិញ? រៀលកម្ពុជា (KHR) គ្មានឯកតាតូចជាងក្នុងការប្រើប្រាស់ប្រចាំថ្ងៃទេ៖ គ្មាននរណាបង់ "កន្លះរៀល" ឡើយ។ ដូច្នេះចំនួន KHR គឺជា integer ទាំងមូលរួចហើយ ហើយ 41000 មានន័យថា 41,000 រៀល។ តាមអត្រាប្រហែល 4,100 រៀលក្នុងមួយដុល្លារ មួយសេនអាមេរិកគឺប្រហែល 41 រៀល ដូច្នេះការបំលែងសេនទៅរៀលគឺជាការគុណ integer ធម្មតា៖
const cents = 1999;
console.log(cents * 41); // 81959 (ប្រហែល 81,959 រៀល)
អត្រាប្តូរប្រាក់ពិតប្រែប្រួលជារៀងរាល់ថ្ងៃ ដូច្នេះចាត់ទុក 41 ជាឧទាហរណ៍ ហើយបង្គត់ទៅចំនួនសមហេតុផល (ហាងច្រើនតែបង្គត់រៀលទៅរយជិតបំផុត)។
ជាចុងក្រោយ នៅព្រំដែនដែលអ្នកបង្ហាញលុយ ឬផ្ញើវាទៅប្រព័ន្ធផ្សេង ត្រូវបំលែង integer ត្រឡប់ទៅជាអក្សរវិញ៖
const cents = 1999;
console.log((cents / 100).toFixed(2)); // "19.99"
នេះមានសុវត្ថិភាព ព្រោះ cents ជា integer៖ ការចែកនឹង 100 ហើយធ្វើទ្រង់ទ្រាយពីរខ្ទង់ទសភាគនឹងបង្កើតខ្ទង់ដែលអ្នកបានរក្សាទុកឡើងវិញយ៉ាងជាក់លាក់។ Payment gateway ជាទូទៅចង់បានបែបនេះឯង៖ ABA PayWay checkout ក្នុង ស៊េរី ABA PayWay យកចំនួនជា string ដូចជា '1.00' ដូច្នេះរក្សាសរុបរបស់អ្នកជាសេនខាងក្នុង ហើយបង្កើត string នោះនៅចុងបំផុត។
9. ច្បាប់នៃការបង្គត់
ឆាប់ឬយឺត អ្នកនឹងចែកលុយ (បែងចែកវិក្កយបត្រ, អនុវត្តការបញ្ចុះតម្លៃ, បន្ថែមពន្ធ) ហើយទទួលបានប្រភាគនៃសេន។ អ្នកត្រូវបង្គត់ ហើយអ្នកគួរសម្រេច របៀប តែម្តងនៅកន្លែងតែមួយ។
JavaScript ផ្តល់ឧបករណ៍មូលដ្ឋានបួនដល់អ្នក៖
| Function | អត្ថន័យ | 2.5 | -2.5 | 2.1 |
|---|---|---|---|---|
Math.round | ជិតបំផុត, ពាក់កណ្តាលឡើងលើ | 3 | -2 | 2 |
Math.floor | ឆ្ពោះទៅអនន្តអវិជ្ជមាន | 2 | -3 | 2 |
Math.ceil | ឆ្ពោះទៅអនន្តវិជ្ជមាន | 3 | -2 | 3 |
Math.trunc | កាត់ប្រភាគចោល | 2 | -2 | 2 |
ការភ្ញាក់ផ្អើលគឺ Math.round(-2.5) ដែលជា -2 មិនមែន -3 ទេ។ JavaScript បង្គត់ពាក់កណ្តាលពិតប្រាកដឆ្ពោះទៅអនន្តវិជ្ជមាន។
ការបង្គត់ពាក់កណ្តាលឡើងលើជានិច្ចមានភាពលំអៀងបន្តិច៖ ក្នុងប្រតិបត្តិការរាប់លាន វាបន្ថែមបន្តិចបន្តួចលើស។ Banker's rounding (ហៅផងដែរថា round half to even) បង្គត់ពាក់កណ្តាលពិតប្រាកដទៅលេខ គូ ជិតបំផុត ដូច្នេះ 0.5 ទៅ 0, 1.5 ទៅ 2, 2.5 ទៅ 2 និង 3.5 ទៅ 4។ កំហុសនៃការបង្គត់លុបគ្នាជាមធ្យម។ ប្រព័ន្ធគណនេយ្យ និងស្តង់ដារខ្លះតម្រូវឱ្យប្រើវា ដូច្នេះសូមសួរថាអាជីវកម្មរបស់អ្នកប្រើច្បាប់មួយណា។ នេះជាកំណែដែលអ្នកអាចប្រើ៖
function roundHalfEven(x) {
const floor = Math.floor(x);
const diff = x - floor;
if (diff === 0.5) {
return floor % 2 === 0 ? floor : floor + 1;
}
return Math.round(x);
}
console.log([0.5, 1.5, 2.5, 3.5].map(roundHalfEven)); // [ 0, 2, 2, 4 ]
console.log(roundHalfEven(2.4)); // 2
console.log(roundHalfEven(2.6)); // 3
floor % 2 === 0 សួរថា "តើ floor ជាលេខគូឬអត់?" ដោយប្រើ operator សំណល់ % ពី នព្វន្ត Modular។ ត្រូវដឹងថារឿងនេះដំណើរការល្អតែពេល x អាចតំណាងបានជាក់លាក់ប៉ុណ្ណោះ៖ តម្លៃដូចជា 2.5 អាច ប៉ុន្តែតម្លៃដែលបានឆ្លងកាត់នព្វន្តទសភាគរួចហើយប្រហែលជាមិនទៅដល់ .5 ត្រឹមត្រូវទេ។ នោះគឺជាមូលហេតុមួយទៀតដើម្បីស្ថិតក្នុង integer ឱ្យបានយូរបំផុតតាមដែលអាចធ្វើទៅបាន។
ឥឡូវនេះជាការភ្ញាក់ផ្អើលរបស់ toFixed និង Math.round៖
console.log(Math.round(1.005 * 100)); // 100 (អ្នកប្រហែលរំពឹង 101)
console.log((1.005).toFixed(2)); // "1.00" (អ្នកប្រហែលរំពឹង "1.01")
console.log((2.5).toFixed(0)); // "3"
console.log((1.45).toFixed(1)); // "1.4"
ទសភាគ 1.005 ត្រូវបានរក្សាទុកជាអ្វីមួយ ទាបជាងបន្តិច 1.005 (1.00499999999999989...) ដូច្នេះការបង្គត់ឱ្យត្រឹមត្រូវផ្តល់ 1.00។ កុំព្យូទ័រកំពុងធ្វើតាមគណិតវិទ្យាយ៉ាងល្អឥតខ្ចោះ គ្រាន់តែវាទទួលបាន input ខុសបន្តិចពីអ្វីដែលអ្នកបានវាយ។ មេរៀន៖ parse តម្លៃពីអក្សរទៅជាសេន integer នៅគែម ហើយកុំបង្គត់កាកសំណល់ floating-point តូចៗត្រឡប់ទៅជាលុយវិញ។
10. រូបមន្ត៖ Module money
ដល់ពេលបន្ថែម module របស់ជំពូកនេះទៅ mathlab ហើយ។ បង្កើត src/money.js៖
// src/money.js
// បំលែងដុល្លារ (ទសភាគ) ទៅជាសេនទាំងមូល។
export function toCents(dollars) {
return Math.round(dollars * 100);
}
// ធ្វើទ្រង់ទ្រាយចំនួន integer សម្រាប់ការបង្ហាញ។
// USD: amount ជាសេន។ KHR: amount ជារៀលទាំងមូល។
export function formatMoney(amount, currency = "USD") {
if (currency === "KHR") {
return amount.toLocaleString("en-US") + "៛";
}
const sign = amount < 0 ? "-" : "";
const abs = Math.abs(amount);
const dollars = Math.trunc(abs / 100);
const cents = abs % 100;
return (
sign + "$" + dollars.toLocaleString("en-US") + "." + String(cents).padStart(2, "0")
);
}
// តើនេះជា integer ដែលយើងនៅតែអាចទុកចិត្តបានឬទេ?
export function isSafeAmount(n) {
return Number.isSafeInteger(n);
}
// បូកចំនួន integer ពីរ ដោយបដិសេធលទ្ធផលដែលមិនអាចទុកចិត្តបាន។
export function addCents(a, b) {
const sum = a + b;
if (!Number.isSafeInteger(sum)) {
throw new RangeError("Amount is outside the safe integer range");
}
return sum;
}
// Factorial ជាក់លាក់ដោយប្រើ BigInt។
export function bigFactorial(n) {
let result = 1n;
for (let i = 2n; i <= BigInt(n); i++) {
result *= i;
}
return result;
}
// បង្គត់ពាក់កណ្តាលទៅគូ ("banker's rounding")។
export function roundHalfEven(x) {
const floor = Math.floor(x);
const diff = x - floor;
if (diff === 0.5) {
return floor % 2 === 0 ? floor : floor + 1;
}
return Math.round(x);
}
ព័ត៌មានលម្អិតខ្លះ៖ padStart(2, "0") បំពេញសេនឱ្យមានពីរតួអក្សរ ដូច្នេះ 5 ក្លាយជា "05"។ toLocaleString("en-US") បន្ថែមសញ្ញាបំបែករាយពាន់។ abs % 100 គឺជាសំណល់បន្ទាប់ពីចែកនឹង 100 ដែលជាផ្នែកសេន។
ឥឡូវ re-export វាពី src/index.js ដោយបន្ថែមមួយបន្ទាត់ (រក្សាបន្ទាត់ដែលជំពូកមុនៗបានបន្ថែម)៖
export * from "./money.js";
បន្ថែមកូដនេះនៅខាងក្រោមបំផុតនៃ demo.js ហើយដំណើរការ node demo.js៖
import {
toCents, formatMoney, isSafeAmount, addCents, bigFactorial, roundHalfEven,
} from "./src/money.js";
console.log("--- money ---");
console.log(toCents(19.99)); // 1999
console.log(formatMoney(1999)); // $19.99
console.log(formatMoney(100000050)); // $1,000,000.50
console.log(formatMoney(-1999)); // -$19.99
console.log(formatMoney(41000, "KHR")); // 41,000៛
console.log(isSafeAmount(2 ** 53)); // false
console.log(addCents(1999, 501)); // 2500
console.log(bigFactorial(25)); // 15511210043330985984000000n
console.log([0.5, 1.5, 2.5, 3.5].map(roundHalfEven)); // [ 0, 2, 2, 4 ]
try {
addCents(Number.MAX_SAFE_INTEGER, 1);
} catch (error) {
console.log(error.name + ": " + error.message);
// RangeError: Amount is outside the safe integer range
}
ប្រសិនបើអ្នកមាន import ផ្សេងទៀតនៅខាងលើ demo.js រួចហើយ សូមផ្លាស់ទីបន្ទាត់ import នេះឡើងទៅទីនោះ៖ ES module import អាចនៅកន្លែងណាក៏បាននៅកំពូលនៃ top level ប៉ុន្តែកូដរៀបរយល្អរក្សាវាជាមួយគ្នា។
11. សាកល្បងដោយខ្លួនឯង
- រកមើលការវិលត្រឡប់។ ដោយប្រើ
Int8Array(signed 8-bit) រក្សាទុក127បូក 1 ហើយបោះពុម្ពលទ្ធផល។ ទាយចម្លើយជាមុន រួចបញ្ជាក់។ តើអ្វីកើតឡើងជាមួយUint16Arrayនិង65535? - រកមើល ID ដែលបាត់។ ដំណើរការ
console.log(9007199254740993)ហើយប្រៀបធៀបជាមួយconsole.log(9007199254740993n)។ បន្ទាប់មកសរសេរ functionisIdSafe(id)ដែលត្រឡប់trueតែពេលNumber.isSafeInteger(id)ជា true ប៉ុណ្ណោះ ហើយសាកល្បងជាមួយ2 ** 53 - 1និង2 ** 53។ - ជួសជុលកន្ត្រកទិញទំនិញ។ កន្ត្រកមួយមានតម្លៃបី៖
1.10,2.20និង3.30។ បូកវាជាទសភាគ បន្ទាប់មកបំលែងនីមួយៗទៅជាសេនដោយtoCentsហើយបូក integer។ បោះពុម្ពសរុបទាំងពីរ ហើយប្រៀបធៀប។ ប្រើformatMoneyដើម្បីបោះពុម្ពទីពីរ។ - បន្ថែម VAT។ សរសេរ
addVat(cents, percent)ដែលត្រឡប់cents + Math.round((cents * percent) / 100)។ សាកល្បងជាមួយ1999សេន និង10ភាគរយ។ ហេតុអ្វីយើងគុណមុនចែក? - ប្រកួតការបង្គត់។ សម្រាប់តម្លៃ
0.5,1.5,2.5,-0.5និង-1.5បោះពុម្ពMath.round(x)ក្បែរroundHalfEven(x)។ តើពួកវាខុសគ្នាត្រង់ណា ហើយមួយណាមានភាពលំអៀងតិចជាងលើតម្លៃច្រើន? - Factorial សុវត្ថិភាពធំបំផុត។ សរសេរ loop ដោយប្រើលេខធម្មតាដែលគុណ
1 * 2 * 3 * ...ហើយឈប់មុនពេលNumber.isSafeIntegerក្លាយជា false។ វាគួរឈប់នៅ18។ បន្ទាប់មកគណនាbigFactorial(30)ហើយរាប់ខ្ទង់របស់វាដោយប្រើString(bigFactorial(30)).length។
បន្ទាប់ យើងនឹងថយក្រោយពី "តើលេខអាចធំប៉ុនណា" ទៅ "តើលេខលូតលាស់លឿនប៉ុនណា" ជាមួយ function, ស្វ័យគុណ និង logarithm៖ Function, ការលូតលាស់ និង Logarithm។
ទិញកាហ្វេជូនខ្ញុំ
ចូលចិត្តមេរៀននេះមែនទេ? សូមពិចារណាទិញកាហ្វេជូនខ្ញុំមួយកែវ ដើម្បីគាំទ្រការបង្កើតមាតិកាឥតគិតថ្លៃបន្ថែមទៀត!