293

Here is the software version number:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

How can I compare this?

Assume the correct order is:

"1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"

The idea is simple...: Read the first digit, than, the second, after that the third... But I can't convert the version number to float number... You also can see the version number like this:

"1.0.0.0", "1.0.1.0", "2.0.0.0", "2.0.0.1", "2.0.1.0"

And this is clearer to see what is the idea behind... But, how can I convert it into a computer program?

6
  • 15
    This would be a good fizzbuzz-type interview question. Commented Jul 26, 2011 at 15:33
  • 4
    This why all software version numbers should be integers like 2001403. When you want to display it in some friendly way like "2.0.14.3" then you format the version number at presentation time. Commented May 10, 2013 at 21:50
  • 3
    The general problem here is Semantic Version comparisons, and it's non-trivial (see #11 at semver.org). Fortunately, there is an official library for that, the semantic versioner for npm. Commented Sep 2, 2015 at 0:23
  • 4
    Found a simple script that compares semvers Commented Jan 5, 2017 at 14:16
  • @jarmod so you have 2001403, is it 2.0.14.3 or 20.1.4.3 or 2.0.1.43? This approach is limiting if not flawed. Commented Oct 26, 2020 at 3:11

57 Answers 57

202

semver

The semantic version parser used by npm.

$ npm install semver
var semver = require('semver');

semver.diff('3.4.5', '4.3.7') //'major'
semver.diff('3.4.5', '3.3.7') //'minor'
semver.gte('3.4.8', '3.4.7') //true
semver.ltr('3.4.8', '3.4.7') //false

semver.valid('1.2.3') // '1.2.3'
semver.valid('a.b.c') // null
semver.clean(' =v1.2.3 ') // '1.2.3'
semver.satisfies('1.2.3', '1.x || >=2.5.0 || 5.0.0 - 7.2.3') // true
semver.gt('1.2.3', '9.8.7') // false
semver.lt('1.2.3', '9.8.7') // true

var versions = [ '1.2.3', '3.4.5', '1.0.2' ]
var max = versions.sort(semver.rcompare)[0]
var min = versions.sort(semver.compare)[0]
var max = semver.maxSatisfying(versions, '*')

Semantic Versioning Link :
https://www.npmjs.com/package/semver#prerelease-identifiers

Sign up to request clarification or add additional context in comments.

14 Comments

Yes. This is the correct answer - comparing versions is non-trivial (see #11 at semver.org), and there are production-level libraries that do the job.
technically, it's not the right answers, since node.js and javascript is different. I supposed the original question was targeted more for browser. But google brought me here and luckily i'm using node :)
semver it's a npm package, it can be used on any JS environment! THIS IS the right answer
@artuska well then simply go for another package like semver-compare - 233B (less than 0.5kB!) gzipped : )
semver only supports A.B.C style versions. If you need to work with A.B.C.D versions (as suggested in the question), you're out of luck.
|
168

The basic idea to make this comparison would be to use Array.split to get arrays of parts from the input strings and then compare pairs of parts from the two arrays; if the parts are not equal we know which version is smaller.

There are a few of important details to keep in mind:

  1. How should the parts in each pair be compared? The question wants to compare numerically, but what if we have version strings that are not made up of just digits (e.g. "1.0a")?
  2. What should happen if one version string has more parts than the other? Most likely "1.0" should be considered less than "1.0.1", but what about "1.0.0"?

Here's the code for an implementation that you can use directly (gist with documentation):

function versionCompare(v1, v2, options) {
    var lexicographical = options && options.lexicographical,
        zeroExtend = options && options.zeroExtend,
        v1parts = v1.split('.'),
        v2parts = v2.split('.');

    function isValidPart(x) {
        return (lexicographical ? /^\d+[A-Za-z]*$/ : /^\d+$/).test(x);
    }

    if (!v1parts.every(isValidPart) || !v2parts.every(isValidPart)) {
        return NaN;
    }

    if (zeroExtend) {
        while (v1parts.length < v2parts.length) v1parts.push("0");
        while (v2parts.length < v1parts.length) v2parts.push("0");
    }

    if (!lexicographical) {
        v1parts = v1parts.map(Number);
        v2parts = v2parts.map(Number);
    }

    for (var i = 0; i < v1parts.length; ++i) {
        if (v2parts.length == i) {
            return 1;
        }

        if (v1parts[i] == v2parts[i]) {
            continue;
        }
        else if (v1parts[i] > v2parts[i]) {
            return 1;
        }
        else {
            return -1;
        }
    }

    if (v1parts.length != v2parts.length) {
        return -1;
    }

    return 0;
}

This version compares parts naturally, does not accept character suffixes and considers "1.7" to be smaller than "1.7.0". The comparison mode can be changed to lexicographical and shorter version strings can be automatically zero-padded using the optional third argument.

There is a JSFiddle that runs "unit tests" here; it is a slightly expanded version of ripper234's work (thank you).

Important note: This code uses Array.map and Array.every, which means that it will not run in IE versions earlier than 9. If you need to support those you will have to provide polyfills for the missing methods.

27 Comments

