PDA

View Full Version : formatting for dollars



charlesb
11-04-2005, 07:12 PM
I have a web site I am working on that takes dynamic data (coming in from a JavaScript array) and displays it in an HTML form. The problem is, I can't find any way in HTML, XHTML, or JavaScript to format the data in the cell to a proper currency format. I want the data to gain the structure of ###,###.## where the hash marks represent a floating point value.

I would really like to do this via JavaScript and not ASP or PHP, as I know nothing about them. One suggestion I have received is to use a scalable integer, but I'd kind of like to handle it via floating point math to make the code easier to understand and maintain.

Any suggestions? I would think, given how currency values are so common in everyday use that something like this would exist, but I've not yet found it.

jscheuer1
11-04-2005, 09:12 PM
If you have commas in the output, math (at some point) is out of the question. Try various initial values for the variable tot:


<script type="text/javascript">
var tot = 1000324.4
var tot=tot.toString().replace(/,/g, '')
tot=tot.indexOf('.')==-1? tot+='.00' : tot.length-tot.indexOf('.')==1? tot+='0' : tot
tot=tot.split('.')
tot[2]=''
var count=0
for (var i_tem = tot[0].length-1 ; i_tem > -1 ; i_tem--){
if (count&&count%3==0)
tot[2]+=','
tot[2]+=tot[0].charAt(i_tem)
count++
}
tot[0]=''
for (var i_tem = tot[2].length-1 ; i_tem > -1 ; i_tem--)
tot[0]+=tot[2].charAt(i_tem)
tot='$'+tot[0]+'.'+Math.round(Math.abs('.'+tot[1])*100)
tot=tot.length-tot.indexOf('.')==2? tot+='0' : tot
alert(tot)
</script>

Since I don't know the full spectrum of possible initial values, their origin or how they typically get into the table cells, the above will serve as a general idea. If you need help adapting it, give me the specifics.

mwinter
11-05-2005, 02:12 AM
I want the data to gain the structure of ###,###.## where the hash marks represent a floating point value.See my toCurrency (http://www.dynamicdrive.com/forums/showthread.php?t=4026#post13971) method.


One suggestion I have received is to use a scalable integer, but I'd kind of like to handle it via floating point math to make the code easier to understand and maintain.Whenever money is involved, calculations should always be performed using scaled integers, with as much integer arithmetic as possible. Floating point maths is inaccurate due to the inability to accurately represent some fractional parts in base-2.

Mike

jscheuer1
11-05-2005, 05:18 AM
Very interesting, Mike. It ignores the commas that the OP requested but otherwise works very well. I'm wondering how long it will take me to understand what you've done. I'll either learn a lot or give up :) Anyways, I got interested in this one and played around with my version a bit more. I've come up with a function that formats dollars and cents with a dollar sign, commas (if appropriate) and two decimal places, with (I believe) iron clad 'error checking' on the input for validity. In the demo below the input field activates the function 'onchange' meaning to see your result appear in the same input field where you entered the raw data, you must click anywhere else on the page:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
<html>
<head>
<title>Format to Dollars and Decimal Cents - Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<script type="text/javascript">
function dollarize(tot){
var orig=tot, count=0
var tot=tot.toString().replace(/[,\$]/g, '')
for (var i_tem = 0; i_tem < tot.length; i_tem++){
count=tot.charAt(i_tem)=='.'? count+1 : count
if ((isNaN(tot.charAt(i_tem))&&tot.charAt(i_tem)!=='.')||count>1){
alert('Valid Numbers or Valid Dollar Values only Please')
return orig;
}
}
tot=tot.indexOf('.')==-1? tot+='.0' : tot.length-tot.indexOf('.')==1? tot+='0' : tot
tot=tot.split('.')
tot[2]=''
count=0
tot[0]=parseInt(tot[0], 10).toString()
for (var i_tem = tot[0].length-1 ; i_tem > -1 ; i_tem--){
if (count&&count%3==0)
tot[2]+=','
tot[2]+=tot[0].charAt(i_tem)
count++
}
tot[0]=''
for (var i_tem = tot[2].length-1 ; i_tem > -1 ; i_tem--)
tot[0]+=tot[2].charAt(i_tem)
tot='$'+(tot[0]==''? '0' : tot[0])+'.'+Math.round(Math.abs('.'+tot[1])*100)
tot=tot.length-tot.indexOf('.')==2? tot+='0' : tot
return tot;
}
</script>
</head>
<body>
Enter Dollar amount below:<br>
<input type="text" onchange="this.value=dollarize(this.value)">
</body>
</html>

mwinter
11-05-2005, 12:22 PM
It ignores the commas that the OP requested but otherwise works very well.No, it doesn't. :p I just assumed that I explained the arguments in that thread (I usually do).

