برنامه نویسی

فراتر از دو برابر: شیوه های اساسی بزرگ برای محاسبات مالی دقیق

اگر کمی در زمینه توسعه نرم افزار بوده اید ، به خصوص در هر نقطه نزدیک به برنامه های مالی ، احتمالاً قانون طلایی را شنیده اید: برای پول از دو برابر (یا شناور) استفاده نکنید. این دانش رایج است و به دلایل خوبی-حسابی با نقطه شناور می تواند منجر به خطاهای دقیق شود که کابوس در امور مالی است. راه حل Go-to Java.Math.BigDecimal است.

با این حال ، جابجایی به BigDecimal یک گلوله جادویی نیست. برای استفاده صحیح از آن تفاوت های ظریف وجود دارد که می تواند حتی توسعه دهندگان با تجربه را نیز طی کند. من بارها و بارها دیده ام که این موضوعات ظاهر می شود ، بنابراین بیایید به برخی از نقاط مهم که اغلب نادیده گرفته می شوند ، شیرجه بزنیم.

با رشته ها اولیه: موضوع دقیق و مقیاس

ممکن است شما وسوسه شوید که یک بزرگ مانند این ایجاد کنید:

// Don't do this!
BigDecimal amount = new BigDecimal(0.1);
System.out.println(amount); // Might print something like 0.1000000000000000055511151231257827021181583404541015625
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

“صبر کن ، چی؟” بله ، این دقیقاً 0.1 نیست. مشکل این است که خود به معنای واقعی کلمه 0.1 نمی تواند کاملاً در نقطه شناور باینری نمایش داده شود. هنگامی که آن را به سازنده BigDecimal (Double) منتقل می کنید ، در اصل از یک بازنمایی در حال حاضر نادرست در حال ایجاد یک bigdecimal هستید.

روش صحیح برای اولیه سازی BigDecimal با یک مقدار اعشاری خاص استفاده از سازنده رشته است:

BigDecimal correctAmount = new BigDecimal("0.1");
System.out.println(correctAmount); // Prints 0.1
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

چرا؟ زیرا رشته “0.1” یک نمایش دقیق است. BigDecimal از مفاهیم دقت (تعداد کل ارقام) و مقیاس (تعداد رقم در سمت راست نقطه اعشاری) استفاده می کند. سازنده رشته به BigDecimal اجازه می دهد تا این نمایندگی را به طور دقیق تجزیه کند و دقت و مقیاس مورد نظر را حفظ کند.

دو برابر شدی؟ از bigdecimal.valueof استفاده کنید ()

بعضی اوقات ، ممکن است یک مقدار مضاعف از یک کتابخانه یا یک سیستم خارجی دریافت کنید و باید آن را به BigDecimal تبدیل کنید. اگر با یک متغیر مضاعف گیر کرده اید ، به دلایل ذکر شده در بالا از سازنده جدید BigDecimal (دو برابر) خودداری کنید.

در عوض ، از روش کارخانه استاتیک bigDecimal.valueof () استفاده کنید:

double priceDouble = 0.1;
// BigDecimal stillNotIdeal = new BigDecimal(priceDouble); // Avoid if possible
BigDecimal betterPrice = BigDecimal.valueOf(priceDouble);
System.out.println(betterPrice); // Prints 0.1
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

BigDecimal.Valueof (دو برابر) به طور کلی نسبت به BigDecimal جدید (دو برابر) ترجیح داده می شود زیرا اغلب نتیجه قابل پیش بینی تری می دهد. از نمایش رشته متعارف دو برابر (به عنوان مثال ، Double.ToString (DoubleVal)) استفاده می کند ، که می تواند به جلوگیری از برخی از موارد دقیق دقیق تر که مستقیماً با سازنده مضاعف مشاهده می کنید ، کمک کند. با این حال ، به یاد داشته باشید بهترین روش این است که اگر کنترل ورودی را کنترل کنید ، با رشته ها یا عدد صحیح شروع کنید.

قالب بندی شناورها؟ BigDecimal دوست شماست ، نه String.Format ()

وقتی نوبت به نمایش شماره های شناور ، به ویژه ارز می رسد ، قالب بندی مهم است. ممکن است به طور غریزی به String.format () برسید:

double value = 123.456;
// String formatted = String.format("%.2f", value); 
// Potential for rounding surprises
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در حالی که String.Format () برای موارد اساسی کار می کند ، هنگام برخورد با دقت که BigDecimal ارائه می دهد ، بهتر است به BigDecimal اجازه دهید قالب بندی خود را کنترل کند ، اغلب در رابطه با NumberFormat برای نمادها و کنوانسیون های ارز خاص محلی.

برای تنظیم مقیاس ساده:

BigDecimal preciseValue = new BigDecimal("123.456789");
BigDecimal roundedValue = preciseValue.setScale(2, RoundingMode.HALF_UP); // Explicit rounding
System.out.println(roundedValue); // Prints 123.46

// For currency formatting:
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(currencyFormatter.format(roundedValue)); // Prints $123.46
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

