The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

##----------------------------------------------------------------------------
## Module Generic - ~/lib/Module/Generic/Number.pm
## Version v2.2.0
## Copyright(c) 2024 DEGUEST Pte. Ltd.
## Author: Jacques Deguest <jack@deguest.jp>
## Created 2021/03/20
## Modified 2025/03/14
## All rights reserved
##
## This program is free software; you can redistribute it and/or modify it
## under the same terms as Perl itself.
##----------------------------------------------------------------------------
BEGIN
{
use v5.26.1;
use strict;
use warnings;
use vars qw( $SUPPORTED_LOCALES $DEFAULT $NUMBER_RE );
# use Nice::Try;
use POSIX qw( Inf NaN );
use Regexp::Common qw( number );
$NUMBER_RE = $RE{num}{real};
use Scalar::Util ();
use overload (
# I know there is the nomethod feature, but I need to provide return_object set to true or false
# And I do not necessarily want to catch all the operation.
'""' => sub { return( shift->{_number} ); },
'-' => sub { return( shift->compute( @_, { op => '-', return_object => 1 }) ); },
'+' => sub { return( shift->compute( @_, { op => '+', return_object => 1 }) ); },
'*' => sub { return( shift->compute( @_, { op => '*', return_object => 1 }) ); },
'/' => sub { return( shift->compute( @_, { op => '/', return_object => 1 }) ); },
'%' => sub { return( shift->compute( @_, { op => '%', return_object => 1 }) ); },
# Exponent
'**' => sub { return( shift->compute( @_, { op => '**', return_object => 1 }) ); },
# Bitwise AND
'&' => sub { return( shift->compute( @_, { op => '&', return_object => 1 }) ); },
# Bitwise OR
'|' => sub { return( shift->compute( @_, { op => '|', return_object => 1 }) ); },
# Bitwise XOR
'^' => sub { return( shift->compute( @_, { op => '^', return_object => 1 }) ); },
# Bitwise shift left
'<<' => sub { return( shift->compute( @_, { op => '<<', return_object => 1 }) ); },
# Bitwise shift right
'>>' => sub { return( shift->compute( @_, { op => '>>', return_object => 1 }) ); },
'x' => sub { return( shift->compute( @_, { op => 'x', return_object => 1, type => 'scalar' }) ); },
'+=' => sub { return( shift->compute( @_, { op => '+=', return_object => 1 }) ); },
'-=' => sub { return( shift->compute( @_, { op => '-=', return_object => 1 }) ); },
'*=' => sub { return( shift->compute( @_, { op => '*=', return_object => 1 }) ); },
'/=' => sub { return( shift->compute( @_, { op => '/=', return_object => 1 }) ); },
'%=' => sub { return( shift->compute( @_, { op => '%=', return_object => 1 }) ); },
'**=' => sub { return( shift->compute( @_, { op => '**=', return_object => 1 }) ); },
'<<=' => sub { return( shift->compute( @_, { op => '<<=', return_object => 1 }) ); },
'>>=' => sub { return( shift->compute( @_, { op => '>>=', return_object => 1 }) ); },
'x=' => sub { return( shift->compute( @_, { op => 'x=', return_object => 1 }) ); },
# '.=' => sub { return( shift->compute( @_, { op => '.=', return_object => 1 }) ); },
'.=' => sub
{
my( $self, $other, $swap ) = @_;
my $op = '.=';
no strict;
my $operation = $swap ? "${other} ${op} \$self->{_number}" : "\$self->{_number} ${op} ${other}";
my $res = eval( $operation );
warn( "Error with formula \"$operation\": $@" ) if( $@ && $self->_warnings_is_enabled );
return if( $@ );
# Concatenated something. If it still look like a number, we return it as an object
if( $res =~ /^$NUMBER_RE$/ )
{
return( $self->clone( $res ) );
}
# Otherwise we pass it to the scalar module
else
{
return( Module::Generic::Scalar->new( "$res" ) );
}
},
'<' => sub { return( shift->compute( @_, { op => '<', boolean => 1 }) ); },
'<=' => sub { return( shift->compute( @_, { op => '<=', boolean => 1 }) ); },
'>' => sub { return( shift->compute( @_, { op => '>', boolean => 1 }) ); },
'>=' => sub { return( shift->compute( @_, { op => '>=', boolean => 1 }) ); },
'<=>' => sub { return( shift->compute( @_, { op => '<=>', return_object => 0 }) ); },
'==' => sub { return( shift->compute( @_, { op => '==', boolean => 1 }) ); },
'!=' => sub { return( shift->compute( @_, { op => '!=', boolean => 1 }) ); },
'eq' => sub { return( shift->compute( @_, { op => 'eq', boolean => 1 }) ); },
'ne' => sub { return( shift->compute( @_, { op => 'ne', boolean => 1 }) ); },
'++' => sub
{
my( $self ) = @_;
return( ++$self->{_number} );
},
'--' => sub
{
my( $self ) = @_;
return( --$self->{_number} );
},
'fallback' => 1,
);
# Largest integer a 32-bit Perl can handle is based on the mantissa
# size of a double float, which is up to 53 bits. While we may be
# able to support larger values on 64-bit systems, some Perl integer
# operations on 64-bit integer systems still use the 53-bit-mantissa
# double floats. To be safe, we cap at 2**53; use Math::BigFloat
# instead for larger numbers.
use constant MAX_INT => 2**53;
our( $VERSION ) = 'v2.2.0';
};
# use strict;
no warnings 'redefine';
use utf8;
$SUPPORTED_LOCALES =
{
aa_DJ => [qw( aa_DJ.UTF-8 aa_DJ.ISO-8859-1 aa_DJ.ISO8859-1 )],
aa_ER => [qw( aa_ER.UTF-8 )],
aa_ET => [qw( aa_ET.UTF-8 )],
af_ZA => [qw( af_ZA.UTF-8 af_ZA.ISO-8859-1 af_ZA.ISO8859-1 )],
ak_GH => [qw( ak_GH.UTF-8 )],
am_ET => [qw( am_ET.UTF-8 )],
an_ES => [qw( an_ES.UTF-8 an_ES.ISO-8859-15 an_ES.ISO8859-15 )],
anp_IN => [qw( anp_IN.UTF-8 )],
ar_AE => [qw( ar_AE.UTF-8 ar_AE.ISO-8859-6 ar_AE.ISO8859-6 )],
ar_BH => [qw( ar_BH.UTF-8 ar_BH.ISO-8859-6 ar_BH.ISO8859-6 )],
ar_DZ => [qw( ar_DZ.UTF-8 ar_DZ.ISO-8859-6 ar_DZ.ISO8859-6 )],
ar_EG => [qw( ar_EG.UTF-8 ar_EG.ISO-8859-6 ar_EG.ISO8859-6 )],
ar_IN => [qw( ar_IN.UTF-8 )],
ar_IQ => [qw( ar_IQ.UTF-8 ar_IQ.ISO-8859-6 ar_IQ.ISO8859-6 )],
ar_JO => [qw( ar_JO.UTF-8 ar_JO.ISO-8859-6 ar_JO.ISO8859-6 )],
ar_KW => [qw( ar_KW.UTF-8 ar_KW.ISO-8859-6 ar_KW.ISO8859-6 )],
ar_LB => [qw( ar_LB.UTF-8 ar_LB.ISO-8859-6 ar_LB.ISO8859-6 )],
ar_LY => [qw( ar_LY.UTF-8 ar_LY.ISO-8859-6 ar_LY.ISO8859-6 )],
ar_MA => [qw( ar_MA.UTF-8 ar_MA.ISO-8859-6 ar_MA.ISO8859-6 )],
ar_OM => [qw( ar_OM.UTF-8 ar_OM.ISO-8859-6 ar_OM.ISO8859-6 )],
ar_QA => [qw( ar_QA.UTF-8 ar_QA.ISO-8859-6 ar_QA.ISO8859-6 )],
ar_SA => [qw( ar_SA.UTF-8 ar_SA.ISO-8859-6 ar_SA.ISO8859-6 )],
ar_SD => [qw( ar_SD.UTF-8 ar_SD.ISO-8859-6 ar_SD.ISO8859-6 )],
ar_SS => [qw( ar_SS.UTF-8 )],
ar_SY => [qw( ar_SY.UTF-8 ar_SY.ISO-8859-6 ar_SY.ISO8859-6 )],
ar_TN => [qw( ar_TN.UTF-8 ar_TN.ISO-8859-6 ar_TN.ISO8859-6 )],
ar_YE => [qw( ar_YE.UTF-8 ar_YE.ISO-8859-6 ar_YE.ISO8859-6 )],
as_IN => [qw( as_IN.UTF-8 )],
ast_ES => [qw( ast_ES.UTF-8 ast_ES.ISO-8859-15 ast_ES.ISO8859-15 )],
ayc_PE => [qw( ayc_PE.UTF-8 )],
az_AZ => [qw( az_AZ.UTF-8 )],
be_BY => [qw( be_BY.UTF-8 be_BY.CP1251 )],
bem_ZM => [qw( bem_ZM.UTF-8 )],
ber_DZ => [qw( ber_DZ.UTF-8 )],
ber_MA => [qw( ber_MA.UTF-8 )],
bg_BG => [qw( bg_BG.UTF-8 bg_BG.CP1251 )],
bhb_IN => [qw( bhb_IN.UTF-8 )],
bho_IN => [qw( bho_IN.UTF-8 )],
bn_BD => [qw( bn_BD.UTF-8 )],
bn_IN => [qw( bn_IN.UTF-8 )],
bo_CN => [qw( bo_CN.UTF-8 )],
bo_IN => [qw( bo_IN.UTF-8 )],
br_FR => [qw( br_FR.UTF-8 br_FR.ISO-8859-1 br_FR.ISO8859-1 br_FR.ISO-8859-15 br_FR.ISO8859-15 )],
brx_IN => [qw( brx_IN.UTF-8 )],
bs_BA => [qw( bs_BA.UTF-8 bs_BA.ISO-8859-2 bs_BA.ISO8859-2 )],
byn_ER => [qw( byn_ER.UTF-8 )],
ca_AD => [qw( ca_AD.UTF-8 ca_AD.ISO-8859-15 ca_AD.ISO8859-15 )],
ca_ES => [qw( ca_ES.UTF-8 ca_ES.ISO-8859-1 ca_ES.ISO8859-1 ca_ES.ISO-8859-15 ca_ES.ISO8859-15 )],
ca_FR => [qw( ca_FR.UTF-8 ca_FR.ISO-8859-15 ca_FR.ISO8859-15 )],
ca_IT => [qw( ca_IT.UTF-8 ca_IT.ISO-8859-15 ca_IT.ISO8859-15 )],
ce_RU => [qw( ce_RU.UTF-8 )],
ckb_IQ => [qw( ckb_IQ.UTF-8 )],
cmn_TW => [qw( cmn_TW.UTF-8 )],
crh_UA => [qw( crh_UA.UTF-8 )],
cs_CZ => [qw( cs_CZ.UTF-8 cs_CZ.ISO-8859-2 cs_CZ.ISO8859-2 )],
csb_PL => [qw( csb_PL.UTF-8 )],
cv_RU => [qw( cv_RU.UTF-8 )],
cy_GB => [qw( cy_GB.UTF-8 cy_GB.ISO-8859-14 cy_GB.ISO8859-14 )],
da_DK => [qw( da_DK.UTF-8 da_DK.ISO-8859-1 da_DK.ISO8859-1 )],
de_AT => [qw( de_AT.UTF-8 de_AT.ISO-8859-1 de_AT.ISO8859-1 de_AT.ISO-8859-15 de_AT.ISO8859-15 )],
de_BE => [qw( de_BE.UTF-8 de_BE.ISO-8859-1 de_BE.ISO8859-1 de_BE.ISO-8859-15 de_BE.ISO8859-15 )],
de_CH => [qw( de_CH.UTF-8 de_CH.ISO-8859-1 de_CH.ISO8859-1 )],
de_DE => [qw( de_DE.UTF-8 de_DE.ISO-8859-1 de_DE.ISO8859-1 de_DE.ISO-8859-15 de_DE.ISO8859-15 )],
de_LI => [qw( de_LI.UTF-8 )],
de_LU => [qw( de_LU.UTF-8 de_LU.ISO-8859-1 de_LU.ISO8859-1 de_LU.ISO-8859-15 de_LU.ISO8859-15 )],
doi_IN => [qw( doi_IN.UTF-8 )],
dv_MV => [qw( dv_MV.UTF-8 )],
dz_BT => [qw( dz_BT.UTF-8 )],
el_CY => [qw( el_CY.UTF-8 el_CY.ISO-8859-7 el_CY.ISO8859-7 )],
el_GR => [qw( el_GR.UTF-8 el_GR.ISO-8859-7 el_GR.ISO8859-7 )],
en_AG => [qw( en_AG.UTF-8 )],
en_AU => [qw( en_AU.UTF-8 en_AU.ISO-8859-1 en_AU.ISO8859-1 )],
en_BW => [qw( en_BW.UTF-8 en_BW.ISO-8859-1 en_BW.ISO8859-1 )],
en_CA => [qw( en_CA.UTF-8 en_CA.ISO-8859-1 en_CA.ISO8859-1 )],
en_DK => [qw( en_DK.UTF-8 en_DK.ISO-8859-15 en_DK.ISO8859-15 )],
en_GB => [qw( en_GB.UTF-8 en_GB.ISO-8859-1 en_GB.ISO8859-1 en_GB.ISO-8859-15 en_GB.ISO8859-15 )],
en_HK => [qw( en_HK.UTF-8 en_HK.ISO-8859-1 en_HK.ISO8859-1 )],
en_IE => [qw( en_IE.UTF-8 en_IE.ISO-8859-1 en_IE.ISO8859-1 en_IE.ISO-8859-15 en_IE.ISO8859-15 )],
en_IN => [qw( en_IN.UTF-8 )],
en_NG => [qw( en_NG.UTF-8 )],
en_NZ => [qw( en_NZ.UTF-8 en_NZ.ISO-8859-1 en_NZ.ISO8859-1 )],
en_PH => [qw( en_PH.UTF-8 en_PH.ISO-8859-1 en_PH.ISO8859-1 )],
en_SG => [qw( en_SG.UTF-8 en_SG.ISO-8859-1 en_SG.ISO8859-1 )],
en_US => [qw( en_US.UTF-8 en_US.ISO-8859-1 en_US.ISO8859-1 en_US.ISO-8859-15 en_US.ISO8859-15 )],
en_ZA => [qw( en_ZA.UTF-8 en_ZA.ISO-8859-1 en_ZA.ISO8859-1 )],
en_ZM => [qw( en_ZM.UTF-8 )],
en_ZW => [qw( en_ZW.UTF-8 en_ZW.ISO-8859-1 en_ZW.ISO8859-1 )],
eo => [qw( eo.UTF-8 eo.ISO-8859-3 eo.ISO8859-3 )],
eo_US => [qw( eo_US.UTF-8 )],
es_AR => [qw( es_AR.UTF-8 es_AR.ISO-8859-1 es_AR.ISO8859-1 )],
es_BO => [qw( es_BO.UTF-8 es_BO.ISO-8859-1 es_BO.ISO8859-1 )],
es_CL => [qw( es_CL.UTF-8 es_CL.ISO-8859-1 es_CL.ISO8859-1 )],
es_CO => [qw( es_CO.UTF-8 es_CO.ISO-8859-1 es_CO.ISO8859-1 )],
es_CR => [qw( es_CR.UTF-8 es_CR.ISO-8859-1 es_CR.ISO8859-1 )],
es_CU => [qw( es_CU.UTF-8 )],
es_DO => [qw( es_DO.UTF-8 es_DO.ISO-8859-1 es_DO.ISO8859-1 )],
es_EC => [qw( es_EC.UTF-8 es_EC.ISO-8859-1 es_EC.ISO8859-1 )],
es_ES => [qw( es_ES.UTF-8 es_ES.ISO-8859-1 es_ES.ISO8859-1 es_ES.ISO-8859-15 es_ES.ISO8859-15 )],
es_GT => [qw( es_GT.UTF-8 es_GT.ISO-8859-1 es_GT.ISO8859-1 )],
es_HN => [qw( es_HN.UTF-8 es_HN.ISO-8859-1 es_HN.ISO8859-1 )],
es_MX => [qw( es_MX.UTF-8 es_MX.ISO-8859-1 es_MX.ISO8859-1 )],
es_NI => [qw( es_NI.UTF-8 es_NI.ISO-8859-1 es_NI.ISO8859-1 )],
es_PA => [qw( es_PA.UTF-8 es_PA.ISO-8859-1 es_PA.ISO8859-1 )],
es_PE => [qw( es_PE.UTF-8 es_PE.ISO-8859-1 es_PE.ISO8859-1 )],
es_PR => [qw( es_PR.UTF-8 es_PR.ISO-8859-1 es_PR.ISO8859-1 )],
es_PY => [qw( es_PY.UTF-8 es_PY.ISO-8859-1 es_PY.ISO8859-1 )],
es_SV => [qw( es_SV.UTF-8 es_SV.ISO-8859-1 es_SV.ISO8859-1 )],
es_US => [qw( es_US.UTF-8 es_US.ISO-8859-1 es_US.ISO8859-1 )],
es_UY => [qw( es_UY.UTF-8 es_UY.ISO-8859-1 es_UY.ISO8859-1 )],
es_VE => [qw( es_VE.UTF-8 es_VE.ISO-8859-1 es_VE.ISO8859-1 )],
et_EE => [qw( et_EE.UTF-8 et_EE.ISO-8859-1 et_EE.ISO8859-1 et_EE.ISO-8859-15 et_EE.ISO8859-15 )],
eu_ES => [qw( eu_ES.UTF-8 eu_ES.ISO-8859-1 eu_ES.ISO8859-1 eu_ES.ISO-8859-15 eu_ES.ISO8859-15 )],
eu_FR => [qw( eu_FR.UTF-8 eu_FR.ISO-8859-1 eu_FR.ISO8859-1 eu_FR.ISO-8859-15 eu_FR.ISO8859-15 )],
fa_IR => [qw( fa_IR.UTF-8 )],
ff_SN => [qw( ff_SN.UTF-8 )],
fi_FI => [qw( fi_FI.UTF-8 fi_FI.ISO-8859-1 fi_FI.ISO8859-1 fi_FI.ISO-8859-15 fi_FI.ISO8859-15 )],
fil_PH => [qw( fil_PH.UTF-8 )],
fo_FO => [qw( fo_FO.UTF-8 fo_FO.ISO-8859-1 fo_FO.ISO8859-1 )],
fr_BE => [qw( fr_BE.UTF-8 fr_BE.ISO-8859-1 fr_BE.ISO8859-1 fr_BE.ISO-8859-15 fr_BE.ISO8859-15 )],
fr_CA => [qw( fr_CA.UTF-8 fr_CA.ISO-8859-1 fr_CA.ISO8859-1 )],
fr_CH => [qw( fr_CH.UTF-8 fr_CH.ISO-8859-1 fr_CH.ISO8859-1 )],
fr_FR => [qw( fr_FR.UTF-8 fr_FR.ISO-8859-1 fr_FR.ISO8859-1 fr_FR.ISO-8859-15 fr_FR.ISO8859-15 )],
fr_LU => [qw( fr_LU.UTF-8 fr_LU.ISO-8859-1 fr_LU.ISO8859-1 fr_LU.ISO-8859-15 fr_LU.ISO8859-15 )],
fur_IT => [qw( fur_IT.UTF-8 )],
fy_DE => [qw( fy_DE.UTF-8 )],
fy_NL => [qw( fy_NL.UTF-8 )],
ga_IE => [qw( ga_IE.UTF-8 ga_IE.ISO-8859-1 ga_IE.ISO8859-1 ga_IE.ISO-8859-15 ga_IE.ISO8859-15 )],
gd_GB => [qw( gd_GB.UTF-8 gd_GB.ISO-8859-15 gd_GB.ISO8859-15 )],
gez_ER => [qw( gez_ER.UTF-8 )],
gez_ET => [qw( gez_ET.UTF-8 )],
gl_ES => [qw( gl_ES.UTF-8 gl_ES.ISO-8859-1 gl_ES.ISO8859-1 gl_ES.ISO-8859-15 gl_ES.ISO8859-15 )],
gu_IN => [qw( gu_IN.UTF-8 )],
gv_GB => [qw( gv_GB.UTF-8 gv_GB.ISO-8859-1 gv_GB.ISO8859-1 )],
ha_NG => [qw( ha_NG.UTF-8 )],
hak_TW => [qw( hak_TW.UTF-8 )],
he_IL => [qw( he_IL.UTF-8 he_IL.ISO-8859-8 he_IL.ISO8859-8 )],
hi_IN => [qw( hi_IN.UTF-8 )],
hne_IN => [qw( hne_IN.UTF-8 )],
hr_HR => [qw( hr_HR.UTF-8 hr_HR.ISO-8859-2 hr_HR.ISO8859-2 )],
hsb_DE => [qw( hsb_DE.UTF-8 hsb_DE.ISO-8859-2 hsb_DE.ISO8859-2 )],
ht_HT => [qw( ht_HT.UTF-8 )],
hu_HU => [qw( hu_HU.UTF-8 hu_HU.ISO-8859-2 hu_HU.ISO8859-2 )],
hy_AM => [qw( hy_AM.UTF-8 hy_AM.ARMSCII-8 hy_AM.ARMSCII8 )],
ia_FR => [qw( ia_FR.UTF-8 )],
id_ID => [qw( id_ID.UTF-8 id_ID.ISO-8859-1 id_ID.ISO8859-1 )],
ig_NG => [qw( ig_NG.UTF-8 )],
ik_CA => [qw( ik_CA.UTF-8 )],
is_IS => [qw( is_IS.UTF-8 is_IS.ISO-8859-1 is_IS.ISO8859-1 )],
it_CH => [qw( it_CH.UTF-8 it_CH.ISO-8859-1 it_CH.ISO8859-1 )],
it_IT => [qw( it_IT.UTF-8 it_IT.ISO-8859-1 it_IT.ISO8859-1 it_IT.ISO-8859-15 it_IT.ISO8859-15 )],
iu_CA => [qw( iu_CA.UTF-8 )],
iw_IL => [qw( iw_IL.UTF-8 iw_IL.ISO-8859-8 iw_IL.ISO8859-8 )],
ja_JP => [qw( ja_JP.UTF-8 ja_JP.EUC-JP ja_JP.EUCJP )],
ka_GE => [qw( ka_GE.UTF-8 ka_GE.GEORGIAN-PS ka_GE.GEORGIANPS )],
kk_KZ => [qw( kk_KZ.UTF-8 kk_KZ.PT154 kk_KZ.RK1048 )],
kl_GL => [qw( kl_GL.UTF-8 kl_GL.ISO-8859-1 kl_GL.ISO8859-1 )],
km_KH => [qw( km_KH.UTF-8 )],
kn_IN => [qw( kn_IN.UTF-8 )],
ko_KR => [qw( ko_KR.UTF-8 ko_KR.EUC-KR ko_KR.EUCKR )],
kok_IN => [qw( kok_IN.UTF-8 )],
ks_IN => [qw( ks_IN.UTF-8 )],
ku_TR => [qw( ku_TR.UTF-8 ku_TR.ISO-8859-9 ku_TR.ISO8859-9 )],
kw_GB => [qw( kw_GB.UTF-8 kw_GB.ISO-8859-1 kw_GB.ISO8859-1 )],
ky_KG => [qw( ky_KG.UTF-8 )],
lb_LU => [qw( lb_LU.UTF-8 )],
lg_UG => [qw( lg_UG.UTF-8 lg_UG.ISO-8859-10 lg_UG.ISO8859-10 )],
li_BE => [qw( li_BE.UTF-8 )],
li_NL => [qw( li_NL.UTF-8 )],
lij_IT => [qw( lij_IT.UTF-8 )],
ln_CD => [qw( ln_CD.UTF-8 )],
lo_LA => [qw( lo_LA.UTF-8 )],
lt_LT => [qw( lt_LT.UTF-8 lt_LT.ISO-8859-13 lt_LT.ISO8859-13 )],
lv_LV => [qw( lv_LV.UTF-8 lv_LV.ISO-8859-13 lv_LV.ISO8859-13 )],
lzh_TW => [qw( lzh_TW.UTF-8 )],
mag_IN => [qw( mag_IN.UTF-8 )],
mai_IN => [qw( mai_IN.UTF-8 )],
mg_MG => [qw( mg_MG.UTF-8 mg_MG.ISO-8859-15 mg_MG.ISO8859-15 )],
mhr_RU => [qw( mhr_RU.UTF-8 )],
mi_NZ => [qw( mi_NZ.UTF-8 mi_NZ.ISO-8859-13 mi_NZ.ISO8859-13 )],
mk_MK => [qw( mk_MK.UTF-8 mk_MK.ISO-8859-5 mk_MK.ISO8859-5 )],
ml_IN => [qw( ml_IN.UTF-8 )],
mn_MN => [qw( mn_MN.UTF-8 )],
mni_IN => [qw( mni_IN.UTF-8 )],
mr_IN => [qw( mr_IN.UTF-8 )],
ms_MY => [qw( ms_MY.UTF-8 ms_MY.ISO-8859-1 ms_MY.ISO8859-1 )],
mt_MT => [qw( mt_MT.UTF-8 mt_MT.ISO-8859-3 mt_MT.ISO8859-3 )],
my_MM => [qw( my_MM.UTF-8 )],
nan_TW => [qw( nan_TW.UTF-8 )],
nb_NO => [qw( nb_NO.UTF-8 nb_NO.ISO-8859-1 nb_NO.ISO8859-1 )],
nds_DE => [qw( nds_DE.UTF-8 )],
nds_NL => [qw( nds_NL.UTF-8 )],
ne_NP => [qw( ne_NP.UTF-8 )],
nhn_MX => [qw( nhn_MX.UTF-8 )],
niu_NU => [qw( niu_NU.UTF-8 )],
niu_NZ => [qw( niu_NZ.UTF-8 )],
nl_AW => [qw( nl_AW.UTF-8 )],
nl_BE => [qw( nl_BE.UTF-8 nl_BE.ISO-8859-1 nl_BE.ISO8859-1 nl_BE.ISO-8859-15 nl_BE.ISO8859-15 )],
nl_NL => [qw( nl_NL.UTF-8 nl_NL.ISO-8859-1 nl_NL.ISO8859-1 nl_NL.ISO-8859-15 nl_NL.ISO8859-15 )],
nn_NO => [qw( nn_NO.UTF-8 nn_NO.ISO-8859-1 nn_NO.ISO8859-1 )],
nr_ZA => [qw( nr_ZA.UTF-8 )],
nso_ZA => [qw( nso_ZA.UTF-8 )],
oc_FR => [qw( oc_FR.UTF-8 oc_FR.ISO-8859-1 oc_FR.ISO8859-1 )],
om_ET => [qw( om_ET.UTF-8 )],
om_KE => [qw( om_KE.UTF-8 om_KE.ISO-8859-1 om_KE.ISO8859-1 )],
or_IN => [qw( or_IN.UTF-8 )],
os_RU => [qw( os_RU.UTF-8 )],
pa_IN => [qw( pa_IN.UTF-8 )],
pa_PK => [qw( pa_PK.UTF-8 )],
pap_AN => [qw( pap_AN.UTF-8 )],
pap_AW => [qw( pap_AW.UTF-8 )],
pap_CW => [qw( pap_CW.UTF-8 )],
pl_PL => [qw( pl_PL.UTF-8 pl_PL.ISO-8859-2 pl_PL.ISO8859-2 )],
ps_AF => [qw( ps_AF.UTF-8 )],
pt_BR => [qw( pt_BR.UTF-8 pt_BR.ISO-8859-1 pt_BR.ISO8859-1 )],
pt_PT => [qw( pt_PT.UTF-8 pt_PT.ISO-8859-1 pt_PT.ISO8859-1 pt_PT.ISO-8859-15 pt_PT.ISO8859-15 )],
quz_PE => [qw( quz_PE.UTF-8 )],
raj_IN => [qw( raj_IN.UTF-8 )],
ro_RO => [qw( ro_RO.UTF-8 ro_RO.ISO-8859-2 ro_RO.ISO8859-2 )],
ru_RU => [qw( ru_RU.UTF-8 ru_RU.KOI8-R ru_RU.KOI8R ru_RU.ISO-8859-5 ru_RU.ISO8859-5 ru_RU.CP1251 )],
ru_UA => [qw( ru_UA.UTF-8 ru_UA.KOI8-U ru_UA.KOI8U )],
rw_RW => [qw( rw_RW.UTF-8 )],
sa_IN => [qw( sa_IN.UTF-8 )],
sat_IN => [qw( sat_IN.UTF-8 )],
sc_IT => [qw( sc_IT.UTF-8 )],
sd_IN => [qw( sd_IN.UTF-8 )],
sd_PK => [qw( sd_PK.UTF-8 )],
se_NO => [qw( se_NO.UTF-8 )],
shs_CA => [qw( shs_CA.UTF-8 )],
si_LK => [qw( si_LK.UTF-8 )],
sid_ET => [qw( sid_ET.UTF-8 )],
sk_SK => [qw( sk_SK.UTF-8 sk_SK.ISO-8859-2 sk_SK.ISO8859-2 )],
sl_SI => [qw( sl_SI.UTF-8 sl_SI.ISO-8859-2 sl_SI.ISO8859-2 )],
so_DJ => [qw( so_DJ.UTF-8 so_DJ.ISO-8859-1 so_DJ.ISO8859-1 )],
so_ET => [qw( so_ET.UTF-8 )],
so_KE => [qw( so_KE.UTF-8 so_KE.ISO-8859-1 so_KE.ISO8859-1 )],
so_SO => [qw( so_SO.UTF-8 so_SO.ISO-8859-1 so_SO.ISO8859-1 )],
sq_AL => [qw( sq_AL.UTF-8 sq_AL.ISO-8859-1 sq_AL.ISO8859-1 )],
sq_MK => [qw( sq_MK.UTF-8 )],
sr_ME => [qw( sr_ME.UTF-8 )],
sr_RS => [qw( sr_RS.UTF-8 )],
ss_ZA => [qw( ss_ZA.UTF-8 )],
st_ZA => [qw( st_ZA.UTF-8 st_ZA.ISO-8859-1 st_ZA.ISO8859-1 )],
sv_FI => [qw( sv_FI.UTF-8 sv_FI.ISO-8859-1 sv_FI.ISO8859-1 sv_FI.ISO-8859-15 sv_FI.ISO8859-15 )],
sv_SE => [qw( sv_SE.UTF-8 sv_SE.ISO-8859-1 sv_SE.ISO8859-1 sv_SE.ISO-8859-15 sv_SE.ISO8859-15 )],
sw_KE => [qw( sw_KE.UTF-8 )],
sw_TZ => [qw( sw_TZ.UTF-8 )],
szl_PL => [qw( szl_PL.UTF-8 )],
ta_IN => [qw( ta_IN.UTF-8 )],
ta_LK => [qw( ta_LK.UTF-8 )],
tcy_IN => [qw( tcy_IN.UTF-8 )],
te_IN => [qw( te_IN.UTF-8 )],
tg_TJ => [qw( tg_TJ.UTF-8 tg_TJ.KOI8-T tg_TJ.KOI8T )],
th_TH => [qw( th_TH.UTF-8 th_TH.TIS-620 th_TH.TIS620 )],
the_NP => [qw( the_NP.UTF-8 )],
ti_ER => [qw( ti_ER.UTF-8 )],
ti_ET => [qw( ti_ET.UTF-8 )],
tig_ER => [qw( tig_ER.UTF-8 )],
tk_TM => [qw( tk_TM.UTF-8 )],
tl_PH => [qw( tl_PH.UTF-8 tl_PH.ISO-8859-1 tl_PH.ISO8859-1 )],
tn_ZA => [qw( tn_ZA.UTF-8 )],
tr_CY => [qw( tr_CY.UTF-8 tr_CY.ISO-8859-9 tr_CY.ISO8859-9 )],
tr_TR => [qw( tr_TR.UTF-8 tr_TR.ISO-8859-9 tr_TR.ISO8859-9 )],
ts_ZA => [qw( ts_ZA.UTF-8 )],
tt_RU => [qw( tt_RU.UTF-8 )],
ug_CN => [qw( ug_CN.UTF-8 )],
uk_UA => [qw( uk_UA.UTF-8 uk_UA.KOI8-U uk_UA.KOI8U )],
unm_US => [qw( unm_US.UTF-8 )],
ur_IN => [qw( ur_IN.UTF-8 )],
ur_PK => [qw( ur_PK.UTF-8 )],
uz_UZ => [qw( uz_UZ.UTF-8 uz_UZ.ISO-8859-1 uz_UZ.ISO8859-1 )],
ve_ZA => [qw( ve_ZA.UTF-8 )],
vi_VN => [qw( vi_VN.UTF-8 )],
wa_BE => [qw( wa_BE.UTF-8 wa_BE.ISO-8859-1 wa_BE.ISO8859-1 wa_BE.ISO-8859-15 wa_BE.ISO8859-15 )],
wae_CH => [qw( wae_CH.UTF-8 )],
wal_ET => [qw( wal_ET.UTF-8 )],
wo_SN => [qw( wo_SN.UTF-8 )],
xh_ZA => [qw( xh_ZA.UTF-8 xh_ZA.ISO-8859-1 xh_ZA.ISO8859-1 )],
yi_US => [qw( yi_US.UTF-8 yi_US.CP1255 )],
yo_NG => [qw( yo_NG.UTF-8 )],
yue_HK => [qw( yue_HK.UTF-8 )],
zh_CN => [qw( zh_CN.UTF-8 zh_CN.GB18030 zh_CN.GBK zh_CN.GB2312 )],
zh_HK => [qw( zh_HK.UTF-8 zh_HK.BIG5-HKSCS zh_HK.BIG5HKSCS )],
zh_SG => [qw( zh_SG.UTF-8 zh_SG.GBK zh_SG.GB2312 )],
zh_TW => [qw( zh_TW.UTF-8 zh_TW.EUC-TW zh_TW.EUCTW zh_TW.BIG5 )],
zu_ZA => [qw( zu_ZA.UTF-8 zu_ZA.ISO-8859-1 zu_ZA.ISO8859-1 )],
};
$DEFAULT =
{
# The local currency symbol.
currency_symbol => '€',
# The decimal point character, except for currency values, cannot be an empty string
decimal_point => '.',
# The number of digits after the decimal point in the local style for currency values.
frac_digits => 2,
# The sizes of the groups of digits, except for currency values. unpack( "C*", $grouping ) will give the number
grouping => (CORE::chr(3) x 2),
# The standardized international currency symbol.
int_curr_symbol => '€',
# The number of digits after the decimal point in an international-style currency value.
int_frac_digits => 2,
# Same as n_cs_precedes, but for internationally formatted monetary quantities.
int_n_cs_precedes => '',
# Same as n_sep_by_space, but for internationally formatted monetary quantities.
int_n_sep_by_space => '',
# Same as n_sign_posn, but for internationally formatted monetary quantities.
int_n_sign_posn => 1,
# Same as p_cs_precedes, but for internationally formatted monetary quantities.
int_p_cs_precedes => 1,
# Same as p_sep_by_space, but for internationally formatted monetary quantities.
int_p_sep_by_space => 0,
# Same as p_sign_posn, but for internationally formatted monetary quantities.
int_p_sign_posn => 1,
# The decimal point character for currency values.
mon_decimal_point => '.',
# Like grouping but for currency values.
mon_grouping => (CORE::chr(3) x 2),
# The separator for digit groups in currency values.
mon_thousands_sep => ',',
# Like p_cs_precedes but for negative values.
n_cs_precedes => 1,
# Like p_sep_by_space but for negative values.
n_sep_by_space => 0,
# Like p_sign_posn but for negative currency values.
n_sign_posn => 1,
# The character used to denote negative currency values, usually a minus sign.
negative_sign => '-',
# 1 if the currency symbol precedes the currency value for nonnegative values, 0 if it follows.
p_cs_precedes => 1,
# 1 if a space is inserted between the currency symbol and the currency value for nonnegative values, 0 otherwise.
p_sep_by_space => 0,
# The location of the positive_sign with respect to a nonnegative quantity and the currency_symbol, coded as follows:
# 0 Parentheses around the entire string.
# 1 Before the string.
# 2 After the string.
# 3 Just before currency_symbol.
# 4 Just after currency_symbol.
p_sign_posn => 1,
# The character used to denote nonnegative currency values, usually the empty string.
positive_sign => '',
# The separator between groups of digits before the decimal point, except for currency values
thousands_sep => ',',
};
my $map =
{
decimal => [qw( decimal_point mon_decimal_point )],
grouping => [qw( grouping mon_grouping )],
position_neg => [qw( n_sign_posn int_n_sign_posn )],
position_pos => [qw( n_sign_posn int_p_sign_posn )],
precede => [qw( p_cs_precedes int_p_cs_precedes )],
precede_neg => [qw( n_cs_precedes int_n_cs_precedes )],
precision => [qw( frac_digits int_frac_digits )],
sign_neg => [qw( negative_sign )],
sign_pos => [qw( positive_sign )],
space_pos => [qw( p_sep_by_space int_p_sep_by_space )],
space_neg => [qw( n_sep_by_space int_n_sep_by_space )],
symbol => [qw( currency_symbol int_curr_symbol )],
thousand => [qw( thousands_sep mon_thousands_sep )],
};
# This serves 2 purposes:
# 1) to silence warnings issued from Number::Format when it uses an empty string when evaluating a number, e.g. '' == 1
# 2) to ensure that blank numerical values are not interpreted to anything else than equivalent of empty
# For example, an empty frac_digits will default to 2 in Number::Format even if the user does not want any. Of course, said user could also have set it to 0
# So here we use this hash reference of numeric properties to ensure the option parameters are set to a numeric value (0) when they are empty.
my $numerics =
{
grouping => 0,
frac_digits => 0,
int_frac_digits => 0,
int_n_cs_precedes => 0,
int_p_cs_precedes => 0,
int_n_sep_by_space => 0,
int_p_sep_by_space => 0,
int_n_sign_posn => 1,
int_p_sign_posn => 1,
mon_grouping => 0,
n_cs_precedes => 0,
n_sep_by_space => 0,
n_sign_posn => 1,
p_cs_precedes => 0,
p_sep_by_space => 0,
# Position of positive sign. 1 = before (0 = parentheses)
p_sign_posn => 1,
};
sub init
{
my $self = shift( @_ );
return( $self->error( "No number was provided." ) ) if( !scalar( @_ ) );
my $num = shift( @_ );
return( $self->error( "Number provided is undefined" ) ) if( !defined( $num ) );
# Trigger overloading to string operation
$num = "$num";
return( $self->error( "Number value provided is empty" ) ) if( !CORE::length( $num ) );
return( Module::Generic::Infinity->new( $num ) ) if( POSIX::isinf( $num ) );
return( Module::Generic::Nan->new( $num ) ) if( POSIX::isnan( $num ) );
use utf8;
my @k = keys( %$map );
@$self{ @k } = ( '' x scalar( @k ) );
$self->{lang} = '';
$self->{default} = $DEFAULT;
$self->{decimal_fill} = 0;
$self->{encoding} = 'utf-8';
$self->{neg_format} = '-x';
$self->{kilo_suffix} = 'K';
$self->{mega_suffix} = 'M';
$self->{giga_suffix} = 'G';
$self->{kibi_suffix} = 'KiB';
$self->{mebi_suffix} = 'MiB';
$self->{gibi_suffix} = 'GiB';
$self->{_init_strict_use_sub} = 1;
$self->SUPER::init( @_ );
$self->{_original} = $num;
my $default = $self->default;
my $curr_locale = POSIX::setlocale( &POSIX::LC_ALL );
# perllocale: "If no second argument is provided and the category is LC_ALL, the result is implementation-dependent. It may be a string of concatenated locale names (separator also implementation-dependent) or a single locale name."
# e.g.: 'LC_NUMERIC=en_GB.UTF-8;LC_CTYPE=de_AT.utf8;LC_COLLATE=en_GB.UTF-8;LC_TIME=en_GB.UTF-8;LC_MESSAGES=en_GB.UTF-8;LC_MONETARY=en_GB.UTF-8;LC_ADDRESS=en_GB.UTF-8;LC_IDENTIFICATION=en_GB.UTF-8;LC_MEASUREMENT=en_GB.UTF-8;LC_PAPER=en_GB.UTF-8;LC_TELEPHONE=en_GB.UTF-8;'
if( defined( $curr_locale ) &&
CORE::length( $curr_locale ) &&
CORE::index( $curr_locale, ';' ) != -1 )
{
my @parts = CORE::split( /;/, $curr_locale );
my $elems = {};
for( @parts )
{
my( $n, $v ) = split( /=/, $_, 2 );
$elems->{ $n } = $v;
}
$curr_locale = $elems->{LC_NUMERIC} || $elems->{LC_MESSAGES} || $elems->{LC_MONETARY};
}
if( $self->{lang} )
{
# try-catch
local $@;
my $try_locale = sub
{
my $loc;
# The user provided only a language code such as fr_FR. We try it, and also other known combination like fr_FR.UTF-8 and fr_FR.ISO-8859-1, fr_FR.ISO8859-1
# Try several possibilities
if( index( $_[0], '.' ) == -1 )
{
$loc = POSIX::setlocale( &POSIX::LC_ALL, $_[0] );
$_[0] =~ s/^(?<locale>[a-z]{2,3})_(?<country>[a-z]{2})$/$+{locale}_\U$+{country}\E/;
if( !$loc && CORE::exists( $SUPPORTED_LOCALES->{ $_[0] } ) )
{
foreach my $supported ( @{$SUPPORTED_LOCALES->{ $_[0] }} )
{
if( ( $loc = POSIX::setlocale( &POSIX::LC_ALL, $supported ) ) )
{
$_[0] = $supported;
last;
}
}
}
}
# We got something like fr_FR.ISO-8859
# The user is specific, so we try as is
else
{
$loc = POSIX::setlocale( &POSIX::LC_ALL, $_[0] );
}
return( $loc );
};
if( my $loc = eval{ $try_locale->( $self->{lang} ) } )
{
my $lconv = POSIX::localeconv();
# Encode and I18N::Langinfo are both core modules since before perl 5.26.1, which is our minimum requirement
$self->_load_class( 'Encode' ) || return( $self->pass_error );
$self->_load_class( 'I18N::Langinfo' ) || return( $self->pass_error );
my $encoding = eval
{
Encode::resolve_alias( I18N::Langinfo::langinfo( I18N::Langinfo::CODESET() ) );
} || 'utf-8';
if( $@ )
{
warn( "Error trying to resolve alias for POSIX::localeconv codeset: $@" ) if( $self->_is_warnings_enabled );
}
$self->encoding( $encoding );
# Set back the LC_ALL to what it was, because we do not want to disturb the user environment
POSIX::setlocale( &POSIX::LC_ALL, $curr_locale );
if( $lconv && scalar( keys( %$lconv ) ) )
{
my @grouping = unpack("C*", $lconv->{grouping});
$lconv->{grouping} = $grouping[0];
@grouping = unpack("C*", $lconv->{mon_grouping});
$lconv->{mon_grouping} = $grouping[0];
$default = $lconv;
if( my $decoded = $self->decode_lconv( $default ) )
{
$default = $decoded;
}
}
}
elsif( $@ )
{
return( $self->error( "An error occurred while getting the locale information for \"$self->{lang}\": $@" ) );
}
else
{
return( $self->error( "Language \"$self->{lang}\" is not supported by your system." ) );
}
}
elsif( $curr_locale && ( my $lconv = POSIX::localeconv() ) )
{
# Encode and I18N::Langinfo are both core modules since before perl 5.26.1, which is our minimum requirement
$self->_load_class( 'Encode' ) || return( $self->pass_error );
$self->_load_class( 'I18N::Langinfo' ) || return( $self->pass_error );
my $encoding = eval
{
Encode::resolve_alias( I18N::Langinfo::langinfo( I18N::Langinfo::CODESET() ) );
} || 'utf-8';
if( $@ )
{
warn( "Error trying to resolve alias for POSIX::localeconv codeset: $@" ) if( $self->_is_warnings_enabled );
}
$self->encoding( $encoding );
if( scalar( keys( %$lconv ) ) )
{
my @grouping = unpack("C*", $lconv->{grouping});
$lconv->{grouping} = $grouping[0];
@grouping = unpack("C*", $lconv->{mon_grouping});
$lconv->{mon_grouping} = $grouping[0];
$default = $lconv;
if( my $decoded = $self->decode_lconv( $default ) )
{
$default = $decoded;
}
}
# To simulate running on Windows
# my $fail = [qw(
# frac_digits
# int_frac_digits
# n_cs_precedes
# n_sep_by_space
# n_sign_posn
# p_cs_precedes
# p_sep_by_space
# p_sign_posn
# )];
# @$lconv{ @$fail } = ( -1 ) x scalar( @$fail );
$self->{lang} = $curr_locale;
}
no warnings 'uninitialized';
foreach my $prop ( keys( %$map ) )
{
my $ref = $map->{ $prop };
# Already set by user
next if( CORE::length( $self->{ $prop } ) );
foreach my $lconv_prop ( @$ref )
{
if( CORE::defined( $default->{ $lconv_prop } ) )
{
# Number::Format bug RT #71044 when running on Windows
# This is a workaround when values are lower than 0 (i.e. -1)
if( CORE::exists( $numerics->{ $lconv_prop } ) &&
CORE::length( $default->{ $lconv_prop } ) &&
# It may be a non-numeric value which would wreak the following condition
$default->{ $lconv_prop } =~ /\d+/ &&
$default->{ $lconv_prop } < 0 )
{
$default->{ $lconv_prop } = $numerics->{ $lconv_prop };
}
# POSIX::localeconv returned an incomplete hash and we need certain default values
# For example a locale C.UTF-8 would only have the property decimal_point set to '.' and nothing else
elsif( !CORE::length( $default->{ $lconv_prop } ) &&
CORE::exists( $numerics->{ $lconv_prop } ) )
{
$default->{ $lconv_prop } = $numerics->{ $lconv_prop };
}
$self->$prop( $default->{ $lconv_prop } );
last;
}
# Set it to undef then
else
{
if( CORE::exists( $numerics->{ $lconv_prop } ) )
{
$default->{ $lconv_prop } = $numerics->{ $lconv_prop };
}
$self->$prop( $default->{ $lconv_prop } );
}
}
}
# Convert Japanese double bytes numbers to regular digits.
$num =~ tr/[\x{FF10}-\x{FF19}]+ー/[0-9]+-/;
if( $num !~ /^$NUMBER_RE$/ )
{
$self->{_number} = $self->unformat( $num );
}
else
{
$self->{_number} = $num;
}
return( $self->error( "Invalid number: $num (", overload::StrVal( $num ), ")" ) ) if( !defined( $self->{_number} ) );
return( $self );
}
sub abs { return( shift->_func( 'abs' ) ); }
# sub asin { return( shift->_func( 'asin', { posix => 1 } ) ); }
# This class does not convert to an HASH, but the TO_JSON method will convert to a string
sub as_hash { return( $_[0] ); }
sub atan { return( shift->_func( 'atan', { posix => 1 } ) ); }
sub atan2 { return( shift->_func( 'atan2', @_ ) ); }
sub as_array
{
return( Module::Generic::Array->new( [ shift->{_number} ] ) );
}
sub as_boolean
{
return( Module::Generic::Boolean->new( shift->{_number} ? 1 : 0 ) );
}
sub as_scalar
{
return( Module::Generic::Scalar->new( shift->{_number} ) );
}
sub as_string { return( shift->{_number} ) }
sub cbrt { return( shift->_func( 'cbrt', { posix => 1 } ) ); }
sub ceil { return( shift->_func( 'ceil', { posix => 1 } ) ); }
sub chr
{
return( Module::Generic::Scalar->new( CORE::chr( $_[0]->{_number} ) ) );
}
sub clone
{
my $self = shift( @_ );
my $new;
if( !$self->_is_object( $self ) )
{
my $num = shift( @_ ) // 0;
$new = $self->new( $new );
return( $self->pass_error ) if( !defined( $new ) );
}
else
{
my $num = @_ ? shift( @_ ) : $self->{_number};
return( Module::Generic::Infinity->new( $num ) ) if( POSIX::isinf( $num ) );
return( Module::Generic::Nan->new( $num ) ) if( POSIX::isnan( $num ) );
$new = $self->SUPER::clone;
return( $self->pass_error ) if( !defined( $new ) );
$new->{_number} = ( CORE::exists( $num->{_number} ) ? $num->{_number} : $num );
}
return( $new );
}
sub compute
{
my $self = shift( @_ );
my $opts = pop( @_ );
my( $other, $swap, $nomethod, $bitwise ) = @_;
if( !defined( $opts ) ||
ref( $opts ) ne 'HASH' ||
!exists( $opts->{op} ) ||
!defined( $opts->{op} ) ||
!length( $opts->{op} ) )
{
die( "No argument 'op' provided" );
}
my $op = $opts->{op};
my $other_val = Scalar::Util::blessed( $other ) ? $other : "\"$other\"";
my $operation = $swap ? ( defined( $other_val ) ? $other_val : 'undef' ) . " ${op} \$self->{_number}" : "\$self->{_number} ${op} " . ( defined( $other_val ) ? $other_val : 'undef' );
no warnings 'uninitialized';
no strict;
local $@;
if( $opts->{return_object} )
{
my $res = eval( $operation );
no overloading;
warn( "Error with return formula \"$operation\" using object $self having number '$self->{_number}': $@" ) if( $@ && $self->_warnings_is_enabled );
return if( $@ );
return( Module::Generic::Scalar->new( $res ) ) if( $opts->{type} eq 'scalar' );
return( Module::Generic::Infinity->new( $res ) ) if( POSIX::isinf( $res ) );
return( Module::Generic::Nan->new( $res ) ) if( POSIX::isnan( $res ) );
# undef may be returned for example on platform supporting NaN when using <=>
return( $self->clone( $res ) ) if( defined( $res ) );
return;
}
elsif( $opts->{boolean} )
{
my $res = eval( $operation );
no overloading;
warn( "Error with boolean formula \"$operation\" using object $self having number '$self->{_number}': $@" ) if( $@ && $self->_warnings_is_enabled );
return if( $@ );
# return( $res ? $self->true : $self->false );
return( $res );
}
else
{
# return( eval( $operation ) );
my $res = eval( $operation );
return( $res );
}
}
sub cos { return( shift->_func( 'cos' ) ); }
sub currency { return( shift->_set_get_prop( 'symbol', @_ ) ); }
sub decimal { return( shift->_set_get_prop( 'decimal', @_ ) ); }
# sub decimal_digits { return( shift->_set_get_prop( 'decimal_digits', @_ ) ); }
sub decimal_fill { return( shift->_set_get_prop( 'decimal_fill', @_ ) ); }
sub decode_lconv
{
my $self = shift( @_ );
my $ref = shift( @_ );
return( $self->error( "Value provided is not an hash reference." ) ) if( !$self->_is_hash( $ref => 'strict' ) );
my $encoding = $self->encoding || 'utf-8';
foreach my $prop ( keys( %$ref ) )
{
my $v = $ref->{ $prop };
next if( utf8::is_utf8( $v ) || $self->_is_empty( $v ) );
my $rv = eval
{
return( Encode::decode(
$encoding,
$v,
Encode::FB_CROAK()
) );
};
if( $@ )
{
warn( "Error trying to decode POSIX::localeconv property ${prop} and language $self->{lang}: $@" ) if( $self->_is_warnings_enabled );
next;
}
$ref->{ $prop } = $rv;
}
return( $ref );
};
sub default { return( shift->_set_get_hash_as_mix_object( 'default', @_ ) ); }
sub encoding { return( shift->_set_get_scalar( 'encoding', @_ ) ); }
sub exp { return( shift->_func( 'exp' ) ); }
sub floor { return( shift->_func( 'floor', { posix => 1 } ) ); }
sub format
{
my $self = shift( @_ );
my $precision;
$precision = shift( @_ ) if( scalar( @_ ) && $_[0] =~ /^\d+$/ );
my $opts = $self->_get_args_as_hash( @_ );
no overloading;
my $number = $self->{_number};
# If value provided was undefined, we leave it undefined, otherwise we would be at risk of returning 0, and 0 is very different from undefined
return( $number ) if( !defined( $number ) );
$precision //= $opts->{precision} // $self->precision;
my $thousands_sep = $opts->{thousand} // $self->thousand;
my $decimal_point = $opts->{decimal} // $self->decimal;
my $trailing_zeroes = $opts->{decimal_fill} // $self->decimal_fill // 1;
my $grouping = $opts->{grouping} // $self->grouping // 3;
for( $precision, $thousands_sep, $decimal_point, $trailing_zeroes, $grouping )
{
$_ = $_->scalar if( $self->_can( $_ => 'scalar' ) );
}
$grouping = 3 unless( $self->_is_integer( $grouping ) );
# Taken from Number::Format. Credit to William R. Ward
# Handle negative numbers
my $sign = $number <=> 0;
$number = CORE::abs( $number ) if( $sign < 0 );
# round off $number
$number = $self->_round( $number => $precision );
# no overloading;
# detect scientific notation
my $exponent = 0;
if( $number =~ /^(-?[\d.]+)e([+-]\d+)$/ )
{
# Don't attempt to format numbers that require scientific notation.
return( $number );
}
# Split integer and decimal parts of the number and add commas
my $integer = CORE::int( $number );
my $decimal;
# Note: In perl 5.6 and up, string representation of a number
# automagically includes the locale decimal point. This way we
# will detect the decimal part correctly as long as the decimal
# point is 1 character.
if( CORE::length( $integer ) < CORE::length( $number ) )
{
$decimal = CORE::substr( $number, CORE::length( $integer ) + 1 );
}
$decimal = '' unless( defined( $decimal ) );
# Add trailing 0's if $trailing_zeroes is set.
if( $trailing_zeroes && $precision > CORE::length( $decimal ) )
{
$decimal .= '0' x ( $precision - CORE::length( $decimal ) );
}
# Add the commas (or whatever is in thousands_sep). If thousands_sep is the empty
# string, do nothing.
if( $thousands_sep && $grouping > 0 )
{
# Add leading 0's so length($integer) is divisible by 3
$integer = '0' x ( $grouping - ( CORE::length( $integer ) % $grouping ) ) . $integer;
# Split $integer into groups of 3 characters and insert commas
# $integer = CORE::join( $thousands_sep, CORE::grep{ $_ ne '' } CORE::split( /(...)/, $integer ) );
$integer = CORE::join( $thousands_sep, CORE::grep{ $_ ne '' } CORE::split( /(.{$grouping})/, $integer ) );
# Taken from perllocale:
# Grouping goes from right to left (low to high digits).
# 1 while $integer =~ s/(\d)(\d{$grouping}($|$thousands_sep))/$1$thousands_sep$2/;
# Strip off leading zeroes and optional thousands separator
$integer =~ s/^0+(?:\Q$thousands_sep\E)?//;
}
$integer = '0' if( $integer eq '' );
# Combine integer and decimal parts and return the result.
my $result = ( CORE::defined( $decimal ) && CORE::length( $decimal ) )
? CORE::join( $decimal_point, $integer, $decimal )
: $integer;
my $res = ( $sign < 0 ) ? $self->_format_negative( $result ) : $result;
return( $self->pass_error ) if( !defined( $res ) );
return( Module::Generic::Scalar->new( $res ) );
}
# sub format_binary { return( Module::Generic::Scalar->new( CORE::sprintf( '%b', shift->{_number} ) ) ); }
sub format_binary
{
return( Module::Generic::Scalar->new( CORE::sprintf( '%b', shift->{_number} ) ) );
}
sub format_bytes
{
my $self = shift( @_ );
# no overloading;
my $number = $self->{_number};
# See comment in format() method
return( $number ) if( !defined( $number ) );
my $opts = $self->_get_args_as_hash( @_ );
return( $self->error( "Negative number not allowed in format_bytes()" ) ) if( $number < 0 );
# Taken from Number::Format. Credit to William R. Ward
# Set default for precision. Test using defined because it may be 0.
$opts->{precision} //= $self->precision->scalar // 2;
$opts->{mode} ||= 'traditional';
my( $ksuff, $msuff, $gsuff );
if( $opts->{mode} =~ /^iec(60027)?$/i )
{
( $ksuff, $msuff, $gsuff ) = @$self{ qw( kibi_suffix mebi_suffix gibi_suffix ) };
return( $self->error( "'base' option not allowed in iec60027 mode" ) ) if( CORE::exists( $opts->{base} ) );
}
elsif( $opts->{mode} =~ /^trad(itional)?$/i )
{
( $ksuff, $msuff, $gsuff ) = @$self{ qw( kilo_suffix mega_suffix giga_suffix ) };
}
else
{
return( $self->error( "Unsupported mode '$opts->{mode}'" ) );
}
# Set default for "base" option. Calculate threshold values for
# kilo, mega, and giga values. On 32-bit systems tera would cause
# overflows so it is not supported. Useful values of "base" are
# 1024 or 1000, but any number can be used. Larger numbers may
# cause overflows for giga or even mega, however.
my $mult = $self->_get_multipliers( $opts->{base} ) ||
return( $self->pass_error );
# Process "unit" option. Set default, then take first character
# and convert to upper case.
$opts->{unit} = 'auto' unless( defined( $opts->{unit} ) );
my $unit = CORE::uc( CORE::substr( $opts->{unit}, 0, 1 ) );
# Process "auto" first (default). Based on size of number,
# automatically determine which unit to use.
if( $unit eq 'A' )
{
if( $number >= $mult->{giga} )
{
$unit = 'G';
}
elsif( $number >= $mult->{mega} )
{
$unit = 'M';
}
elsif( $number >= $mult->{kilo} )
{
$unit = 'K';
}
else
{
$unit = 'N';
}
}
# Based on unit, whether specified or determined above, divide the
# number and determine what suffix to use.
my $suffix = '';
if( $unit eq 'G' )
{
$number /= $mult->{giga};
$suffix = $gsuff;
}
elsif( $unit eq 'M' )
{
$number /= $mult->{mega};
$suffix = $msuff;
}
elsif( $unit eq 'K' )
{
$number /= $mult->{kilo};
$suffix = $ksuff;
}
elsif( $unit ne 'N' )
{
return( $self->error( "Invalid 'unit' option value \"$unit\"" ) );
}
# Format the number and add the suffix.
my $result = $self->new( $number )->format( $opts->{precision} ) . $suffix;
return( $self->pass_error ) if( !defined( $result ) );
return( Module::Generic::Scalar->new( $result ) );
}
sub format_hex
{
return( Module::Generic::Scalar->new( CORE::sprintf( '0x%X', shift->{_number} ) ) );
}
sub format_money
{
my $self = shift( @_ );
my( $precision, $curr_symbol ) = @_;
$precision = $self->precision->scalar if( !defined( $precision ) || !CORE::length( "$precision" ) || $precision !~ /^\d+$/ );
$curr_symbol = $self->currency->scalar if( !defined( $curr_symbol ) || !CORE::length( "$curr_symbol" ) );
# no overloading;
my $number = $self->{_number};
# See comment in format() method
return( $number ) if( !defined( $number ) );
# Taken from Number::Format. Credit to William R. Ward
my $frac_digits = $self->precision->scalar;
# Determine precision for decimal portion
$precision = $frac_digits unless( defined( $precision ) );
# fallback
# $precision = $self->decimal_digits unless( defined( $precision ) );
# default
$precision = 2 unless( defined( $precision ) );
# Determine sign and absolute value
my $sign = $number <=> 0;
$number = CORE::abs( $number ) if( $sign < 0 );
# format it first
$number = $self->format(
precision => $precision,
);
return( $self->pass_error ) if( !defined( $number ) );
# Now we make sure the decimal part has enough zeroes
my $decimal_point = $self->decimal->scalar;
my( $integer, $decimal ) = CORE::split( /\Q$decimal_point\E/, "$number", 2 );
$decimal //= '';
# $decimal = '0' x $precision if( !$decimal && $precision );
$decimal = '0' x $precision unless( $decimal );
$decimal .= '0' x ( $precision - CORE::length( $decimal ) );
# Extract positive or negative values
my( $sep_by_space, $cs_precedes, $sign_posn, $sign_symbol );
if( $sign < 0 )
{
$sep_by_space = $self->space_neg;
$cs_precedes = $self->precede_neg;
$sign_posn = $self->position_neg;
$sign_symbol = $self->sign_neg // '';
}
else
{
$sep_by_space = $self->space_pos;
$cs_precedes = $self->precede_pos;
$sign_posn = $self->position_pos;
$sign_symbol = $self->sign_pos // '';
}
# Combine it all back together.
my $result = $precision
? CORE::join( $self->decimal, $integer, $decimal )
: $integer;
# Determine where spaces go, if any
my( $sign_sep, $curr_sep );
if( $sep_by_space == 0 )
{
$sign_sep = $curr_sep = '';
}
elsif( $sep_by_space == 1 )
{
$sign_sep = '';
$curr_sep = ' ';
}
elsif( $sep_by_space == 2 )
{
$sign_sep = ' ';
$curr_sep = '';
}
else
{
return( $self->error( "Invalid space (space_neg or space_pos) value provided." ) );
}
my $rv;
# Add sign, if any
if( $sign_posn >= 0 && $sign_posn <= 2 )
{
# Combine with currency symbol and return
if( $curr_symbol ne '' )
{
if( $cs_precedes )
{
$result = $curr_symbol . $curr_sep . $result;
}
else
{
$result = $result . $curr_sep . $curr_symbol;
}
}
if( $sign_posn == 0 )
{
$rv = "($result)";
}
elsif( $sign_posn == 1 )
{
$rv = $sign_symbol . $sign_sep . $result;
}
# $sign_posn == 2
else
{
$rv = $result . $sign_sep . $sign_symbol;
}
}
elsif( $sign_posn == 3 || $sign_posn == 4 )
{
if( $sign_posn == 3 )
{
$curr_symbol = $sign_symbol . $sign_sep . $curr_symbol;
}
# $sign_posn == 4
else
{
$curr_symbol = $curr_symbol . $sign_sep . $sign_symbol;
}
# Combine with currency symbol and return
if( $cs_precedes )
{
$rv = $curr_symbol. $curr_sep . $result;
}
else
{
$rv = $result . $curr_sep . $curr_symbol;
}
}
else
{
return( $self->error( "Invalid *_sign_posn value" ) );
}
return if( !defined( $rv ) );
return( Module::Generic::Scalar->new( $rv ) );
}
sub format_negative
{
my $self = shift( @_ );
# no overloading;
# my $number = $self->{_number};
# See comment in format() method
# return( $number ) if( !defined( $number ) );
my $format = shift( @_ ) // $self->neg_format->scalar;
my $new = $self->format || return( $self->pass_error );
$number = "$new";
if( CORE::index( $format, 'x' ) == -1 )
{
return( $self->error( "Letter x must be present in picture in format_negative()" ) );
}
$number =~ s/^-//;
$format =~ s/x/$number/;
return if( !defined( $number ) );
$self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
return( Module::Generic::Scalar->new( $format ) );
}
sub format_picture
{
my $self = shift( @_ );
my $picture;
if( ( scalar( @_ ) == 1 && !$self->_is_hash( $_[0] ) ) ||
( ( @_ % 2 ) && !$self->_is_hash( $_[0] ) ) )
{
$picture = shift( @_ );
}
my $opts = $self->_get_args_as_hash( @_ );
no overloading;
my $number = $self->{_number};
# See comment in format() method
return( $num ) if( !defined( $number ) );
# Taken from Number::Format. Credit to William R. Ward
$picture //= $opts->{picture};
return( $self->error( "No picture was provided to format number." ) ) if( !CORE::defined( $picture ) || !CORE::length( "$picture" ) );
# Handle negative numbers
my $neg_format = $self->neg_format->scalar;
my( $neg_prefix ) = $neg_format =~ /^([^x]+)/;
my( $pic_prefix ) = $picture =~ /^([^\#]+)/;
my $neg_pic = $neg_format;
( my $pos_pic = $neg_format ) =~ s/[^x\s]/ /g;
( my $pos_prefix = $neg_prefix ) =~ s/[^x\s]/ /g;
$neg_pic =~ s/x/$picture/;
$pos_pic =~ s/x/$picture/;
my $sign = $number <=> 0;
$number = CORE::abs( $number ) if( $sign < 0 );
$picture = $sign < 0 ? $neg_pic : $pos_pic;
my $sign_prefix = $sign < 0 ? $neg_prefix : $pos_prefix;
# Split up the picture and return error if there is more than one $decimal_point
my $decimal_point = $self->decimal->scalar;
my( $pic_int, $pic_dec, @cruft ) = CORE::split( /\Q$decimal_point\E/, $picture );
$pic_int = '' unless( defined( $pic_int ) );
$pic_dec = '' unless( defined( $pic_dec ) );
return( $self->error( "Only one decimal separator permitted in picture" ) ) if( @cruft );
# Obtain precision from the length of the decimal part...
# start with copying it
my $precision = $pic_dec;
# eliminate all non-# characters
$precision =~ s/[^\#]//g;
# take the length of the result
$precision = CORE::length( $precision );
# Format the number
$number = $self->_round( $number => $precision );
# Obtain the length of the integer portion just like we did for $precision
# start with copying it
my $intsize = $pic_int;
# eliminate all non-# characters
$intsize =~ s/[^\#]//g;
# take the length of the result
$intsize = CORE::length( $intsize );
# Split up $number same as we did for $picture earlier
my( $num_int, $num_dec ) = CORE::split( /\./, $number, 2 );
$num_int = '' unless( defined( $num_int ) );
$num_dec = '' unless( defined( $num_dec ) );
# Check if the integer part will fit in the picture
if( CORE::length( $num_int ) > $intsize )
{
# convert # to * and return it
$picture =~ s/\#/\*/g;
$pic_prefix = '' unless( defined( $pic_prefix ) );
$picture =~ s/^(\Q$sign_prefix\E)(\Q$pic_prefix\E)([[:blank:]\h]*)/$2$3$1/;
return( Module::Generic::Scalar->new( $picture ) );
}
# Split each portion of number and picture into arrays of characters
my @num_int = CORE::split( //, $num_int );
my @num_dec = CORE::split( //, $num_dec );
my @pic_int = CORE::split( //, $pic_int );
my @pic_dec = CORE::split( //, $pic_dec );
# Now we copy those characters into @result.
my @result;
if( $picture =~ /\Q$decimal_point\E/ )
{
@result = ( $decimal_point )
}
# For each characture in the decimal part of the picture, replace '#'
# signs with digits from the number.
my $char;
foreach $char ( @pic_dec )
{
$char = ( shift( @num_dec ) || 0 ) if( $char eq '#' );
CORE::push( @result, $char );
}
# For each character in the integer part of the picture (moving right
# to left this time), replace '#' signs with digits from the number,
# or spaces if we've run out of numbers.
my $thousand = $self->thousand->scalar;
while( $char = CORE::pop( @pic_int ) )
{
$char = CORE::pop( @num_int ) if( $char eq '#' );
if( !defined( $char ) ||
$char eq $thousand &&
$#num_int < 0 )
{
$char = ' ';
}
CORE::unshift( @result, $char );
}
# Combine @result into a string and return it.
my $result = CORE::join( '', @result );
$sign_prefix = '' unless( defined( $sign_prefix ) );
$pic_prefix = '' unless( defined( $pic_prefix ) );
$result =~ s/^(\Q$sign_prefix\E)(\Q$pic_prefix\E)(\s*)/$2$3$1/;
return if( !defined( $result ) );
return( Module::Generic::Scalar->new( $result ) );
}
sub from_binary
{
my $self = shift( @_ );
my $binary = shift( @_ );
return( $self->error( "No binary value was provided to instantiate a new number object." ) ) if( !defined( $binary ) || !CORE::length( $binary ) );
# Nice trick to convert from binary to decimal. See perlfunc -> oct
my $res = CORE::oct( "0b${binary}" );
return if( !defined( $res ) );
my $rv;
# try-catch
local $@;
eval
{
$rv = $self->clone( $res );
};
if( $@ )
{
return( $self->error( "Error while getting number from binary value \"$binary\": $@" ) );
}
return( $rv );
}
sub from_hex
{
my $self = shift( @_ );
my $hex = shift( @_ );
return( $self->error( "No hex value was provided to instantiate a new number object." ) ) if( !defined( $hex ) || !CORE::length( $hex ) );
my $res = CORE::hex( $hex );
# hex() actually does not return undef
return( $self->error( "Error while getting number from hexadecimal value \"$hex\": $!" ) ) if( !defined( $res ) );
return( $self->clone( $res ) );
}
sub gibi_suffix { return( shift->_set_get_prop( 'gibi_suffix', @_ ) ); }
sub giga_suffix { return( shift->_set_get_prop( 'giga_suffix', @_ ) ); }
sub grouping { return( shift->_set_get_prop( 'grouping', @_ ) ); }
sub int { return( shift->_func( 'int' ) ); }
{
no warnings 'once';
*is_decimal = \&is_float;
}
sub is_decimal { return( ( shift->{_number} % 1 ) != 0 ); }
sub is_empty { return( CORE::length( shift->{_number} ) == 0 ); }
sub is_even { return( !( shift->{_number} % 2 ) ); }
sub is_finite { return( shift->_func( 'isfinite', { posix => 1 }) ); }
sub is_float { return( (POSIX::modf( shift->{_number} ))[0] != 0 ); }
sub is_infinite { return( shift->_func( 'isinf', { posix => 1 }) ); }
sub is_int { return( (POSIX::modf( shift->{_number} ))[0] == 0 ); }
sub is_nan { return( shift->_func( 'isnan', { posix => 1}) ); }
{
no warnings 'once';
*is_neg = \&is_negative;
}
sub is_negative { return( shift->_func( 'signbit', { posix => 1 }) != 0 ); }
sub is_normal { return( shift->_func( 'isnormal', { posix => 1}) ); }
sub is_odd { return( shift->{_number} % 2 ); }
{
no warnings 'once';
*is_pos = \&is_positive;
}
sub is_positive { return( shift->_func( 'signbit', { posix => 1 }) == 0 ); }
sub kibi_suffix { return( shift->_set_get_prop( 'kibi_suffix', @_ ) ); }
sub kilo_suffix { return( shift->_set_get_prop( 'kilo_suffix', @_ ) ); }
sub lang { return( shift->_set_get_scalar_as_object( 'lang', @_ ) ); }
sub length { return( $_[0]->clone( CORE::length( $_[0]->{_number} ) ) ); }
sub locale { return( shift->_set_get_scalar_as_object( 'lang', @_ ) ); }
sub log { return( shift->_func( 'log' ) ); }
sub log2 { return( shift->_func( 'log2', { posix => 1 } ) ); }
sub log10 { return( shift->_func( 'log10', { posix => 1 } ) ); }
sub max { return( shift->_func( 'fmax', @_, { posix => 1 } ) ); }
sub mebi_suffix { return( shift->_set_get_prop( 'mebi_suffix', @_ ) ); }
sub mega_suffix { return( shift->_set_get_prop( 'mega_suffix', @_ ) ); }
sub min { return( shift->_func( 'fmin', @_, { posix => 1 } ) ); }
sub mod { return( shift->_func( 'fmod', @_, { posix => 1 } ) ); }
sub neg_format { return( shift->_set_get_prop( 'neg_format', @_ ) ); }
sub oct { return( shift->_func( 'oct' ) ); }
sub position_neg { return( shift->_set_get_prop( 'position_neg', @_ ) ); }
sub position_pos { return( shift->_set_get_prop( 'position_pos', @_ ) ); }
sub pow { return( shift->_func( 'pow', @_, { posix => 1 } ) ); }
sub precede { return( shift->_set_get_prop( 'precede', @_ ) ); }
sub precede_neg { return( shift->_set_get_prop( 'precede_neg', @_ ) ); }
sub precede_pos { return( shift->_set_get_prop( 'precede', @_ ) ); }
sub precision { return( shift->_set_get_prop( 'precision', @_ ) ); }
sub rand { return( shift->_func( 'rand' ) ); }
sub real { return( shift->{_number} ); }
# sub round { return( $_[0]->clone( CORE::sprintf( '%.*f', CORE::int( CORE::length( $_[1] ) ? $_[1] : 0 ), $_[0]->{_number} ) ) ); }
sub round
{
my $self = shift( @_ );
my $precision;
if( scalar( @_ ) == 1 )
{
$precision = shift( @_ );
if( !$self->_is_integer( $precision ) )
{
return( $self->error( "precision value provided '", ( $precision // '' ), "' is not an integer." ) );
}
elsif( $precision < 0 )
{
return( $self->error( "precision provided '$precision' is negatie. It must be positive." ) );
}
}
else
{
return( $self->error( 'Usage: my $n2 = $n->round( $precision );' ) );
}
my $new = CORE::sprintf( '%.*f', $precision, $self->{_number} );
return( $self->clone( $new ) );
}
sub round_zero { return( shift->_func( 'round', @_, { posix => 1 } ) ); }
sub round2
{
my $self = shift( @_ );
no overloading;
my $precision;
if( scalar( @_ ) == 1 )
{
$precision = shift( @_ );
if( !$self->_is_integer( $precision ) )
{
return( $self->error( "precision value provided '", ( $precision // '' ), "' is not an integer." ) );
}
elsif( $precision < 0 )
{
return( $self->error( "precision provided '$precision' is negatie. It must be positive." ) );
}
}
else
{
return( $self->error( 'Usage: my $n2 = $n->round2( $precision );' ) );
}
my $number = $self->{_number};
# See comment in format() method
return( $number ) if( !defined( $number ) );
unless( CORE::int( $precision ) == $precision )
{
return( $self->error( "precision option value must be integer" ) );
}
if (CORE::ref( $number ) && $number->isa( 'Math::BigFloat' ) )
{
my $rounded = $number->copy;
$rounded->precision( -$precision );
return if( !defined( $rounded ) );
my $clone = $self->clone;
$clone->{_number} = $rounded;
return( $clone );
}
my $sign = $number <=> 0;
my $multiplier = ( 10 ** $precision );
my $result = CORE::abs( $number );
my $product = $result * $multiplier;
if( $product > MAX_INT )
{
return( $self->error( "round2() overflow. Try smaller precision or use Math::BigFloat" ) )
}
# We need to add 1e-14 to avoid some rounding errors due to the
# way floating point numbers work - see string-eq test in t/round.t
$result = CORE::int( $product + .5 + 1e-14 ) / $multiplier;
$result = -$result if( $sign < 0 );
return if( !defined( $result ) );
my $clone = $self->clone;
$clone->{_number} = $result;
return( $clone );
}
sub scalar { return( shift->as_string ); }
sub sign_neg { return( shift->_set_get_prop( 'sign_neg', @_ ) ); }
sub sign_pos { return( shift->_set_get_prop( 'sign_pos', @_ ) ); }
sub sin { return( shift->_func( 'sin' ) ); }
{
no warnings 'once';
*space = \&space_pos;
}
sub space_neg { return( shift->_set_get_prop( 'space_neg', @_ ) ); }
sub space_pos { return( shift->_set_get_prop( 'space_pos', @_ ) ); }
sub sqrt { return( shift->_func( 'sqrt' ) ); }
sub symbol { return( shift->_set_get_prop( 'symbol', @_ ) ); }
sub tan { return( shift->_func( 'tan', { posix => 1 } ) ); }
sub thousand { return( shift->_set_get_prop( 'thousand', @_ ) ); }
sub unformat
{
my $self = shift( @_ );
my $formatted = shift( @_ );
return( $self->error( "No value to unformat was provided." ) ) if( !defined( $formatted ) );
my $opts = $self->_get_args_as_hash( @_ );
# require at least one digit
unless( $formatted =~ /\d/ )
{
return( $self->error( "No digit found in number to unformat" ) );
}
# Regular expression for detecting decimal point
my $decimal_point = $self->decimal->scalar;
my $pt = qr/\Q$decimal_point\E/;
# Detect if it ends with one of the kilo / mega / giga suffixes.
my( $kilo, $mega, $giga, $kibi, $mebi, $gibi ) = @$self{qw( kilo_suffix mega_suffix giga_suffix kibi_suffix mebi_suffix gibi_suffix )};
my $kp = ( $formatted =~ s/[[:blank:]\h]*($kilo|$kibi)[[:blank:]\h]*$// );
my $mp = ( $formatted =~ s/[[:blank:]\h]*($mega|$mebi)[[:blank:]\h]*$// );
my $gp = ( $formatted =~ s/[[:blank:]\h]*($giga|$gibi)[[:blank:]\h]*$// );
my $mult = $self->_get_multipliers( $opts->{base} );
# Split number into integer and decimal parts
my( $integer, $decimal, @cruft ) = CORE::split( $pt, $formatted );
return( $self->error( "Only one decimal separator permitted" ) ) if( @cruft );
# It's negative if the first non-digit character is a -
my $sign = $formatted =~ /^\D*-/ ? -1 : 1;
my $neg_format = $self->neg_format->scalar;
my( $before_re, $after_re ) = CORE::split( /x/, $neg_format, 2 );
$sign = -1 if( $formatted =~ /\Q$before_re\E(.+)\Q$after_re\E/ );
# Strip out all non-digits from integer and decimal parts
$integer = '' unless( defined( $integer ) );
$decimal = '' unless( defined( $decimal ) );
$integer =~ s/\D//g;
$decimal =~ s/\D//g;
# Join back up, using period, and add 0 to make Perl think it's a number
my $num2 = CORE::join( '.', $integer, $decimal ) + 0;
$num2 = -$num2 if( $sign < 0 );
# Scale the number if it ended in kilo or mega suffix.
$num2 *= $mult->{kilo} if( $kp );
$num2 *= $mult->{mega} if( $mp );
$num2 *= $mult->{giga} if( $gp );
my $clone = $self->clone;
$clone->{_original} = $num;
$clone->{_number} = $num2;
$clone->debug( $self->debug );
return( $clone );
}
# Shared with format() and format_negative()
sub _format_negative
{
my( $self, $number, $format ) = @_;
$format //= $self->neg_format->scalar;
if( CORE::index( $format, 'x' ) == -1 )
{
return( $self->error( "Letter x must be present in picture in format_negative()" ) );
}
$number =~ s/^-//;
$format =~ s/x/$number/;
return( $format );
}
sub _func
{
my $self = shift( @_ );
my $func = shift( @_ ) || return( $self->error( "No function was provided." ) );
my $opts = {};
no strict;
$opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' );
my $namespace = $opts->{posix} ? 'POSIX' : 'CORE';
my $val = @_ ? shift( @_ ) : undef;
my $expr = defined( $val ) ? "${namespace}::${func}( \$self->{_number}, $val )" : "${namespace}::${func}( \$self->{_number} )";
local $@;
my $res = eval( $expr );
return( $self->pass_error( $@ ) ) if( $@ );
return if( !defined( $res ) );
return( Module::Generic::Infinity->new( $res ) ) if( POSIX::isinf( $res ) );
return( Module::Generic::Nan->new( $res ) ) if( POSIX::isnan( $res ) );
return( $self->clone( $res ) );
}
# _get_multipliers returns the multipliers to be used for kilo, mega,
# and giga (un-)formatting. Used in format_bytes and unformat_number.
# For internal use only.
sub _get_multipliers
{
my $self = shift( @_ );
my $base = shift( @_ );
if( !defined( $base ) || $base == 1024 )
{
return({
kilo => 0x00000400,
mega => 0x00100000,
giga => 0x40000000
});
}
elsif( $base == 1000 )
{
return({
kilo => 1_000,
mega => 1_000_000,
giga => 1_000_000_000
});
}
else
{
return( $self->error( "base overflow" ) ) if( $base **3 > MAX_INT );
unless( $base > 0 && $base == CORE::int( $base ) )
{
return( $self->error( "base must be a positive integer" ) );
}
return({
kilo => $base,
mega => $base ** 2,
giga => $base ** 3
});
}
}
sub _round
{
my( $self, $num, $precision ) = @_;
return( CORE::sprintf( '%.*f', $precision, $num ) );
}
sub _set_get_prop
{
my $self = shift( @_ );
my $prop = shift( @_ );
if( @_ )
{
my $val = shift( @_ );
# $val = $val->scalar if( $self->_is_object( $val ) && $val->isa( 'Module::Generic::Scalar' ) );
$val = "$val" if( CORE::defined( $val ) );
# I do not want to set a default value of '' to $self->{ $prop } because if its value is undef, it should remain so
no warnings 'uninitialized';
if( !CORE::defined( $val ) || ( CORE::defined( $val ) && $val ne $self->{ $prop } ) )
{
$self->_set_get_scalar_as_object( $prop, $val );
# If an error was set, we return nothing
# $self->formatter( $self->new_formatter ) || return;
}
}
return( $self->_set_get_scalar_as_object( $prop ) );
}
sub FREEZE
{
my $self = CORE::shift( @_ );
my $serialiser = CORE::shift( @_ ) // '';
my $class = CORE::ref( $self );
my %hash = %$self;
# Return an array reference rather than a list so this works with Sereal and CBOR
# On or before Sereal version 4.023, Sereal did not support multiple values returned
CORE::return( [$class, \%hash] ) if( $serialiser eq 'Sereal' && Sereal::Encoder->VERSION <= version->parse( '4.023' ) );
# But Storable want a list with the first element being the serialised element
CORE::return( $class, \%hash );
}
sub STORABLE_freeze { CORE::return( CORE::shift->FREEZE( @_ ) ); }
sub STORABLE_thaw { CORE::return( CORE::shift->THAW( @_ ) ); }
# NOTE: CBOR will call the THAW method with the stored classname as first argument, the constant string CBOR as second argument, and all values returned by FREEZE as remaining arguments.
# NOTE: Storable calls it with a blessed object it created followed with $cloning and any other arguments initially provided by STORABLE_freeze
sub THAW
{
my( $self, undef, @args ) = @_;
my $ref = ( CORE::scalar( @args ) == 1 && CORE::ref( $args[0] ) eq 'ARRAY' ) ? CORE::shift( @args ) : \@args;
my $class = ( CORE::defined( $ref ) && CORE::ref( $ref ) eq 'ARRAY' && CORE::scalar( @$ref ) > 1 ) ? CORE::shift( @$ref ) : ( CORE::ref( $self ) || $self );
my $hash = CORE::ref( $ref ) eq 'ARRAY' ? CORE::shift( @$ref ) : {};
my $new;
# Storable pattern requires to modify the object it created rather than returning a new one
if( CORE::ref( $self ) )
{
foreach( CORE::keys( %$hash ) )
{
$self->{ $_ } = CORE::delete( $hash->{ $_ } );
}
$new = $self;
}
else
{
$new = CORE::bless( $hash => $class );
}
CORE::return( $new );
}
sub TO_JSON { return( shift->as_string ); }
# NOTE: package Module::Generic::NumberSpecial
BEGIN
{
use strict;
use warnings;
use parent -norequire, qw( Module::Generic::Number );
use overload ('""' => sub{ $_[0]->{_number} },
'+=' => sub{ &_catchall( @_[0..2], '+' ) },
'-=' => sub{ &_catchall( @_[0..2], '-' ) },
'*=' => sub{ &_catchall( @_[0..2], '*' ) },
'/=' => sub{ &_catchall( @_[0..2], '/' ) },
'%=' => sub{ &_catchall( @_[0..2], '%' ) },
'**=' => sub{ &_catchall( @_[0..2], '**' ) },
'<<=' => sub{ &_catchall( @_[0..2], '<<' ) },
'>>=' => sub{ &_catchall( @_[0..2], '>>' ) },
'x=' => sub{ &_catchall( @_[0..2], 'x' ) },
'.=' => sub{ &_catchall( @_[0..2], '.' ) },
nomethod => \&_catchall,
fallback => 1,
);
use Want;
use POSIX qw( Inf NaN );
our( $VERSION ) = '0.1.0';
};
sub new
{
my $this = shift( @_ );
return( bless( { _number => CORE::shift( @_ ) } => ( ref( $this ) || $this ) ) );
}
sub clone { return( shift->new( @_ ) ); }
sub is_finite { return( 0 ); }
sub is_float { return( 0 ); }
sub is_infinite { return( 0 ); }
sub is_int { return( 0 ); }
sub is_nan { return( 0 ); }
sub is_normal { return( 0 ); }
sub length { return( CORE::length( shift->{_number} ) ); }
sub _catchall
{
my( $self, $other, $swap, $op ) = @_;
no strict;
my $expr = $swap ? "$other $op $self->{_number}" : "$self->{_number} $op $other";
local $@;
my $res = eval( $expr );
CORE::warn( "Error evaluating expression \"$expr\": $@" ) if( $@ );
return if( $@ );
return( Module::Generic::Number->new( $res ) ) if( POSIX::isnormal( $res ) );
return( Module::Generic::Infinity->new( $res ) ) if( POSIX::isinf( $res ) );
return( Module::Generic::Nan->new( $res ) ) if( POSIX::isnan( $res ) );
return( $res );
}
sub _func
{
my $self = shift( @_ );
my $func = shift( @_ ) || return( $self->error( "No function was provided." ) );
my $opts = {};
no strict;
$opts = pop( @_ ) if( ref( $_[-1] ) eq 'HASH' );
my $namespace = $opts->{posix} ? 'POSIX' : 'CORE';
my $val = @_ ? shift( @_ ) : undef;
my $expr = defined( $val ) ? "${namespace}::${func}( $self->{_number}, $val )" : "${namespace}::${func}( $self->{_number} )";
local $@;
my $res = eval( $expr );
CORE::warn( $@ ) if( $@ );
return if( !defined( $res ) );
return( Module::Generic::Number->new( $res ) ) if( POSIX::isnormal( $res ) );
return( Module::Generic::Infinity->new( $res ) ) if( POSIX::isinf( $res ) );
return( Module::Generic::Nan->new( $res ) ) if( POSIX::isnan( $res ) );
return( $res );
}
AUTOLOAD
{
my( $method ) = our $AUTOLOAD =~ /([^:]+)$/;
# If we are chained, return our null object, so the chain continues to work
if( want( 'OBJECT' ) )
{
# No, this is NOT a typo. rreturn() is a function of module Want
rreturn( $_[0] );
}
# Otherwise, we return infinity, whether positive or negative or NaN depending on what was set
return( $_[0]->{_number} );
};
DESTROY {};
# NOTE: package Module::Generic::Infinity
# Purpose is to allow chaining of methods when infinity is returned
# At the end of the chain, Inf or -Inf is returned
BEGIN
{
use strict;
use warnings;
use parent -norequire, qw( Module::Generic::NumberSpecial );
our( $VERSION ) = '0.1.0';
};
sub is_infinite { return( 1 ); }
# NOTE: package Module::Generic::Nan
BEGIN
{
use strict;
use warnings;
use parent -norequire, qw( Module::Generic::NumberSpecial );
our( $VERSION ) = '0.1.0';
};
sub is_nan { return( 1 ); }
1;
__END__