All are optional strings. The first (c) is the currency symbol, which defaults to none (an empty string). The second (t) is the thousands grouping symbol. This also defaults to none, so the OP will want to pass a comma. The third (d) is the decimal symbol, which defaults to a dot.

I do have some other code that could be used to more easily parse the resulting strings (including European formats: 1.234,56) back to numbers, but as I recall the regular expression pattern it uses is broken. I didn't check last night, I just decided not to post it.


I'm wondering how long it will take me to understand what you've done.You know I have no problem explaining any part of that post.


var tot=tot.toString().replace(/[,\$]/g, '')The backslash isn't necessary within the character class. A literal carat (^) would need escaping if it was first in the class. A literal closing bracket (]) would need escaping anywhere, as would a backslash itself. A literal hyphen (-) would need escaping if it had pattern characters before and after (for matching 'a', 'z', and '-', an escape is needed in [a-z], but not [-az] or [az-]).


for (var i_tem = 0; i_tem < tot.length; i_tem++){
count=tot.charAt(i_tem)=='.'? count+1 : count
if ((isNaN(tot.charAt(i_tem))&&tot.charAt(i_tem)!=='.')||count>1){
alert('Valid Numbers or Valid Dollar Values only Please')
return orig;
}
}I'd use a regular expression here. For instance, in the isReal method in the other thread (http://www.dynamicdrive.com/forums/showthread.php?t=4026#post13971). I find them simpler to work with.


tot[0]=parseInt(tot[0], 10).toString()I'm curious about that because I don't believe it does anything. You've already validated the string, so it will only contain digits and a single dot. As you've split on that delimiter, the elements will only contain digits, so the string above should always be the same before and after. Unless I've missed something.


for (var i_tem = tot[2].length-1 ; i_tem > -1 ; i_tem--)
tot[0]+=tot[2].charAt(i_tem)
You could use the reverse method here, instead.

Mike

jscheuer1
11-05-2005, 01:36 PM
Mike, I'll look into those delimiter options.


You know I have no problem explaining any part of that post

Thanks, it is more fun to work out why code works, like a puzzle. That way I usually remember more of it's methodology. I'm not worried about it though, I know you are always good that way, I was still focusing on getting my version working though. On to your questions as regards that, I too am always willing to explain the workings of my alleged mind:

This:


var tot=tot.toString().replace(/[,\$]/g, '')

was condensed from a longer version that did require the escaping the $.




for (var i_tem = 0; i_tem < tot.length; i_tem++){
count=tot.charAt(i_tem)=='.'? count+1 : count
if ((isNaN(tot.charAt(i_tem))&&tot.charAt(i_tem)!=='.')||count>1){
alert('Valid Numbers or Valid Dollar Values only Please')
return orig;
}
}
I'd use a regular expression here. For instance, in the isReal method in the other thread. I find them simpler to work with.

Just what I am more familiar with in this case.

This was added last:


tot[0]=parseInt(tot[0], 10).toString()

when I discovered that an initial value of '00.00' would yield '$00.00' when what I was looking for, for that, is '$0.00'




for (var i_tem = tot[2].length-1 ; i_tem > -1 ; i_tem--)
tot[0]+=tot[2].charAt(i_tem)


You could use the reverse method here, instead.

I went right to the reverse method in one of my favorite online resources for scripting methods but, it appeared to me that it only applied to arrays. In the above, I am reversing a string. I thought, "How odd you can reverse an array but not a string", and thought I might be missing something but, just wanted to get it written so, came up with the above method.

