##----------------------------------------------------------------------------
## Module Generic - ~/lib/Module/Generic/Number.pm
## Version v2.4.0
## Copyright(c) 2026 DEGUEST Pte. Ltd.
## Author: Jacques Deguest <jack@deguest.jp>
## Created 2021/03/20
## Modified 2026/06/06
## All rights reserved
## 
## This program is free software; you can redistribute  it  and/or  modify  it
## under the same terms as Perl itself.
##----------------------------------------------------------------------------
package Module::Generic::Number;
BEGIN
{
    use v5.16.0;
    use strict;
    use warnings;
    warnings::register_categories( 'Module::Generic' );
    use parent qw( Module::Generic );
    use vars qw( $SUPPORTED_LOCALES $DEFAULT $NUMBER_RE $LOCALE_LOCK );
    use Config;
    if( $] >= 5.022 )
    {
        require POSIX;
        POSIX->import( qw( cbrt isfinite isinf isnan isnormal signbit ) );
    }
    else
    {
        my $POS_INF = 9**9**9;
        my $NEG_INF = -$POS_INF;
        *isinf = sub
        {
            return(0) if( !defined( $_[0] ) );
            no warnings 'numeric';
            return( $_[0] == $POS_INF || $_[0] == $NEG_INF );
        };
        *isnan = sub
        {
            return(0) if( !defined( $_[0] ) );
            no warnings 'numeric';
            return( $_[0] != $_[0] );
        };
        *isfinite = sub { my $n = $_[0]; return( $n == $n && $n != $POS_INF && $n != $NEG_INF ? 1 : 0 ); };
        *isnormal = sub
        {
            my $n = $_[0];
            return(0) if( $n != $n );
            return(0) if( $n == 0 );
            return(0) if( $n == $POS_INF || $n == $NEG_INF );
            my $abs = $n < 0 ? -$n : $n;
            return( $abs >= 2.2250738585072014e-308 ? 1 : 0 );
        };
        # signbit: returns non-zero for negative numbers (including -0.0)
        *signbit = sub
        {
            my $n = $_[0];
            return(1) if( $n < 0 );
            return(0) if( $n > 0 );
            # zero or NaN: check string representation for -0
            return( sprintf( "%g", $n ) =~ /^-/ ? 1 : 0 );
        };
        # cbrt: handle negatives correctly (real cube root)
        *cbrt = sub
        {
            my $n = $_[0];
            return( $n < 0 ? -( ( -$n ) ** ( 1/3 ) ) : $n ** ( 1/3 ) );
        };
    }

    # 2026-05-17: Regexp::Common does not install under perl v5.10.1 because of an error in its test t/test_comments.t
    # use Regexp::Common qw( number );
    # $NUMBER_RE = $RE{num}{real};
    $NUMBER_RE = qr/(?:(?i)(?:[-+]?)(?:(?=[.]?[0123456789])(?:[0123456789]*)(?:(?:[.])(?:[0123456789]{0,}))?)(?:(?:[E])(?:(?:[-+]?)(?:[0123456789]+))|))/;
    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} ); },
        # numeric context
        '0+' => \&as_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( 'Module::Generic' ) );
            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;
    use constant HAS_THREADS => $Config{useithreads};
    if( HAS_THREADS )
    {
        require threads;
        require threads::shared;
        threads->import();
        threads::shared->import();
        our $LOCALE_LOCK :shared;
    }
    our( $VERSION ) = 'v2.4.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_flexible =
{
    decimal             => [qw( decimal_point mon_decimal_point )],
    grouping            => [qw( grouping mon_grouping )],
    mon_decimal         => [qw( mon_decimal_point decimal_point )],
    mon_grouping        => [qw( mon_grouping grouping )],
    mon_thousand        => [qw( mon_thousands_sep thousands_sep )],
    position_neg        => [qw( n_sign_posn int_n_sign_posn )],
    position_pos        => [qw( p_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 )],
};

my $map_posix_strict =
{
    decimal             => [qw( decimal_point )],
    grouping            => [qw( grouping )],
    mon_decimal         => [qw( mon_decimal_point )],
    mon_grouping        => [qw( mon_grouping )],
    mon_thousand        => [qw( mon_thousands_sep )],
    position_neg        => [qw( n_sign_posn int_n_sign_posn )],
    position_pos        => [qw( p_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 )],
};

my $map = $map_flexible;

# 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" if( ref( $num ) );
    return( $self->error( "Number value provided is empty" ) ) if( !CORE::length( $num ) );
    {
        no warnings;
        return( Module::Generic::Infinity->new( $num ) ) if( isinf( $num ) );
        return( Module::Generic::Nan->new( $num ) ) if( isnan( $num ) );
    }
    use utf8;
    my @k = keys( %$map );
    @$self{ @k } = ( '' ) x scalar( @k );
    $self->{lang} = '';
    $self->{posix_strict} = 1;
    $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;
    $self->{_fields} = [qw(
        lang posix_strict decimal_fill encoding neg_format kilo_suffix mega_suffix
        giga_suffix kibi_suffix mebi_suffix gibi_suffix _number
    )];
    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} )
    {
        # Lock the threads while we change the locale to check if it is available, and get its definition.
        lock( $LOCALE_LOCK ) if( HAS_THREADS );
        # 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
            # RT https://rt.cpan.org/Public/Bug/Display.html?id=132664
            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( 'Module::Generic' ) );
            }
            $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 ) ) )
            {
                $lconv->{grouping}     = $self->_normalise_lconv_grouping( $lconv->{grouping} );
                $lconv->{mon_grouping} = $self->_normalise_lconv_grouping( $lconv->{mon_grouping} );
                $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( 'Module::Generic' ) );
        }
        $self->encoding( $encoding );
        if( scalar( keys( %$lconv ) ) )
        {
            $lconv->{grouping}     = $self->_normalise_lconv_grouping( $lconv->{grouping} );
            $lconv->{mon_grouping} = $self->_normalise_lconv_grouping( $lconv->{mon_grouping} );
            $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';
    my $active_map = $self->{posix_strict} ? $map_posix_strict : $map_flexible;
    foreach my $prop ( keys( %$active_map ) )
    {
        my $ref = $active_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
                # https://rt.cpan.org/Ticket/Display.html?id=71044
                # 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
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Array' ) || return( $self->pass_error );
    return( Module::Generic::Array->new( [ $self->{_number} ] ) );
}

sub as_boolean
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Boolean' ) || return( $self->pass_error );
    return( Module::Generic::Boolean->new( $self->{_number} ? 1 : 0 ) );
}

sub as_number
{
    my $self = shift( @_ );
    return( $self->{_number} + 0 );
}

sub as_scalar
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( $self->{_number} ) );
}

sub as_string { return( shift->{_number} ) }

sub cbrt
{
    my $self = shift( @_ );
    if( $] >= 5.022 )
    {
        return( $self->_func( 'cbrt', { posix => 1 } ) );
    }
    else
    {
        my $n = $self->{_number};
        # Handle negative numbers properly: cbrt(-x) = -cbrt(x)
        my $res = $n < 0 ? -( ( -$n ) ** ( 1/3 ) ) : $n ** ( 1/3 );
        return if( !defined( $res ) );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
        return( $self->clone( $res ) );
    }
}

sub ceil { return( shift->_func( 'ceil', { posix => 1 } ) ); }

sub chr
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( CORE::chr( $self->{_number} ) ) );
}

sub clone
{
    my $self = shift( @_ );
    my $new;
    # Called as a class function
    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( isinf( $num ) );
        return( Module::Generic::Nan->new( $num ) ) if( isnan( $num ) );
        $new = $self->SUPER::clone;
        return( $self->pass_error ) if( !defined( $new ) );
        $new->{_number} = ( $self->_is_a( $num => 'Module::Generic::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( 'Module::Generic' ) );
        return if( $@ );

        # Here we need to die, because we are inside 'compute', which is call in overloading. We simply cannot return an error object.
        $self->_load_class( 'Module::Generic::Scalar' ) ||
            die( "Unable to load Module::Generic::Scalar" );
        return( Module::Generic::Scalar->new( $res ) ) if( $opts->{type} eq 'scalar' );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( 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( 'Module::Generic' ) );
        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 ) )
    {
        next if( $prop eq 'grouping' || $prop eq 'mon_grouping' );
        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( 'Module::Generic' ) );
            next;
        }
        $ref->{ $prop } = $rv;
    }
    return( $ref );
};

# sub default { return( shift->_set_get_hash_as_mix_object( 'default', @_ ) ); }
sub default { return( shift->_set_get_hash( '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 );

    # detect scientific notation
    my $exponent = 0;
    # if( $number =~ /^(-?[\d.]+)e([+-]\d+)$/ )
    # {
    #     # Don't attempt to format numbers that require scientific notation.
    #     return( $number );
    # }
    # Detect scientific notation and preserve it exactly
    # 1.23e+45
    if( "$number" =~ /^(-?\d+(?:\.\d+)?)[eE]([+-]?\d+)+$/i )
    {
        my $mantissa = $1;
        my $exponent = $2;
        # Preserve the exact string representation without formatting
        my $result = "$mantissa" . "e" . "$exponent";
        $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
        return( Module::Generic::Scalar->new( $result ) );
    }

    # round off $number
    $number = $self->_round( $number => $precision );

    # 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 ) );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( $res ) );
}

# sub format_binary { return( Module::Generic::Scalar->new( CORE::sprintf( '%b', shift->{_number} ) ) ); }
sub format_binary
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( CORE::sprintf( '%b', $self->{_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 ) );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( $result ) );
}

sub format_hex
{
    my $self = shift( @_ );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( CORE::sprintf( '0x%X', $self->{_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 ) );

    # NOTE: Money follows the LC_MONETARY category through the dedicated monetary trio
    # resolved in init() (mon_decimal, mon_thousand, mon_grouping). We fall back to the
    # numeric trio only when a monetary value was not resolved at all, so that a locale
    # missing monetary data still produces a sensible result.
    my $mon_decimal_point = $self->mon_decimal->scalar;
    my $mon_thousands_sep = $self->mon_thousand->scalar;
    my $mon_grouping      = $self->mon_grouping->scalar;
    $mon_decimal_point    = $self->decimal->scalar  if( !defined( $mon_decimal_point ) || !CORE::length( $mon_decimal_point ) );
    $mon_thousands_sep    = $self->thousand->scalar if( !defined( $mon_thousands_sep ) || !CORE::length( $mon_thousands_sep ) );
    $mon_grouping         = $self->grouping->scalar if( !defined( $mon_grouping )      || !CORE::length( $mon_grouping ) );

    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,
        decimal   => $mon_decimal_point,
        thousand  => $mon_thousands_sep,
        grouping  => $mon_grouping,
    );
    return( $self->pass_error ) if( !defined( $number ) );

    # Now we make sure the decimal part has enough zeroes
    my $decimal_point = $mon_decimal_point;
    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( $mon_decimal_point, $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 ) );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    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 );
    my $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 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 ) );
    $self->_load_class( 'Module::Generic::Scalar' ) || return( $self->pass_error );
    return( Module::Generic::Scalar->new( $result ) );
}

# <https://stackoverflow.com/a/483708/4814971>
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( isfinite( shift->{_number} ) ? 1 : 0 ); }

sub is_float { return( (POSIX::modf( shift->{_number} ))[0] != 0 ); }

sub is_infinite { return( isinf( shift->{_number} ) ? 1 : 0 ); }

sub is_int { return( (POSIX::modf( shift->{_number} ))[0] == 0 ); }

sub is_nan { return( isnan( shift->{_number} ) ); }

{
    no warnings 'once';
    *is_neg = \&is_negative;
}

sub is_negative { return( signbit( shift->{_number} ) != 0 ? 1 : 0 ); }

sub is_normal { return( isnormal( shift->{_number} ) ? 1 : 0 ); }

sub is_odd { return( shift->{_number} % 2 ); }

{
    no warnings 'once';
    *is_pos = \&is_positive;
}

sub is_positive { return( signbit( shift->{_number} ) == 0 ? 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
{
    my $self = shift( @_ );
    if( $] >= 5.022 )
    {
        return( $self->_func( 'log2', { posix => 1 } ) );
    }
    else
    {
        my $n = $self->{_number};
        my $res = ( CORE::log( $n ) / CORE::log(2) );
        return if( !defined( $res ) );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
        return( $self->clone( $res ) );
    }
}

sub log10 { return( shift->_func( 'log10', { posix => 1 } ) ); }

sub max
{
    my $self = shift( @_ );
    my $other = shift( @_ );
    if( $] >= 5.022 )
    {
        return( $self->_func( 'fmax', $other, { posix => 1 } ) );
    }
    else
    {
        my $n = $self->{_number};
        # fmax: if one is NaN, return the other; otherwise the larger.
        # We don't fully replicate IEEE NaN semantics here, just the common case.
        my $res;
        if( isnan( $n ) ) { $res = $other; }
        elsif( isnan( $other ) ) { $res = $n; }
        else { $res = $n > $other ? $n : $other; }
        return if( !defined( $res ) );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
        return( $self->clone( $res ) );
    }
}

