كيفية تقريب الأرقام إلى أقرب 0.05 أو زيادة أخرى في JavaScript
تعلم كيفية تقريب العملات والأرقام إلى زيادات محددة مثل 0.05 أو 0.10 للدول التي لا تحتوي على عملات معدنية صغيرة
مقدمة
يضيف عميل في كندا عناصر إلى سلة التسوق بإجمالي 11.23 دولار، لكن الرسوم النهائية تظهر 11.25 دولار. يحدث هذا لأن كندا ألغت البنس في عام 2013، لذلك تقرب المعاملات النقدية إلى أقرب 0.05. يحتاج تطبيقك إلى عرض أسعار تطابق ما يدفعه العملاء فعلياً.
يحل التقريب إلى زيادات محددة هذه المشكلة. يمكنك تقريب الأرقام إلى أقرب نيكل (0.05)، أو دايم (0.10)، أو أي زيادة أخرى. ينطبق هذا على تنسيق العملات، وأنظمة القياس، وأي سيناريو تحتاج فيه القيم إلى التوافق مع زيادات محددة.
يوضح لك هذا الدليل كيفية تقريب الأرقام إلى زيادات مخصصة باستخدام JavaScript، بما في ذلك التنفيذ اليدوي وواجهة برمجة التطبيقات الحديثة Intl.NumberFormat.
لماذا التقريب إلى زيادات محددة
ألغت العديد من الدول العملات المعدنية ذات الفئات الصغيرة لأسباب عملية. إنتاج ومعالجة هذه العملات يكلف أكثر من قيمتها الاسمية. تقرب المعاملات النقدية في هذه الدول إلى أقرب عملة متاحة.
الدول التي تستخدم تقريب 0.05 للمدفوعات النقدية:
- كندا (ألغت البنس في عام 2013)
- أستراليا (ألغت عملات 1 و2 سنت)
- هولندا (تقرب إلى أقرب 0.05 يورو)
- بلجيكا (تقرب إلى أقرب 0.05 يورو)
- أيرلندا (تقرب إلى أقرب 0.05 يورو)
- إيطاليا (تقرب إلى أقرب 0.05 يورو)
- فنلندا (تقرب إلى أقرب 0.05 يورو)
- سويسرا (أصغر عملة هي 0.05 فرنك سويسري)
الدول التي تستخدم تقريب 0.10:
- نيوزيلندا (ألغت عملة 5 سنت في عام 2006)
تخطط الولايات المتحدة لإيقاف إنتاج البنسات بحلول أوائل عام 2026، مما سيتطلب تقريباً بقيمة 0.05 للمعاملات النقدية.
ينطبق التقريب النقدي على إجمالي المعاملة النهائي فقط، وليس على العناصر الفردية. تظل المدفوعات الإلكترونية دقيقة لأنه لا يتم تبادل عملات معدنية فعلية.
بخلاف العملة، قد تحتاج إلى زيادات مخصصة لـ:
- أنظمة القياس ذات متطلبات الدقة المحددة
- الحسابات العلمية المقربة إلى دقة الأداة
- عناصر التحكم في واجهة المستخدم التي تنطبق على قيم محددة
- استراتيجيات التسعير التي تستخدم نقاط سعر نفسية
المفهوم الرياضي وراء تقريب الزيادة
التقريب إلى زيادة يعني إيجاد أقرب مضاعف لتلك الزيادة. تقسم الصيغة الرقم على الزيادة، وتقرب إلى أقرب عدد صحيح، ثم تضرب مرة أخرى في الزيادة.
roundedValue = Math.round(value / increment) * increment
على سبيل المثال، تقريب 11.23 إلى أقرب 0.05:
- القسمة: 11.23 / 0.05 = 224.6
- التقريب: Math.round(224.6) = 225
- الضرب: 225 * 0.05 = 11.25
تحول القسمة الزيادة إلى خطوات أعداد صحيحة. يجد تقريب 224.6 إلى 225 أقرب خطوة. يحول الضرب مرة أخرى إلى المقياس الأصلي.
بالنسبة لـ 11.27 مقرباً إلى أقرب 0.05:
- القسمة: 11.27 / 0.05 = 225.4
- التقريب: Math.round(225.4) = 225
- الضرب: 225 * 0.05 = 11.25
يتم تقريب كل من 11.23 و 11.27 إلى 11.25 لأنهما يقعان ضمن 0.025 من تلك القيمة. تظل نقطة المنتصف 11.25 عند 11.25.
تنفيذ تقريب الزيادة يدوياً
أنشئ دالة تقرب أي رقم إلى زيادة محددة:
function roundToIncrement(value, increment) {
return Math.round(value / increment) * increment;
}
console.log(roundToIncrement(11.23, 0.05)); // 11.25
console.log(roundToIncrement(11.27, 0.05)); // 11.25
console.log(roundToIncrement(11.28, 0.05)); // 11.30
console.log(roundToIncrement(4.94, 0.10)); // 4.90
console.log(roundToIncrement(4.96, 0.10)); // 5.00
يعمل هذا مع أي زيادة، وليس فقط العملة:
console.log(roundToIncrement(17, 5)); // 15
console.log(roundToIncrement(23, 5)); // 25
console.log(roundToIncrement(2.7, 0.5)); // 2.5
يمكن أن تؤدي العمليات الحسابية للفاصلة العائمة في JavaScript إلى أخطاء دقة صغيرة. للحسابات المالية حيث تهم الدقة، اضرب القيم في قوة 10 للعمل مع الأعداد الصحيحة:
function roundToIncrementPrecise(value, increment) {
// Find the number of decimal places in the increment
const decimals = (increment.toString().split('.')[1] || '').length;
const multiplier = Math.pow(10, decimals);
// Convert to integers
const valueInt = Math.round(value * multiplier);
const incrementInt = Math.round(increment * multiplier);
// Round and convert back
return Math.round(valueInt / incrementInt) * incrementInt / multiplier;
}
console.log(roundToIncrementPrecise(11.23, 0.05)); // 11.25
يستخدم Math.round() التقريب للأعلى عند المنتصف، حيث تُقرَّب القيم الواقعة في المنتصف تمامًا بين زيادتين للأعلى. تحتاج بعض التطبيقات إلى أوضاع تقريب مختلفة:
function roundToIncrementWithMode(value, increment, mode = 'halfUp') {
const divided = value / increment;
let rounded;
switch (mode) {
case 'up':
rounded = Math.ceil(divided);
break;
case 'down':
rounded = Math.floor(divided);
break;
case 'halfDown':
rounded = divided % 1 === 0.5 ? Math.floor(divided) : Math.round(divided);
break;
case 'halfUp':
default:
rounded = Math.round(divided);
break;
}
return rounded * increment;
}
console.log(roundToIncrementWithMode(11.225, 0.05, 'halfUp')); // 11.25
console.log(roundToIncrementWithMode(11.225, 0.05, 'halfDown')); // 11.20
console.log(roundToIncrementWithMode(11.23, 0.05, 'up')); // 11.25
console.log(roundToIncrementWithMode(11.23, 0.05, 'down')); // 11.20
استخدام Intl.NumberFormat مع roundingIncrement
تدعم المتصفحات الحديثة خيار roundingIncrement في Intl.NumberFormat، والذي يتعامل مع تقريب الزيادات تلقائيًا عند تنسيق الأرقام.
الاستخدام الأساسي لتقريب النيكل بالدولار الكندي:
const formatter = new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5
});
console.log(formatter.format(11.23)); // CA$11.25
console.log(formatter.format(11.27)); // CA$11.25
console.log(formatter.format(11.28)); // CA$11.30
يقبل خيار roundingIncrement قيمًا محددة فقط:
- 1، 2، 5، 10، 20، 25، 50، 100، 200، 250، 500، 1000، 2000، 2500، 5000
للتقريب إلى 0.05، اضبط maximumFractionDigits: 2 و roundingIncrement: 5. للتقريب إلى 0.10، اضبط maximumFractionDigits: 1 و roundingIncrement: 1، أو احتفظ بـ maximumFractionDigits: 2 مع roundingIncrement: 10.
تقريب الفرنك السويسري إلى 0.05:
const chfFormatter = new Intl.NumberFormat('de-CH', {
style: 'currency',
currency: 'CHF',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5
});
console.log(chfFormatter.format(8.47)); // CHF 8.45
console.log(chfFormatter.format(8.48)); // CHF 8.50
تقريب الدولار النيوزيلندي إلى 0.10:
const nzdFormatter = new Intl.NumberFormat('en-NZ', {
style: 'currency',
currency: 'NZD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 10
});
console.log(nzdFormatter.format(15.94)); // $15.90
console.log(nzdFormatter.format(15.96)); // $16.00
يمكنك دمج roundingIncrement مع أوضاع تقريب مختلفة:
const formatterUp = new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: 5,
roundingMode: 'ceil'
});
console.log(formatterUp.format(11.21)); // CA$11.25 (rounded up)
أوضاع التقريب المتاحة:
ceil: التقريب نحو اللانهاية الموجبةfloor: التقريب نحو اللانهاية السالبةexpand: التقريب بعيدًا عن الصفرtrunc: التقريب نحو الصفرhalfCeil: تقريب النصف نحو اللانهاية الموجبةhalfFloor: تقريب النصف نحو اللانهاية السالبةhalfExpand: تقريب النصف بعيدًا عن الصفر (افتراضي)halfTrunc: تقريب النصف نحو الصفرhalfEven: تقريب النصف إلى الزوجي (تقريب المصرفيين)
القيود المهمة لـ roundingIncrement:
- يجب أن يكون لكل من
minimumFractionDigitsوmaximumFractionDigitsنفس القيمة - لا يمكن الدمج مع تقريب الأرقام المعنوية
- يعمل فقط عندما يكون
roundingPriorityهوauto(الافتراضي)
تحقق مما إذا كان المتصفح يدعم roundingIncrement:
function supportsRoundingIncrement() {
try {
const formatter = new Intl.NumberFormat('en-US', {
roundingIncrement: 5,
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const options = formatter.resolvedOptions();
return options.roundingIncrement === 5;
} catch (e) {
return false;
}
}
if (supportsRoundingIncrement()) {
// Use Intl.NumberFormat with roundingIncrement
} else {
// Fall back to manual rounding
}
أمثلة تطبيق عملية
أنشئ منسق عملة يتعامل مع التقريب النقدي بناءً على اللغة المحلية:
function createCashFormatter(locale, currency) {
// Define cash rounding rules by currency
const cashRoundingRules = {
'CAD': { increment: 5, digits: 2 },
'AUD': { increment: 5, digits: 2 },
'EUR': { increment: 5, digits: 2 }, // Some eurozone countries
'CHF': { increment: 5, digits: 2 },
'NZD': { increment: 10, digits: 2 }
};
const rule = cashRoundingRules[currency];
if (!rule) {
// No special rounding needed
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
});
}
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: rule.digits,
maximumFractionDigits: rule.digits,
roundingIncrement: rule.increment
});
}
const cadFormatter = createCashFormatter('en-CA', 'CAD');
console.log(cadFormatter.format(47.83)); // CA$47.85
const usdFormatter = createCashFormatter('en-US', 'USD');
console.log(usdFormatter.format(47.83)); // $47.83 (no rounding)
التعامل مع المدفوعات النقدية والبطاقات:
function formatPaymentAmount(amount, currency, locale, paymentMethod) {
if (paymentMethod === 'cash') {
const formatter = createCashFormatter(locale, currency);
return formatter.format(amount);
} else {
// Electronic payments use exact amounts
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount);
}
}
console.log(formatPaymentAmount(11.23, 'CAD', 'en-CA', 'cash')); // CA$11.25
console.log(formatPaymentAmount(11.23, 'CAD', 'en-CA', 'card')); // CA$11.23
حساب المبلغ الفعلي المطلوب تحصيله:
function calculateCashTotal(items, currency) {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
// Apply cash rounding only to the final total
const cashRoundingRules = {
'CAD': 0.05,
'AUD': 0.05,
'CHF': 0.05,
'NZD': 0.10
};
const increment = cashRoundingRules[currency];
if (!increment) {
return subtotal;
}
return roundToIncrement(subtotal, increment);
}
function roundToIncrement(value, increment) {
return Math.round(value / increment) * increment;
}
const items = [
{ price: 5.99 },
{ price: 3.49 },
{ price: 1.75 }
];
console.log(calculateCashTotal(items, 'CAD')); // 11.25
console.log(calculateCashTotal(items, 'USD')); // 11.23
استخدام تقريب الزيادة للقيم غير النقدية:
// Temperature display rounded to nearest 0.5 degrees
function formatTemperature(celsius) {
const rounded = roundToIncrement(celsius, 0.5);
return new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'celsius',
minimumFractionDigits: 1,
maximumFractionDigits: 1
}).format(rounded);
}
console.log(formatTemperature(22.3)); // 22.5°C
console.log(formatTemperature(22.6)); // 22.5°C
// Slider values snapped to increments
function snapToGrid(value, gridSize) {
return roundToIncrement(value, gridSize);
}
console.log(snapToGrid(47, 5)); // 45
console.log(snapToGrid(53, 5)); // 55
اعتبارات مهمة وحالات خاصة
طبّق التقريب النقدي على إجمالي المعاملة النهائية فقط، وليس على عناصر الأسطر الفردية. تقريب كل عنصر بشكل منفصل ينتج نتيجة مختلفة عن تقريب المجموع:
const items = [1.23, 1.23, 1.23];
// Wrong: rounding each item
const wrongTotal = items
.map(price => roundToIncrement(price, 0.05))
.reduce((sum, price) => sum + price, 0);
console.log(wrongTotal); // 3.75
// Correct: rounding the total
const correctTotal = roundToIncrement(
items.reduce((sum, price) => sum + price, 0),
0.05
);
console.log(correctTotal); // 3.70
اعرض المبالغ المقربة للمستخدمين، لكن احفظ المبالغ الدقيقة في قاعدة البيانات. هذا يحافظ على الدقة للمحاسبة ويسمح لك بتغيير قواعد التقريب دون فقدان المعلومات:
const transaction = {
items: [
{ id: 1, price: 5.99 },
{ id: 2, price: 3.49 },
{ id: 3, price: 1.75 }
],
subtotal: 11.23, // Store exact value
currency: 'CAD',
paymentMethod: 'cash'
};
// Calculate display amount
const displayAmount = roundToIncrement(transaction.subtotal, 0.05); // 11.25
يمكن أن يكون لدى البلدان المختلفة التي تستخدم نفس العملة قواعد تقريب نقدي مختلفة. يُستخدم اليورو في بلدان لديها ولا تملك تقريباً نقدياً. تحقق من اللوائح المحددة للبلد، وليس فقط العملة.
تُقرّب المبالغ السالبة نحو الصفر للقيمة المطلقة، ثم تُطبّق الإشارة السالبة:
console.log(roundToIncrement(-11.23, 0.05)); // -11.25
console.log(roundToIncrement(-11.22, 0.05)); // -11.20
يمكن أن تفقد الأرقام الكبيرة جداً الدقة مع العمليات الحسابية للفاصلة العائمة. بالنسبة للتطبيقات المالية التي تتعامل مع مبالغ كبيرة، فكر في استخدام العمليات الحسابية للأعداد الصحيحة مع أصغر وحدة عملة:
function roundToIncrementCents(cents, incrementCents) {
return Math.round(cents / incrementCents) * incrementCents;
}
// Work in cents
const amountCents = 1123; // $11.23
const roundedCents = roundToIncrementCents(amountCents, 5); // 1125
const dollars = roundedCents / 100; // 11.25
اختبر تطبيق التقريب الخاص بك مع الحالات الخاصة:
// Test cases for 0.05 rounding
const testCases = [
{ input: 0.00, expected: 0.00 },
{ input: 0.01, expected: 0.00 },
{ input: 0.02, expected: 0.00 },
{ input: 0.025, expected: 0.05 },
{ input: 0.03, expected: 0.05 },
{ input: 0.99, expected: 1.00 },
{ input: -0.03, expected: -0.05 },
{ input: 11.225, expected: 11.25 }
];
testCases.forEach(({ input, expected }) => {
const result = roundToIncrement(input, 0.05);
console.log(`${input} -> ${result} (expected ${expected})`);
});
خيار roundingIncrement في Intl.NumberFormat مدعوم في المتصفحات الحديثة. أضاف Firefox الدعم في الإصدار 93. بالنسبة للتطبيقات التي تدعم المتصفحات الأقدم، قم بتطبيق حل بديل:
function formatCurrency(amount, locale, currency, increment) {
try {
const formatter = new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
roundingIncrement: increment
});
// Check if roundingIncrement was actually applied
if (formatter.resolvedOptions().roundingIncrement === increment) {
return formatter.format(amount);
}
} catch (e) {
// roundingIncrement not supported
}
// Fallback: manual rounding
const rounded = roundToIncrement(amount, increment / 100);
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(rounded);
}
وثّق سلوك التقريب الخاص بك بوضوح في الواجهات الموجهة للمستخدم. يحتاج المستخدمون إلى فهم سبب اختلاف المبالغ المعروضة عن إجماليات العناصر:
<div class="cart-summary">
<div class="line-item">Subtotal: $11.23</div>
<div class="line-item total">Total (cash): $11.25</div>
<div class="note">Cash payments rounded to nearest $0.05</div>
</div>
لدى الولايات القضائية المختلفة لوائح مختلفة حول من يستفيد من التقريب (العميل أو التاجر) وكيفية التعامل مع الحالات الخاصة. استشر اللوائح المحلية لحالة الاستخدام المحددة الخاصة بك.