كيفية تقريب الأرقام إلى أقرب 0.05 أو زيادة أخرى في جافاسكريبت
تعلم كيفية تقريب العملات والأرقام إلى زيادات محددة مثل 0.05 أو 0.10 للدول التي لا تستخدم العملات المعدنية الصغيرة
مقدمة
يضيف عميل في كندا عناصر إلى سلة التسوق بإجمالي 11.23 دولارًا، لكن الرسوم النهائية تظهر 11.25 دولارًا. يحدث هذا لأن كندا ألغت عملة البنس في عام 2013، لذلك يتم تقريب المعاملات النقدية إلى أقرب 0.05. يحتاج تطبيقك إلى عرض أسعار تطابق ما يدفعه العملاء فعليًا.
التقريب إلى زيادات محددة يحل هذه المشكلة. يمكنك تقريب الأرقام إلى أقرب خمسة سنتات (0.05)، أو عشرة سنتات (0.10)، أو أي زيادة أخرى. ينطبق هذا على تنسيق العملات، وأنظمة القياس، وأي سيناريو تحتاج فيه القيم إلى التوافق مع زيادات محددة.
يوضح هذا الدليل كيفية تقريب الأرقام إلى زيادات مخصصة باستخدام جافا سكريبت، بما في ذلك التنفيذ اليدوي وواجهة برمجة التطبيقات الحديثة Intl.NumberFormat.
لماذا نقرب إلى زيادات محددة
ألغت العديد من البلدان العملات المعدنية ذات الفئات الصغيرة لأسباب عملية. إنتاج ومعالجة هذه العملات المعدنية يكلف أكثر من قيمتها الاسمية. تقرب المعاملات النقدية في هذه البلدان إلى أقرب عملة معدنية متاحة.
البلدان التي تستخدم تقريب 0.05 للمدفوعات النقدية:
- كندا (ألغت البنس في عام 2013)
- أستراليا (ألغت عملات 1 و2 سنت)
- هولندا (تقرب إلى أقرب 0.05 يورو)
- بلجيكا (تقرب إلى أقرب 0.05 يورو)
- أيرلندا (تقرب إلى أقرب 0.05 يورو)
- إيطاليا (تقرب إلى أقرب 0.05 يورو)
- فنلندا (تقرب إلى أقرب 0.05 يورو)
- سويسرا (أصغر عملة معدنية هي 0.05 فرنك سويسري)
البلدان التي تستخدم تقريب 0.10:
- نيوزيلندا (ألغت عملة الخمسة سنتات في عام 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
يمكن أن تؤدي عمليات حساب الفاصلة العائمة في جافاسكريبت إلى أخطاء دقة صغيرة. بالنسبة لحسابات العملات حيث الدقة مهمة، قم بضرب القيم بقوة 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، والذي يتعامل مع تقريب الزيادات تلقائيًا عند تنسيق الأرقام.
الاستخدام الأساسي لتقريب الدولار الكندي إلى أقرب 5 سنتات:
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 (تم التقريب لأعلى)
أوضاع التقريب المتاحة:
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()) {
// استخدم Intl.NumberFormat مع roundingIncrement
} else {
// الرجوع إلى التقريب اليدوي
}
أمثلة على التطبيق العملي
إنشاء منسق عملة يتعامل مع تقريب النقد بناءً على اللغة المحلية:
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];
// خطأ: تقريب كل عنصر
const wrongTotal = items
.map(price => roundToIncrement(price, 0.05))
.reduce((sum, price) => sum + price, 0);
console.log(wrongTotal); // 3.75
// صحيح: تقريب المجموع
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, // تخزين القيمة الدقيقة
currency: 'CAD',
paymentMethod: 'cash'
};
// حساب المبلغ المعروض
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;
}
// العمل بالسنتات
const amountCents = 1123; // $11.23
const roundedCents = roundToIncrementCents(amountCents, 5); // 1125
const dollars = roundedCents / 100; // 11.25
اختبر تنفيذ التقريب الخاص بك مع الحالات الحدية:
// حالات اختبار للتقريب إلى 0.05
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
});
// تحقق مما إذا كان roundingIncrement قد تم تطبيقه بالفعل
if (formatter.resolvedOptions().roundingIncrement === increment) {
return formatter.format(amount);
}
} catch (e) {
// roundingIncrement غير مدعوم
}
// الحل البديل: التقريب اليدوي
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">المجموع الفرعي: $11.23</div>
<div class="line-item total">المجموع (نقدًا): $11.25</div>
<div class="note">المدفوعات النقدية مقربة إلى أقرب $0.05</div>
</div>
الولايات القضائية المختلفة لديها لوائح مختلفة حول من يستفيد من التقريب (العميل أو التاجر) وكيفية التعامل مع الحالات الحدية. استشر اللوائح المحلية لحالة الاستخدام المحددة الخاصة بك.