I had to round numbers that are a result of MySQL Querys. I didn't find a good solution for rounding in the correct way (truncating is not the correct way in my opinion).
I wrote this code that rounds float numbers and also formatted the numbers in the Spanish-Mexican way (using comma as a thousands, millions, etc.. (like 1425 -> 1,1425)
I know it is very inefficient, but it does the work in the right way. If you have any suggestions please let me know to make it more efficient.
Sorry for the Spanish variables and comments.. I'm Mexican!!! ;)
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
CodePudding user response:
That seems to be a lot of code to round a number. I can't quite follow all details in Spanish but if you have no special criteria then basics may suffice, with sprintf
$num = sprintf "%.2f", $num if $num =~ /\./;
(This would add .00
to an integer so I condition it on having .
. It'd better be a legit number.)
The sprintf
uses round half to even, per the IEEE754 round to nearest (integer) ties to even rule. (Windows may differ as it doesn't respect the IEEE spec but Strawberry Perl does round to even, thanks to ikegami for the note.)
There are also libraries, like Math::Round or Math::BigFloat. Please research this topic with care, in particular if your application is sensitive to details (like financial or scientific ones can be), since work with floating point is fraught with tricky points. See also perlfaq4 on rounding (etc)
Then you can add "thousands separator" (using ,
as desired), by replacing every triplet of digits by comma itself, from the back (in the substring up to the decimal point). Best wrap that in a sub
sub commify {
local $_ = shift;
1 while s/^([- ]?[0-9] )([0-9]{3})/$1,$2/;
return $_;
}
See perlfaq5 for more