金额转中文大写于英文大写,改进了精度误差问题(php版本)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29238009/article/details/79528531
/**
 * 金额转换成英文大写
 * @param $money 金额
 * @param $curr 货币种类,只能输入里面有的5种货币类型,不存在则return
 * @return bool|string
 */
function num_to_eng($money,$curr){
    $currency_type = [
        '502'=>['US DOLLARS' , 'CENTS'],
        '142' => ['YUAN','CENTS'],
        '110'=>['HONG KONG DOLLARS', 'CENTS'],
        '116'=>['JAPANESE YEN','CENTS'],
        '303'=>['POUND STERLING','PENCE'],
        '300'=>['EURO','CENTS'],
    ];
    if(!in_array(strval($curr),array_keys($currency_type)))
        return '';
    $enws=array(
        0=>"zero",1=>"one",2=>"two",3=>"three",4=>"four",
        5=>"five",6=>"six",7=>"seven",8=>"eight",9=>"nine",
        10=>"ten",11=>"eleven",12=>"twelve",
        13=>"thirteen",14=>"fourteen", 15=>"fifteen",
        16=>"sixteen",17=>"seventeen",18=>"eighteen",19=>"nineteen",
        20=>"twenty",30=>"thirty",40=>"forty",50=>"fifty",
        60=>"sixty",70=>"seventy",80=>"eighty",90=>"ninety");
    $unit = [
        0 => '',
        1 => 'thousand ',
        2 => 'million ',
        3 => 'billion ',

    ];
    //保留两位小数
    $money = round($money,2);
    //分割整数位和小数位
    $int_deci = explode('.',$money);
    //保存整数位
    $int = $int_deci[0];
    //超过千亿,报错
    if(strlen($int) > 12)
        return false;
    //不存在小数位则填上 ‘00’
    if(isset($int_deci[1]))
        $deci = substr(floatval($money*100),-2);  //这里要用string函数去提取小数位,用intval floatval会有精度问题,如0.80变成0.79
    else
        $deci = '00';
    $int = [
        0 => intval($int % 1000),  // 个位
        1 => intval(intval($int / 1000) % 1000),  // 千位
        2 => intval(intval($int / (1000 * 1000)) % 1000),   //百万位
        3 => intval(intval($int / (1000 * 1000 * 1000)) % 1000),   //十亿位
    ];
    $str = '';
    //处理整数位,下面的字符拼接,都后置位留空,前置位不留空
    foreach($int as $k=>$v){
        if($v <= 0) continue;
        $single = intval($v % 10);  //个位
        $ten = intval(intval($v / 10) % 10);  //十位
        $hund = intval(intval($v / 100) % 10);  //百位
        $sub_str = '';
        if($hund > 0)
            $sub_str .= $enws[$hund].' hundred ';
        if($ten > 0 || $single > 0){
            if($ten == 0){
                $sub_str .= 'and '.$enws[$single].' ';
            }elseif($ten == 1){
                $sub_str .= 'and '.$enws[$ten*10+$single].' ';
            }else{
                $sub_str .= 'and '.$enws[$ten*10].' ';
                if($single > 0)
                    $sub_str .= $enws[$single].' ';
            }
        }
        $sub_str .= $unit[$k];
        $str = $sub_str.$str;
    }
    $str = 'SAY '.$currency_type[$curr][0].' '.$str;
    //处理分数
    $single = intval($deci % 10);  //个位
    $ten = intval(intval($deci / 10) % 10);  //十位
    if($ten > 0 || $single > 0){
        if($ten == 0){
            $str .= $currency_type[$curr][1].' '.$enws[$single].' ';
        }elseif($ten == 1){
            $str .= $currency_type[$curr][1].' '.$enws[$ten*10+$single].' ';
        }else{
            $str .= $currency_type[$curr][1].' '.$enws[$ten*10].' ';
            if($single > 0)
                $str .= $enws[$single].' ';
        }
    }
    $str .= 'only';
    $str = strtoupper($str);
    return $str;
}

/**
 * 金额转换成中文大写
 * @param $money 金额
 * @param bool $complex 是否繁体,默认true繁体
 * @return bool|mixed|string
 */
