Replace numbers to 3 decimal places using RegEx - regex

I need to replace only numbers that are decimal to 3 places.
The following example is working fine.
Output look like this:
0.000
But i can type 0..
How can i do only one decimal point (.) 0.000
Here is my directive:
app.directive('allowDecimalNumbers', function () {
return {
restrict: 'A',
link: function (scope, elm, attrs, ctrl) {
elm.on('keypress', function (event) {
var $input = $(this);
var value = $input.val();
value = value.replace(/[^0-9\.]/g, '')
if(value == "" && event.which == 46) {
return false;
}
var findsDot = new RegExp(/\./g)
var containsDot = value.match(findsDot)
if (containsDot != null && ([46, 110, 190].indexOf(event.which) > -1)) {
event.preventDefault();
return false;
}
var arrValue = value.split('.');
if (value.split('.').length == 2) {
if(value.split('.')[1].length > 2) {
event.preventDefault();
return false;
}
}
$input.val(value);
if (event.which == 64 || event.which == 16) {
// numbers
return false;
}
else if (event.which >= 48 && event.which <= 57 || event.which == 46) {
// numbers
return true;
}
else {
event.preventDefault();
return false;
}
});
}
}});
Here is my html:
<input type="number" allow-decimal-numbers ng-model="length1" >

Note that validation can cause problem depending on locale, in french decimal separator is , and input type="number" prevents . to be typed.
could make it working removing code, note that . doesn't need to be escaped when it is in a character set ( between [ and ])
var app = angular.module('app', []);
app.directive('allowDecimalNumbers', function () {
return {
restrict: 'A',
link: function (scope, elm, attrs, ctrl) {
elm.on('keydown', function (event) {
var $input = $(this);
var value = $input.val();
if ([8, 13, 16, 27, 37, 38, 39, 40, 46].indexOf(event.which) > -1) {
// backspace, enter, shift, escape, arrows, delete
return true;
} else if ( (event.which >= 48 && event.which <= 57 ||
event.which >= 96 && event.which <= 105) && !value.match(/[.,]\d{3}/)) {
// numbers
return true;
} else if ([46, 110, 190, 188].indexOf(event.which) > -1 && !value.match(/[.,]/)) {
// dot and numpad dot
return true;
} else {
event.preventDefault();
return false;
}
});
}
}
});
Note that a digit can't be inserted after there's a decimal separator followed by three digit but we can't check where the digit is inserted even if it's in front.

Related

How to prevent inheriting styles from previous bullet in Slate.js?

Go to slate example https://www.slatejs.org/examples/richtext and create bulleted list.
If you change the style by making the text bold, italic or to any other style midway, the style carry forwards to the next bullet on pressing enter.
Is there any way to prevent this?
Problem reproduction image
This is what I have in handleKeyDown
const handleKeyDown = (event) => {
// other code
if (
event.key === 'Enter' &&
!(
SlateHelpers.isBlockActive(editor, 'bulleted-list') ||
SlateHelpers.isBlockActive(editor, 'numbered-list')
)
) {
event.preventDefault();
const newLine = {
type: 'paragraph',
children: [{ text: '', marks: [] }],
};
Transforms.insertNodes(editor, newLine);
}
if (
event.key === 'Enter' &&
(SlateHelpers.isBlockActive(editor, 'bulleted-list') ||
SlateHelpers.isBlockActive(editor, 'numbered-list'))
) {
const [block] = SlateEditor.node(editor, editor.selection);
if (block.text === '') {
event.preventDefault();
if (SlateHelpers.isBlockActive(editor, 'bulleted-list')) {
SlateHelpers.toggleBlock(editor, 'bulleted-list');
}
if (SlateHelpers.isBlockActive(editor, 'numbered-list')) {
SlateHelpers.toggleBlock(editor, 'numbered-list');
}
}
}
// other code
}
Which is being passed like so
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={handleKeyDown}
spellCheck={false}
/>

Exception based on condition on Apps Script in Google Sheet

With this script I can exclude to insert the same value column in Google Sheet for maximum 100 times.
But I am trying to exclude (with if statement) some values from this script, in particular the date "25/12/2022" and the date "12/01/2012".
How could I proceed?
function onEdit(e) {
var r = e.range;
var s = r.getSheet();
if (s.getName() === 'New Rise 2022' && r.getColumn() === 27) {
var newValue = r.getDisplayValue();
if (newValue == "") return;
var count = s.getRange('AA1:AA').getDisplayValues().filter(([a]) => a === newValue).length;
if (count > 99) {
r.setValue(e.oldValue);
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('Questa data è stata già inserita 100 volte');
}
}
}
Update:
function onEdit(e) {
var r = e.range;
var s = r.getSheet();
if (s.getName() === 'New Rise 2022' && r.getColumn() === 27) {
var newValue = r.getDisplayValue();
if (newValue == "") return;
var count = s.getRange('AA1:AA').getDisplayValues().filter(([a]) => a === newValue).length;
if (count > 99 || e.range.getDisplayValue() == "25/12/2012" || e.range.getDisplayValue() == "12/01/2012") {
r.setValue(e.oldValue);
r.setNumberFormat('dd/mm/YYYY');
SpreadsheetApp.flush();
SpreadsheetApp.getUi().alert('Questa data è stata già inserita 100 volte');
}
}
}
How about this?
function onEdit(e) {
const sh = e.range.getSheet();
const x = ["25/12/2022","12/01/2012"];
const idx = x.indexOf(e.value);
if (sh.getName() === 'New Rise 2022' && e.range.columnStart == 27 && e.value && !~idx) {
var count = sh.getRange('AA1:AA' + sh.getLastRow()).getDisplayValues().flat().filter(e => e == e.value).length;
if (count > 99) {
e.range.setValue(e.oldValue);
}
}
}
You can get the newly entered display value and compare it against the "forbidden" values
Therefore, retrieve the latest modified cell with e.range:
...
if (count > 99 || e.range.getDisplayValue() == "25/12/2022" || e.range.getDisplayValue() == "12/01/2012") {
...
}
...
Note:
I understood that what you are interested in is the displayed value (date in this case), but depending on your date formatting the display value will be different from the value you typed in.
If it is the typed in value you are after, you can retrieve it with e.value:
...
console.log("e.value: " + e.value)
console.log("e.range.getDisplayValue(): " + e.range.getDisplayValue())
if (count > 99 || e.value == "25/12/2022" || e.value == "12/01/2012") {
...
}
...
References:
Event Objects
getDisplayValue()
UPDATE:
If you have problems with number formatting you can use the method setNumberFormat().
Modify your code block in the if statement to
r.setValue(e.oldValue);
r.setNumberFormat('dd/mm/YYYY');

How to select first chars with a custom word boundary?

I've test cases with a series of words like this :
{
input: "Halley's Comet",
expected: "HC",
},
{
input: "First In, First Out",
expected: "FIFO",
},
{
input: "The Road _Not_ Taken",
expected: "TRNT",
},
I want with one regex to match all first letters of these words, avoid char: "_" to be matched as a first letter and count single quote in the word.
Currently, I have this regex working on pcre syntax but not with Go regexp package : (?<![a-zA-Z0-9'])([a-zA-Z0-9'])
I know lookarounds aren't supported by Go but I'm looking for a good way to do that.
I also use this func to get an array of all strings : re.FindAllString(s, -1)
Thanks for helping.
Something that plays with character classes and word boundaries should suffice:
\b_*([a-z])[a-z]*(?:'s)?_*\b\W*
demo
Usage:
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?i)\b_*([a-z])[a-z]*(?:'s)?_*\b\W*`)
fmt.Println(re.ReplaceAllString("O'Brian's dog", "$1"))
}
ftr, regexp less solution
package main
import (
"fmt"
)
func main() {
inputs := []string{"Hallمرحباey's Comet", "First In, First Out", "The Road _Not_ Taken", "O'Brian's Dog"}
c := [][]string{}
w := [][]string{}
for _, input := range inputs {
c = append(c, firstLet(input))
w = append(w, words(input))
}
fmt.Printf("%#v\n", w)
fmt.Printf("%#v\n", c)
}
func firstLet(in string) (out []string) {
var inword bool
for _, r := range in {
if !inword {
if isChar(r) {
inword = true
out = append(out, string(r))
}
} else if r == ' ' {
inword = false
}
}
return out
}
func words(in string) (out []string) {
var inword bool
var w []rune
for _, r := range in {
if !inword {
if isChar(r) {
w = append(w, r)
inword = true
}
} else if r == ' ' {
if len(w) > 0 {
out = append(out, string(w))
w = w[:0]
}
inword = false
} else if r != '_' {
w = append(w, r)
}
}
if len(w) > 0 {
out = append(out, string(w))
}
return out
}
func isChar(r rune) bool {
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
}
outputs
[][]string{[]string{"Hallمرحباey's", "Comet"}, []string{"First", "In,", "First", "Out"}, []string{"The", "Road", "Not", "Taken"}, []string{"O'Brian's", "Dog"}}
[][]string{[]string{"H", "C"}, []string{"F", "I", "F", "O"}, []string{"T", "R", "N", "T"}, []string{"O", "D"}}