با استفاده از روش SetScale () BigDecimal با یک mode صریح ، کنترل کاملی در مورد چگونگی گرد شدن ، که در زمینه های مالی بسیار مهم است ، دارید.

مقایسه BigDecimal: برابر () در مقابل مقایسه () – دام مقیاس!

این یکی بسیاری از مردم را نیش می زند. شما دو شیء BigDecimal دارید که همان ارزش عددی را نشان می دهند ، می گویند 10.00 دلار و 10.0 دلار.

BigDecimal val1 = new BigDecimal("10.0");  // scale = 1
BigDecimal val2 = new BigDecimal("10.00"); // scale = 2

System.out.println(val1.equals(val2)); // Prints false!
System.out.println(val1.hashCode() == val2.hashCode()); // Likely false!
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

چرا نادرست؟ زیرا bigDecimal.Equals () هم ارزش و هم مقیاس را در نظر می گیرد. از آنجا که “10.0” (مقیاس 1) و “10.00” (مقیاس 2) مقیاس های مختلفی دارند ، برابر است () نادرست است. به همین ترتیب ، مقادیر هش کد () آنها احتمالاً متفاوت خواهد بود.

اگر می خواهید بررسی کنید که آیا دو شیء BigDecimal بدون توجه به مقیاس آنها ، یک مقدار عددی را نشان می دهند ، باید از Compareto () استفاده کنید:

System.out.println(val1.compareTo(val2) == 0); // Prints true!

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

مقایسه () بازگشت:

  1. 0 اگر مقادیر از نظر عددی برابر باشند.
  2. یک عدد صحیح منفی اگر Val1 از نظر عددی کمتر از Val2 باشد.
  3. یک عدد صحیح مثبت اگر Val1 از نظر عددی بیشتر از Val2 باشد.

بنابراین ، برای برابری منطقی مبالغ پولی ، همیشه از مقایسه () == 0 استفاده کنید.

قاتل ساکت: سرریز عددی با بدوی

فراتر از BigDecimal ، بیایید به یک Gremlin عددی عمومی: سرریز. تمام انواع عددی بدوی در جاوا (بایت ، کوتاه ، int ، طولانی و حتی کاراکتر در صورت استفاده عددی) دارای طیف ثابت از مقادیر آنها هستند.

چه اتفاقی می افتد که از این محدوده فراتر بروید؟

int maxIntValue = Integer.MAX_VALUE; // 2147483647
int result = maxIntValue + 1;
System.out.println(result); // Prints -2147483648 (Integer.MIN_VALUE)

long largeNumber = Long.MAX_VALUE;
long overflowed = largeNumber + 100; // Wraps around to a negative number
System.out.println(overflowed);
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به چیزی ترسناک توجه می کنید؟ بدون خطایی! بدون استثنا! محاسبه بی صدا به اطراف می پیچد. این می تواند منجر به اشکالات فوق العاده ظریف و خطرناک شود ، به ویژه در محاسبات مربوط به مقادیر ، شمارش یا شناسه.

راه حل؟ از آنجا که جاوا 8 ، کلاس ریاضی روشهای حسابی “دقیق” را ارائه می دهد که یک Arithmeticexception را بر روی سرریز پرتاب می کند:

try {
    int sum = Math.addExact(Integer.MAX_VALUE, 1);
    System.out.println(sum);
} catch (ArithmeticException e) {
    System.err.println("Overflow detected! " + e.getMessage());
}

try {
    long product = Math.multiplyExact(Long.MAX_VALUE / 2, 3); // This would overflow
    System.out.println(product);
} catch (ArithmeticException e) {
    System.err.println("Overflow detected during multiplication! " + e.getMessage());
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سایر روشهای مفید شامل SubtractExact () ، MultiplyExact () ، incrementExact () ، DecrementExact () و tointexact () (برای تبدیل طولانی به ایمن). استفاده از این روش ها باعث می شود کد شما با عدم موفقیت در هنگام وقوع سرریز ، سریعتر شود.

پیچیدن

کار با شماره در نرم افزار ، به ویژه هنگامی که پول یا مقادیر مهم درگیر هستند ، نیاز به دقت دارد. BigDecimal ابزاری قدرتمند است ، اما مانند هر ابزاری ، باید پیچیدگی های آن را درک کنید. و همیشه به محدودیت های انواع بدوی توجه داشته باشید!

با توجه به این نکات:

  1. از سازندگان رشته برای bigDecimal (bigDecimal جدید (“0.1”) استفاده کنید.
  2. اگر دو برابر داشته باشید ، bigDecimal.Valueof (دو برابر) را ترجیح دهید.
  3. فرمت با استفاده از روشهای BigDecimal (SetScale ()) و NumberFormat.
  4. مقادیر را با مقایسه () مقایسه کنید ، نه برابر ().
  5. محافظت در برابر سرریز اولیه با روش های Math.xxexact ().

شما کد دقیق تر ، قابل اعتماد و قابل نگهداری تر می نویسید. برنامه نویسی مبارک!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا