يتطلب ضغط العديد من الأعداد الحقيقية اللانهائية في عدد محدود من البتات تمثيلاً تقريبيًا. تقوم معظم البرامج بتخزين نتيجة حسابات الأعداد الصحيحة بحد أقصى 32 أو 64 بت. بالنظر إلى أي عدد ثابت من البتات، فإن معظم الحسابات ذات الأعداد الحقيقية ستنتج كميات لا يمكن تمثيلها بدقة باستخدام هذا العدد من البتات. لذلك، يجب غالبًا تقريب نتيجة حساب الفاصلة العائمة لتتناسب مع تمثيلها المحدود. يعد خطأ التقريب هذا سمة مميزة لحساب الفاصلة العائمة. لذلك، أثناء التعامل مع العمليات الحسابية بأرقام الفاصلة العائمة (خاصة إذا كانت الحسابات من حيث المال) نحتاج إلى الاهتمام بأخطاء التقريب في لغة البرمجة. دعونا نرى مثالا:
Javapublic class Main { public static void main(String[] args) { double a = 0.7; double b = 0.9; double x = a + 0.1; double y = b - 0.1; System.out.println('x = ' + x); System.out.println('y = ' + y ); System.out.println(x == y); } }
كيفية تعطيل وضع المطور
الإخراج:
x = 0.7999999999999999
y = 0.8
false
الإجابة هنا ليست ما توقعناه بسبب التقريب الذي قام به مترجم جافا.
السبب وراء خطأ التقريب
تطبق أنواع البيانات العائمة والمزدوجة مواصفات النقطة العائمة IEEE 754. وهذا يعني أن الأرقام يتم تمثيلها في شكل مثل:
SIGN FRACTION * 2 ^ EXP 0.15625 = (0.00101)2والتي يتم تمثيلها بتنسيق الفاصلة العائمة على النحو التالي: 1.01 * 2^-3
لا يمكن تمثيل جميع الكسور تمامًا ككسر من قوة العدد اثنين. كمثال بسيط 0.1 = (0.000110011001100110011001100110011001100110011001100110011001… )2 وبالتالي لا يمكن تخزينها داخل متغير الفاصلة العائمة.
مثال آخر:
javapublic class Main { public static void main(String[] args) { double a = 0.7; double b = 0.9; double x = a + 0.1; double y = b - 0.1; System.out.println('x = ' + x); System.out.println('y = ' + y ); System.out.println(x == y); } }
الإخراج:
x = 0.7999999999999999
y = 0.8
false
مثال آخر:
Javapublic class Main { public static void main(String args[]) { double a = 1.0; double b = 0.10; double x = 9 * b; a = a - (x); // Value of a is expected as 0.1 System.out.println('a = ' + a); } }
الإخراج:
a = 0.09999999999999998كيفية تصحيح أخطاء التقريب؟
- تقريب النتيجة: يمكن استخدام الدالة Round() لتقليل أي تأثيرات لعدم دقة التخزين الحسابي للفاصلة العائمة. يمكن للمستخدم تقريب الأرقام إلى عدد المنازل العشرية المطلوبة لإجراء الحساب. على سبيل المثال، أثناء التعامل مع العملة، من المحتمل أن تقوم بالتقريب إلى منزلتين عشريتين.
- الخوارزميات والوظائف: استخدم خوارزميات مستقرة عدديًا أو صمم وظائفك الخاصة للتعامل مع مثل هذه الحالات. يمكنك اقتطاع/تقريب الأرقام التي لست متأكدًا من صحتها (يمكنك حساب الدقة الرقمية للعمليات أيضًا)
- فئة عشرية كبيرة: يمكنك استخدام java.math.BigDecimal فئة مصممة لتمنحنا الدقة خاصة في حالة الأعداد الكسرية الكبيرة. يوضح البرنامج التالي كيفية إزالة الخطأ:
import java.math.BigDecimal; import java.math.RoundingMode; public class Main { public static void main(String args[]) { BigDecimal a = new BigDecimal('1.0'); BigDecimal b = new BigDecimal('0.10'); BigDecimal x = b.multiply(new BigDecimal('9')); a = a.subtract(x); // Rounding to 1 decimal place a = a.setScale(1 RoundingMode.HALF_UP); System.out.println('a = ' + a); } }
الإخراج:
0.1هنا a = a.setScale(1 RoundingMode.HALF_UP);
جولات aإلى منزلة عشرية واحدة باستخدام وضع التقريب HALF_UP. لذا فإن استخدام BigDecimal يوفر تحكمًا أكثر دقة في العمليات الحسابية والتقريبية التي يمكن أن تكون مفيدة بشكل خاص للحسابات المالية أو الحالات الأخرى التي تكون فيها الدقة أمرًا بالغ الأهمية.
ملاحظة هامة:
يقوم Math.round بتقريب القيمة إلى أقرب عدد صحيح. وبما أن 0.10 أقرب إلى 0 من 1، فسيتم تقريبه إلى 0. وبعد التقريب والقسمة على 1.0 تكون النتيجة 0.0. لذا يمكنك ملاحظة الفرق بين المخرجات باستخدام فئة BigDecimal ووظيفة Maths.round.
Javapublic class Main { public static void main(String args[]) { double a = 1.0; double b = 0.10; double x = 9 * b; a = a - (x); /* We use Math.round() function to round the answer to closest long then we multiply and divide by 1.0 to to set the decimal places to 1 place (this can be done according to the requirements.*/ System.out.println('a = ' + Math.round(a*1.0)/1.0); } }
الإخراج:
0.0