Changeset 1362

Show
Ignore:
Timestamp:
04/19/08 09:29:33 (3 months ago)
Author:
cederberg@gmail.com
Message:

Re-implemented MochiKit.Format.truncToFixed() to avoid weird floating-point rounding errors for some numbers (fixes #275).
Re-implemented MochiKit.Format.twoDigitFloat() to improve clarity and simplicity.
Corrected MochiKit.Format.roundToFixed() for negative numbers.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • mochikit/trunk/MochiKit/Format.js

    r1319 r1362  
    144144        var res = numerator / denominator; 
    145145        if (!isNaN(res)) { 
    146             return MochiKit.Format.twoDigitFloat(numerator / denominator); 
     146            return MochiKit.Format.twoDigitFloat(res); 
    147147        } 
    148148    } 
     
    151151 
    152152/** @id MochiKit.Format.twoDigitFloat */ 
    153 MochiKit.Format.twoDigitFloat = function (someFloat) { 
    154     var sign = (someFloat < 0 ? '-' : ''); 
    155     var s = Math.floor(Math.abs(someFloat) * 100).toString(); 
    156     if (s == '0') { 
    157         return s; 
    158     } 
    159     if (s.length < 3) { 
    160         if (s.length == 1) { 
    161             s = '0' + s; 
    162         } 
    163         while (s.charAt(s.length - 1) == '0') { 
    164             s = s.substring(0, s.length - 1); 
    165         } 
    166         return sign + '0.' + s; 
    167     } 
    168     var head = sign + s.substring(0, s.length - 2); 
    169     var tail = s.substring(s.length - 2, s.length); 
    170     if (tail == '00') { 
    171         return head; 
    172     } else if (tail.charAt(1) == '0') { 
    173         return head + '.' + tail.charAt(0); 
    174     } else { 
    175         return head + '.' + tail; 
     153MochiKit.Format.twoDigitFloat = function (aNumber) { 
     154    var res = roundToFixed(aNumber, 2); 
     155    if (res.indexOf(".00") > 0) { 
     156        return res.substring(0, res.length - 3); 
     157    } else if (res.charAt(res.length - 1) == "0") { 
     158        return res.substring(0, res.length - 1); 
     159    } else { 
     160        return res; 
    176161    } 
    177162}; 
     
    211196/** @id MochiKit.Format.truncToFixed */ 
    212197MochiKit.Format.truncToFixed = function (aNumber, precision) { 
    213     aNumber = Math.floor(aNumber * Math.pow(10, precision)); 
    214     var res = (aNumber * Math.pow(10, -precision)).toFixed(precision); 
    215     if (res.charAt(0) == ".") { 
    216         res = "0" + res; 
     198    var res = Math.floor(aNumber).toFixed(0); 
     199    if (aNumber < 0) { 
     200        res = Math.ceil(aNumber).toFixed(0); 
     201        if (res.charAt(0) != "-" && precision > 0) { 
     202            res = "-" + res; 
     203        } 
     204    } 
     205    if (res.indexOf("e") < 0 && precision > 0) { 
     206        var tail = aNumber.toString(); 
     207        if (tail.indexOf("e") > 0) { 
     208            tail = "."; 
     209        } else if (tail.indexOf(".") < 0) { 
     210            tail = "."; 
     211        } else { 
     212            tail = tail.substring(tail.indexOf(".")); 
     213        } 
     214        if (tail.length - 1 > precision) { 
     215            tail = tail.substring(0, precision + 1); 
     216        } 
     217        while (tail.length - 1 < precision) { 
     218            tail += "0"; 
     219        } 
     220        res += tail; 
    217221    } 
    218222    return res; 
     
    221225/** @id MochiKit.Format.roundToFixed */ 
    222226MochiKit.Format.roundToFixed = function (aNumber, precision) { 
    223     return MochiKit.Format.truncToFixed( 
    224         aNumber + 0.5 * Math.pow(10, -precision), 
    225         precision 
    226     ); 
     227    var upper = Math.abs(aNumber) + 0.5 * Math.pow(10, -precision); 
     228    var res = MochiKit.Format.truncToFixed(upper, precision); 
     229    if (aNumber < 0) { 
     230        res = "-" + res; 
     231    } 
     232    return res; 
    227233}; 
    228234 
    229235/** @id MochiKit.Format.percentFormat */ 
    230 MochiKit.Format.percentFormat = function (someFloat) { 
    231     return MochiKit.Format.twoDigitFloat(100 * someFloat) + '%'; 
     236MochiKit.Format.percentFormat = function (aNumber) { 
     237    return MochiKit.Format.twoDigitFloat(100 * aNumber) + '%'; 
    232238}; 
    233239 
  • mochikit/trunk/packed/MochiKit/MochiKit.js

    r1360 r1362  
    20732073var res=_264/_265; 
    20742074if(!isNaN(res)){ 
    2075 return MochiKit.Format.twoDigitFloat(_264/_265); 
     2075return MochiKit.Format.twoDigitFloat(res); 
    20762076} 
    20772077} 
     
    20792079}; 
    20802080MochiKit.Format.twoDigitFloat=function(_267){ 
    2081 var sign=(_267<0?"-":""); 
    2082 var s=Math.floor(Math.abs(_267)*100).toString(); 
    2083 if(s=="0"){ 
    2084 return s; 
    2085 
    2086 if(s.length<3){ 
    2087 if(s.length==1){ 
    2088 s="0"+s; 
    2089 
    2090 while(s.charAt(s.length-1)=="0"){ 
    2091 s=s.substring(0,s.length-1); 
    2092 
    2093 return sign+"0."+s; 
    2094 
    2095 var head=sign+s.substring(0,s.length-2); 
    2096 var tail=s.substring(s.length-2,s.length); 
    2097 if(tail=="00"){ 
    2098 return head; 
    2099 }else{ 
    2100 if(tail.charAt(1)=="0"){ 
    2101 return head+"."+tail.charAt(0); 
    2102 }else{ 
    2103 return head+"."+tail; 
    2104 
    2105 
    2106 }; 
    2107 MochiKit.Format.lstrip=function(str,_26d){ 
     2081var res=roundToFixed(_267,2); 
     2082if(res.indexOf(".00")>0){ 
     2083return res.substring(0,res.length-3); 
     2084}else{ 
     2085if(res.charAt(res.length-1)=="0"){ 
     2086return res.substring(0,res.length-1); 
     2087}else{ 
     2088return res; 
     2089
     2090
     2091}; 
     2092MochiKit.Format.lstrip=function(str,_26a){ 
    21082093str=str+""; 
    21092094if(typeof (str)!="string"){ 
    21102095return null; 
    21112096} 
    2112 if(!_26d){ 
     2097if(!_26a){ 
    21132098return str.replace(/^\s+/,""); 
    21142099}else{ 
    2115 return str.replace(new RegExp("^["+_26d+"]+"),""); 
    2116 } 
    2117 }; 
    2118 MochiKit.Format.rstrip=function(str,_26f){ 
     2100return str.replace(new RegExp("^["+_26a+"]+"),""); 
     2101} 
     2102}; 
     2103MochiKit.Format.rstrip=function(str,_26c){ 
    21192104str=str+""; 
    21202105if(typeof (str)!="string"){ 
    21212106return null; 
    21222107} 
    2123 if(!_26f){ 
     2108if(!_26c){ 
    21242109return str.replace(/\s+$/,""); 
    21252110}else{ 
    2126 return str.replace(new RegExp("["+_26f+"]+$"),""); 
    2127 } 
    2128 }; 
    2129 MochiKit.Format.strip=function(str,_271){ 
     2111return str.replace(new RegExp("["+_26c+"]+$"),""); 
     2112} 
     2113}; 
     2114MochiKit.Format.strip=function(str,_26e){ 
    21302115var self=MochiKit.Format; 
    2131 return self.rstrip(self.lstrip(str,_271),_271); 
    2132 }; 
    2133 MochiKit.Format.truncToFixed=function(_273,_274){ 
    2134 _273=Math.floor(_273*Math.pow(10,_274)); 
    2135 var res=(_273*Math.pow(10,-_274)).toFixed(_274); 
    2136 if(res.charAt(0)=="."){ 
    2137 res="0"+res; 
     2116return self.rstrip(self.lstrip(str,_26e),_26e); 
     2117}; 
     2118MochiKit.Format.truncToFixed=function(_270,_271){ 
     2119var res=Math.floor(_270).toFixed(0); 
     2120if(_270<0){ 
     2121res=Math.ceil(_270).toFixed(0); 
     2122if(res.charAt(0)!="-"&&_271>0){ 
     2123res="-"+res; 
     2124
     2125
     2126if(res.indexOf("e")<0&&_271>0){ 
     2127var tail=_270.toString(); 
     2128if(tail.indexOf("e")>0){ 
     2129tail="."; 
     2130}else{ 
     2131if(tail.indexOf(".")<0){ 
     2132tail="."; 
     2133}else{ 
     2134tail=tail.substring(tail.indexOf(".")); 
     2135
     2136
     2137if(tail.length-1>_271){ 
     2138tail=tail.substring(0,_271+1); 
     2139
     2140while(tail.length-1<_271){ 
     2141tail+="0"; 
     2142
     2143res+=tail; 
    21382144} 
    21392145return res; 
    21402146}; 
    2141 MochiKit.Format.roundToFixed=function(_276,_277){ 
    2142 return MochiKit.Format.truncToFixed(_276+0.5*Math.pow(10,-_277),_277); 
     2147MochiKit.Format.roundToFixed=function(_274,_275){ 
     2148var _276=Math.abs(_274)+0.5*Math.pow(10,-_275); 
     2149var res=MochiKit.Format.truncToFixed(_276,_275); 
     2150if(_274<0){ 
     2151res="-"+res; 
     2152
     2153return res; 
    21432154}; 
    21442155MochiKit.Format.percentFormat=function(_278){ 
  • mochikit/trunk/tests/test_Format.js

    r1314 r1362  
    88    t.is( truncToFixed(0.15, 1), "0.1", "truncToFixed no round" ); 
    99    t.is( truncToFixed(0.15, 0), "0", "truncToFixed zero (edge case)" ); 
     10    t.is( truncToFixed(568.80, 2), "568.80", "truncToFixed 568.80, floating-point error" ); 
     11    t.is( truncToFixed(1.23e+20, 2), "123000000000000000000.00", "truncToFixed 1.23e+20" ); 
     12    t.is( truncToFixed(1.23e+21, 2), "1.23e+21", "truncToFixed 1.23e+21" ); 
     13    t.is( truncToFixed(-1.23e+20, 2), "-123000000000000000000.00", "truncToFixed -1.23e+20" ); 
     14    t.is( truncToFixed(-1.23e+21, 2), "-1.23e+21", "truncToFixed -1.23e+21" ); 
     15    t.is( truncToFixed(1.23e-10, 2), "0.00", "truncToFixed 1.23e-10" ); 
    1016 
    1117    t.is( roundToFixed(0.1234, 3), "0.123", "roundToFixed truncate" ); 
     
    1319    t.is( roundToFixed(0.15, 1), "0.2", "roundToFixed round" ); 
    1420    t.is( roundToFixed(0.15, 0), "0", "roundToFixed zero (edge case)" ); 
     21    t.is( roundToFixed(568.80, 2), "568.80", "roundToFixed 568.80, floating-point error" ); 
    1522 
    1623    t.is( twoDigitFloat(-0.1234), "-0.12", "twoDigitFloat -0.1234 correct"); 
     
    2431    t.is( twoDigitFloat(0.23), "0.23", "twoDigitFloat 0.23 correct"); 
    2532    t.is( twoDigitFloat(0.01), "0.01", "twoDigitFloat 0.01 correct"); 
     33    t.is( twoDigitFloat(568.80), "568.8", "twoDigitFloat, floating-point error"); 
     34 
    2635    t.is( percentFormat(123), "12300%", "percentFormat 123 correct"); 
    2736    t.is( percentFormat(1.23), "123%", "percentFormat 123 correct");