抽空写了一个类把数字转换成英语或是汉语表达

发表于:2007-05-25来源:作者:点击数: 标签:英语抽空换成写了数字
今天总算有点空,抽时间写了一个类,把数字转换成英语或汉语表达,用法很简单。下面是代码及演示: 文件:textnumber.class.php [code:1:9ee57400d9] ?php if!defined'_IN_APP'exit; classTextNumber var$resource=null; var$number=0; var$groupLength=3; v

今天总算有点空,抽时间写了一个类,把数字转换成英语或汉语表达,用法很简单。下面是代码及演示:


文件:textnumber.class.php
[code:1:9ee57400d9]
<?php
if(!defined('_IN_APP')) exit;

class TextNumber
{
var $resource = null;
var $number = 0;
var $groupLength = 3;
var $wordSeperated = true;

function setResource($resource = null){
$this->resource = $resource;
}

function setNumber($number){
$this->number = $number;
}

function setWordSeperated($seperated){
$this->wordSeperated = $seperated;
}

function split_number(&$sign, &$int, &$fraction){
list($int, $fraction) = explode('.', $this->number);
$sign = '';
if ($int{0} == '-'){
$sign = '-';
$int = substr($int, 1);
}
$int = preg_replace('/[^0-9]/', '', $int);
$int = preg_replace('/^[0]+/', '', $int);
$int = ($int == '')? '0' : $int;
$fraction = preg_replace('/[^0-9]/', '', $fraction);
if (preg_match('/^0*$/', $fraction))
$fraction = '';
}

function split_group($int){
$int = strrev($int);
$int = chunk_split($int, $this->groupLength, ',');
$int = substr(strrev($int), 1);
return $int;
}

function addSeperator($word = ''){
if ($this->wordSeperated){
if ($word != '')
$word .= ' ';
}
return $word;
}

function trans_sign($sign){
if ($sign == '-'){
if (isset($this->resource['group']['-']))
return $this->addSeperator($this->resource['group']['-']);
else
return '-';
}else{
return '';
}
}

function trans_int($int){
return $this->split_group($int);
}

function trans_dec_point($fraction = 0){
if (isset($this->resource['group']['.'])){
$point = $this->resource['group']['.'];
$point = $this->addSeperator($point);
}else{
$point = '.';
}
return ($fraction == 0)? '' : $point;
}

function trans_fraction($fraction){
return ($fraction == 0)? '' : $fraction;
}

function getText(){
$this->split_number($sign, $int, $fraction);
$ssign = $this->trans_sign($sign);
$sint = $this->trans_int($int);
$spoint = $this->trans_dec_point($fraction);
$sfrac = $this->trans_fraction($fraction);
return trim($ssign.$sint.$spoint.$sfrac);
}
}

//---------------------------------------------------------------------------

class TextNumberFactory
{
function &createTextNumber($lang = 'en_US'){
$class = 'TextNumber_'.$lang;
if (class_exists($class)){
$textnumber =& new $class;
if (!is_subclass_of($textnumber, 'TextNumber')){
unset($textnumber);
$textnumber =& new TextNumber;
}
}else{
$textnumber =& new TextNumber;
}
return $textnumber;
}
}

//---------------------------------------------------------------------------

class TextNumber_en_US extends TextNumber
{
function TextNumber_en_US(){
$this->__construct();
}

function __construct(){
$this->resource = array(
'group_int' => 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',
100 => 'hundred'
),
'group' => array(
0 => '',
1 => 'thousand',
2 => 'million',
3 => 'billion',
4 => 'trillion',
'-' => 'minus',
'.' => 'point',
'group_number' => 4
)
);
}

function trans_group_int($group_int){
$trans =& $this->resource['group_int'];
$h = intval(floor($group_int / 100));
$r = $group_int % 100;
$sh = '';
if ($h != 0){
$sh = $this->addSeperator($trans[$h]);
$sh .= $this->addSeperator($trans[100]);
}
$sr = '';
if ($r != 0){
if ($r <= 20){
$sr = $this->addSeperator($trans[$r]);
}else{
$rr = $r % 10;
$rt = $r - $rr;
if ($rr == 0){
$sr = $this->addSeperator($trans[$r]);
}else{
$sr = $trans[$rt];
$sr .= '-'.$this->addSeperator($trans[$rr]);
}
}
}
$ss = '';
if ($sh != '' && $sr != ''){
$ss = $sh. $this->addSeperator('and') .$sr;
}else{
$ss = $sh.$sr;
}
return $ss;
}

