Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Округление double


Автор: firedrago 26.9.2005, 01:07
Всем привет,
посоветуйте как точнее всего реализовать округление double до сотых или тысячных....

я делал это так, но работает оно как-то не точно.....хотя по идее должно....

при раскладе Nbr.formatDouble(1.0049, 2) получается 1.00 а мне надо 1.01

Код

    public static String formatDouble(double number,int fractionDigits) {
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMANY );
        String res = "";
        nf.setMinimumFractionDigits(fractionDigits);
        nf.setMaximumFractionDigits(fractionDigits);
        res = nf.format(number);
            
        return res;
    }



Автор: Ch0bits 26.9.2005, 02:09
Потому что [0.000; 0.005] = 0.00 и (0.005; 0.01] = 0.01
1.0049 так и получается 1.00, по моему так и должно быть. smile

Автор: Macross 26.9.2005, 08:51
Если мне память не изменяет, то по правилам математики 1.0049 округляеться именно до 1.00. Округление идет по следующему знаку за нужным, в данном случае по третьему, а 4 округляеться до 0.

Автор: firedrago 26.9.2005, 11:02
да округление математически правильное, но если для бугалтерии, то спустя какое-то время будет потеря денег...

Автор: 3,14 26.9.2005, 11:59
firedrago, тогда отсылай в процедуру не number, а
Код

java.lang.Double.Trunc(number * 1000) / 1000

Автор: Ch0bits 26.9.2005, 12:12
Цитата(firedrago @ 26.9.2005, 11:02)
но если для бугалтерии, то спустя какое-то время будет потеря денег

Поэтому там считают с большей точностью и только в отчётах округляют до копеек.

Автор: Guest 26.9.2005, 13:53
Могу посоветовать следующее, как сделать проще не знаю smile

Код

public static String formatDouble(double number,int fractionDigits) {        
        Pattern p=Pattern.compile("\\d*\\.\\d{"+fractionDigits+"}(\\d*)");
        Matcher matcher=p.matcher(Double.toString(number));
        matcher.find();
        double t=Double.parseDouble(matcher.group(1));
        int ret=0;
        while(t>1){            
            t/=10;            
            t=Math.round(t);            
        }
        for(int i=0;i<fractionDigits;i++)t/=10;        
        
        NumberFormat nf = NumberFormat.getNumberInstance(Locale.GERMANY );    
        String res = "";    
        nf.setMinimumFractionDigits(fractionDigits);    
        nf.setMaximumFractionDigits(fractionDigits);    
        res = nf.format(number+t);    
             
        return res;    
}

Автор: firedrago 26.9.2005, 14:30
всем спасибо, я думаю для начала подойдет.
если еще какие-то идеи возникнут, буду благодарен.

Автор: carper 3.10.2005, 16:19
firedrago
Цитата

да округление математически правильное, но если для бугалтерии, то спустя какое-то время будет потеря денег...


Не знаю, полезно ли, но рекомендую почитать статьи об округлении в финансовых расчетах.
Там куча правил, очень мало, что имеющих с обычным округлением.

Вот, более-менее стандартные типы (признанные математиками), разумеется, это только то немногое, что я вспомнил из того, чего не знаю smile

1.35 = 1.4
1.35 = 1.3
1.39 = 1.3 (не округление, а просто "отбрасывание" "лишних" цифр)
1.35 = 1.4 но 1.45 = 1.4 (округление в нечетную сторону)
Встречал и варинат
1.351= 1.3, но 1.356 = 1.4

Тут все дело в том, что в финансах в обычную математику вмешивается целая куча параметров, таких как, например, необходимость использования бесконечных дробей (типа партия из 3 товаров за 10 рублей в принципе не может быть реализована строго по 10/3 руб. за штуку).
Необходимость расчета с максимальной точностью вплоть до заданного отчетного периода, после которого сумма округляется и далее цикл повторяется, потом идет перерасчет с налоговой на сальдо и сумма снова корректируется.
Целые статьи пишутся о том, как максимально сгладить эффект сальдо при финансовых расчетах.

