View Full Version : New Code Challenge!
beetle
01-24-2003, 08:52 PM
Ok folks, here's a new challenge for you, and this one can actually be useful!
The Challenge
Create a String/Number method to convert to a currency format. The method's name to be toCurrency
Specs The String method should be the primary method, with the Number method calling on the String method.
Should allow for specification of decimal places (default: 2)
Should allow for rounding or truncating (default: round)
Should allow for digit grouping (default: off)
Should allow for prepending of a monetary unit (default: off)
Should allow for custom decimal char (default: .)
Should allow for custom grouping char (default: ,)
Should allow for custom monetary symbol (default: $)
Should return a string containing the requested money format
Should work with ANY size number or number-string
Should return NaN if the String has non-numeric characters
Should be readable code, this is not a fewest-code-lines contest, but rather best/most efficient
Create supplemental methods or nested functions only if absolutely necessary for efficiency
Commenting or lack thereof will have no affect on grading
Bonus: Allow for appending of monetary unit if applicable (ie the ¢ for dollar amounts less than 1)I think it should go without saying that this should be made with standard code so it can be widely compatible.
Contest will end Wed the 18th (read: 29th) at 12pm CST or earlier, if I deem necessary.
Good luck!
Hint: If you look around, I've posted a rudimentary version of such a method in a couple posts already
mordred
01-24-2003, 10:12 PM
Wednesday the 18th? Which year? ;)
beetle
01-24-2003, 10:20 PM
Originally posted by mordred
Wednesday the 18th? Which year? ;) Hehe, OOOps.
Make that the 29th :o
A1ien51
01-24-2003, 10:22 PM
Good challange...
Borgtex
01-24-2003, 11:38 PM
Another string challenge? :(
beetle
01-24-2003, 11:58 PM
Originally posted by Borgtex
Another string challenge? :( Sorry, it's what I got. HTML is a string. :P
Algorithm
01-26-2003, 04:04 AM
OK, here's my entry:
Object.prototype.toCurrency = function(prefix, separator, fDigits, fRound, fSeparator, suffix);
This function can be called from any string, object, or initialized variable. Any or all of the arguments can be omitted. Since JavaScript does not allow a function call of format call( , , value), you may substitute null to retain the default setting.
Quick rundown of each argument:
prefix: Specifies the prefix to the currency string. Defaults: '' if omitted; '$' if non-string and evaluates to true.
separator: Specifies a separator to be placed every three digits in the string. Defaults: '' if omitted; ',' if non-string and evaluates to true.
fDigits: Number of fractional digits to be shown. Defaults to 2 if omitted or non-numeric, or to 0 if negative.
fRound: Specifies type of rounding. There are four types that can be specified:
- Floor (rounds down): Negative number
- Ceiling (rounds up): Positive number
- Truncate : Zero, or boolean false
- Round (default): Anything else
fSeparator: Specifies the fractional separator. Defaults: '.' if omitted; '' if non-string and evaluates to false.
suffix: Specifies the suffix to the currency string. Defaults: '' if omitted or non-string.Object.prototype.toCurrency = function(prefix, separator, fDigits, fRound, fSeparator, suffix){
if(/^\D*$/.test(this) || !(/^\s*(\-?)0*(\d*)(\.\d*)?\s*$/.test(this))) return 'NaN';
var setArg = function(arg,tval,dval){
var t = typeof(arg);
return (t=='undefined'||arg==null)?dval:(t=='string')?arg:(arg)?tval:"";
}
prefix = setArg(prefix,'$','');
separator = setArg(separator,',','');
fSeparator = setArg(fSeparator,'.','.');
suffix = setArg(suffix,'','');
fDigits = (typeof(fDigits)!='number')?2:(fDigits>0)?Math.floor(fDigits):0;
var n = RegExp.$2 || '0';
var f = RegExp.$3.slice(1,fDigits+1);
var r = parseFloat('0.'+RegExp.$3.slice(fDigits+1));
if((typeof(fRound)=='number')?(
((RegExp.$1)?(fRound<0):(fRound>0)) && (r>0)
):(
((typeof(fRound)=='boolean')?fRound:true) && ((RegExp.$1)?(r>0.5):(r>=0.5))
)){
var i; r = '';
for(i=(fDigits-1);(i>=0 && ((!r) || r.charAt(0)=='0'));i--){
r = '' + ((parseInt(f.charAt(i))+1)%10) + r;
}
f = f.slice(0,i+1) + r;
if((!r) || r.charAt(0)=='0'){
r = '';
for(i=(n.length-1);(i>=0 && ((!r) || r.charAt(0)=='0'));i--){
r = '' + ((parseInt(n.charAt(i))+1)%10) + r;
}
n = n.slice(0,i+1) + r;
if((!r) || r.charAt(0)=='0') n = '1' + n;
}
} else {
while(f.length < fDigits) f += '0';
}
if(parseFloat(n+'.'+f)>0) prefix = RegExp.$1 + prefix;
if(f) f = fSeparator + f;
if(separator){
var digits = 3;
while(n.length > digits){
f = separator + n.slice(n.length-digits) + f;
n = n.slice(0,-digits);
}
}
return prefix + n + f + suffix;
}
joh6nn
01-26-2003, 11:50 AM
i think that commented code ought to be required for challenges like this. makes it easier to judge entries, and to figure out how the winning entry works.
anyway, here's my entry. tested on Moz 1.1. i will grant that technically, it doesn't meet all the requirements; it will not return NaN if it's used on something that's not a number. in it's defense, though, it's a method of numbers, and can't be used on anything that's not a number, so NaN shouldn't ever come up.
also, my entry is really 3 methods, not one. i could have broken it down farther, i think, but it was starting to get late. the reason i did this, is because i like to keep my code reusable, and i thought this was better achieved by separating out the building blocks of the function.
dollar: the prefix to prepended. ' ' for none
comma: the group delimiter
three: the size of the groups
period: the decimal char
r_c: "round" or "chop" the decimals
laenge: the number of decimals to show.
cents: the suffix to append if the result is less than 1
Number.prototype.group = function(comma, three) {
var delimit = comma || ',', size = three || 3; //if no arguments were passed, assign defaults
var temp = "", that = this + '', j = 0; // declare our variables
for (var i = that.length - 1; i >= 0; i--) { //we have to count backwards from the end of the string
temp = that.charAt(i) + temp; // last char + 2nd last char + 3rd last char et al
if ((++j % size) == 0) { temp = delimit + temp; } // if we've done the right number of characters, delimit
}
return temp;
}
Number.prototype.roundTo = function(num_1) {
return ( Math.round(this * Math.pow(10, num_1)) / Math.pow(10, num_1) ); //this is mostly self explanatory
}
Number.prototype.toCurrency = function(dollar, comma, three, period, r_c, laenge, cents) {
var prefix = dollar || '$', groupDelimit = comma || ',', groupSize = three || 3, decimal = period || '.';
var round = r_c || 'round', dlong = laenge || 2, suffix = cents || '¢'; //if no arguments were passed, assign defaults
var temp, that = this + '', whole, fractional = ''; // declare our variables
if (dlong == 0) {decimal == '';} // if there are no decimals, we don't need a decimal char
if (round = 'round') { that = this.roundTo(dlong) + ''; } //round now, so that if it rounds up, then the whole part rounds up too.
fractional = (that.indexOf('.') > -1) ? that.slice(that.indexOf('.')+1, that.indexOf('.') + 1 + dlong) : ''; //in all cases, we assign truncate, and assign our decimals to a variable
if ( fractional.length < dlong ) { for ( var i = fractional.length ; i < dlong; i++ ) { fractional += '0'; } } //append 0's, if necessary
whole = (that.indexOf('.') > -1) ? Number(that.slice(0, that.indexOf('.'))) : Number(that); // assign the whole part to a variable
if (whole < 1) { temp = fractional + suffix; } // if the whole part is 0, then return answer in cents
else { temp = prefix + whole.group(groupDelimit, groupSize) + decimal + fractional; } // otherwise, put the pieces together
return temp;
}
whammy
01-27-2003, 01:05 AM
My humble entry:
function toCurrency(num,numdecimalplaces,truncate,group,groupchar,decchar,monunit,customunit) {
/*
truncate - default: 0 (round), 1 (truncate)
group - default: 0 (no), 1 (group)
*/
var dp = Math.pow(10,numdecimalplaces)
truncate ? num = Math.floor(num * dp) / dp : num = Math.round(num * dp) / dp;
decchar ? void 0 : decchar = ".";
if(!isNaN(num)) {
num.toString().indexOf(".") == -1 ? num += "." : void 0;
while(num.toString().split(".")[1].length < numdecimalplaces) {
num += "0";
}
if(group) {
var decsplit = num.toString().split(".");
var objRegExp = new RegExp('(-?\[0-9]+)([0-9]{3})');
while(objRegExp.test(decsplit[0])) decsplit[0] = decsplit[0].replace(objRegExp,'$1,$2');
num = decsplit.join(".");
}
monunit ? (customunit ? num = customunit + num : num = "$" + num) : void 0;
num = (groupchar ? num.toString().replace(/\,/g,groupchar) : num);
}
return num.toString().replace(/\./,decchar);
}
beetle
01-30-2003, 04:38 PM
Well, I cooked up a little test page to help me grade these. Hard to decide. Although they all need some tweaking to be 'production ready', from the looks of it, joh6nn has the best entry, being that is produces the most accurate results in most cases.
http://www.peterbailey.net/tocurrency.htm
What do you guys think?
Vladdy
01-30-2003, 05:47 PM
Originally posted by beetle
What do you guys think?
How about extending the deadline?:D :D I've been awfully busy lately.....
beetle
01-30-2003, 06:00 PM
Originally posted by Vladdy
How about extending the deadline?:D :D I've been awfully busy lately..... Sure, I dont mind making this open ended. Let's shoot for, oh.. Monday? That would be Monday, February the 3rd. I live in the Central Time Zone, so you people can figure out when that is for you
ConfusedOfLife
01-30-2003, 07:41 PM
Hi, it might look funny to you, but our date system is totally different with yours. It means that whenever I wana check up a date, I have to go to the diary! So, by saying the 29th, do you mean the 29th of February? Since January is already finished ( as I checked it! ).
PS: Most of the times I even forget our date too! I remember when I wana write a cheque ppl help me with the day, then they go to the month, but they can't tolerate it when they see I don't remember the year either!
Algorithm
01-30-2003, 08:56 PM
Originally posted by beetle
Well, I cooked up a little test page to help me grade these. Hard to decide. Although they all need some tweaking to be 'production ready', from the looks of it, joh6nn has the best entry, being that is produces the most accurate results in most cases.
http://www.peterbailey.net/tocurrency.htm
What do you guys think?
As far as I can tell, my script is performing to spec in all cases.
In the first case, you're specifically specifying that the decimal char should be an empty string, so my implementation works as directed.
And as for cases involving a suffix, the reason my code always appends it is for situations in which a suffix would be needed in all cases, such as "54 dollars and 23 cents" (in which case " dollars and " is the fractional separator and " cents" is the suffix). Besides, the practice of omitting the fractional separator and appending a suffix when the amount is less than 1 only makes sense if there will always be the same number of fractional digits. Converting from "0.67" to "6700 cents" doesn't make sense at all.
Additionally, joh6nn's script doesn't handle negative numbers, and won't handle numbers in excess of 100,000,000,000,000, so I don't see why you're claiming his script is more accurate.
beetle
01-30-2003, 09:16 PM
Algorithm
Your comments defending your script remind me of Joel's article on architecture astronauts (http://www.joelonsoftware.com/articles/fog0000000018.html). My point is, regardless of your strict adherence to the specs, you shouldn't lose sight of the fact that this script is intended to format a string or number as currency. Yes, an empty string can be the grouping or decimal character, but does ANY known monetary or numerical system use that. No. Also, according to your profile, you live in the US so you should darn well know the expressing a dollar value as $ X.XX ¢ is just absurd.
I never said anyone's submission was perfect or best, I simply stated that with the test data I used, joh6nn's submission produced the most number of accurate results. I didn't acutally count. Given different or more test data, the results could very well be different.
Don't get me wrong, you are obviously a very talented programmer, and I mean no ill-will by my comments.
A few problems i see with joh6nn's script is that it doesn't handle negative numbers, grouping of large values, and that i had to insert the String method for him (easy, but still)
whammy
01-30-2003, 11:48 PM
Would it be possible for me to submit a new entry, as this:
Data format is:
monetaryUnit, groupingChar, decimalChar, bRound, decimalPlaces, monetarySuffix
was not posted in the initial challenge? :)
Also, I get quite different test results (correct in all cases except that of negative numbers), because of the way I was passing parameters to the function.
I could easily amend my script to give the correct results (minus a necessary negative number fix - which should also be simple), by allowing for your method of passing data to the function (I should have considered that part, though...).
beetle
01-31-2003, 12:04 AM
No no no, whammy, that's just the order that the test-data is displayed in on the testing page ;)
However, feel free to edit you existing post, just let me know here or by PM when it has been done :D
joh6nn
01-31-2003, 12:34 AM
beetle, can you clarify what you mean by you had to insert the string method? also, what do you mean that it won't handle large groupings? even if i can't re-submit it, i'd like to fix the shortcomings of my entry.
also, i'd like to give props to algorithm, for his setArg() function. very nice.
beetle
01-31-2003, 01:18 AM
Oh, I mean this
String.prototype.toCurrency= function(d, c, t, p, r, l, ce )
{
return parseFloat( this ).toCurrency( d, c, t, p, r, l, ce );
}
So the method can be enacted on Strings, too. You can see how my test page works by viewing the source
Anyhow, I took Algorithm's word that your version didn't work for really big numbers, I have yet to test it (busy day today) so apologies if both he and I are incorrect on that.
This isn't a real strict "do it right the first time, or else" contest. I'm looking for a real, usable function so please, anyone, feel free to modify your original post, just notify me here or by PM so I know when to re-test it.
whammy
01-31-2003, 02:31 AM
According to Netscape 4.78, your method of passing values to the function:
alert(toCurrency(this.value,,,,,,,))
for example, is incorrect... :confused:
Algorithm
01-31-2003, 03:46 AM
The reason joh6nn's function doesn't work for large values is that it relies implicitly on the Number.toString() method, which returns values in scientific notation once they get sufficiently large.
I think I will let my current function stand. The only change I could think to make to it would be to add a seventh parameter to define when the suffix is displayed, but to stay true to my astronaut heritage ;) I would have to require the argument be a function reference, which gets altogether too complicated.
beetle
01-31-2003, 04:34 AM
Originally posted by whammy
According to Netscape 4.78, your method of passing values to the function:
alert(toCurrency(this.value,,,,,,,))
for example, is incorrect... :confused: Who me? That's not how I pass them. Look a the code on the page, it's pretty straightfoward, even for a JS Dunce like you (HAHA! :p )
Just kidding, dude ;)
A1ien51
01-31-2003, 07:32 AM
Quick Question, I am playing around with this with an old script I have and not sure what fRound is exactly.
Can someone explain it, I just can't seem to see what it is, (I blame it on lack of sleep)
thanks
A1ien51
Also am I reading this the same
Spot-->function
1--> $, Euro Etc
2--> Comma
3--> Decimal Point
4--> ????
5--> Number Decimal Places to be shown
6--> Cents Sign
is 4 to be rounded or not??
Hopefully when I wake up tom. there will be an answer to my stupidity!!
beetle
01-31-2003, 01:26 PM
Yes, 4 should be a boolean indicating
true: round
false: truncate
A1ien51
01-31-2003, 04:55 PM
Okay here is my bad looking Regular expressionless code, full of rebuilding the wheel, but I think it does the basic job. I am not sure if I got all of the defaults working the right way, but I guess we will see. I hope I have it in the right format for you too. If not just yell, and I wil fix it.
<script>
//Show features //true=yes false=no
var AvoidCentHolder = true; //Show extra zero before cent. (example false=01¢ true=1¢)
//Specifications
var DecPlaces=2;
var DecimalSymbol = ".";
Object.prototype.toCurrency = function(prefix, separator, fDigits, fRound, fSeparator, suffix){
Cur=this*1;
if(Cur){ //Check to see if valid number
if(Cur<0)Neg=true; //Check for Neg. Number
else Neg=false;
if(fSeparator==false)fSeparator=DecPlaces;
if(fSeparator){ //Cut-off -- Round to n-decimal places Part#1
if(fRound)Cur=Math.round(Cur*Math.pow(10,fSeparator))/Math.pow(10,fSeparator);
else Cur=Math.floor(Cur*Math.pow(10,fSeparator))/Math.pow(10,fSeparator);
}
Cur+="";Parts=Cur.split(".");
if(Parts.length==1){
if(Parts[0]>=0){
Parts[1]="00";
}
}
if(fSeparator){ //Cut-off -- Round to n-decimal places Part#2 -- Fill Gap
if(Parts[1].length<fSeparator){
diff=fSeparator-Parts[1].length;
for(i=0;i<diff;i++){Parts[1]+="0";}
}
}
if(separator){ //Add commas to group
Spots=Math.floor((Parts[0].length-1)/3);
CurrentPost=Parts[0].length;
for(i=0;i<Spots;i++){
CurrentPost-=3;
Parts[0]=Parts[0].slice(0,CurrentPost)+separator+Parts[0].slice(CurrentPost,Parts[0].length);
}
}
if(suffix &&Parts[0]=="0"){ //Show cent sign if it applies
if(Parts[1].length>2){Parts[1]=Parts[1].slice(0,2)+DecimalSymbol+Parts[1].slice(2,Parts[1].length);}
if(Parts[1].slice(0,1)==0&&AvoidCentHolder)Parts[1]=Parts[1].slice(1,Parts[1].length);
Cur = Parts[1]+suffix;
}
else{ //If Whole Dollar Amount
if(prefix){M=prefix;}
else M="";
if(Neg)N="-";
else N="";
if(fDigits)DecimalSymbol=fDigits;
Cur=M+N+Parts[0]+DecimalSymbol+Parts[1];
}
}
return(Cur);
}
</script>
whammy
02-04-2003, 09:26 PM
Hmm, not sure why your testing method doesn't match up with mine, but I put in the same figures and my script worked where your test of it didn't (except monetary suffix, I didn't add that in since it was optional). :confused:
Here's the code with added () for negative numbers:
<script type="text/javascript">
<!--
function toCurrency(num,numdecimalplaces,truncate,group,groupchar,decchar,monunit,customunit) {
var dp = Math.pow(10,numdecimalplaces)
truncate ? num = Math.floor(num * dp) / dp : num = Math.round(num * dp) / dp;
decchar ? void 0 : decchar = ".";
neg = num < 0;
if(!isNaN(num)) {
num.toString().indexOf(".") == -1 ? num += "." : void 0;
while(num.toString().split(".")[1].length < numdecimalplaces) {
num += "0";
}
if(group) {
var decsplit = num.toString().split(".");
var objRegExp = new RegExp('(-?\[0-9]+)([0-9]{3})');
while(objRegExp.test(decsplit[0])) decsplit[0] = decsplit[0].replace(objRegExp,'$1,$2');
num = decsplit.join(".");
}
monunit ? (customunit ? num = customunit + num : num = "$" + num) : void 0;
num = (groupchar ? num.toString().replace(/\,/g,groupchar) : num);
}
return neg == false ? num.toString().replace(/\./,decchar).replace(/\-/,'') : "(" + num.toString().replace(/\./,decchar).replace(/\-/,'') + ")";
}
// -->
</script>
<form>
<input type="text" onblur="alert(toCurrency(this.value,2,0,1,',','.',1,0))" />
</form>
beetle
02-04-2003, 10:01 PM
Ok, this contest is finished for the time being. No new entries please while I grade the submissions.
A1ien51
02-07-2003, 09:18 PM
Is the professor done grading yet?
beetle
02-07-2003, 09:30 PM
Originally posted by A1ien51
Is the professor done grading yet? Hehe, no. Big project rollout here at work this week. I'll "grade" this weekend :p
joh6nn
10-12-2003, 02:11 AM
i recently had need of this script, so i thought that i'd post the corrections i'd made to it. i've cleaned it up a bit, and added a few more comments. it's still relying on JavaScript's built in Number().toString() method, so it still can't handle extremely large numbers, but if i recall, that boundry is somewhere up over a trillion or so, so it's not likely that it's a problem you'll run into.
anyway, here it is:
Number.prototype.group = function(comma, three) {
/*
if no arguments were passed, assign defaults
*/
var delimit = (comma == undefined) ? ',' : comma;
var size = (three == undefined) ? 3 : three;
var temp = "", prefix = '', decimal = '', that = this + '', j = 0;
/*
fixes a bug encountered when grouping negative numbers
*/
if ( this < 0 ) {
prefix = "-";
that = that.slice(1);
}
/*
i'm not gonna worry about grouping after the decimals place
it rarely comes up, anyway
*/
if (that.indexOf('.') >= 0) {
decimal = that.slice(that.indexOf('.') + 1);
that = that.slice(0, that.indexOf('.'));
}
/*
count backwards from the end of the string
if we've done the right number of characters, delimit
*/
for (var i = that.length - 1; i >= 0; i--) {
temp = that.charAt(i) + temp;
if (((++j % size) == 0) && (i != 0) ){ temp = delimit + temp; }
}
return (prefix + temp);
}
Number.prototype.roundTo = function(num_1) {
return ( Math.round(this * Math.pow(10, num_1)) / Math.pow(10, num_1) ); //this is mostly self explanatory
}
Number.prototype.toCurrency = function(dollar, comma, three, period, r_c, laenge, cents) {
/*
if no arguments were passed, assign defaults
*/
var prefix = (dollar == undefined) ? '$' : dollar;
var groupDelimit = (comma == undefined) ? ',' : comma;
var groupSize = (three == undefined) ? 3 : three;
var decimal = (period == undefined) ? '.' : period;
var round = (r_c == undefined) ? 'round' : r_c;
var dlong = (laenge == undefined) ? 2 : laenge;
var suffix = (cents == undefined) ? '¢' : cents;
var temp, that = this + '', whole, fractional = '';
/*
if there are no decimals, we don't need a decimal char
*/
if (dlong == 0) {decimal == '';}
/*
round now, so that if it rounds up, then the whole part rounds up too.
*/
if (round = 'round') { that = this.roundTo(dlong) + ''; }
/*
i didn't like how "$-" looked as a prfefix for negative numbers, so i added this in to change it to "-$".
it also fixes a bug for numbers between -1 and 0
*/
if ( this < 0 ) {
prefix = "-" + prefix;
fracprefix = "-";
that = that.slice(1);
}
/*
assign our decimals to a variable
*/
fractional = (that.indexOf('.') > -1) ? that.slice(that.indexOf('.')+1, that.indexOf('.') + 1 + dlong) : '';
/*
assign the whole part to a variable
*/
whole = (that.indexOf('.') > -1) ? Number(that.slice(0, that.indexOf('.'))) : Number(that);
/*
append 0's, if necessary
*/
if ( fractional.length < dlong ) {
for ( var i = fractional.length ; i < dlong; i++ ) {
fractional = (whole == 0) ? ('0' + fractional) : (fractional + '0');
}
}
/*
if the whole part is 0, then return answer in cents
*/
if (whole == 0) { temp = fracprefix + fractional + suffix; }
/*
otherwise, put the pieces together
*/
else { temp = prefix + whole.group(groupDelimit, groupSize) + decimal + fractional; }
return temp;
}
vBulletin® v3.8.2, Copyright ©2000-2012, Jelsoft Enterprises Ltd.