function trans_int($int){
if ($int <= 20) return $this->addSeperator($this->resource['group_int'][intval($int)]);
$groups = explode(',', $this->split_group($int));
$groups = array_reverse($groups);
$cgroup = $this->resource['group']['group_number'];
$result = '';
foreach($groups as $key => $group_int){
$sgroup = '';
$k = $key % $cgroup;
if ((int)$group_int != 0){
$sgroup = $this->resource['group'][$k];
$sgroup = $this->addSeperator($sgroup);
$sgroup = $this->trans_group_int($group_int).$sgroup;
}
$ss = '';
if ($k == 0 && $key >= $cgroup)
$ss = $this->addSeperator($this->resource['group'][$cgroup]);
$result = $sgroup.$ss.$result;
}
return $result;
}

function trans_fraction($fraction){
if ($fraction == '') return '';
$fraction = preg_replace('/([0-9])/e', '$this->addSeperator($this->resource[\'group_int\'][\\1])', $fraction);
return $fraction;
}

function getText($isupper = false){
$text = parent::getText();
if ($isupper){
$text = strtoupper($text);
}
return $text;
}
}

//---------------------------------------------------------------------------

class TextNumber_zh_CN extends TextNumber
{
var $groupLength = 4;
var $wordSeperated = false;

function TextNumber_zh_CN(){
$this->__construct();
}

function __construct(){
$this->resource = array(
'group_int' => array(
0 => '零',
1 => '一',
2 => '二',
3 => '三',
4 => '四',
5 => '五',
6 => '六',
7 => '七',
8 => '八',
9 => '九'
),
'group_int_digital' => array(
0 => '',
1 => '十',
2 => '百',
3 => '千'
),
'group' => array(
0 => '',
1 => '万',
2 => '亿',
3 => '兆',
'-' => '负',
'.' => '点',
'group_number' => 3
),
'trans_upper' => array(
'一' => '壹',
'二' => '贰',
'三' => '叁',
'四' => '肆',
'五' => '伍',
'六' => '陆',
'七' => '柒',
'八' => '捌',
'九' => '玖',
'十' => '拾',
'百' => '佰',
'千' => '仟'
)
);
}

function trans_group_int($group_int){
$si = strrev($group_int);
$ss = '';
$i = 0;
if (preg_match('/^([0]+)/', $si, $matches)){
$i = strlen($matches[1]);
}
for(; $i < strlen($si); $i++){
$s = ($si{$i} == '0')? '' : $this->addSeperator($this->resource['group_int_digital'][$i]);
$ss = $si{$i}.$s.$ss;
}
return $ss;
}

function trans_int($int){
if ($int < 10) return $this->addSeperator($this->resource['group_int'][intval($int)]);
/*
if ($int < 20){
$sint = $this->addSeperator($this->resource['group_int'][10]);
$sint .= $this->addSeperator($this->resource['group_int'][$int % 10]);
return $sint;
}
*/
$groups = explode(',', $this->split_group($int));
$groups = array_reverse($groups);
$cgroup = $this->resource['group']['group_number'];
$result = '';
foreach($groups as $key => $group_int){
$sgroup = '';
$k = $key % $cgroup;
if ((int)$group_int != 0){
$sgroup = $this->resource['group'][$k];
$sgroup = $this->addSeperator($sgroup);
$sgroup = $this->trans_group_int($group_int).$sgroup;
if ($k != 0 && substr($group_int, -1) == '0')
$sgroup .= '0';
}else{
$sgroup = '0';
}
$ss = '';
if ($k == 0 && $key >= $cgroup){
$ss = $this->addSeperator($this->resource['group'][$cgroup]);
if (substr($group_int, -1) == '0')
$ss .= '0';
}
$result = $sgroup.$ss.$result;
}
$result = preg_replace('/[0]+/', '0', $result);
$result = preg_replace('/(^|[^1-9])0([^1-9]|$)/', '\\1\\2', $result);
$result = preg_replace('/([0-9])/e', '$this->addSeperator($this->resource[\'group_int\'][\\1])', $result);
return $result;
}

function trans_fraction($fraction){
if ($fraction == '') return '';
$fraction = preg_replace('/([0-9])/e', '$this->addSeperator($this->resource[\'group_int\'][\\1])', $fraction);
return $fraction;
}

function getText($isupper = false){
$text = parent::getText();
if ($isupper && isset($this->resource['trans_upper'])){
$text = strtr($text, $this->resource['trans_upper']);
}
return $text;
}
}
?>
[/code:1:9ee57400d9]