sub mebi_suffix { return( shift->_set_get_prop( 'mebi_suffix', @_ ) ); }

sub mega_suffix { return( shift->_set_get_prop( 'mega_suffix', @_ ) ); }

sub min
{
    my $self = shift( @_ );
    my $other = shift( @_ );
    if( $] >= 5.022 )
    {
        return( $self->_func( 'fmin', $other, { posix => 1 } ) );
    }
    else
    {
        my $n = $self->{_number};
        my $res;
        if( isnan( $n ) ) { $res = $other; }
        elsif( isnan( $other ) ) { $res = $n; }
        else { $res = $n < $other ? $n : $other; }
        return if( !defined( $res ) );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
        return( $self->clone( $res ) );
    }
}

sub mod { return( shift->_func( 'fmod', @_, { posix => 1 } ) ); }

sub mon_decimal { return( shift->_set_get_prop( 'mon_decimal', @_ ) ); }

sub mon_grouping { return( shift->_set_get_prop( 'mon_grouping', @_ ) ); }

sub mon_thousand { return( shift->_set_get_prop( 'mon_thousand', @_ ) ); }

sub neg_format { return( shift->_set_get_prop( 'neg_format', @_ ) ); }

sub oct { return( shift->_func( 'oct' ) ); }

sub posix_strict { return( shift->_set_get_boolean( 'posix_strict', @_ ) ); }

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 negative. 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
{
    my $self = shift( @_ );
    my @args = @_;
    if( $] >= 5.022 )
    {
        return( $self->_func( 'round', @args, { posix => 1 } ) );
    }
    else
    {
        my $n = $self->{_number};
        # round to nearest, ties away from zero (same semantics as C99 round())
        my $res;
        if( $n >= 0 )
        {
            $res = CORE::int( $n + 0.5 );
        }
        else
        {
            $res = -CORE::int( -$n + 0.5 );
        }
        return if( !defined( $res ) );
        return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
        return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
        return( $self->clone( $res ) );
    }
}

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 negative. 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" ) );
    }
    my $num = $formatted;

    # 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( isinf( $res ) );
    return( Module::Generic::Nan->new( $res ) ) if( 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
        });
    }
}

# NOTE: POSIX::localeconv()->{grouping} and {mon_grouping} are not simple scalar
# integers. Per POSIX, they are byte strings where each byte indicates the size of one
# digit group, applied from the lowest order to the highest.
# Two byte values have special meaning:
#   0        repeat the previous element for the rest of the digits.
#   CHAR_MAX no further grouping is to be performed.
# CHAR_MAX is platform-dependent: on systems where char is signed it is 127 (SCHAR_MAX),
# on systems where char is unsigned it is 255 (UCHAR_MAX). We treat any value >= 127 as
# "stop grouping", which covers both cases since a legitimate group size never exceeds
# a handful of units.
# An empty string, undef, or a leading 0 byte all mean "no grouping".
sub _normalise_lconv_grouping
{
    my $self = shift( @_ );
    my $value = shift( @_ );
    return(0) if( !defined( $value ) || !CORE::length( $value ) );
    my @grouping = unpack( 'C*', $value );
    return(0) if( !scalar( @grouping ) );
    my $first = $grouping[0];
    return(0) if( !defined( $first ) || $first == 0 || $first >= 127 );
    return( $first );
}

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 );

    # We keep a strict allow-list to avoid accidentally freezing DBI handles or other
    # process-local state.
    my @props = ( @{$self->{_fields}}, keys( %$map ) );

    my $hash = {};
    foreach my $prop ( @props )
    {
        if( CORE::exists( $self->{ $prop } ) &&
            defined( $self->{ $prop } ) &&
            CORE::ref( $self->{ $prop } ) ne 'CODE' )
        {
            $hash->{ $prop } = $self->{ $prop };
        }
    }

    # 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.
    if( $serialiser eq 'Sereal' )
    {
        require Sereal::Encoder;
        require version;

        if( version->parse( Sereal::Encoder->VERSION ) <= version->parse( '4.023' ) )
        {
            CORE::return( [$class, $hash] );
        }
    }

    # But Storable wants a list with the first element being the serialised element
    CORE::return( $class, $hash );
}

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
    if( $serialiser eq 'Sereal' )
    {
        require Sereal::Encoder;
        require version;
    
        if( version->parse( Sereal::Encoder->VERSION ) <= version->parse( '4.023' ) )
        {
            CORE::return( [$class, \%hash] );
        }
    }
    # 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_number ); }

