四舍五入 Perl 中的浮点数 ..大型低效但有效的解决方案

Rounding Floating point numbers in Perl ..large inefficient but working solution

提问人:Augusto Ayala Ledesma 提问时间:7/5/2022 最后编辑:zdimAugusto Ayala Ledesma 更新时间:7/8/2022 访问量:116

问:

我不得不对MySQL查询的结果进行四舍五入。我没有找到以正确的方式进行舍入的好解决方案(在我看来,截断不是正确的方法)。

我编写了这段代码,将浮点数四舍五入,并以西班牙语-墨西哥方式格式化数字(使用逗号作为千、百万等)。(如 1425 -> 1,1425)

我知道它效率非常低,但它以正确的方式完成工作。如果您有任何建议,请告诉我以提高效率。

对不起,西班牙语变量和评论..我是墨西哥人!!;)

sub formatea_numero                 # recibe una cifra sin formato y le agrega comas por cada 3 digitos
{
    my($importe_orig,$dec_a_redondear) = @_;

    my @digitos_corregido;
    my $contador=0;
    my $signo;
    my $importex;
    my $decimal_con_ceros_al_inicio = 0;

    ### SIGNO ######################
    ### Obteniendo el signo si lo tiene
    if($importe_orig=~/([-|+])(.*)/)    # SI TIENE SIGNO $1 EL NUM ES $2
    {
        $signo = $1;
        $importex = substr $importe_orig, 1;
    }
    else                                # NO TIENE SIGNO EL NUM ES $importe_orig
    {
        $importex = $importe_orig;
    }
    
    ###  DECIMALES ################
    ### Si tiene decimales.. se obtiene la parte entera y la parte decimal
    if($importex=~/\./)     # Tiene decimales
    {   
        if ($dec_a_redondear ne "" && $dec_a_redondear == 0)    # Caso Especial de que son 0 decimales a redondear
        {
            $importex=~/(.*)\.(.)(.*)/;
            $parte_entera = $1;
            $primer_decimal = $2;
            $resto_decimal = $3;
            if ($primer_decimal >= 5)
            {$parte_entera = $parte_entera+1;}          
        }
        else
        {
        
                if($importex=~/(.*)\.(.+)/)     # Parte entera $1  Parte decimal $2
                {
                    $parte_entera = $1;
                    $parte_decimal = $2;

                    if ($parte_decimal=~/^(0+).*/ ) # Caso especial si el decimal inicia en 0  ejem 3.0015 1.0000003  1.04  etc
                    {
                        $decimal_con_ceros_al_inicio =1;
                        $parte_decimal = "1" . $parte_decimal;
                    }           
                }
                
            ########### REDONDEAR DECIMALES 
            
                if ($decimal_con_ceros_al_inicio)
                {   
                    $dec_a_redondear =  1 + $dec_a_redondear;
                    $num_decimales_original = 1+ length $parte_decimal;
                }
                else
                {   $num_decimales_original = length $parte_decimal;}



                if ($dec_a_redondear>=0 && $dec_a_redondear < $num_decimales_original)
                {
                    
                    $parte_decimal_1 = substr($parte_decimal,0,$dec_a_redondear);   # se obtienen los digitos hasta el numero de decimales que se quiere redondear
                    $siguiente_decimal = substr($parte_decimal,$dec_a_redondear,1); # se obtiene el primer dígito a descartar .. si es mayor que 5 se le agrega uno al anterior digito
                    
                    if($siguiente_decimal >=5)
                    {
                        $largo_inicial_parte_decimal_1 = length $parte_decimal_1;           
                        $parte_decimal_1 = $parte_decimal_1 +1;
                        $largo_final_parte_decimal_1 = length $parte_decimal_1;

                        if($largo_final_parte_decimal_1 <= $largo_inicial_parte_decimal_1)
                        {
                            $parte_decimal = $parte_decimal_1;
                        }
                        else
                        {
                            $parte_entera = $parte_entera + 1;
                            $parte_decimal = $parte_decimal_1 - 1;
                            $parte_decimal = 0;
                        }
                    }
                    else
                    {
                        $parte_decimal = $parte_decimal_1;
                    }
                    

                    
                } # cierra if ($dec_a_redondear>0 && $dec_a_redondear < $num_decimales_original)

                    if ($decimal_con_ceros_al_inicio)
                    {   
                        $parte_decimal=~/^1(.+)/;
                        $parte_decimal = $1;
                    }



                ########### TERMINA DECIMALES (redondeando) 
        }   # Cierra if ($dec_a_redondear ne "" && $dec_a_redondear == 0)   # Caso Especial de que son 0 decimales a redondear
        
    } # cierra  if($importex=~/\./)     # Tiene decimales

        ########### FORMATEANDO LOS MILES ###########

    if($importex=~/\./)     # Tiene decimales
    {       
        @digitos = split(//,$parte_entera);
    }
    else                    # No tiene decimales
    {
        @digitos = split(//,$importex);
    }

    @digitos= reverse(@digitos);    
    
    foreach $digito(@digitos)
    {   
        if ($contador ==3)
        {
            push (@digitos_corregido,",");
            push (@digitos_corregido,$digito);
            $contador=1;
        }
        else
        {
            push (@digitos_corregido,$digito);
            $contador++;
        }
    }
    
    @digitos_corregido = reverse(@digitos_corregido);

    $importe2 = join('',@digitos_corregido);
    ### Termina de procesar la parte entera 
    
    
    ### Se integra el signo, la parte entera formateada y la parte decimal
    if($importex=~/\./)
    {
        if ($dec_a_redondear ne "" && $dec_a_redondear == 0)
        {   $importe2 = $importe2;}
        else
        {   
            if ($parte_decimal >0)
            {$importe2 = $importe2 . "." .$parte_decimal;}
            else
            {$importe2 = $importe2;}
        }
    }

    if($importe_orig=~/([-|+])(.*)/)
    {
        $importe2 = $signo . $importe2 ;        
    }

    return $importe2;
} # cierra sub formatea_numero                  # recibe una cifra sin formato y le agrega comas por cada 3 digitos
Perl 浮点 格式 数字 舍入

评论

1赞 TLP 7/6/2022
你把所有这些代码都写成四舍五入?这似乎有些过分了。?也应该有很多数学模块。int($num + 0.5)
0赞 TLP 7/6/2022
另外,你忘了问一个问题。仅仅转储你的代码并不是在stackoverflow上提问的好方法。
1赞 jhnc 7/6/2022
如果您需要处理负面因素,@TLP稍微复杂一些。还建议不要使用 int 进行舍入。另请参阅建议在某些情况下实现自己的perldoc -f intperldoc -q round
1赞 jhnc 7/6/2022
codereview.stackexchange.com 可能是这个问题的更好地方
0赞 zdim 7/6/2022
那么,你希望它们如何四舍五入 -- 到给定的小数位数呢?(例如,将每个数字四舍五入到小数点后 2 位。还是有什么特殊的标准?(对不起,我的西班牙语太糟糕了,我无法完全了解您的代码正在做什么的细节。你知道内置吗?sprintf

答:

4赞 zdim 7/6/2022 #1

这似乎是很多代码来四舍五入一个数字。我不能完全理解西班牙语的所有细节,但如果你没有特殊标准,那么基础知识可能就足够了,使用 sprintf

$num = sprintf "%.2f", $num  if $num =~ /\./;

(这将增加一个整数,因此我将其设置为具有 .最好是一个合法的数字。.00.

使用四舍五入到偶数,根据IEEE754四舍五入到最接近的(整数)平数规则。(Windows 可能会有所不同,因为它不遵守 IEEE 规范,但 Strawberry Perl 确实四舍五入,感谢 ikegami 的注释。sprintf

还有一些库,如 Math::RoundMath::BigFloat。请仔细研究这个主题,特别是如果你的应用程序对细节很敏感(比如财务或科学问题),因为浮点的工作充满了棘手的问题。另请参阅关于四舍五入的 perlfaq4 (etc)

然后,您可以添加“千位分隔符”(根据需要使用),方法是将每个三元组数字替换为逗号+本身,从后面(在子字符串中直到小数点)。最好把它包装在潜艇里,

sub commify {
    local $_ = shift;  
    1 while s/^([-+]?[0-9]+)([0-9]{3})/$1,$2/;   
    return $_;
}

有关更多信息,请参见 perlfaq5

评论

0赞 zdim 7/6/2022
要将精度 (in ) 传递给它,$precsprintf "%.${prec}f", $num
0赞 ikegami 7/6/2022
回复“我认为 Windows 上不是这种情况”,Strawberry Perl 四舍五入甚至
0赞 ikegami 7/6/2022
回复 “sprintf ”%.${prec}f“, $num”,sprintf "%.*f", $prec, $num
0赞 Augusto Ayala Ledesma 7/7/2022
多谢。。@zdim我将检查Math::Round
0赞 Augusto Ayala Ledesma 7/7/2022
并感谢您的 commify 例行公事..这简化了我的很多工作。多谢