文件:test.php
[code:1:9ee57400d9]
<?php
require_once ('textnumber.class.php');

$lang = array('en_us', 'zh_cn');
$number = '-00123020456006010335678901201.00086789';

echo "$number<br><br>\n";
for($i = 0; $i < count($lang); $i++){
$textnumber =& TextNumberFactory::createTextNumber($lang[$i]);
$textnumber->setNumber($number);
$sNormal = $textnumber->getText(false);
$sUpper = $textnumber->getText(true);
echo "$sNormal<br><br>\n";
echo "$sUpper<br><br>\n";
}
?>
[/code:1:9ee57400d9]

输出结果:
[code:1:9ee57400d9]
-00123020456006010335678901201.00086789

minus one hundred and twenty-three trillion twenty billion four hundred and fifty-six million six thousand ten trillion three hundred and thirty-five billion six hundred and seventy-eight million nine hundred and one thousand two hundred and one point zero zero zero eight six seven eight nine

MINUS ONE HUNDRED AND TWENTY-THREE TRILLION TWENTY BILLION FOUR HUNDRED AND FIFTY-SIX MILLION SIX THOUSAND TEN TRILLION THREE HUNDRED AND THIRTY-FIVE BILLION SIX HUNDRED AND SEVENTY-EIGHT MILLION NINE HUNDRED AND ONE THOUSAND TWO HUNDRED AND ONE POINT ZERO ZERO ZERO EIGHT SIX SEVEN EIGHT NINE

负一百二十三兆零二百零四亿五千六百万零六千零一十兆零三千三百五十六亿七千八百九十万零一千二百零一点零零零八六七八九

负壹佰贰拾叁兆零贰佰零肆亿伍仟陆佰万零陆仟零壹拾兆零叁仟叁佰伍拾陆亿柒仟捌佰玖拾万零壹仟贰佰零壹点零零零捌陆柒捌玖
[/code:1:9ee57400d9]

由于本人比较懒,所以都没有写注释,不过相信懂OO的人都看得懂。用法极简单,就按test.php中的写法照套就可以了,就是一二三的三步曲,第一步从类工厂中建立一个对象,第二步对该对象赋一个数字字符串待转换,第三步就是取得转换后的文字并输出了。在textnumber.class.php中,有一个基类,一个类工厂,两个派生类(英语与汉语)。至于说它们怎么协调工作,大家自己看看吧。

 shukebeita 回复于:2004-05-07 10:10:37
这个东西不错怎么没人顶呢? 学习是点滴积累的过程,说不好我哪天会用上的。

 yb0312 回复于:2004-05-07 10:56:09
偶一看到 class 就头疼

 夜猫子 回复于:2004-05-07 12:50:37
我顶了的,不过我的顶法和你们不同,嘿嘿。

 yb0312 回复于:2004-05-07 16:20:52
难道面向对象真的是php的趋势吗?偶看上它可是看上它的结构化编程方式,
哎谁叫俺是学tc的。郁闷中~

 夜猫子 回复于:2004-05-07 19:07:16
“面向对象与其说是一种技术,不如说是一种信仰”,记不清楚谁说的了。
我是信仰不坚定的那类,用四川话来说叫:“乱匹才,啥子都来”,呵呵。

 pangty 回复于:2004-05-08 14:42:09
