سؤال

لقد كنت أعاني من كابوس الدقة في Java وSQL Server حتى النقطة التي لم أعد أعرف فيها بعد الآن.أنا شخصياً أفهم المشكلة والسبب الكامن وراءها، لكن شرح ذلك للعميل في منتصف الطريق عبر العالم هو أمر غير ممكن (على الأقل بالنسبة لي).

الوضع هو هذا.لدي عمودين في SQL Server - الكمية INT والسعر FLOAT.قيم هذه العناصر هي - 1250 و10.8601 - ومن أجل الحصول على القيمة الإجمالية، فإن الكمية * السعر والنتيجة هي 13575.124999999998 (في كل من Java وSQL Server).هذا صحيح.تكمن المشكلة في أن العميل لا يريد رؤية ذلك، فهو يرى هذا الرقم فقط على أنه 13575.125 وهذا كل شيء.في مكان واحد يمكنهم رؤيته بدقة رقمين عشريين وفي مكان آخر بدقة 4 أرقام عشرية.عند العرض بأربعة أرقام عشرية يكون الرقم صحيحًا - 13575.125، ولكن عند العرض برقمين عشريين يعتقدون أن الرقم خاطئ - 13575.12 - يجب أن يكون 13575.13 بدلاً من ذلك!

يساعد.

هل كانت مفيدة؟

المحلول

ومشكلتك هي أنك تستخدم عوامات. على الجانب جافا، تحتاج إلى استخدام BigDecimal، لا تطفو أو ضعف، وعلى الجانب SQL تحتاج إلى استخدام عشري (19،4) (أو عشري (19،3) إذا كان يساعد القفز إلى مستوى دقة الخاص بك). لا تستخدم نوع المال لأن الرياضيات على نوع المال في SQL أسباب الاقتطاع، وليس التقريب. حقيقة أن يتم تخزين البيانات كنوع تعويم (الذي تقوله هو غير قابلة للتغيير) لا تؤثر على هذا، لديك فقط لتحويله في أول فرصة قبل القيام الرياضيات على ذلك.

في المثال المحددة التي تعطي، تحتاج إلى أولا الحصول على رقم دقيق 4 العشري ووضعها في BigDecimal أو عشري (19،4) كما هو الحال قد تكون، ومن ثم مزيد من جولة إلى 2 العشرية الدقة. ثم (إذا كنت باعتقال) سوف تحصل على النتيجة التي تريدها.

نصائح أخرى

BigDecimal . تعويم ليس نوع approciate لتمثيل المال. وسوف تعامل مع التقريب بشكل صحيح. تعويم سوف تنتج دائما أخطاء التقريب.

لتخزين المبالغ النقدية، لا تعد قيم النقطة العائمة هي الطريقة الصحيحة.من الوصف الخاص بك، من المحتمل أن أتعامل مع المبالغ كأعداد صحيحة طويلة بقيمة المبلغ النقدي مضروبًا في 10 ^ 5 كتنسيق تخزين قاعدة البيانات.

يجب أن تكون قادرًا على التعامل مع الحسابات بمبالغ لا تفقد الدقة، لذا فإن النقطة العائمة هنا مرة أخرى ليست هي الطريقة الصحيحة.إذا كانت المبالغ الإجمالية بين المدين والائتمان أقل بمقدار سنت واحد في دفتر الأستاذ، فإن دفتر الأستاذ يفشل في نظر الأشخاص الماليين، لذا تأكد من أن برنامجك يعمل في هُم مجال المشكلة، وليس لك.إذا لم تتمكن من استخدام الفصول الموجودة مقابل مبالغ مالية، فأنت بحاجة إلى إنشاء فصلك الخاص الذي يعمل معه amount * 10^5 والتنسيقات حسب الدقة المطلوبة فقط لأغراض الإدخال والإخراج.

وأعتقد أنني أرى مشكلة.

و10.8601 لا يمكن أن تكون ممثلة تماما، وذلك في حين أن التقريب إلى 13575،125 أعمال OK أنه من الصعب الحصول عليها لجولة إلى 0.13 لإضافة 0.005 فقط لا يحصل تماما هناك. ومما زاد الطين بلة، 0.005 ليس لديها تمثيل المحدد سواء، حتى ينتهي بك الأمر أقل قليلا قليلا من 0.13.

واختياراتك ثم يتم إما الجولة مرتين، مرة واحدة لثلاثة أرقام ثم مرة واحدة ل2، أو القيام حساب الأفضل أن تبدأ. استخدام طويل أو تنسيق عالية الدقة، على نطاق وبحلول عام 1000 للحصول على * 0،125 إلى * 125. هل التقريب باستخدام الأعداد الصحيحة دقيقة.