# NOTE: package Module::Generic::NumberSpecial
package Module::Generic::NumberSpecial;
BEGIN
{
    use strict;
    use warnings;
    use parent -norequire, qw( Module::Generic::Number );
    use vars qw( $POS_INF $NEG_INF );
    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,
                 );
    $POS_INF = 9**9**9;
    $NEG_INF = -$POS_INF;
    if( $] >= 5.022 )
    {
        require POSIX;
        POSIX->import( qw( isinf isnan ) );
    }
    else
    {
        *isinf = sub
        {
            return(0) if( !defined( $_[0] ) );
            no warnings 'numeric';
            return( $_[0] == $POS_INF || $_[0] == $NEG_INF );
        };
        *isnan = sub
        {
            return(0) if( !defined( $_[0] ) );
            no warnings 'numeric';
            return( $_[0] != $_[0] );
        };
    }
    use Wanted;
    our( $VERSION ) = '0.1.0';
};

sub new
{
    my $this = CORE::shift( @_ );
    my $val  = CORE::shift( @_ );
    if( $] < 5.022 )
    {
        my $str = "$val";
        my $is_nan;
        {
            # Suppress numeric comparison warnings: on perl < 5.22, IEEE special values
            # may carry a non-numeric string representation depending on the C library,
            # and the standard NaN-detection idiom ($val != $val) will warn if $val
            # happens to be a plain non-numeric string. The check is still semantically
            # correct.
            no warnings 'numeric';
            $is_nan = ( $val != $val );
        }
        # Fallback to string pattern matching (covers libc variants like:
        # "nan", "-nan", "nan(0x...)", "1.#QNAN", "1.#IND", "nanq", etc.)
        $is_nan ||= ( $str =~ /^[+-]?(?:nan|1\.\#(?:ind|qnan|snan))/i );
        if( $is_nan )
        {
            $val = 'NaN';
        }
        else
        {
            no warnings 'numeric';
            if( $val == $POS_INF )
            {
                $val = 'Inf';
            }
            elsif( $val == -$POS_INF )
            {
                $val = '-Inf';
            }
            elsif( $str =~ /^([+-]?)(?:inf(?:inity)?|1\.\#inf)/i )
            {
                $val = ( $1 eq '-' ) ? '-Inf' : 'Inf';
            }
        }
    }
    return( bless( { _number => $val } => ( 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( isnormal( $res ) );
    return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
    return( Module::Generic::Nan->new( $res ) ) if( 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( isnormal( $res ) );
    return( Module::Generic::Infinity->new( $res ) ) if( isinf( $res ) );
    return( Module::Generic::Nan->new( $res ) ) if( isnan( $res ) );
    return( $res );
}

# NOTE: AUTOLOAD
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 Wanted
        rreturn( $_[0] );
    }
    # Otherwise, we return infinity, whether positive or negative or NaN depending on what was set
    return( $_[0]->{_number} );
};

# NOTE: DESTROY
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
package Module::Generic::Infinity;
BEGIN
{
    use strict;
    use warnings;
    use parent -norequire, qw( Module::Generic::NumberSpecial );
    use overload (
        '""' => sub { return( $_[0]->{_number} ); },
        fallback => 1,
    );

    our( $VERSION ) = '0.1.0';
};

sub is_infinite { return(1); }

# NOTE: package Module::Generic::Nan
package Module::Generic::Nan;
BEGIN
{
    use strict;
    use warnings;
    use parent -norequire, qw( Module::Generic::NumberSpecial );
    use overload (
        '""' => sub { 'NaN' },
        fallback => 1,
    );
    our( $VERSION ) = '0.1.0';
};

sub is_nan { return(1); }

1;

__END__