jscheuer1
11-05-2005, 02:37 PM
OK, I messed around with this a bit more and this incorporates most of Mike's suggestions. I wasn't sure how to use regular expressions in the validation because of the need to ensure that the decimal point appears only once in the input. I also updated it to take care of an error that had crept in when I added the parseInt check, and condensed the code a bit as well (Mike's idea about the reverse method helped with this too):


<script type="text/javascript">
function dollarize(q){
var tot=q.toString().replace(/[,$]/g, ''), count=0
for (var i_tem = 0; i_tem < tot.length; i_tem++){
count=tot.charAt(i_tem)=='.'? count+1 : count
if ((isNaN(tot.charAt(i_tem))&&tot.charAt(i_tem)!=='.')||count>1){
alert('Valid Numbers or Valid Dollar Values only Please')
return q;
}
}
tot=tot.indexOf('.')==-1? tot+='.0' : tot.length-tot.indexOf('.')==1? tot+='0' : tot
tot=tot.split('.')
tot[2]=''
count=0
tot[0]=tot[0]==''? '0' : parseInt(tot[0], 10).toString()
for (i_tem = tot[0].length-1 ; i_tem > -1 ; i_tem--){
if (count&&count%3==0)
tot[2]+=','
tot[2]+=tot[0].charAt(i_tem)
count++
}
tot='$'+tot[2].split('').reverse().join('')+'.'+Math.round(Math.abs('.'+tot[1])*100)
tot=tot.length-tot.indexOf('.')==2? tot+='0' : tot
return tot;
}
</script>

mwinter
11-05-2005, 02:38 PM
[My offer to explain]

Thanks, it is more fun to work out why code works, like a puzzle. That way I usually remember more of it's methodology.I know, which is why I just restated the offer. Besides, I'm all for people learning things for themselves - it's what I did (for the most part). :D


This was added last:


tot[0]=parseInt(tot[0], 10).toString()

when I discovered that an initial value of '00.00' would yield '$00.00' when what I was looking for, for that, is '$0.00'Ah, I see. The regular expression I referred to wouldn't allow leading zeros. If I was concerned about it, I'd strip them out of the string at the start before validation:



string.replace(/^0*(\d)/, '$1')

/* Or, to maintain the sign:
*
* (/^([+-]?)0*(\d)/, '$1$2')
*/
Your way could be simplified a bit:



tot[0] = String(+tot[0]);



I went right to the reverse method in one of my favorite online resources for scripting methods but, it appeared to me that it only applied to arrays.I could have sworn there was one, but obviously not. I wouldn't have ever used either to date, in any case.

Mike

jscheuer1
11-06-2005, 03:13 AM
Looks like we cross posted again, Mike. Been a while. Anyways, you might want to check over my post from right before yours in this thread, just out of interest. I incorporated some of your ideas, streamlined a bit and found and fixed a problem with parseInt(). Two main things:

1 ) Found a way to reverse a string using the reverse() method:


string=string.split('').reverse().join('');

Doesn't work with a true number, so if there is any doubt:


string=string.toString().split('').reverse().join('');

2 ) I'm at a loss as to how to use a regex to determine if there are none or one and only none or one decimal point(s) in the original value, seems doable, though it might not be as economical, code wise, as my method. I'll leave the determination on processing efficiency up to you.

mwinter
11-06-2005, 06:23 PM
I [...] found and fixed a problem with parseInt().The parseInt function, applied to an empty string, will return NaN. The unary plus (+) operator - the alternative I suggested - will type-convert an empty string, or a string of white space, to +0.

I tend to reserve parseInt for situations where I want to convert from bases other than decimal or hexadecimal (as unary + handles both ), or when the string ends with non-digit characters that can be safely ignored, such as the units in CSS length values.


1 ) Found a way to reverse a string using the reverse() method:


string=string.split('').reverse().join('');Personally, I'd avoid it altogether.


Doesn't work with a true number, so if there is any doubt:


string=string.toString().split('').reverse().join('');Though calling [i]toString method works here, I prefer to use the String constructor function for conversions and allow the interpreter to determine how to perform the operation. This works in all circumstances, whereas an object doesn't necessarily need to have a toString method.

I'm not saying, "Change it!"; just sharing methodology.


2 ) I'm at a loss as to how to use a regex to determine if there are none or one and only none or one decimal point(s) in the original value, seems doable, though it might not be as economical, code wise, as my method.The simplest regular expression that would match a number - either integer or floating point - is:



/\d+(\.\d+)?/
This would allow leading zeros, but it would also match any string that contained a number. So, to prevent the latter, one can add assertions that match the beginning and end of a string:



/^\d+(\.\d+)?$/
The expression would now match a string that contained only a number.

