小数位数的处理(JS前端,C#后台,SQL处理,报表处理)
最近零零散散的做了一些小数位数相关的内容,这里稍微总结一下。前段时间,我们的系统里面需要做一个根据小数位数设置来处理数据的需求,在我开发之前,系统里面已经有过处理。系统里面有金额和积分小数位数处理设置,这里以金额设置为例,之前的做法是这样的:
1. 系统里面配置默认参数:ConsumeMoneyIndexChoice,默认值为:“保留小数”,就是保留两位小数的意思,有一位的话就显示一位。然后用户在参数设置里面也可以下拉选择来进行修改,之后便在消费收银(其他业务就不一一列举了,总之是有接近十个业务之多)界面进行控制。系统的前台页面用的Handler与js,在js加载时就在相对应的Handler里面请求参数,包括其他好几个参数(因为系统比较灵活,确实参数设置较多了些)一起请求到前台,然后在数值变化数值计算设计到数值变化的地方用if else 语句来处理前台数值。可能一个页面就有好几个地方十分雷同的出现,if else,处理OK了到了结账页面,结账页面又会到结账页面对应的Handler里面请求参数,包括小数位数设置的参数,然后也是好几个if else,当然,据我阅读代码,if else还有没写全的情况,可能后期参数个数有 变动,地方太多没改过来,也有一些地方干脆也没有进行if else判断了,估计是判断累了,呵呵。总之,是有点凌乱了。。结账之后到了后台,后台写了一个公共方法,对消费单据的数据按照参数再进行一次判断和处理。
2. 我阅读完之后头就有点大,然而任务在这,没办法,只好硬着头皮改,因为新的需求需要新增几种小数位数的处理,我也一个页面一个页面的在原来的基础上进行添加新的逻辑,完善之前不完善的逻辑,修改起来进度缓慢,十分耗力,关键是修改后试运行(就是丢到网站上运行,并不能用编辑器运行的)也会经常性的有错误出现。。
3. 我于是停下来,想办法。。。。
4. 经过了一些尝试,突然想起来前两天看了会儿JS的教学视频,将的三个高级函数,有一个惰性函数,有一种模糊的感觉,似乎可以用上。。。于是再百度了下,尝试了下,在程序刚开始加载的时候去请求一次参数,并保存下来(这里的保存下来的意思,是系统在调用前台的方法时时按一个方式处理),看惰性函数吧
Init = (function () { Ext.Ajax.request({ url: "Module/CheckOut/CheckOutHandler.ashx?action=GetMoneyPointIndexChoice", method: "POST", success: function (response, ops) { var o = Ext.decode(response.responseText); console.log("同步请求金额小数位数" + "#" + o.ConsumeMoneyIndexChoice); var ConsumeMoneyIndexChoice = o.ConsumeMoneyIndexChoice; /*折后金额小数点设置*/ switch (ConsumeMoneyIndexChoice) { case "type1": //四舍五入到元 return Number.prototype.toFixedTotalMoney = function () { return Number(Math.round(this)); } break; case "type2": //四舍五入到角 return Number.prototype.toFixedTotalMoney = function () { return Number(parseFloat(this).toFixed(1)); } break; case "type3": //四舍五入到分 return Number.prototype.toFixedTotalMoney = function () { return Number(parseFloat(this).toFixed(2)); } break; case "type4": //抹零到元 return Number.prototype.toFixedTotalMoney = function () { return Number(parseInt(this)); } break; case "type5": //抹零到角 return Number.prototype.toFixedTotalMoney = function () { return Number(parseInt(this * 10) / 10); } break; default: //四舍五入到分 return Number.prototype.toFixedTotalMoney = function () { return Number(parseFloat(this).toFixed(2)); } break; } return newNumber; } }); })();
这个js在页面刚开始加载时加载,请求到具体的值后再确定toFixedTotalMoney()的处理方式。这样,不再需要在多个Handler里面多次请求,不需要在前台if else,更重要的是
,相对来说,更加的符合开闭原则,利于后期维护。
不过前台有一个问题,在四舍五入的时候(就是调用toFixed())有的时候出现误差,如:
更准确的方式是自己写,例如下面的 四舍五入,抹零,相对的比较(有时候前台相加后会出现微小误差,可以绝对值比较相差<1e-6这样判断是否相等)
//四舍五入算法 //v,要处理的数据,e,保留的小数位数, //e为正,保留小数点后面的位数,e为负,保留小数点前面的数 //MoneyPointRound1(3.555,1)=3.6 //MoneyPointRound1(3.555,-1)=0 //MoneyPointRound1(6.555, -1)=10 var MoneyPointRound = function (v, e) { var t = 1; for (; e > 0; t *= 10, e--); for (; e < 0; t /= 10, e++); debugger return Math.round(v * t) / t; } //抹去多余位数算法 //MoneyPointIgnore(3.555,1)=3.5 //MoneyPointIgnore(3.555,2)=3.55 //MoneyPointIgnore(3.555, -1) = 0 //MoneyPointIgnore(6.555, -1)=0 var MoneyPointIgnore = function (v, e) { var t = 1; for (; e > 0; t *= 10, e--); for (; e < 0; t /= 10, e++); debugger return parseInt(v * t) / t; } ///用于前台判断两个值是否相等 //处理parseFloat(110.12+149.72)=259.84000000000003!=259.84的情况 //统一判断入口,后期可以修改判断精度 //也可以相减后的绝对值误差来做 abs(q-b)<1e-6 //Num1,Num2为数值 var EqualNumber = function (num1, num2) { if (Number(parseFloat(num1).toFixed(6)) == Number(parseFloat(num2).toFixed(6))) { return true; } return false; } //数量和折后金额保持2位小数 Number.prototype.toFixed2Decimal = function () { return Number(parseFloat(this).toFixed(2)); } //数量和折后金额保持4位小数 Number.prototype.toFixed4Decimal = function () { return Number(parseFloat(this).toFixed(4)); }
下面的toFixed4Decimal看起来多此一举,但有时候未必,例如我们的结账页面,有时候全部保持2位,有时候3位,页面里面都是toFixed(2)改起来较多,有时候可以考虑后期可能的修改并为此做好准备。易扩展易修改。
5. 后台的方法时这样的,使用方法:price.toMath("保留两位小数");
public static class MathHelper { /// <summary> /// 数值小数处理 /// </summary> /// <param name="value"></param> /// <param name="flag">消费积分小数位类型(参考系统参数)</param> /// <returns></returns> public static decimal toMath(this decimal value, string flag) { decimal globalValue; switch (flag.Trim()) { case "四舍五入到元": globalValue = Math.Round(value, MidpointRounding.AwayFromZero); break; case "四舍五入到角": globalValue = Convert.ToDecimal(value.ToString("#0.0")); break; case "四舍五入到分": globalValue = Convert.ToDecimal(value.ToString("#0.00")); break; case "抹零到元": globalValue = Math.Floor(value); break; case "抹零到角": globalValue = Math.Floor(value * 10) / 10; break; case "保留两位小数": globalValue = Convert.ToDecimal(value.ToString("#0.00")); break; case "抹零取整": globalValue = Math.Floor(value); break; case "四舍五入取整": globalValue = Math.Round(value, MidpointRounding.AwayFromZero); break; case "保留四位小数": globalValue = Convert.ToDecimal(value.ToString("0.####")); break; default: globalValue = Convert.ToDecimal(value.ToString("#0.00")); break; } return globalValue; } }
但是有这样一个问题,系统里面之前商品数量都是保留两位小数的,现在为了某些贵重金属行业需求,支持了4位小数,但是大量的客户都是两位的,如图:
这时候一种较好的处理方式是,默认两位,但是有三位则显示三位,有四位显示四位,大于四位的显示四位。后来可以这样写一个函数:
/// <summary> /// 保留4位小数,末位为零的去掉,但是至少保留两位 /// </summary> /// <param name="Number"></param> /// <returns></returns> public decimal GetFixed2To4Decimal(decimal number) { decimal index2 = Convert.ToDecimal(number.ToString("#0.00")); decimal index4 = Convert.ToDecimal(number.ToString("0.####")); if (index2 == index4) { return index2; } else { return index4; } }
number.ToString("0.####") 是保留4位小数,末位的0去掉。
6. 我们还有销售报表和库存报表,也需要处理,也是默认四位,末位去零,至少两位。报表是用的Report Service,数据源是SQL,查了下, Money类型刚好符合,
select Convert(MONEY,@quantity) 就是我需要的,例如2.3 SQL 查出来是2.30,但是用报表直接用的查询字段的时候,就都还原为4位了,大量的用户其实都是两位小数,
多余的0的显示看起来冗余杂乱不清晰,想着写SQL函数,可是函数只能有一个返回类型,返回Decimal,则1.23会返回1,Decimal(18,2)?decimal(18,3)? 显然都不行,
将Money类型转成字符串?不幸的是,SELECT CAST(CONVERT(MONEY,1.2324) AS NVARCHAR(MAX))的结果是1.23,怪了,可就是怪了,一时无解。
7.在网上 搜索 Report Service 函数,搜到一点有用的信息:http://bbs.csdn.net/topics/390716722,
里面说的Format函数,好像有用,然后再写上IFF条件判断,似乎可以。。最终终于尝试OK,
CAST(Discount AS DECIMAL(18,4)) AS Discount, NumberFixed =IIf( CDec(Format(Fields!Number.Value,"0.####"))=CDec(Format(Fields!Number.Value,"#0.00")),Format(Fields!Number.Value,"#0.00"),Format(Fields!Number.Value,"0.####")) TotalMoneyFixed =IIf( CDec(Format(Fields!TotalMoney.Value,"0.####"))=CDec(Format(Fields!TotalMoney.Value,"#0.00")),Format(Fields!TotalMoney.Value,"#0.00"),Format(Fields!TotalMoney.Value,"0.####"))
SQL里面decimal(18,4),然后展示的时候进行判断,当然由于判断较为复杂,不适合那些经过了复杂的计算后的值的处理。我就讲报表主表,及上方的汇总信息显示为4位,在 数量,折后金额等列用上上面的判断。
OK 写到这里吧