ding

 longnetpro 回复于:2004-05-08 22:19:15
对了,这个类中有个resource,一看起来似乎没什么用,其实是为了扩展并将语言内容与代码分离所设的。比如说德语,它讲数字的语法与英语几乎一样,只是在几百与几十连在一起时用了连词und(等于英语的and)并没有用空格连接,而且几十与几个之间也不用-。因此它整个代码与英语的差不多,只需将英语类派生一下,并重载一下trans_group_int这个方法(其中的代码也基本一样),再将resource的内容换成德语的(或是用setRource方法从外部临时加入)就差不多了,再有些细微的地方稍微改一下就OK了——从这里OO的好处才能看得出来,否则,真的与写几个函数堆砌一下没有什么区别了。同样的方式可以处理日文(在中文上派生;或是将中文与日文从同一个类派生,不过就要将中文那个类要分成一个东方语言类与中文类,中文类从东方语言类派生,以后日文也从东方语言类派生出来,如果两种语言处理是完全相同的话,就只需要指定不同的resource就可以了,甚至代码都不用改了)。

深入体会OO的确不是那么容易的事。

 numlock 回复于:2004-05-09 13:42:13
不光要顶!还要藏!

 vidarz 回复于:2004-05-09 14:57:42
我收藏了:
http://www.phpchina.org/blog/index.php?op=ViewArticle&articleId=181&blogId=1

 longnetpro 回复于:2004-05-16 12:55:39
发现小BUG,现在已修正。原来对0这个数翻译不准,细看发现是有一个键值写错了。对于汉语的小于20的数,比如说16,应该翻译为“十六”,但原来翻译为“一十六”,现也已修正了。

 sports98 回复于:2004-05-16 13:24:25
[quote:616a903b36="longnetpro"]发现小BUG,现在已修正。原来对0这个数翻译不准,细看发现是有一个键值写错了。对于汉语的小于20的数,比如说16,应该翻译为“十六”,但原来翻译为“一十六”,现也已修正了。[/quote:616a903b36]

我个人觉得你应该两个都考虑

现在银行那边使用的表示方式是

16 =>  一十六

我们口语是

16 => 十六

 longnetpro 回复于:2004-05-16 13:41:02
[quote:b413f47ec4="sports98"]

我个人觉得你应该两个都考虑

现在银行那边使用的表示方式是

16 =>  一十六

我们口语是

16 => 十六[/quote:b413f47ec4]

那还是正规一点好吧。我把新加的那一段注释掉了。

 yutian 回复于:2004-05-24 09:10:04
好东西



楼主要多多努力

 longnetpro 回复于:2004-05-24 14:48:52
[quote:8ba33812ab="yutian"]好东西



楼主要多多努力[/quote:8ba33812ab]

谢谢!不过努不努力是我自己的事。只要我的东西对得起观众就可以了。另外,我不太喜欢这种说话的口气。

 dualface 回复于:2004-05-24 16:03:33
看了n久,终于看懂了:)

 longnetpro 回复于:2004-05-24 16:45:47
首先恭喜楼上的。然后再问一句,你的n等于几啊?应该最大值为2就对了。如果看懂了,不妨说说有没有什么问题或是经验,交流一下。

 dualface 回复于:2004-05-24 17:54:48
n估计接近10了哈。
主要是没注释看得有点晕。

问题和经验暂时都没有,因为还没有实际用过。我觉得一个东西要实际应用才能够发现问题。

 huabingl 回复于:2004-05-25 15:42:19
好dd,看来我得抓紧时间了,不然好类被楼主写光了

 tonera 回复于:2004-05-26 09:27:58
建议增加一个容错性检查。比如我输入20.2.2,程序把它当作20.2。输入字符,一律当作0。是否考虑增加一个数据校验的函数,只允许浮点数和整数。

 longnetpro 回复于:2004-05-26 14:29:19
[quote:ec71afa23a="tonera"]建议增加一个容错性检查。比如我输入20.2.2,程序把它当作20.2。输入字符,一律当作0。是否考虑增加一个数据校验的函数,只允许浮点数和整数。[/quote:ec71afa23a]

可是可以,我原来也考虑过。其实这就是我的容错性,我人为过滤掉所有非法字符,总之无论输入何种内容,总可以输出一个值,就是说,我这个类输入时可以是任何字符串,而翻译规则是由我人为定的。其实你的建议是对输入形式的限制,即限制它只能为浮点或是整数。这个其实你可以自己限制它。我这个类的功能只是把有效的字符串翻译成文字,就是说,无论输入是什么,必然先在内部要转换成一个有效的数字字符串,我这个类只对有效数字起作用。那我为什么可以允许任意字符串都可以被翻译呢?因为我这个类需要一个默认的处理。由于我这个类可以将任意字符串都按我定义的规则转换为一个有效的字符串,因此任意字符串都是可翻译的,这样就保证了最大输入集总能被翻译,而不会出错。如果用户想自己限制输入的形式,比如只允许输入浮点形式或是整数,其实是一件非常简单的事,怎么做呢?从我的这个类再派生一个类出来,将方法setNumber重载一下即可,你可以在这个被重载的方法中加入检测及出错提示,并将内部的成员变量$number按你自己定义的规则转换成一个合法的字符串,为以后的翻译作好准备。而当你做好这些之后,你会发现,你不用再多写一行代码,就达到了你的要求。你做的仅仅就是派生出一个新类,然后重载setNumber方法,在这个方法中对输入做检测,如果不成功,就给出一个错误提示并将内部的成员变量$this->number按你自己定义的规则设一个有效值,否则$this->number就直接为你设置的值。因此,在我的类中,不必做任何检测,因为任意字符串都可按我的规则翻译,如果你不想按我的规则翻译,就按我上面叙述的步骤让程序按你定义的规则翻译。我的类就是要求对任意的字符串都有一个规则可以翻译。 —— 从这个例子可以体现出了面向对象的优点和特点。

为了让你看得更清楚,我直接给出这个派生类,因为它很简单:

[code:1:ec71afa23a]

class myNumber extends TextNumber
{
    function setNumber($number){
        $regex = '/[+\\-]?[0-9]([.][0-9]*)?/';
        if (!preg_match($regex, $number)){
            $number = '0';
            echo 'Invalid number format.';
        }
        parent::setNumber($number);
    }
}

[/code:1:ec71afa23a]

用法与基类用法一样,只是要注意在new的时候把类名变为myNumber即可,当输入有效时,按基类的默认规则翻译,当输入无效时,输出一个错误信息,并将内部的成员变量设为'0',最后翻译的也是'0'这个字符串。

 tonera 回复于:2004-05-26 16:31:53
呵呵,确实如此。我想我一开始就提出了一个错误的建议。

我当初的想法不是针对这个类的,而是你那个演示界面。

我觉得没必要继承一个类来完成数据的合法性校验,完全可以在类的接口外进行验证,通过后再由类的完成转换。这样应该更高效。

另外:你上面那个例子中的正则,
[code:1:40cf982195] $regex = '/[+\\-]?[0-9]([.][0-9]*)?/'; [/code:1:40cf982195]
我有不同意见,你看换成这个是不是更好:
[code:1:40cf982195]$regex='/^[+\\-]?[0-9]+([.][0-9]*)?$/';[/code:1:40cf982195]

 longnetpro 回复于:2004-05-26 20:54:41
哦,对,昨天我上班到半夜才回,随手写的,脑子不清醒,也没有调试,犯了低级错误。你写的正则是对的。

至于为什么要继承,其实还是为了封装性和重用性。当然,这些手段有多种,你可以写一个函数,也可以用继承,反正你看着怎么方便怎么习惯就怎么用吧。

如果只针对那个演示界面,就更好办了,连类代码都不用改,加个JS用正则判断一下就可以了。而且由于这个类有容错性,也不可能有人能绕开检查搞攻击,因为所有的非法字符最终都被滤掉了(在基类计算之前,内部调用split_group方法的时候),即使外部对输入没有任何限制,内部也不会出错,总有一个规则可以翻译字符串。

原文转自:http://www.ltesting.net