Это я к чему, а к тому, что тут JAVA не причем, вы имеете дело с типичным бизнес-правилом и для него надо самому писать JAVA метод, реализующий бизнес-функцию.
Это желательно делать даже для стандартного округления, т.к. в дальнейшем при необходимости изменения бизнес-правила надо будет только изменить одну функцию.

Например, для простейшего варианта округления (внимание! работает неверно для большого кол-ва знаков после запятой, >7, кажется, что можно решить введением BigDecimal)
можно написать что-то вроде (разумеется, неоптимизированный вариант, я его только сейчас изобрел smile

Код


public class MyMath {

  public static void main(String[] args) {
    System.out.println(round_BusinessRule1_234(12.0049,2,1));
  }
  
  //shift - это сдвиг, в вашем примере - 1.
  public static   double  round_BusinessRule1_234( double myNum, int digits, int shift)
  {
    double   tmp = Math.pow(10.0, digits+shift);
    double result= java.lang.Math.round(myNum*tmp)/tmp;
    tmp = Math.pow(10.0, digits);
    return java.lang.Math.round(result*tmp)/tmp;
  }

}






Автор: Guest 4.10.2005, 19:52
В азбуке Refactoring: Improving the Design of Existing Code by Martin Fowler,
Фаулер пишет:
Цитата
These examples are there only to illustrate the refactorings, nothing more. In particular you'll notice that in the examples I use double to represent monetary values. I've done this only to make the examples simpler, as the representation is not important to the refactoring. I strongly advise against using doubles for money in commercial software. When I represent money I use the Quantity pattern [Fowler, AP].

[Fowler, AP] это видимо отсыл к M.Fowler Analysis Patterns: Reusable Object Models. Reading, Mass.: Addison-Wesley, 1997. A book of domain model patterns.
http://www.google.ru/search?q=quantity%20pattern

Автор: firedrago 4.10.2005, 23:13
я тут еще нашел интересную библиотеку на яве http://www.doc.ic.ac.uk/~oce/numerics/download.htm с исходниками, там много всего есть, правда еще времени не было с ней разобраться....
и в отношении округленяй вот что в ней...
Код

package com.financelabs.numerics;

import java.math.BigDecimal;

/**
 * This class provides a set of general purpose/shared utility methods that are 
 * used by classes in the numerics package. 
 */
public class Utilities 
{
    /**
     * This method rounds the supplied decimal to the specified number of
     * decimal places.
     * @param a_dDecimal The decimal to round
     * @param a_iScale The number of places to scale the decimal to
     * @return The decimal (a_dDecimal) scaled to the specified number of 
     *          places i.e. (a_iScale). 
     */
    public static double round(double a_dDecimal, int a_iScale)
    {
        double result;

        if (a_iScale>0)
        {
            boolean l_bNegate=(a_dDecimal<0);

            BigDecimal l_bgDecimal=new BigDecimal(a_dDecimal);
            BigDecimal l_bgScaledDecimal=l_bgDecimal.setScale(a_iScale,BigDecimal.ROUND_DOWN);
            BigDecimal l_bgResult = l_bgScaledDecimal.abs();

            if (l_bNegate) //if it was less than zero anywhere, then negate it again
                result = l_bgResult.negate().doubleValue();
            else
                result = l_bgResult.doubleValue();
        }
        else
            result=a_dDecimal;
        
        return result;
    }


    /**
     * This method calculates the payoff of an option
     * @param a_dStrikePrice The strike price of the option
     * @param a_dUnderlyingAssetPrice The price of the underlying asset
     * @param a_bCall Specifies whether the option is a call or put
     * @param a_iScale Specifies the number of places to scale the result to
     * @return The payoff of the option
     */
    public static double calculatePayoff(   double a_dStrikePrice,
                                            double a_dUnderlyingAssetPrice,
                                            boolean a_bCall,
                                            int a_iScale)
    {
        double result=0.0;
        if (a_bCall)
            result= Utilities.round(a_dUnderlyingAssetPrice-a_dStrikePrice,a_iScale);
        else
            result=Utilities.round(a_dStrikePrice-a_dUnderlyingAssetPrice,a_iScale);

        if (result<0.0) result=0.0;

        return result;
    }

    /**
     * This method calcuates the present-value of a bond from the given set of 
     * cashflows and cashflow yields.
     * @param a_dCashFlow The cashflow from the bond
     * @param a_dCashFlowYield The cashflow yield from the bond
     * @return The present-value (P) of the bond
     */
    public static double calculateBondPresentValue(double[] a_dCashflow,
                                        double a_dCashflowYield)
                                        throws IllegalArgumentException
    {            
        double result=0.0;

        int l_iArrayLength=a_dCashflow.length;        
        double l_dPlaceHolder;
        
        for (int i=0;i<l_iArrayLength;i++)
        {
            l_dPlaceHolder=Math.pow(1.0 + a_dCashflowYield,-(double)(i+1));
            l_dPlaceHolder*=a_dCashflow[i];
            result+=l_dPlaceHolder;
        }//end for loop

        if (l_iArrayLength>0)
            result/=(double)l_iArrayLength;
        
        return result;
    }

    /**
     * This method calcuates the dollar-duration of a bond from the given set of 
     * cashflows and cashflow yields.
     * @param a_dCashFlow The cashflow from the bond
     * @param a_dCashFlowYield The cashflow yield from the bond
     * @return The dollar-duration (K) of the bond
     * same length.
     */
    public static double calculateBondDollarDuration(double[] a_dCashflow,
                                                    double a_dCashflowYield)
    {
        double result=0.0;

        int l_iArrayLength=a_dCashflow.length;        
        double l_dPlaceHolder;
        
        for (int i=0;i<l_iArrayLength;i++)
        {
            l_dPlaceHolder=Math.pow(1.0 + a_dCashflowYield,-(double)(i+2));
            l_dPlaceHolder*=(double)(i+1) * a_dCashflow[i];
            result+=l_dPlaceHolder;
        }//end for loop

        if (l_iArrayLength>0)
            result/=(double)l_iArrayLength;        
        return result;
    }

    /**
     * This method calcuates the dollar-convexivity of a bond from the given set of 
     * cashflows and cashflow yields.
     * @param a_dCashFlow The cashflow from the bond
     * @param a_dCashFlowYield The cashflow yield from the bond
     * @return The dollar-convexivity (Q) of the bond
     */
    public static double calculateBondDollarConvexivity(double[] a_dCashflow,
                                                 double a_dCashflowYield)
                                                 throws IllegalArgumentException
    {
        double result=0.0;
        int l_iArrayLength=a_dCashflow.length;        
        double l_dPlaceHolder;
        
        for (int i=0;i<l_iArrayLength;i++)
        {
            l_dPlaceHolder=Math.pow(1.0 + a_dCashflowYield,-(double)(i+3));
            l_dPlaceHolder*=(double)(i+1)*(double)(i+2) * a_dCashflow[i];
            result+=l_dPlaceHolder;
        }//end for loop

        if (l_iArrayLength>0)
            result/=(double)l_iArrayLength;        
        return result;
    }            
}

Автор: hatsumeika 7.10.2005, 20:46
очень не рекомендую использовать для хранения денег или значений, используемых для вычисления денег double.

Ибо:
Код

System.out.println(0.33 / 11);

0.030000000000000002


лучше использовать BigDecimal.
при этом нужно использовать конструктор BigDecimal(String), а не BigDecimal(double). Т.е. нужно писать BigDecimal("0.1"), а не BigDecimal(0.1)

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)