jqgrid: How to define filter presets / templates inside a combo-box?

I have a jqgrid containing some data to filter. I'd like to define a combo box with some pre-defined filter sets / templates.
If a user selects an item of the combobox, the grid should automatically apply combined filters. Preferably, the combo box should be integrated into a toolbar or jqGrid's pager, but also in the html page would be fine.
For example:
COMBO BOX
Item templates filter parameters
___________
|Expired | << Timeout = true
|Last Week | << OpenDate between 02/13/2012 and 02/16/2012
|Last Month | << OpenDate between 01/01/2012 and 02/16/2012
|......... | ......
Thanks in advance for your help
jqGrid supports Searching Templates in the Advance Searching (see "Searching"/ "Search Templates" in the official jqGrid demo), but there are currently no searching templates support in the Toolbar Filtering.
I find your question very interesting. In the old question I described how one can use generic external filters to send additional information to the server. The way can be good in case of remote data, but it can't be used directly in the local grid or in the grid with the loadonce: true option.
So I created the demo which shows how the filter templates can be implemented in Toolbar Filtering and how to integrated the template in the jqGrid. I used toolbar: [true, "top"] to have additional empty toolbar above the column headers:
In the implementation I used the refreshSerchingToolbar function which I suggested originally here. It's important to understand, that the refreshSerchingToolbar function fill in the filter toolbar only the information which can be exactly represented by the filter. For example the filter for "Closed" rows can be represented in the filter toolbar (see the picture above), but the interval of dates "Last Week" and "Last Month" con't. In the cases the data in the grid will be filtered, but the corresponding fields of the filter toolbar stay empty.
The most important part of the code of the demo you can find below
var $grid = $("#list"),
initDate = function (elem) {
$(elem).datepicker({
dateFormat: 'dd-M-yy',
autoSize: true,
changeYear: true,
changeMonth: true,
showButtonPanel: true,
showWeek: true
});
},
numberTemplate = {formatter: 'number', align: 'right', sorttype: 'number', editable: true/*,
searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge', 'nu', 'nn', 'in', 'ni'] }*/},
dateTemplate = {width: 80, align: 'center', sorttype: 'date',
formatter: 'date', formatoptions: { newformat: 'd-M-Y' }, editable: true, datefmt: 'd-M-Y',
editoptions: { dataInit: initDate },
searchoptions: { sopt: ['eq', 'ne', 'lt', 'le', 'gt', 'ge'], dataInit: initDate }},
yesNoTemplate = {align: 'center', editable: true, formatter: 'checkbox',
edittype: 'checkbox', editoptions: {value: 'Yes:No', defaultValue: 'No'},
stype: 'select', searchoptions: { sopt: ['eq', 'ne'], value: ':Any;true:Yes;false:No' }},
myDefaultSearch = 'cn',
getColumnIndex = function (columnIndex) {
var cm = this.jqGrid('getGridParam', 'colModel'), i, l = cm.length;
for (i = 0; i < l; i++) {
if ((cm[i].index || cm[i].name) === columnIndex) {
return i; // return the colModel index
}
}
return -1;
},
refreshSerchingToolbar = function (myDefaultSearch) {
var filters, i, l, rules, rule, iCol, cmi, control, tagName,
$this = $(this),
postData = $this.jqGrid('getGridParam', 'postData'),
cm = $this.jqGrid('getGridParam', 'colModel');
for (i = 0, l = cm.length; i < l; i++) {
control = $("#gs_" + $.jgrid.jqID(cm[i].name));
if (control.length > 0) {
tagName = control[0].tagName.toUpperCase();
if (tagName === "SELECT") { // && cmi.stype === "select"
control.find("option[value='']")
.attr('selected', 'selected');
} else if (tagName === "INPUT") {
control.val('');
}
}
}
if (typeof (postData.filters) === "string" &&
typeof (this.ftoolbar) === "boolean" && this.ftoolbar) {
filters = $.parseJSON(postData.filters);
if (filters && filters.groupOp === "AND" && typeof (filters.groups) === "undefined") {
// only in case of advance searching without grouping we import filters in the
// searching toolbar
rules = filters.rules;
for (i = 0, l = rules.length; i < l; i++) {
rule = rules[i];
iCol = getColumnIndex.call($this, rule.field);
if (iCol >= 0) {
cmi = cm[iCol];
control = $("#gs_" + $.jgrid.jqID(cmi.name));
if (control.length > 0 &&
(((typeof (cmi.searchoptions) === "undefined" ||
typeof (cmi.searchoptions.sopt) === "undefined")
&& rule.op === myDefaultSearch) ||
(typeof (cmi.searchoptions) === "object" &&
$.isArray(cmi.searchoptions.sopt) &&
cmi.searchoptions.sopt.length > 0 &&
cmi.searchoptions.sopt[0] === rule.op))) {
tagName = control[0].tagName.toUpperCase();
if (tagName === "SELECT") { // && cmi.stype === "select"
control.find("option[value='" + $.jgrid.jqID(rule.data) + "']")
.attr('selected', 'selected');
} else if (tagName === "INPUT") {
control.val(rule.data);
}
}
}
}
}
}
},
templateClosed = {
groupOp: "AND",
rules: [
{ field: "closed", op: "eq", data: "true" }
]
},
templateLastWeek = {
groupOp: "AND",
rules: [
{ field: "invdate", op: "ge", "data": "13-Feb-2012" },
{ field: "invdate", op: "le", "data": "16-Feb-2012"}
]
},
templateLastMonth = {
groupOp: "AND",
rules: [
{ field: "invdate", op: "ge", "data": "16-Jan-2012" },
{ field: "invdate", op: "le", "data": "16-Feb-2012"}
]
},
myFilterTemplateLabel = 'Filter by Template: ',
myFilterTemplateNames = ['Closed', 'Last Week', 'Last Month'],
myFilterTemplates = [templateClosed, templateLastWeek, templateLastMonth],
iTemplate,
cTemplates = myFilterTemplateNames.length,
templateOptions = '',
reloadWithNewFilterTemplate = function () {
var iTemplate = parseInt($('#filterTemplates').val(), 10),
postData = $grid.jqGrid('getGridParam', 'postData');
if (isNaN(iTemplate)) {
$grid.jqGrid('setGridParam', {search: false});
} else if (iTemplate >= 0) {
$.extend(postData, {
filters: JSON.stringify(myFilterTemplates[iTemplate])
});
$grid.jqGrid('setGridParam', {search: true});
}
$grid.trigger('reloadGrid', [{current: true, page: 1}]);
};
$grid.jqGrid({
...
toolbar: [true, "top"],
loadComplete: function () {
var $this = $(this);
if (typeof (this.ftoolbar) !== "boolean") {
// create toolbar if needed
$this.jqGrid('filterToolbar',
{stringResult: true, searchOnEnter: true, defaultSearch: myDefaultSearch});
}
refreshSerchingToolbar.call(this, myDefaultSearch);
}
});
$.extend($.jgrid.search, {
multipleSearch: true,
multipleGroup: true,
recreateFilter: true,
closeOnEscape: true,
closeAfterSearch: true,
overlay: 0,
tmplLabel: myFilterTemplateLabel,
tmplNames: myFilterTemplateNames,
tmplFilters: myFilterTemplates
});
$grid.jqGrid('navGrid', '#pager', {edit: false, add: false, del: false});
for (iTemplate = 0; iTemplate < cTemplates; iTemplate++) {
templateOptions += '<option value="' + iTemplate + '">' +
myFilterTemplateNames[iTemplate] + '</option>';
}
$('#t_' + $.jgrid.jqID($grid[0].id)).append('<label for="filterTemplates">'+
myFilterTemplateLabel + '</label>' +
'<select id="filterTemplates"><option value="">Not filtered</option>' +
templateOptions + '</select>');
$('#filterTemplates').change(reloadWithNewFilterTemplate).keyup(function (e) {
// some web browsers like Google Chrome don't fire "change" event
// if the select will be "scrolled" by keybord. Moreover some browsers
// like Internet Explorer don't change the select option on pressing
// of LEFT or RIGHT key. Another web browsers like Google Chrome do this.
// We make refrech of the grid in any from the cases. If needed one
// could modify the code to reduce unnneded reloading of the grid,
// but for the demo with a few local rows it's such optimization
// isn't really needed
var keyCode = e.keyCode || e.which;
if (keyCode === $.ui.keyCode.PAGE_UP || keyCode === $.ui.keyCode.PAGE_DOWN ||
keyCode === $.ui.keyCode.END || keyCode === $.ui.keyCode.HOME ||
keyCode === $.ui.keyCode.UP || keyCode === $.ui.keyCode.DOWN ||
keyCode === $.ui.keyCode.LEFT || keyCode === $.ui.keyCode.RIGHT) {
reloadWithNewFilterTemplate();
}
});