Here is an improved version with some unit tests: jsfiddle.net/ripper234/Xv9WL/28
Hey All, I've rolled this gist into a gitrepo with tests and everything and put it up on npm and bower so I can include it in my projects more easily. github.com/gabe0x02/version_compare
@GabrielLittman: Hey, thanks for taking the time to do that! However all code on SO is licensed with CC-BY-SA by default. That means you can't have your package be GPL-licensed. I know lawyering is not what anyone is here for, but it would be good if you fixed it.
@GabrielLittman: GPL is actually very restrictive in the sense that you are forced to GPL-license all code that comes into contact with existing GPL code. Anyway, for future reference: a good and widely used "do whatever you want, no strings attached" license is MIT.
@GabrielLittman: there are already established libraries written by seasoned devs that perform semver comparisons.
|
155

The simplest is to use localeCompare :

a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })

This will return:

  • 0: version strings are equal
  • 1: version a is greater than b
  • -1: version b is greater than a

Disclaimer: This will not handle pre-release versions correctly (like 1.0.1-1)

8 Comments

@JuanMendes This does have a limitation that the version strings must always have the same number of parts. So when passed 1.0 and 1.0.0.0, localeCompare shows that 1.0.0.0 is greater.
@Mordred That's the behavior I expect, I don't expect those to be treated the same. See semver.org semver.org/#spec-item-11
Love it, but unfortunately it can pass this test 1.0.0-alpha < 1.0.0. See semver.org/#spec-item-11
A patch to support 1.0.0-alpha < 1.0.0 and more cases: gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb
For the versioning we're handling in our app, this works perfectly. It's so simple and short!
|
68
// Return 1 if a > b
// Return -1 if a < b
// Return 0 if a == b
function compare(a, b) {
    if (a === b) {
       return 0;
    }

    var a_components = a.split(".");
    var b_components = b.split(".");

    var len = Math.min(a_components.length, b_components.length);

    // loop while the components are equal
    for (var i = 0; i < len; i++) {
        // A bigger than B
        if (parseInt(a_components[i]) > parseInt(b_components[i])) {
            return 1;
        }

        // B bigger than A
        if (parseInt(a_components[i]) < parseInt(b_components[i])) {
            return -1;
        }
    }

    // If one's a prefix of the other, the longer one is greater.
    if (a_components.length > b_components.length) {
        return 1;
    }

    if (a_components.length < b_components.length) {
        return -1;
    }

    // Otherwise they are the same.
    return 0;
}

console.log(compare("1", "2"));
console.log(compare("2", "1"));

console.log(compare("1.0", "1.0"));
console.log(compare("2.0", "1.0"));
console.log(compare("1.0", "2.0"));
console.log(compare("1.0.1", "1.0"));

7 Comments

I think the line: var len = Math.min(a_components.length, b_components.length); will cause versions 2.0.1.1 and 2.0.1 to be treated as equal will it?
No. Look just after the loop! If one string is a prefix of the other (i.e. loop reaches the end), then the longer one is taken as higher.
Perhaps you were put off my stumbling over the English language in the comment...
@Joe I know is a bit old answer but I was using the function. Testing a = '7' and b = '7.0' returns -1 because 7.0 is longer. Got any suggestion for that? ( console.log(compare("7", "7.0")); //returns -1 )
@RaphaelDDL compare length of both arrays and add 0's to the shortest until lengths are equal.
|
55

This very small, yet very fast compare function takes version numbers of any length and any number size per segment.

Return values:
- a number < 0 if a < b
- a number > 0 if a > b
- 0 if a = b

So you can use it as compare function for Array.sort();

EDIT: Bugfixed Version stripping trailing zeros to recognize "1" and "1.0.0" as equal

function cmpVersions (a, b) {
    var i, diff;
    var regExStrip0 = /(\.0+)+$/;
    var segmentsA = a.replace(regExStrip0, '').split('.');
    var segmentsB = b.replace(regExStrip0, '').split('.');
    var l = Math.min(segmentsA.length, segmentsB.length);

    for (i = 0; i < l; i++) {
        diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10);
        if (diff) {
            return diff;
        }
    }
    return segmentsA.length - segmentsB.length;
}

// TEST
console.log(
['2.5.10.4159',
 '1.0.0',
 '0.5',
 '0.4.1',
 '1',
 '1.1',
 '0.0.0',
 '2.5.0',
 '2',
 '0.0',
 '2.5.10',
 '10.5',
 '1.25.4',
 '1.2.15'].sort(cmpVersions));
// Result:
// ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"]

8 Comments

Failed with '0.0' and '0.0.0'. See fiddle: jsfiddle.net/emragins/9e9pweqg
@emragins When would you need to do that?
@emragins : I don't see where it fails. It outputs ["0.0.0", "0.0", "0.4.1", "0.5", "1.0.0", "1", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] where your code outputs ["0.0", "0.0.0", "0.4.1", "0.5", "1", "1.0.0", "1.1", "1.2.15", "1.25.4", "2", "2.5.0", "2.5.10", "2.5.10.4159", "10.5"] , which is perfectly the same, since 0.0 and 0.0.0 are considered to be equal, which means it is irrelevant whether '0.0' is before '0.0.0' or vice versa.
I agree this is an usual point. I'm using this with github.com/jonmiles/bootstrap-treeview, which tiers nodes in a manner similar to versions, only it's really just parent/child nodes and their indexes. Ex. Parent: 0.0, child: 0.0.0, 0.0.1. See this issue for more details on why I care: github.com/jonmiles/bootstrap-treeview/issues/251
See answer here stackoverflow.com/questions/6611824/why-do-we-need-to-use-radix. Older browsers used to guess the radix parameter if not specified. A leading zero in a number string like the middle part in "1.09.12" used to be parsed with radix=8 resulting in number 0 instead of expected number 9.
|
45

Simple and short function:

function isNewerVersion (oldVer, newVer) {
  const oldParts = oldVer.split('.')
  const newParts = newVer.split('.')
  for (var i = 0; i < newParts.length; i++) {
    const a = ~~newParts[i] // parse int
    const b = ~~oldParts[i] // parse int
    if (a > b) return true
    if (a < b) return false
  }
  return false
}

Tests:

isNewerVersion('1.0', '2.0') // true
isNewerVersion('1.0', '1.0.1') // true
isNewerVersion('1.0.1', '1.0.10') // true
isNewerVersion('1.0.1', '1.0.1') // false
isNewerVersion('2.0', '1.0') // false
isNewerVersion('2', '1.0') // false
isNewerVersion('2.0.0.0.0.1', '2.1') // true
isNewerVersion('2.0.0.0.0.1', '2.0') // false

3 Comments

You can simplify it with: const a = ~~newParts[i]; In fact this is the most efficient way convert a string into an integer, which returns 0 if variable undefined or contains non-numerical characters.
I often need to know if it's newer or the same, so my code can decide whether to hide a feature that is not supported. Isn't that the question that most are interested in?
Nice and short, exactly what I was looking for. You could also add oldVer.replace(/[^0-9.]/g, '').trim() and newVer.replace(/[^0-9.]/g, '').trim() to handle alpha, beta, or release candidate versions that add text like so: `1.0.0-rc'
17

Taken from http://java.com/js/deployJava.js:

    // return true if 'installed' (considered as a JRE version string) is
    // greater than or equal to 'required' (again, a JRE version string).
    compareVersions: function (installed, required) {

        var a = installed.split('.');
        var b = required.split('.');

        for (var i = 0; i < a.length; ++i) {
            a[i] = Number(a[i]);
        }
        for (var i = 0; i < b.length; ++i) {
            b[i] = Number(b[i]);
        }
        if (a.length == 2) {
            a[2] = 0;
        }

        if (a[0] > b[0]) return true;
        if (a[0] < b[0]) return false;

        if (a[1] > b[1]) return true;
        if (a[1] < b[1]) return false;

        if (a[2] > b[2]) return true;
        if (a[2] < b[2]) return false;

        return true;
    }

3 Comments

Simple, but limited to three version fields.
Realise I'm coming to this late, but I really like this simple solution for semantic versioning because three version fields is what you'll have.
Finally a version I can read easily. Yes, three version fields is the standard so this is useful to most of us
14

Here is another short version that works with any number of sub versions, padded zeros and even numbers with letters (1.0.0b3)

const compareVersions = ((prep, l, i, r) => (a, b) =>
{
    a = prep(a);
    b = prep(b);
    l = Math.max(a.length, b.length);
    i = 0;
    r = i;
    while (!r && i < l)
        //convert into integer, uncluding undefined values
      r = ~~a[i] - ~~b[i++];

    return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
    // treat non-numerical characters as lower version
    // replacing them with a negative number based on charcode of first character
  .replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
    // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
  .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
    // return array
  .split("."));

Function returns:

0 if a = b

1 if a > b

-1 if a < b

1.0         = 1.0.0.0.0.0
1.0         < 1.0.1
1.0b1       < 1.0
1.0b        = 1.0b
1.1         > 1.0.1b
1.1alpha    < 1.1beta
1.1rc1      > 1.1beta
1.1rc1      < 1.1rc2
1.1.0a1     < 1.1a2
1.1.0a10    > 1.1.0a1
1.1.0alpha  = 1.1a
1.1.0alpha2 < 1.1b1
1.0001      > 1.00000.1.0.0.0.01

What it does is replaces non-numerical characters with a negative number based on char code of the first letter, splits string into an array and then compares each item between arrays.

It uses ~~ trick that converts anything into an integer, therefore we don't need to worry about unequal array lengths.

/*use strict*/
const compareVersions = ((prep, l, i, r) => (a, b) =>
{
    a = prep(a);
    b = prep(b);
    l = Math.max(a.length, b.length);
    i = 0;
    r = i;
    while (!r && i < l)
        //convert into integer, uncluding undefined values
      r = ~~a[i] - ~~b[i++];

    return r < 0 ? -1 : (r ? 1 : 0);
})(t => ("" + t)
    // treat non-numerical characters as lower version
    // replacing them with a negative number based on charcode of first character
  .replace(/[^\d.]+/g, c => "." + (c.replace(/[\W_]+/, "").toUpperCase().charCodeAt(0) - 65536) + ".")
    // remove trailing "." and "0" if followed by non-numerical characters (1.0.0b);
  .replace(/(?:\.0+)*(\.-\d+(?:\.\d+)?)\.*$/g, "$1")
    // return array
  .split("."));

//examples

let list = [
  ["1.0",         "1.0.0.0.0.0"],
  ["1.0",         "1.0.1"],
  ["1.0b1",       "1.0"],
  ["1.0b",        "1.0b"],
  ["1.1",         "1.0.1b"],
  ["1.1alpha",    "1.1beta"],
  ["1.1rc1",      "1.1beta"],
  ["1.1rc1",      "1.1rc2"],
  ["1.1.0a1",     "1.1a2"],
  ["1.1.0a10",    "1.1.0a1"],
  ["1.1.0alpha",  "1.1a"],
  ["1.1.0alpha2", "1.1b1"],
  ["1.0001",      "1.00000.1.0.0.0.01"]
]
for(let i = 0; i < list.length; i++)
{
  console.log( list[i][0] + " " + "<=>"[compareVersions(list[i][0], list[i][1]) + 1] + " " + list[i][1] );
}

https://jsfiddle.net/vanowm/p7uvtbor/

Comments

10

Couldn't find a function doing what I wanted here. So I wrote my own. This is my contribution. I hope someone find it useful.

Pros:

  • Handles version strings of arbitrary length. '1' or '1.1.1.1.1'.

  • Defaults each value to 0 if not specified. Just because a string is longer doesn't mean it's a bigger version. ('1' should be the same as '1.0' and '1.0.0.0'.)

  • Compare numbers not strings. ('3'<'21' should be true. Not false.)

  • Don't waste time on useless compares in the loop. (Comparing for ==)

  • You can choose your own comparator.

Cons:

  • It does not handle letters in the version string. (I don't know how that would even work?)

My code, similar to the accepted answer by Jon:

function compareVersions(v1, comparator, v2) {
    "use strict";
    var comparator = comparator == '=' ? '==' : comparator;
    if(['==','===','<','<=','>','>=','!=','!=='].indexOf(comparator) == -1) {
        throw new Error('Invalid comparator. ' + comparator);
    }
    var v1parts = v1.split('.'), v2parts = v2.split('.');
    var maxLen = Math.max(v1parts.length, v2parts.length);
    var part1, part2;
    var cmp = 0;
    for(var i = 0; i < maxLen && !cmp; i++) {
        part1 = parseInt(v1parts[i], 10) || 0;
        part2 = parseInt(v2parts[i], 10) || 0;
        if(part1 < part2)
            cmp = 1;
        if(part1 > part2)
            cmp = -1;
    }
    return eval('0' + comparator + cmp);
}

Examples:

compareVersions('1.2.0', '==', '1.2'); // true
compareVersions('00001', '==', '1.0.0'); // true
compareVersions('1.2.0', '<=', '1.2'); // true
compareVersions('2.2.0', '<=', '1.2'); // false

3 Comments

this version is in my opinion better than the one in the approved answer!
This function is prone to code injection if the comparator parameter is used with unchecked user input! Example: compareVersions('1.2', '==0;alert("cotcha");', '1.2');
@LeJared True. When I wrote it we were not going to use it with user submitted code though. Should have brought it up as a con probably. I have now updated the code to eliminate that possibility. Now though, when webpack and other node.js bundlers have become prevalent, I would suggest that Mohammed Akdim's answer above, using semver, would almost always be the correct answer to this question.
9

You could use String#localeCompare with options

sensitivity

Which differences in the strings should lead to non-zero result values. Possible values are:

  • "base": Only strings that differ in base letters compare as unequal. Examples: a ≠ b, a = á, a = A.
  • "accent": Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples: a ≠ b, a ≠ á, a = A.
  • "case": Only strings that differ in base letters or case compare as unequal. Examples: a ≠ b, a = á, a ≠ A.
  • "variant": Strings that differ in base letters, accents and other diacritic marks, or case compare as unequal. Other differences may also be taken into consideration. Examples: a ≠ b, a ≠ á, a ≠ A.

The default is "variant" for usage "sort"; it's locale dependent for usage "search".

numeric

Whether numeric collation should be used, such that "1" < "2" < "10". Possible values are true and false; the default is false. This option can be set through an options property or through a Unicode extension key; if both are provided, the options property takes precedence. Implementations are not required to support this property.

var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];

versions.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));

console.log(versions);

2 Comments

How does this actually work? What is the undefined above, Language? How come you manage to post this while I read the others ;)
undefined is the locales part, is isn't used here.
8

2017 answer:

v1 = '20.0.12'; 
v2 = '3.123.12';

compareVersions(v1,v2) 
// return positive: v1 > v2, zero:v1 == v2, negative: v1 < v2 
function compareVersions(v1, v2) {
        v1= v1.split('.')
        v2= v2.split('.')
        var len = Math.max(v1.length,v2.length)
        /*default is true*/
        for( let i=0; i < len; i++)
            v1 = Number(v1[i] || 0);
            v2 = Number(v2[i] || 0);
            if (v1 !== v2) return v1 - v2 ;
            i++;
        }
        return 0;
    }

Simplest code for modern browsers:

 function compareVersion2(ver1, ver2) {
      ver1 = ver1.split('.').map( s => s.padStart(10) ).join('.');
      ver2 = ver2.split('.').map( s => s.padStart(10) ).join('.');
      return ver1 <= ver2;
 }

The idea here is to compare numbers but in the form of string. to make the comparison work the two strings must be at the same length. so:

"123" > "99" become "123" > "099"
padding the short number "fix" the comparison

Here I padding each part with zeros to lengths of 10. then just use simple string compare for the answer

Example :

var ver1 = '0.2.10', ver2=`0.10.2`
//become 
ver1 = '0000000000.0000000002.0000000010'
ver2 = '0000000000.0000000010.0000000002'
// then it easy to see that
ver1 <= ver2 // true

2 Comments

would you explain function compareVersion2 what exactly happen ?
Good, then you can use substring instead of padStartfor better compatibility i.e. var zeros = "0000000000"; '0.2.32'.split('.').map( s => zeros.substring(0, zeros.length-s.length) + s ).join('.') will give you 0000000000.0000000002.0000000032 :)
6

I faced the similar issue, and I had already created a solution for it. Feel free to give it a try.

It returns 0 for equal, 1 if the version is greater and -1 if it is less

function compareVersion(currentVersion, minVersion) {
  let current = currentVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))
  let min = minVersion.replace(/\./g," .").split(' ').map(x=>parseFloat(x,10))

  for(let i = 0; i < Math.max(current.length, min.length); i++) {
    if((current[i] || 0) < (min[i] || 0)) {
      return -1
    } else if ((current[i] || 0) > (min[i] || 0)) {
      return 1
    }
  }
  return 0
}


console.log(compareVersion("81.0.1212.121","80.4.1121.121"));
console.log(compareVersion("81.0.1212.121","80.4.9921.121"));
console.log(compareVersion("80.0.1212.121","80.4.9921.121"));
console.log(compareVersion("4.4.0","4.4.1"));
console.log(compareVersion("5.24","5.2"));
console.log(compareVersion("4.1","4.1.2"));
console.log(compareVersion("4.1.2","4.1"));
console.log(compareVersion("4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("4.4.4.4.4.4","4.4.4.4.4"));
console.log(compareVersion("0","1"));
console.log(compareVersion("1","1"));
console.log(compareVersion("1","1.0.00000.0000"));
console.log(compareVersion("","1"));
console.log(compareVersion("10.0.1","10.1"));

1 Comment

The regex is unnecessary. You can simply append . inside the map(): x=>parseFloat("." + x, 10)
6

The (most of the time) correct JavaScript answer in 2020

Both Nina Scholz in March 2020 and Sid Vishnoi in April 2020 post the modern answer:

var versions = ["2.0.1", "2.0", "1.0", "1.0.1", "2.0.0.1"];

versions.sort((a, b) => 
   a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
);

console.log(versions);

localCompare has been around for some time

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator

But what about 1.0a and 1.0.1

localCompare doesn't solve that, still returns 1.0.1 , 1.0a

Michael Deal in his (longish &complex) solution already cracked that in 2013

He converts Numbers to another Base, so they can be sorted better

His answer got me thinking...

666 - Don't think in numbers - 999

Sorting is alphanumeric, based on the ASCII values, so let's (ab)use ASCII as the "base"

My solution is to convert 1.0.2.1 to b.a.c.b to bacb , and then sort

This solves 1.1 vs. 1.0.0.0.1 with: bb vs. baaab

And immediately solves the 1.0a and 1.0.1 sorting problem with notation: baa and bab

Conversion is done with:

    const str = s => s.match(/(\d+)|[a-z]/g)
                      .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);

= Calculate ASCII value for 0...999 Numbers, otherwise concat letter

1.0a >>> [ "1" , "0" , "a" ] >>> [ "b" , "a" , "a" ]

For comparison sake there is no need to concatenate it to one string with .join("")

Oneliner

const sortVersions=(x,v=s=>s.match(/(\d+)|[a-z]/g)
                            .map(c=>c==~~c?String.fromCharCode(97+c):c))
                    =>x.sort((a,b)=>v(b)<v(a)?1:-1)

Test snippet:

function log(label,val){
  document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}

let v = ["1.90.1", "1.9.1", "1.89", "1.090", "1.2", "1.0a", "1.0.1", "1.10", "1.0.0a"];
log('not sorted input :',v);

v.sort((a, b) => a.localeCompare(b,undefined,{numeric:true,sensitivity:'base'   }));
log(' locale Compare :', v); // 1.0a AFTER 1.0.1

const str = s => s.match(/(\d+)|[a-z]/g)
                  .map(c => c == ~~c ? String.fromCharCode(97 + c) : c);
const versionCompare = (a, b) => {
  a = str(a);
  b = str(b);
  return b < a ? 1 : a == b ? 0 : -1;
}

v.sort(versionCompare);
log('versionCompare:', v);

Note how 1.090 is sorted in both results.

My code will not solve the 001.012.001 notation mentioned in one answer, but the localeCompare gets that part of the challenge right.

You could combine the two methods:

  • sort with .localCompare OR versionCompare when there is a letter involved

Final JavaScript solution

const sortVersions = (
  x,
  v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/) 
                             ? v(b) < v(a) ? 1 : -1 
                             : a.localeCompare(b, 0, {numeric: true}))

let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1"];
console.log(sortVersions(v));

Comments

6

Although this question already has a lot of answers, each one promotes their own backyard-brewn solution, whilst we have a whole ecosystem of (battle-)tested libraries for this.

A quick search on NPM, GitHub, X will give us some lovely libs, and I'd want to run through some:

semver-compare is a great lightweight (~230 bytes) lib that's especially useful if you want to sort by version numbers, as the library's exposed method returns -1, 0 or 1 appropriately.

The core of the library:

module.exports = function cmp (a, b) {
    var pa = a.split('.');
    var pb = b.split('.');
    for (var i = 0; i < 3; i++) {
        var na = Number(pa[i]);
        var nb = Number(pb[i]);
        if (na > nb) return 1;
        if (nb > na) return -1;
        if (!isNaN(na) && isNaN(nb)) return 1;
        if (isNaN(na) && !isNaN(nb)) return -1;
    }
    return 0;
};

compare-semver is rather hefty in size (~4.4 kB gzipped), but allows for some nice unique comparisons like to find the minimum/maximum of a stack of versions or to find out if the provided version is unique or less than anything else in a collection of versions.

compare-versions is another small library (~630 bytes gzipped) and follows the spec nicely, meaning you can compare versions with alpha/beta flags and even wildcards (like for minor/patch versions: 1.0.x or 1.0.*)

The point being: there's not always a need to copy-paste code from Stack Overflow, if you can find decent, (unit-)tested versions via your package manager of choice.

5 Comments

The first one is named semver-compare, but it does not support Semantic Versioning. And, this answer is much great and lightweight than it.
@Mr.Míng Semantic versioning is actually without the v.* prefix (semver.org/#is-v123-a-semantic-version) so I'd say semver-compare does support semantic versioning just fine
It might be fine in some cases, but its name is misleading.
How exactly? It does support the semver spec 🤔
Exactly, cmp("1.0.0-b", "1.0.0-a") should returns 1 if supports the semver spec, but it returns 0. See more examples from Semantic Versioning: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
5

Check the function version_compare() from the php.js project. It's is similar to PHP's version_compare().

You can simply use it like this:

version_compare('2.0', '2.0.0.1', '<'); 
// returns true

Comments

5

Forgive me if this idea already been visited in a link I have not seen.

I have had some success with conversion of the parts into a weighted sum like so:

partSum = this.major * Math.Pow(10,9);
partSum += this.minor * Math.Pow(10, 6);
partSum += this.revision * Math.Pow(10, 3);
partSum += this.build * Math.Pow(10, 0);

Which made comparisons very easy (comparing a double). Our version fields are never more than 4 digits.

7.10.2.184  -> 7010002184.0
7.11.0.1385 -> 7011001385.0

I hope this helps someone, as the multiple conditionals seem a bit overkill.

1 Comment

This will break, if this.minor > 999 (will overlap with major)
5

We can now use Intl.Collator API now to create numeric comparators. Browser support is pretty decent, but not supported in Node.js at the time of writing.

const semverCompare = new Intl.Collator("en", { numeric: true }).compare;

const versions = ['1.0.1', '1.10.2', '1.1.1', '1.10.1', '1.5.10', '2.10.0', '2.0.1'];

console.log(versions.sort(semverCompare))

const example2 = ["1.0", "1.0.1", "2.0", "2.0.0.1", "2.0.1"];
console.log(example2.sort(semverCompare))

Comments

5

A dead simple way:

function compareVer(previousVersion, currentVersion) {
 try {
    const [prevMajor, prevMinor = 0, prevPatch = 0] = previousVersion.split('.').map(Number);
    const [curMajor, curMinor = 0, curPatch = 0] = currentVersion.split('.').map(Number);

    if (curMajor > prevMajor) {
      return 'major update';
    }
    if (curMajor < prevMajor) {
      return 'major downgrade';
    }
    if (curMinor > prevMinor) {
      return 'minor update';
    }
    if (curMinor < prevMinor) {
      return 'minor downgrade';
    }
    if (curPatch > prevPatch) {
      return 'patch update';
    }
    if (curPatch < prevPatch) {
      return 'patch downgrade';
    }
    return 'same version';
  } catch (e) {
    return 'invalid format';
  }
}

Output:

compareVer("3.1", "3.1.1") // patch update
compareVer("3.1.1", "3.2") // minor update
compareVer("2.1.1", "1.1.1") // major downgrade
compareVer("1.1.1", "1.1.1") // same version

Comments

4

My less verbose answer than most of the answers here

/**
 * Compare two semver versions. Returns true if version A is greater than
 * version B
 * @param {string} versionA
 * @param {string} versionB
 * @returns {boolean}
 */
export const semverGreaterThan = function(versionA, versionB){
  var versionsA = versionA.split(/\./g),
    versionsB = versionB.split(/\./g)
  while (versionsA.length || versionsB.length) {
    var a = Number(versionsA.shift()), b = Number(versionsB.shift())
    if (a == b)
      continue
    return (a > b || isNaN(b))
  }
  return false
}

1 Comment

you should make it a module and put it on node.js. until then, i'm stealing your code with attribution to you. thank you for this.
4

Few lines of code and good if you don't want to allow letters or symbols. This works if you control the versioning scheme and it's not something a 3rd party provides.

// we presume all versions are of this format "1.4" or "1.10.2.3", without letters
// returns: 1 (bigger), 0 (same), -1 (smaller)
function versionCompare (v1, v2) {
  const v1Parts = v1.split('.')
  const v2Parts = v2.split('.')
  const length = Math.max(v1Parts.length, v2Parts.length)
  for (let i = 0; i < length; i++) {
    const value = (parseInt(v1Parts[i]) || 0) - (parseInt(v2Parts[i]) || 0)
    if (value < 0) return -1
    if (value > 0) return 1
  }
  return 0
}

console.log(versionCompare('1.2.0', '1.2.4') === -1)
console.log(versionCompare('1.2', '1.2.0') === 0)
console.log(versionCompare('1.2', '1') === 1)
console.log(versionCompare('1.2.10', '1.2.1') === 1)
console.log(versionCompare('1.2.134230', '1.2.2') === 1)
console.log(versionCompare('1.2.134230', '1.3.0.1.2.3.1') === -1)

Comments

3

You can use a JavaScript localeCompare method:

a.localeCompare(b, undefined, { numeric: true })

Here is an example:

"1.1".localeCompare("2.1.1", undefined, { numeric: true }) => -1

"1.0.0".localeCompare("1.0", undefined, { numeric: true }) => 1

"1.0.0".localeCompare("1.0.0", undefined, { numeric: true }) => 0

1 Comment

Here's a slightly better version (takes patches and -alpha suffixes into account) gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb
2

The replace() function only replaces the first occurence in the string. So, lets replace the . with ,. Afterwards delete all . and make the , to . again and parse it to float.

for(i=0; i<versions.length; i++) {
    v = versions[i].replace('.', ',');
    v = v.replace(/\./g, '');
    versions[i] = parseFloat(v.replace(',', '.'));
}

finally, sort it:

versions.sort();

Comments

2
// Returns true if v1 is bigger than v2, and false if otherwise.
function isNewerThan(v1, v2) {
      v1=v1.split('.');
      v2=v2.split('.');
      for(var i = 0; i<Math.max(v1.length,v2.length); i++){
        if(v1[i] == undefined) return false; // If there is no digit, v2 is automatically bigger
        if(v2[i] == undefined) return true; // if there is no digit, v1 is automatically bigger
        if(v1[i] > v2[i]) return true;
        if(v1[i] < v2[i]) return false;
      }
      return false; // Returns false if they are equal
    }

1 Comment

Welcome to SO. This question has a lot of good answers already, please refrain from adding new answers unless you add something new.
2

Based on Idan's awesome answer, the following function semverCompare passed most cases of semantic versioning 2.0.0. See this gist for more.

function semverCompare(a, b) {
    if (a.startsWith(b + "-")) return -1
    if (b.startsWith(a + "-")) return  1
    return a.localeCompare(b, undefined, { numeric: true, sensitivity: "case", caseFirst: "upper" })
}

It returns:

  • -1: a < b
  • 0: a == b
  • 1: a > b

Comments

2

The idea is to compare two versions and know which is the biggest. We delete "." and we compare each position of the vector with the other.

// Return 1  if a > b
// Return -1 if a < b
// Return 0  if a == b

function compareVersions(a_components, b_components) {

   if (a_components === b_components) {
       return 0;
   }

   var partsNumberA = a_components.split(".");
   var partsNumberB = b_components.split(".");

   for (var i = 0; i < partsNumberA.length; i++) {

      var valueA = parseInt(partsNumberA[i]);
      var valueB = parseInt(partsNumberB[i]);

      // A bigger than B
      if (valueA > valueB || isNaN(valueB)) {
         return 1;
      }

      // B bigger than A
      if (valueA < valueB) {
         return -1;
      }
   }
}

1 Comment

Epic answer, exactly what I was looking for.
2

Compare function with different conditions:

const compareVer = (ver1, middle, ver2) => {
  const res = new Intl.Collator("en").compare(ver1, ver2)
  let comp

  switch (middle) {
    case "=":
      comp = 0 === res
      break

    case ">":
      comp = 1 === res
      break

    case ">=":
      comp = 1 === res || 0 === res
      break

    case "<":
      comp = -1 === res
      break

    case "<=":
      comp = -1 === res || 0 === res
      break
  }

  return comp
}

console.log(compareVer("1.0.2", "=", "1.0.2")) // true
console.log(compareVer("1.0.3", ">", "1.0.2")) // true
console.log(compareVer("1.0.1", ">=", "1.0.2")) // false
console.log(compareVer("1.0.3", ">=", "1.0.2")) // true
console.log(compareVer("1.0.1", "<", "1.0.2")) // true
console.log(compareVer("1.0.1", "<=", "1.0.2")) // true

Comments

1

Check out this blog post. This function works for numeric version numbers.

function compVersions(strV1, strV2) {
  var nRes = 0
    , parts1 = strV1.split('.')
    , parts2 = strV2.split('.')
    , nLen = Math.max(parts1.length, parts2.length);

  for (var i = 0; i < nLen; i++) {
    var nP1 = (i < parts1.length) ? parseInt(parts1[i], 10) : 0
      , nP2 = (i < parts2.length) ? parseInt(parts2[i], 10) : 0;

    if (isNaN(nP1)) { nP1 = 0; }
    if (isNaN(nP2)) { nP2 = 0; }

    if (nP1 != nP2) {
      nRes = (nP1 > nP2) ? 1 : -1;
      break;
    }
  }

  return nRes;
};

compVersions('10', '10.0'); // 0
compVersions('10.1', '10.01.0'); // 0
compVersions('10.0.1', '10.0'); // 1
compVersions('10.0.1', '10.1'); // -1

Comments

1

If, for example, we want to check if the current jQuery version is less than 1.8, parseFloat($.ui.version) < 1.8 ) would give a wrong result if version is "1.10.1", since parseFloat("1.10.1") returns 1.1. A string compare would also go wrong, since "1.8" < "1.10" evaluates to false.

So we need a test like this

if(versionCompare($.ui.version, "1.8") < 0){
    alert("please update jQuery");
}

The following function handles this correctly:

/** Compare two dotted version strings (like '10.2.3').
 * @returns {Integer} 0: v1 == v2, -1: v1 < v2, 1: v1 > v2
 */
function versionCompare(v1, v2) {
    var v1parts = ("" + v1).split("."),
        v2parts = ("" + v2).split("."),
        minLength = Math.min(v1parts.length, v2parts.length),
        p1, p2, i;
    // Compare tuple pair-by-pair. 
    for(i = 0; i < minLength; i++) {
        // Convert to integer if possible, because "8" > "10".
        p1 = parseInt(v1parts[i], 10);
        p2 = parseInt(v2parts[i], 10);
        if (isNaN(p1)){ p1 = v1parts[i]; } 
        if (isNaN(p2)){ p2 = v2parts[i]; } 
        if (p1 == p2) {
            continue;
        }else if (p1 > p2) {
            return 1;
        }else if (p1 < p2) {
            return -1;
        }
        // one operand is NaN
        return NaN;
    }
    // The longer tuple is always considered 'greater'
    if (v1parts.length === v2parts.length) {
        return 0;
    }
    return (v1parts.length < v2parts.length) ? -1 : 1;
}

Here are some examples:

// compare dotted version strings
console.assert(versionCompare("1.8",      "1.8.1")    <   0);
console.assert(versionCompare("1.8.3",    "1.8.1")    >   0);
console.assert(versionCompare("1.8",      "1.10")     <   0);
console.assert(versionCompare("1.10.1",   "1.10.1")   === 0);
// Longer is considered 'greater'
console.assert(versionCompare("1.10.1.0", "1.10.1")   >   0);
console.assert(versionCompare("1.10.1",   "1.10.1.0") <   0);
// Strings pairs are accepted
console.assert(versionCompare("1.x",      "1.x")      === 0);
// Mixed int/string pairs return NaN
console.assert(isNaN(versionCompare("1.8", "1.x")));
//works with plain numbers
console.assert(versionCompare("4", 3)   >   0);

See here for a live sample and test suite: http://jsfiddle.net/mar10/8KjvP/

6 Comments

arghh, just noticed that ripper234 had posted a fiddle URL in on eof the comments a few months ago that is quite similar. Anyway, I keep my answer here...
This one will also fail (as most of variants around) in these cases: versionCompare('1.09', '1.1') returns "1", the same way as versionCompare('1.702', '1.8').
The code evaluates "1.09" > "1.1" and "1.702" > "1.8", which I think is correct. If you don't agree: can you point to some resource that backs your opinion?
It depends on your principles — as I know there's no strict rule or something. Regarding resources, wikipedia article for "Software versioning" in "Incrementing sequences" says that 1.81 may be a minor version of 1.8, so 1.8 should read as 1.80. Semantic versioning article semver.org/spec/v2.0.0.html also says that 1.9.0 -> 1.10.0 -> 1.11.0, so 1.9.0 is treated as 1.90.0 in comparison like this. So, following this logic, version 1.702 was before version 1.8, which is treated as 1.800.
I see that some rules treat 1.8 < 1.81 < 1.9. But in semver you would use 1.8.1 instead of 1.81. Semver (as I understand it) is defined around the assumption that incrementing a part will always generate a 'later' version, so 1.8 < 1.8.1 < 1.9 < 1.10 < 1.81 < 1.90 < 1.100 . I don't see an indication that this is limted to two digits either. So I would say that my code is fully compliant with semver.
|
1

This is a neat trick. If you are dealing with numeric values, between a specific range of values, you can assign a value to each level of the version object. For instance "largestValue" is set to 0xFF here, which creates a very "IP" sort of look to your versioning.

This also handles alpha-numeric versioning (i.e. 1.2a < 1.2b)

// The version compare function
function compareVersion(data0, data1, levels) {
    function getVersionHash(version) {
        var value = 0;
        version = version.split(".").map(function (a) {
            var n = parseInt(a);
            var letter = a.replace(n, "");
            if (letter) {
                return n + letter[0].charCodeAt() / 0xFF;
            } else {
                return n;
            }
        });
        for (var i = 0; i < version.length; ++i) {
            if (levels === i) break;
            value += version[i] / 0xFF * Math.pow(0xFF, levels - i + 1);
        }
        return value;
    };
    var v1 = getVersionHash(data0);
    var v2 = getVersionHash(data1);
    return v1 === v2 ? -1 : v1 > v2 ? 0 : 1;
};
// Returns 0 or 1, correlating to input A and input B
// Direct match returns -1
var version = compareVersion("1.254.253", "1.254.253a", 3);

Comments

1

I made this based on Kons idea, and optimized it for Java version "1.7.0_45". It's just a function meant to convert a version string to a float. This is the function:

function parseVersionFloat(versionString) {
    var versionArray = ("" + versionString)
            .replace("_", ".")
            .replace(/[^0-9.]/g, "")
            .split("."),
        sum = 0;
    for (var i = 0; i < versionArray.length; ++i) {
        sum += Number(versionArray[i]) / Math.pow(10, i * 3);
    }
    console.log(versionString + " -> " + sum);
    return sum;
}

String "1.7.0_45" is converted to 1.0070000450000001 and this is good enough for a normal comparison. Error explained here: How to deal with floating point number precision in JavaScript?. If need more then 3 digits on any part you can change the divider Math.pow(10, i * 3);.

Output will look like this:

1.7.0_45         > 1.007000045
ver 1.7.build_45 > 1.007000045
1.234.567.890    > 1.23456789

1 Comment

This is a great solution. It is also possible in a one-liner: ("" + versionString).replace("_", ".").replace(/[^0-9.]/g, "").split(".").reverse().reduce((accumulator, value) => accumulator/1000 + Number(value), 0)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.