function num_to_rmb($money,$complex=true){
    if($complex){
        $unit1 = ['元','万','亿'];
        $unit2 = ['','拾','佰','仟'];
        $unit3 = ['零','壹','贰','叁','肆','伍','陆','柒','捌','玖'];
        $unit4 = ['角','分'];
        $zheng = '整';
    }else{
        $unit1 = ['元','万','亿'];
        $unit2 = ['','十','百','千'];
        $unit3 = ['零','一','二','三','四','五','六','七','八','九'];
        $unit4 = ['角','分'];
        $zheng = '整';
    }
    //保留两位小数
    $money = round($money,2);
    //分割整数位和小数位
    $int_deci = explode('.',$money);
    //保存整数位
    $int = $int_deci[0];
    //超过千亿,报错
    if(strlen($int) > 12)
        return false;
    //不存在小数位则填上 ‘00’
    if(isset($int_deci[1]))
        $deci = substr(floatval($money*100),-2);  //这里要用string函数去提取小数位,用intval floatval会有精度问题,如0.80变成0.79
    else
        $deci = '00';
    $int = [
        0 => intval($int % 10000),  // 个位
        1 => intval(intval($int / 10000) % 10000),  // 万位
        2 => intval(intval($int / (10000 * 10000)) % 10000),   //亿位
    ];
    $str = '';
    //处理整数位
    foreach($int as $k=>$v){
        if($v <= 0) continue;
        $single = intval($v % 10);  //个位
        $ten = intval(intval($v / 10) % 10);  //十位
        $hund = intval(intval($v / 100) % 10);  //百位
        $kilo = intval(intval($v / 1000) % 10);  //千位
        $sub_str = '';
        $sub_str .= $unit3[$kilo].$unit2[3];
        $sub_str .= $unit3[$hund].$unit2[2];
        $sub_str .= $unit3[$ten].$unit2[1];
        $sub_str .= $unit3[$single].$unit2[0];
        $sub_str .= $unit1[$k];
        $str = $sub_str.$str;
    }
    //处理分数位
    $fen = intval($deci % 10);
    $jiao = intval(intval($deci / 10) % 10);
    $str .= $unit3[$jiao].$unit4[0];  //角位
    $str .= $unit3[$fen].$unit4[1];   //分位
    //中文规则变换
    for($i=0;$i<3;$i++){
        $str = str_replace($unit3[0].$unit1[0],$unit1[0],$str);  // 零元 -》 元
        $str = str_replace($unit3[0].$unit1[1],$unit1[1],$str);  // 零万 -》 万
        $str = str_replace($unit3[0].$unit1[2],$unit1[2],$str);  // 零亿 -》 亿
        $str = str_replace($unit3[0].$unit4[0],$unit3[0],$str);  // 零角 -》 零
        $str = str_replace($unit3[0].$unit4[1],$unit3[0],$str);  // 零分 -》 零
        $str = str_replace($unit3[0].$unit2[1],$unit3[0],$str);  // 零十 -》 零
        $str = str_replace($unit3[0].$unit2[2],$unit3[0],$str);  // 零百 -》 零
        $str = str_replace($unit3[0].$unit2[3],$unit3[0],$str);  // 零千 -》 零
        $str = str_replace($unit3[0].$unit3[0],$unit3[0],$str);  // 零零 -》 零   ,上面的零十零千可能会变成零零,重复3次差不多能把所有零零归并成一个零字了
        //$str = str_replace($unit3[1].$unit2[1],$unit2[1],$str);  // 一十 -》 十
        $str = mb_trim($str,$unit3[0]);  //除去两边的零

    }
    //不存在分位置,加上 整 字
    if(intval($fen)<=0)
        $str .= $zheng;
    return $str;
}

/**
 * trim处理汉字可能会乱码,重写mb_trim解决这个问题
 * @param $string 需要处理的字符串
 * @param $target 需要trim的字符串
 * @return string
 */
function mb_trim($string,$target){
    for($i=0;$i<1000;$i++){   //最多处理1000次
        $modify = false;
        $lchar = mb_substr($string,0,mb_strlen($target));
        $rchar = mb_substr($string,-mb_strlen($target),mb_strlen($target));
        if($lchar == $target){
            $modify = true;
            $string = mb_substr($string,mb_strlen($target));
        }
        if($rchar == $target){
            $modify = true;
            $string = mb_substr($string,0,mb_strlen($string)-mb_strlen($target));
        }
        if(!$modify)break;
    }
    return $string;
}

使用:

    $num = '123456789.6';
    $eng = num_to_eng($num,142);
    $ch = num_to_rmb($num);
    var_dump([$num,$ch,$eng]);

上面的方法有些思路有参考其他网上的转换方法,但是基本是我自己设计写出来的,可能会有bug,所以大家用之前请自己测试一下~有bug希望在本文留下言,万分感谢。

原来公司的项目这部分的转换方法是直接从网上找的,本来不是什么复杂的功能,但是在后期应用的时候(很久很久以后)发现金额和大写金额对不上,会有一毛钱或者一分钱的误差,很明显就是原来的方法有循环未完全的问题,或者计算精度有问题了。

后面我自己写一遍,以及同事看原来的方法发现,原来的方法里面,在计算后面两位小数的时候,会采用进位两位,然后再用intval() 或者 (int) 类型强制转换小数,然后导致精度有问题,这是语言自身的问题。当然同时也是大多数语言例如JS会存在的问题,这也是计算机底层的问题。所以各位在需要处理高精度数字(例如计算金额)的时候采用其他方法(百度一下,有人已经解决高精度运算的问题的了,这里我就不多说了),不要简单粗暴地加减乘除。

然后我也发现在使用trim的时候,处理中文会发生乱码的现象,查了下php的trim不完全支持中文。。。所以我自己写了个mb_trim()的方法去适配中文(在这里我用着是没什么问题的,如果你要用在其他地方也是请你先测试一下,然后发现有bug在本文留言给我,3Q~)

最后再补充一下,上面的金额转中英文大写的规则,是按照外贸书写规则的(大概吧。。。其实我也不是很确定,但是大致应该差不多,如果有规则上的问题也请你在留言中提出来~我将会尽快改进)。如果你需要其他格式请自行修改,备注都写好了,解题思路也不难,聪明的你肯定很快就能按需修改了~

That’s all,新年后的第一篇文~( • ̀ω•́ )✧

猜你喜欢

转载自blog.csdn.net/qq_29238009/article/details/79528531