Insert commas into number string

Hey there, I'm trying to perform a backwards regular expression search on a string to divide it into groups of 3 digits. As far as i can see from the AS3 documentation, searching backwards is not possible in the reg ex engine.
The point of this exercise is to insert triplet commas into a number like so:
10000000 => 10,000,000
I'm thinking of doing it like so:
string.replace(/(\d{3})/g, ",$1")
But this is not correct due to the search not happening from the back and the replace $1 will only work for the first match.
I'm getting the feeling I would be better off performing this task using a loop.
UPDATE:
Due to AS3 not supporting lookahead this is how I have solved it.
public static function formatNumber(number:Number):String
{
var numString:String = number.toString()
var result:String = ''
while (numString.length > 3)
{
var chunk:String = numString.substr(-3)
numString = numString.substr(0, numString.length - 3)
result = ',' + chunk + result
}
if (numString.length > 0)
{
result = numString + result
}
return result
}
If your language supports postive lookahead assertions, then I think the following regex will work:
(\d)(?=(\d{3})+$)
Demonstrated in Java:
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class CommifyTest {
#Test
public void testCommify() {
String num0 = "1";
String num1 = "123456";
String num2 = "1234567";
String num3 = "12345678";
String num4 = "123456789";
String regex = "(\\d)(?=(\\d{3})+$)";
assertEquals("1", num0.replaceAll(regex, "$1,"));
assertEquals("123,456", num1.replaceAll(regex, "$1,"));
assertEquals("1,234,567", num2.replaceAll(regex, "$1,"));
assertEquals("12,345,678", num3.replaceAll(regex, "$1,"));
assertEquals("123,456,789", num4.replaceAll(regex, "$1,"));
}
}
Found on http://gskinner.com/RegExr/
Community > Thousands separator
Pattern: /\d{1,3}(?=(\d{3})+(?!\d))/g
Replace: $&,
trace ( String("1000000000").replace( /\d{1,3}(?=(\d{3})+(?!\d))/g , "$&,") );
It done the job!
If your regex engine has positive lookaheads, you could do something like this:
string.replace(/(\d)(?=(\d\d\d)+$)/, "$1,")
Where the positive lookahead (?=...) means that the regex only matches when the lookahead expression ... matches.
(Note that lookaround-expressions are not always very efficient.)
While many of these answers work fine with positive integers, many of their argument inputs are cast as Numbers, which implies that they can handle negative values or contain decimals, and here all of the solutions fail. Though the currently selected answer does not assume a Number I was curious to find a solution that could and was also more performant than RegExp (which AS3 does not do well).
I put together many of the answers here in a testing class (and included a solution from this blog and an answer of my own called commaify) and formatted them in a consistent way for easy comparison:
package
{
public class CommaNumberSolutions
{
public static function commaify( input:Number ):String
{
var split:Array = input.toString().split( '.' ),
front:String = split[0],
back:String = ( split.length > 1 ) ? "." + split[1] : null,
n:int = input < 0 ? 2 : 1,
commas:int = Math.floor( (front.length - n) / 3 ),
i:int = 1;
for ( ; i <= commas; i++ )
{
n = front.length - (3 * i + i - 1);
front = front.slice( 0, n ) + "," + front.slice( n );
}
if ( back )
return front + back;
else
return front;
}
public static function getCommaString( input:Number ):String
{
var s:String = input.toString();
if ( s.length <= 3 )
return s;
var i:int = s.length % 3;
if ( i == 0 )
i = 3;
for ( ; i < s.length; i += 4 )
{
var part1:String = s.substr(0, i);
var part2:String = s.substr(i, s.length);
s = part1.concat(",", part2);
}
return s;
}
public static function formatNumber( input:Number ):String
{
var s:String = input.toString()
var result:String = ''
while ( s.length > 3 )
{
var chunk:String = s.substr(-3)
s = s.substr(0, s.length - 3)
result = ',' + chunk + result
}
if ( s.length > 0 )
result = s + result
return result
}
public static function commaCoder( input:Number ):String
{
var s:String = "";
var len:Number = input.toString().length;
for ( var i:int = 0; i < len; i++ )
{
if ( (len-i) % 3 == 0 && i != 0)
s += ",";
s += input.toString().charAt(i);
}
return s;
}
public static function regex1( input:Number ):String
{
return input.toString().replace( /-{0,1}(\d)(?=(\d\d\d)+$)/g, "$1," );
}
public static function regex2( input:Number ):String
{
return input.toString().replace( /-{0,1}\d{1,3}(?=(\d{3})+(?!\d))/g , "$&,")
}
public static function addCommas( input:Number ):String
{
var negative:String = "";
if ( input < 0 )
{
negative = "-";
input = Math.abs(input);
}
var s:String = input.toString();
var results:Array = s.split(/\./);
s = results[0];
if ( s.length > 3 )
{
var mod:Number = s.length % 3;
var output:String = s.substr(0, mod);
for ( var i:Number = mod; i < s.length; i += 3 )
{
output += ((mod == 0 && i == 0) ? "" : ",") + s.substr(i, 3);
}
if ( results.length > 1 )
{
if ( results[1].length == 1 )
return negative + output + "." + results[1] + "0";
else
return negative + output + "." + results[1];
}
else
return negative + output;
}
if ( results.length > 1 )
{
if ( results[1].length == 1 )
return negative + s + "." + results[1] + "0";
else
return negative + s + "." + results[1];
}
else
return negative + s;
}
}
}
Then I tested each for accuracy and performance:
package
{
public class TestCommaNumberSolutions
{
private var functions:Array;
function TestCommaNumberSolutions()
{
functions = [
{ name: "commaify()", f: CommaNumberSolutions.commaify },
{ name: "addCommas()", f: CommaNumberSolutions.addCommas },
{ name: "getCommaString()", f: CommaNumberSolutions.getCommaString },
{ name: "formatNumber()", f: CommaNumberSolutions.formatNumber },
{ name: "regex1()", f: CommaNumberSolutions.regex1 },
{ name: "regex2()", f: CommaNumberSolutions.regex2 },
{ name: "commaCoder()", f: CommaNumberSolutions.commaCoder }
];
verify();
measure();
}
protected function verify():void
{
var assertions:Array = [
{ input: 1, output: "1" },
{ input: 21, output: "21" },
{ input: 321, output: "321" },
{ input: 4321, output: "4,321" },
{ input: 54321, output: "54,321" },
{ input: 654321, output: "654,321" },
{ input: 7654321, output: "7,654,321" },
{ input: 987654321, output: "987,654,321" },
{ input: 1987654321, output: "1,987,654,321" },
{ input: 21987654321, output: "21,987,654,321" },
{ input: 321987654321, output: "321,987,654,321" },
{ input: 4321987654321, output: "4,321,987,654,321" },
{ input: 54321987654321, output: "54,321,987,654,321" },
{ input: 654321987654321, output: "654,321,987,654,321" },
{ input: 7654321987654321, output: "7,654,321,987,654,321" },
{ input: 87654321987654321, output: "87,654,321,987,654,321" },
{ input: -1, output: "-1" },
{ input: -21, output: "-21" },
{ input: -321, output: "-321" },
{ input: -4321, output: "-4,321" },
{ input: -54321, output: "-54,321" },
{ input: -654321, output: "-654,321" },
{ input: -7654321, output: "-7,654,321" },
{ input: -987654321, output: "-987,654,321" },
{ input: -1987654321, output: "-1,987,654,321" },
{ input: -21987654321, output: "-21,987,654,321" },
{ input: -321987654321, output: "-321,987,654,321" },
{ input: -4321987654321, output: "-4,321,987,654,321" },
{ input: -54321987654321, output: "-54,321,987,654,321" },
{ input: -654321987654321, output: "-654,321,987,654,321" },
{ input: -7654321987654321, output: "-7,654,321,987,654,321" },
{ input: -87654321987654321, output: "-87,654,321,987,654,321" },
{ input: .012345, output: "0.012345" },
{ input: 1.012345, output: "1.012345" },
{ input: 21.012345, output: "21.012345" },
{ input: 321.012345, output: "321.012345" },
{ input: 4321.012345, output: "4,321.012345" },
{ input: 54321.012345, output: "54,321.012345" },
{ input: 654321.012345, output: "654,321.012345" },
{ input: 7654321.012345, output: "7,654,321.012345" },
{ input: 987654321.012345, output: "987,654,321.012345" },
{ input: 1987654321.012345, output: "1,987,654,321.012345" },
{ input: 21987654321.012345, output: "21,987,654,321.012345" },
{ input: -.012345, output: "-0.012345" },
{ input: -1.012345, output: "-1.012345" },
{ input: -21.012345, output: "-21.012345" },
{ input: -321.012345, output: "-321.012345" },
{ input: -4321.012345, output: "-4,321.012345" },
{ input: -54321.012345, output: "-54,321.012345" },
{ input: -654321.012345, output: "-654,321.012345" },
{ input: -7654321.012345, output: "-7,654,321.012345" },
{ input: -987654321.012345, output: "-987,654,321.012345" },
{ input: -1987654321.012345, output: "-1,987,654,321.012345" },
{ input: -21987654321.012345, output: "-21,987,654,321.012345" }
];
var i:int;
var len:int = assertions.length;
var assertion:Object;
var f:Function;
var s1:String;
var s2:String;
for each ( var o:Object in functions )
{
i = 0;
f = o.f;
trace( '\rVerify: ' + o.name );
for ( ; i < len; i++ )
{
assertion = assertions[ i ];
s1 = f.apply( null, [ assertion.input ] );
s2 = assertion.output;
if ( s1 !== s2 )
trace( 'Test #' + i + ' Failed: ' + s1 + ' !== ' + s2 );
}
}
}
protected function measure():void
{
// Generate random inputs
var values:Array = [];
for ( var i:int = 0; i < 999999; i++ ) {
values.push( Math.random() * int.MAX_VALUE * ( Math.random() > .5 ? -1 : 1) );
}
var len:int = values.length;
var stopwatch:Stopwatch = new Stopwatch;
var s:String;
var f:Function;
trace( '\rTesting ' + len + ' random values' );
// Test each function
for each ( var o:Object in functions )
{
i = 0;
s = "";
f = o.f;
stopwatch.start();
for ( ; i < len; i++ ) {
s += f.apply( null, [ values[i] ] ) + " ";
}
stopwatch.stop();
trace( o.name + '\t\ttook ' + (stopwatch.elapsed/1000) + 's' ); //(stopwatch.elapsed/len) + 'ms'
}
}
}
}
import flash.utils.getTimer;
class Stopwatch
{
protected var startStamp:int;
protected var stopStamp:int;
protected var _started:Boolean;
protected var _stopped:Boolean;
function Stopwatch( startNow:Boolean = true ):void
{
if ( startNow )
start();
}
public function start():void
{
startStamp = getTimer();
_started = true;
_stopped = false;
}
public function stop():void
{
stopStamp = getTimer();
_stopped = true;
_started = false;
}
public function get elapsed():int
{
return ( _stopped ) ? stopStamp - startStamp : ( _started ) ? getTimer() - startStamp : 0;
}
public function get started():Boolean
{
return _started;
}
public function get stopped():Boolean
{
return _stopped;
}
}
Because of AS3's lack of precision with larger Numbers every class failed these tests:
Test #15 Failed: 87,654,321,987,654,320 !== 87,654,321,987,654,321
Test #31 Failed: -87,654,321,987,654,320 !== -87,654,321,987,654,321
Test #42 Failed: 21,987,654,321.012344 !== 21,987,654,321.012345
Test #53 Failed: -21,987,654,321.012344 !== -21,987,654,321.012345
But only two functions passed all of the other tests: commaify() and addCommas().
The performance tests show that commaify() is the most preformant of all the solutions:
Testing 999999 random values
commaify() took 12.411s
addCommas() took 17.863s
getCommaString() took 18.519s
formatNumber() took 14.409s
regex1() took 40.654s
regex2() took 36.985s
Additionally commaify() can be extended to including arguments for decimal length and zero-padding on the decimal portion — it also outperforms the others at 13.128s:
public static function cappedDecimal( input:Number, decimalPlaces:int = 2 ):Number
{
if ( decimalPlaces == 0 )
return Math.floor( input );
var decimalFactor:Number = Math.pow( 10, decimalPlaces );
return Math.floor( input * decimalFactor ) / decimalFactor;
}
public static function cappedDecimalString( input:Number, decimalPlaces:int = 2, padZeros:Boolean = true ):String
{
if ( padZeros )
return cappedDecimal( input, decimalPlaces ).toFixed( decimalPlaces );
else
return cappedDecimal( input, decimalPlaces ).toString();
}
public static function commaifyExtended( input:Number, decimalPlaces:int = 2, padZeros:Boolean = true ):String
{
var split:Array = cappedDecimalString( input, decimalPlaces, padZeros ).split( '.' ),
front:String = split[0],
back:String = ( split.length > 1 ) ? "." + split[1] : null,
n:int = input < 0 ? 2 : 1,
commas:int = Math.floor( (front.length - n) / 3 ),
i:int = 1;
for ( ; i <= commas; i++ )
{
n = front.length - (3 * i + i - 1);
front = front.slice( 0, n ) + "," + front.slice( n );
}
if ( back )
return front + back;
else
return front;
}
So, I'd offer that commaify() meets the demands of versatility and performance though certainly not the most compact or elegant.
This really isn't the best use of RegEx... I'm not aware of a number formatting function, but this thread seems to provide a solution.
function commaCoder(yourNum):String {
//var yourNum:Number = new Number();
var numtoString:String = new String();
var numLength:Number = yourNum.toString().length;
numtoString = "";
for (i=0; i<numLength; i++) {
if ((numLength-i)%3 == 0 && i != 0) {
numtoString += ",";
}
numtoString += yourNum.toString().charAt(i);
trace(numtoString);
}
return numtoString;
}
If you really are insistent on using RegEx, you could just reverse the string, apply the RegEx replace function, then reverse it back.
A sexeger is good for this. In brief, a sexeger is a reversed regex run against a reversed string that you reverse the output of. It is generally more efficient than the alternative. Here is some pseudocode for what you want to do:
string = reverse string
string.replace(/(\d{3})(?!$)/g, "$1,")
string = reverse string
Here is is a Perl implemntation
#!/usr/bin/perl
use strict;
use warnings;
my $s = 13_456_789;
for my $n (1, 12, 123, 1234, 12345, 123456, 1234567) {
my $s = reverse $n;
$s =~ s/([0-9]{3})(?!$)/$1,/g;
$s = reverse $s;
print "$s\n";
}
You may want to consider NumberFormatter
I'll take the downvotes for not being the requested language, but this non-regex technique should apply (and I arrived here via searching for "C# regex to add commas into number")
var raw = "104241824 15202656 KB 13498560 KB 1612672KB already 1,000,000 or 99.999 or 9999.99";
int i = 0;
bool isnum = false;
var formatted = raw.Reverse().Aggregate(new StringBuilder(), (sb, c) => {
//$"{i}: [{c}] {isnum}".Dump();
if (char.IsDigit(c) && c != ' ' && c!= '.' && c != ',') {
if (isnum) {
if (i == 3) {
//$"ins ,".Dump();
sb.Insert(0, ',');
i = 0;
}
}
else isnum = true;
i++;
}
else {
isnum = false;
i = 0;
}
sb.Insert(0, c);
return sb;
});
results in:
104,241,824 15,202,656 KB 13,498,560 KB 1,612,672KB already 1,000,000 or 99.999 or 9,999.99
// This is a simple code and it works fine...:)
import java.util.Scanner;
public class NumberWithCommas {
public static void main(String a[]) {
Scanner sc = new Scanner(System.in);
String num;
System.out.println("\n enter the number:");
num = sc.next();
printNumber(num);
}
public static void printNumber(String ar) {
int len, i = 0, temp = 0;
len = ar.length();
temp = len / 3;
if (len % 3 == 0)
temp = temp - 1;
len = len + temp;
char[] ch = ar.toCharArray();
char[] ch1 = new char[len];
for (int j = 0, k = (ar.length() - 1); j < len; j++)
{
if (i < 3)
{
ch1[j] = ch[k];
i++;
k--;
}
else
{
ch1[j] = ',';
i = 0;
}
}
for (int j = len - 1; j >= 0; j--)
System.out.print(ch1[j]);
System.out.println("");
}
}
If you can't use lookahead on regular expressions, you can use this:
string.replace(/^(.*?,)?(\d{1,3})((?:\d{3})+)$/, "$1$2,$3")
inside a loop until there's nothing to replace.
For example, a perlish solution would look like this:
my $num = '1234567890';
while ($num =~ s/^(.*?,)?(\d{1,3})((?:\d{3})+)$/$1$2,$3/) {}
Perl RegExp 1 liner:
1 while $VAR{total} =~ s/(.*\d)(\d\d\d)/$1,$2/g;
Try this code. it's simple and best performance.
var reg:RegExp=/\d{1,3}(?=(\d{3})+(?!\d))/g;
var my_num:Number = 48712694;
var my_num_str:String = String(my_num).replace(reg,"$&,");
trace(my_num_str);
::output::
48,712,694