وبالمناسبة، ليس صحيحا تماما القول واحدة من التغيرات المتكررة ما لا نهاية على "نقطة عائمة غير دقيقة"، أو أنه دائما تنتج أخطاء. والمشكلة هي أن الشكل يمكن أن تمثل فقط الكسور التي يمكنك جمع القوى السلبية من سنتين إلى إنشاء. لذلك، من تسلسل 0،01-0،99، 0،25 فقط، 0.50، 0.75، ويكون التمثيل على وجه الدقة. ونتيجة لذلك، FP يتم استخدام أفضل، ومن المفارقات، من خلال التوسع في ذلك بحيث لا يتم استخدام عدد صحيح القيم، فمن دقيقة قدر عدد صحيح نوع البيانات الحسابية. وبطبيعة الحال، ثم بالاضافة الى انك قد استخدمت فقط صحيحة نقطة ثابتة لتبدأ.

كن حذرا، والقياس، مثلا، ،37-37 ليس يزال على وجه الدقة ما لم مقربة. النقطة العائمة <م> يمكن استخدامها للقيم النقدية ولكنها لمزيد من العمل مما يستحق وعادة الخبرة اللازمة غير متوفرة.

إذا كنت لا يمكن إصلاح قاعدة البيانات الأساسية يمكنك إصلاح جافا مثل هذا:

import java.text.DecimalFormat;

public class Temp {

    public static void main(String[] args) {
        double d = 13575.124999999;
        DecimalFormat df2 = new DecimalFormat("#.##");
        System.out.println( " 2dp: "+ Double.valueOf(df2.format(d)) );

        DecimalFormat df4 = new DecimalFormat("#.####");
        System.out.println( " 4dp: "+Double.valueOf(df4.format(d)) );
    }
}

وعلى الرغم من أنك لا يجب تخزين السعر باعتباره float في المقام الأول، يمكنك أن تنظر في تحويله إلى decimal(38, 4)، مثلا، أو money (علما بأن money لديه بعض القضايا منذ نتائج التعبيرات التي تنطوي عليها لا يكون حجمها تعديل حيوي)، وتعريض أنه في وجهة نظر حول السبيل للخروج من SQL Server:

SELECT Qty * CONVERT(decimal(38, 4), Price)

وهكذا، نظرا أنه لا يمكنك تغيير بنية قاعدة البيانات (والذي ربما يكون الخيار الأفضل، بالنظر إلى أن كنت تستخدم غير ثابت الدقة لتمثيل شيء ينبغي أن تكون ثابتة / دقيقة، كما فعل كثيرون آخرون بالفعل ناقش)، ونأمل يمكنك تغيير مكان الشفرة. على الجانب جافا، وأعتقد أن شيئا من هذا القبيلandy_boot الإجابة عليها العمل سوف. على الجانب SQL، كنت في الأساس قد تحتاج إلى تحويل قيمة غير دقيقة لأعلى مستوى من الدقة التي تحتاج إليها ومواصلة ألقى من هناك، في الأساس شيئا من هذا القبيل في قانون SQL:

declare @f  float,
        @n  numeric(20,4),
        @m  money;

select  @f = 13575.124999999998,
        @n = 13575.124999999998,
        @m = 13575.124999999998

select  @f, @n, @m
select  cast(@f as numeric(20,4)), cast(cast(@f as numeric(20,4)) as numeric(20,2))
select  cast(@f as money), cast(cast(@f as money) as numeric(20,2))

إذا كنت لا تستطيع تغيير قاعدة البيانات إلى نوع البيانات العشرية الثابتة، وهو ما كنت قد حاولت والتقريب من خلال اتخاذ اقتطاع ((س + 0.0055) * 10000) / 10000. ثم 1.124999 أن "جولة" ل1.13 وتعطي نتائج مماثلة. رياضيا هذا لا يمكن الاعتماد عليها، ولكن اعتقد انها ستعمل في قضيتك.

ونوع البيانات تعويم لا يمكن أن تمثل الكسور بدقة لأنه base2 بدلا من base10. (أنظر الرابط مريحة :) HTTP : //gregs-blog.com/2007/12/10/dot-net-decimal-type-vs-float-type/)

لحسابات مالية أو أي شيء يتطلب كسور أن تكون ممثلة بدقة، يجب استخدام نوع البيانات العشرى.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top