The regular expression in the other thread (http://www.dynamicdrive.com/forums/showthread.php?t=4026#post13971) is the next evolution, forbidding leading zeros and allowing a sign.

As for usage, it's as simple as:



if(/^\d+(\.\d+)?$/.test(string)) {
/* Matches number pattern */
}
Mike

jscheuer1
11-06-2005, 09:30 PM
With:


if(/^\d+(\.\d+)?$/.test(string))

as I look over the MS JScript Regular Expression Syntax, it looks like:

.04

would not pass. A test confirms this. .04 will pass my method and becomes:

$0.04

The above regex also returns false on:

4.

Which I chose to include as valid because anyone typing it would probably mean:

4

But, the more I think about that one, it is not clear, so I will probably disallow it.

I'm now thinking that I want this as a test:


if(/^\d*\.?\d+$/.test(string))

which I interpret to mean:

One or more digits, preceded by one or no decimal point, preceded by zero or more digits, no letters or other characters allowed. This is to be applied to the string as a test after all commas and a leading dollar sign (if either are present) have been stripped from it. How, if at all, can I combine these two replace() methods into one:


var tot=q.toString().replace(/^\$?/, '').replace(/,/g, '')

mwinter
11-07-2005, 12:50 AM
With:


if(/^\d+(\.\d+)?$/.test(string))

[...] it looks like:

.04

would not pass.Correct, and I wouldn't want it to.


The above regex also returns false on:

4.Again, by design.


Which I chose to include as valid because anyone typing it would probably mean:

4

But, the more I think about that one, it is not clear, so I will probably disallow it.Precisely. The same as with the previous case.

Though numbers that pass even the most stringent tests can be the victims of typos (100.9 instead of 10.09, for example), I believe it's best to eliminate cases that are more likely to be errors, than not. Only the user can really know what they meant to write, so the onus should be on them to get it right, not for software to start guessing.


How, if at all, can I combine these two replace() methods into one:


var tot=q.toString().replace(/^\$?/, '').replace(/,/g, '')Using the Alternative production:



var tot = String(q).replace(/^\$|,/g, '');
The expression would match a dollar symbol at the start of q or any comma. The quantifier isn't necessary.

Notice that alternatives can affect assertions. If the expression was meant to match only a dollar or comma at the start of a string, the alternative would need to be enclosed in parentheses (non-capturing preferably, but those should be avoided until NN4 and the like competely die out):



/^(\$|,)/
Mike

charlesb
11-07-2005, 04:06 PM
Thanks for all the great comments on my orginal post!

Over the weekend I took the first reply I had from John home and tried to figure out what the code was doing. I pretty well sussed it out, and decided it would work well enough for what I need on my web site. Since I will be setting the parameters on what kind of data will be used, the code works for me. John, with your permission, I will use the following in my function library for the web site:


//formats floating point number into standard currency format
function CurrencyFormat(number)
{ //begin function
var amountString = " ";
var count=0;
var subStrings = new Array();

amountString=number.toString().replace(/,/g, '');
//pads decimals with zeros, if required
amountString=amountString.indexOf('.')==-1 ? amountString+='.00' : amountString.length-amountString.indexOf('.')==1 ? amountString+='0' : amountString;
//splits String of amount in array of stings at decimal point: subStrings[0] is whole dollars, subStrings[1] is cents
subStrings=amountString.split('.');
//expands subStrings array
subStrings[2]=" ";

for (var i = subStrings[0].length-1 ; i > -1 ; i--)
//counts out whole dollars in threes, and adds commas as needed
{ //begin for
if (count&&count%3==0)
{ //begin if
subStrings[2]+=",";
} //end if
subStrings[2]+=subStrings[0].charAt(i);
count++;
} //end for

subStrings[0]="";

for (var i = subStrings[2].length-1 ; i > -1 ; i--)
{ //begin for
subStrings[0]+=subStrings[2].charAt(i);
} //end for

amountString="$" + subStrings[0] + "." + Math.round(Math.abs("." + subStrings[1])*100);
//adds leading whole dollar zero if amount is less than $1
amountString=amountString.length-amountString.indexOf(".")==2 ? amountString+="0" : amountString;

return(amountString);
} //end function

Since I come from a Java programming background, I made a few changes to the variable names and syntax appearance, though I did not change any of the code structure. Java is strongly typed, meaning that you can't change the asignment of a variable in mid-program from String to Array and back again. I made the changes (and added the comments) to help me understand what the code was doing.

I will now go back over your other posts to try and follow the discussion. My JavaScript is not as strong as I'd like, and following the discussions here helps improve it!

jscheuer1
11-07-2005, 08:10 PM
I think that your code, if the original amount has 99.5 cents (initial value of say 1.995) will produce unexpected results. Also a leading zero will not be added to the amount in the defined situations as your code states, certainly not by the code following your comment about that (that part is to pad the 'cents' portion with a trailing zero, if needed). The first problem is simply inherited from my example. The second may be a problem inherint in my original or of ommision in your rewrite. In any event, here is my most recent updated version, without either problem and incorporating some of Mike's suggestions for effidiency:

<script type="text/javascript">
function dollarize(q){
var tot=q.toString().replace(/^\$|,/g, ''), count=0
if (!/^\d*\.?\d+$/.test(tot)){
alert('Valid Numbers or Valid Dollar Values only Please')
return q;
}
tot=tot.indexOf('.')==-1? tot+='.0' : tot.length-tot.indexOf('.')==1? tot+='0' : tot
tot=tot.split('.')
tot[1]=Math.round(Math.abs('.'+tot[1])*100).toString()
if(tot[1].length>2){
tot[0]=tot[0]==''? '1' : (parseInt(tot[0], 10)+1).toString()
tot[1]=tot[1].charAt(1).toString()+tot[1].charAt(2)
}
tot[2]=''
tot[0]=tot[0]==''? '0' : parseInt(tot[0], 10).toString()
for (var i_tem = tot[0].length-1 ; i_tem > -1 ; i_tem--){
if (count&&count%3==0)
tot[2]+=','
tot[2]+=tot[0].charAt(i_tem)
count++
}
tot='$'+tot[2].split('').reverse().join('')+'.'+tot[1]
tot=tot.length-tot.indexOf('.')==2? tot+='0' : tot
return tot;
}
</script>