Skip to content

Block Integer parsing of a string if it contains a decimal point.#25

Merged
ctrueden merged 3 commits into
scijava:mainfrom
JaredDavis22:dotToBlockIntegerParse
Jul 17, 2026
Merged

Block Integer parsing of a string if it contains a decimal point.#25
ctrueden merged 3 commits into
scijava:mainfrom
JaredDavis22:dotToBlockIntegerParse

Conversation

@JaredDavis22

@JaredDavis22 JaredDavis22 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Small change improved the performance of the code for my usage.

Does the current code handle decimal numbers with a comma?

eg 10,001 for 10.001

This change will do nothing for those cases.

Quick test below showing the impact of generating the exception.

    public static void main(String[] args) {
        double[] doubles = {1.4, 1.8, 100.56, 3438535,43534545.67575 ,};
        int[] ints = {14, 18, 10056, 34385,43534545 ,};
        final long startTime = System.nanoTime();
        String[] parseThis = new String[doubles.length*2];
        int count =0;
        int good = 0;
        for (int x = 0; x < doubles.length; x++) {
           parseThis[x] = String.valueOf(doubles[x]);
           parseThis[x+ doubles.length] = String.valueOf(ints[x]);
        }
        for (int i = 0; i < 700000 ; i++) {
            try {
                String p = parseThis[i % 10];
                int dot = p.indexOf('.');
                if (dot == -1) {
                    Integer.parseInt(p);
                    good++;
                }
            } catch (NumberFormatException e) {
                // eat it
                count++;
            }
        }
        final long elapsed = System.nanoTime() - startTime;
        System.out.println("stop. good="+good+" exceptions=" + count+ " time="  + elapsed/1000000000.0);
    }

stop. good=350000 exceptions=0 time=0.022590425

change one line

    int dot = p.indexOf('X');

stop. good=350000 exceptions=350000 time=0.436176896

@JaredDavis22

Copy link
Copy Markdown
Contributor Author

Hi - Any feedback on this pr?

@ctrueden
ctrueden force-pushed the dotToBlockIntegerParse branch from aa54f8c to e323c0a Compare July 17, 2026 22:46
@ctrueden
ctrueden merged commit 799ea02 into scijava:main Jul 17, 2026
1 check passed
@ctrueden

Copy link
Copy Markdown
Member

Thanks @JaredDavis22! And apologies for the delay in recent reviews. Always a lot going on.

@ctrueden

Copy link
Copy Markdown
Member

I just triggered the release of parsington 3.3.0 with all the recently merged PRs. Should show up on Maven Central in a few minutes, barring CI complaints.

@JaredDavis22 JaredDavis22 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe start()==-1 is faster than group()=null. Group creates a new string that is then thrown away.

(m.start(3)==-1) && (m.start(4)==-1) ?

A quick and dirty test shows start is a little faster than group. Since this code executes a lot, it makes sense to use the .start format.

output of dirty test: (longer time is worse)
using group()=null stop. good=3500000 exceptions=0 time=0.965323396
using .start: stop. good=3500000 exceptions=0 time=0.654254639

   public static void main(String[] args) {

        final Pattern DECIMAL = Pattern.compile(
        "(([-+]?[0-9]+(\\.[0-9]*)?([Ee][-+]?[0-9]+)?)([DdFfLl])?).*");

        final Matcher matcher = DECIMAL.matcher("");

        double[] doubles = {1.4, 1.8, 100.56, 3438535,43534545.67575 ,};
        int[] ints = {14, 18, 10056, 34385,43534545 ,};
        String[] parseThis = new String[doubles.length*2];

        for (int x = 0; x < doubles.length; x++) {
            parseThis[x] = String.valueOf(doubles[x]);
            parseThis[x+ doubles.length] = String.valueOf(ints[x]);
        }

        {
            int count =0;
            int good = 0;
            final long startTime = System.nanoTime();
            boolean ignored;
            for (int i = 0; i < 7000000; i++) {
                try {
                    String p = parseThis[i % 10];
                    matcher.reset(p);
                    ignored = matcher.matches();
                    if ((matcher.group(3) == null) && (matcher.group(4) == null)) {
                        Integer.parseInt(p);
                        good++;
                    }
                } catch (NumberFormatException e) {
                    // eat it
                    count++;
                }
            }
            final long elapsed = System.nanoTime() - startTime;
            System.out.println("using group()=null   stop. good=" + good + " exceptions=" + count + " time=" + elapsed / 1000000000.0);
        }



        {
            int count =0;
            int good = 0;
            final long startTime = System.nanoTime();
            boolean ignored;
            for (int i = 0; i < 7000000; i++) {
                try {
                    String p = parseThis[i % 10];
                    matcher.reset(p);
                    ignored = matcher.matches();
                    if ((matcher.start(3) == -1) && (matcher.start(4) == -1)) {
                        Integer.parseInt(p);
                        good++;
                    }
                } catch (NumberFormatException e) {
                    // eat it
                    count++;
                }
            }
            final long elapsed = System.nanoTime() - startTime;
            System.out.println("using .start: stop. good=" + good + " exceptions=" + count + " time=" + elapsed / 1000000000.0);
        }

@JaredDavis22

Copy link
Copy Markdown
Contributor Author

Matcher.java from openjdk

It looks like to me that start will be faster when the group exists.

https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/util/regex/Matcher.java

        @Override
        public int start(int group) {
            checkMatch();
            checkGroup(group);
            return groups[group * 2];
        }
        @Override
        public String group(int group) {
            checkMatch();
            checkGroup(group);
            if ((groups[group * 2] == -1) || (groups[group * 2 + 1] == -1))
                return null;
            return text.substring(groups[group * 2] - minStart, groups[group * 2 + 1] - minStart);
        }

ctrueden added a commit that referenced this pull request Jul 18, 2026
@ctrueden

ctrueden commented Jul 18, 2026

Copy link
Copy Markdown
Member

@JaredDavis22 Updated! I used < 0 instead of == -1 because I like it more aesthetically, and believe performance should not be worse, but if you find otherwise, we can change it to == -1.

@JaredDavis22

Copy link
Copy Markdown
Contributor Author

Thanks.

I'm ok with leaving it as < 0. I also believe == or < will perform the same on ints.

fyi:
== -1 matches the doc better.

 * @return  The index of the first character captured by the group,
 *          or {@code -1} if the match was successful but the group
 *          itself did not match anything

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants