开发者

How can I compare arbitrary version numbers?

开发者 https://www.devze.com 2023-04-12 05:29 出处:网络
Does anyone have code to compare two version numbers in JavaScript? I just want simple version comparisons (e.g. \"1.0\" vs \"1.5.6\"), and it should work with numbers or strings. It can ignore traili

Does anyone have code to compare two version numbers in JavaScript? I just want simple version comparisons (e.g. "1.0" vs "1.5.6"), and it should work with numbers or strings. It can ignore trailing beta identifiers like "1.5.6b4", but can otherwise expect the strings to be well-formed. The function should return a signed integer like a nor开发者_如何学编程mal cmp function.

function cmpVersion(a, b)
  return less than one if a < b
  return 0 if a == b
  return greater than one if a > b

I have an answer, but will choose a better or more elegant solution over my own.

(I am using this to compare jQuery.browser.version numbers, but the answer will be more widely applicable)


function cmpVersion(a, b) {
    var i, cmp, len;
    a = (a + '').split('.');
    b = (b + '').split('.');
    len = Math.max(a.length, b.length);
    for( i = 0; i < len; i++ ) {
        if( a[i] === undefined ) {
            a[i] = '0';
        }
        if( b[i] === undefined ) {
            b[i] = '0';
        }
        cmp = parseInt(a[i], 10) - parseInt(b[i], 10);
        if( cmp !== 0 ) {
            return (cmp < 0 ? -1 : 1);
        }
    }
    return 0;
}

function gteVersion(a, b) {
    return cmpVersion(a, b) >= 0;
}
function ltVersion(a, b) {
    return cmpVersion(a, b) < 0;
}

This function handles:

  • numbers or strings as input
  • trailing zeros (e.g. cmpVersion("1.0", 1) returns 0)
  • ignores trailing alpha, b, pre4, etc


If you want to be fully correct, take a look at the discussion on PEP386, especially the heading “the new versioning algorithm”.

Otherwise it seems like your answer is pretty good.


npm uses a nice syntax to compare versions and you can get the same module here: https://github.com/isaacs/node-semver

The following range styles are supported:

  • 1.2.3 A specific version. When nothing else will do. Note that build metadata is still ignored, so 1.2.3+build2012 will satisfy this range.
  • >1.2.3 Greater than a specific version.
  • <1.2.3 Less than a specific version. If there is no prerelease tag on the version range, then no prerelease version will be allowed either, even though these are technically "less than".
  • >=1.2.3 Greater than or equal to. Note that prerelease versions are NOT equal to their "normal" equivalents, so 1.2.3-beta will not satisfy this range, but 2.3.0-beta will.
  • <=1.2.3 Less than or equal to. In this case, prerelease versions ARE allowed, so 1.2.3-beta would satisfy.
  • 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4
  • ~1.2.3 := >=1.2.3-0 <1.3.0-0 "Reasonably close to 1.2.3". When using tilde operators, prerelease versions are supported as well, but a prerelease of the next significant digit will NOT be satisfactory, so 1.3.0-beta will not satisfy ~1.2.3.
  • ^1.2.3 := >=1.2.3-0 <2.0.0-0 "Compatible with 1.2.3". When using caret operators, anything from the specified version (including prerelease) will be supported up to, but not including, the next major version (or its prereleases). 1.5.1 will satisfy ^1.2.3, while 1.2.2 and 2.0.0-beta will not.
  • ^0.1.3 := >=0.1.3-0 <0.2.0-0 "Compatible with 0.1.3". 0.x.x versions are special: the first non-zero component indicates potentially breaking changes, meaning the caret operator matches any version with the same first non-zero component starting at the specified version.
  • ^0.0.2 := =0.0.2 "Only the version 0.0.2 is considered compatible"
  • ~1.2 := >=1.2.0-0 <1.3.0-0 "Any version starting with 1.2"
  • ^1.2 := >=1.2.0-0 <2.0.0-0 "Any version compatible with 1.2"
  • 1.2.x := >=1.2.0-0 <1.3.0-0 "Any version starting with 1.2"
  • ~1 := >=1.0.0-0 <2.0.0-0 "Any version starting with 1"
  • ^1 := >=1.0.0-0 <2.0.0-0 "Any version compatible with 1"
  • 1.x := >=1.0.0-0 <2.0.0-0 "Any version starting with 1"

Ranges can be joined with either a space (which implies "and") or a || (which implies "or").


I have crafted the following function, which supports trailing letters, leading zeroes… (see examples below):

function cmpVersions(a, b) {

    var partsA = a.split('.');
    var partsB = b.split('.');
    var nbParts = Math.max(partsA.length, partsB.length);

    for (var i = 0; i < nbParts; ++i) {
        if (partsA[i] === undefined) {
            partsA[i] = '0';
        }
        if (partsB[i] === undefined) {
            partsB[i] = '0';
        }

        // edit: added this part
        // - fixes the important case "1.2 / 1.10"
        // - but breaks the not-so-important case "1.02 / 1.1"
        var intA = parseInt(partsA[i], 10);
        var intB = parseInt(partsB[i], 10);
        if (!isNaN(intA) && !isNaN(intB)) {
            if (intA > intB) {
                return 1;
            } else if (intA < intB) {
                return -1;
            }
        }

        var compare = partsA[i].localeCompare(partsB[i]);
        if (compare !== 0) {
            return compare;
        }
    }

    return 0;
}

So, a few examples:

// trailing letters
cmpVersion('1.0a', '1.0b'); // -1

// leading zeroes
cmpVersion('1.01', '1.1'); // -1

// "zero" parts
cmpVersion('1', '1.0'); // 0


If you don't need to support leading zeroes, here is a simpler alternative:

function cmpVersions(a, b) {

    function padParts(version) {
        return version
            .split('.')
            .map(function (part) {
                return '00000000'.substr(0, 8 - part.length) + part;
            })
            .join('.');
    }

    a = padParts(a);
    b = padParts(b);

    return a.localeCompare(b);
}


Quick update: I noticed afterwards that the first function sorts "1.2" before "1.10", which is blatantly wrong. Also, the "significant leading zeroes" are tricky and ambiguous (both to interpret and to implement), and Semantic Versioning explicitly avoids them. Therefore, I think the second function should always be preferred.

Update 2: But the second function sorts "1.2a" before "1.1"... I think there is just no "one fits all" function... Pick the "more appropriate" function according to your use case, or better, rather sort by date if you can.

Update 3: Modified the first function to handle correctly the important case "1.2 / 1.10". As a side effect, it breaks the not-so-important case "1.02 / 1.1", and apparently it's now the only caveat (maybe it could be fixed, but I'm not sure it's worth it). Therefore, I'm now recommending the fixed, first function.


function compareVersion(a, b) {
    return compareVersionRecursive(a.split("."), b.split("."));
}

function compareVersionRecursive(a, b) {
    if (a.length == 0) {
        a = [0];
    }
    if (b.length == 0) {
        b = [0];
    }
    if (a[0] != b[0] || (a.length == 1 && b.length == 1)) {
        return a[0] - b[0];
    }
    return compareVersionRecursive(a.slice(1), b.slice(1));
}


I've refactored mine down to be as concise as I can make it. It does not have the check for trailing zeroes but will work for build numbers of any length (e.g. major, major.minor, major.minor.build).

var cmpVersion = function(a, b) {
  let arrA = Array.from(a.split('.'), i => +i);
  let arrB = Array.from(b.split('.'), i => +i);

  for (let i = 0; i < (arrA.length >= arrB.length ? arrA.length : arrB.length); i++) {
    if (arrA[i] && !arrB[i] || arrA[i] > arrB[i]) return 'less than one';
    else if (!arrA[i] && arrB[i] || arrA[i] < arrB[i]) return 'greater than one';
  }
  return 0;
}

Basically, first I created a new array out of each version string so that I can compare each digit individually. Then in the for loop, I choose the length of the longest version string (or the length of the first version string if they're of equal length).

The if statement checks to see if there is a digit in a but not in b or if the digit of a is greater than the digit of b for a corresponding placeholder, in which case it will return 'less than one'.

As well, the else statement checks to see if there is a digit in b but not in a or if the digit of b is greater than the digit of a for a corresponding place value, in which case it will return 'greater than one'.

The last return 0 statement is a catch-all, which our function will get to if the version strings are equal.


If you do not care about the .5.6, use parseInt

var majorV = parseInt("1.5.6",10)

Since you said you care about the minor versions:

function cmpVersion(v1, v2) {
    if(v1===v2) return 0;
    var a1 = v1.toString().split(".");
    var a2 = v2.toString().split(".");
    for( var i = 0; i < a1.length && i < a2.length; i++ ) {
        var diff = parseInt(a1[i],10) - parseInt(a2[i],10);
        if( diff>0 ) {
            return 1;
        }
        else if( diff<0 ) {
            return -1;
        }
    }
    diff = a1.length - a2.length;
    return (diff>0) ? 1 : (diff<0) ? -1 : 0;
}

console.log( cmpVersion( "1.0", "1.56") );
console.log( cmpVersion( "1.56", "1.56") );
console.log( cmpVersion( "1.65", "1.5.6") );
console.log( cmpVersion( "1.0", "1.5.6b3") );


This function returns true if the version is greater than or equal to the minimum version. Assumes 1.0 is greater than 1 when versions are strings. When they are numbers it says they are the same. If you want to have both types return the same then you need to convert the numbers to strings which is also easy. or you can modify the string condition to check if the longer version number has all trailing zeros like 1.1 vs 1.1.0.0.0.0. the second one is all trailing zeros

 function doesMyVersionMeetMinimum(myVersion, minimumVersion) {

    if(typeof myVersion === 'number' && typeof minimumVersion === 'number') {
      return(myVersion >= minimumVersion);
    }

    var v1 = myVersion.split("."), v2 = minimumVersion.split("."), minLength;

    minLength= Math.min(v1.length, v2.length);

    for(i=0; i<minLength; i++) {
        if(Number(v1[i]) < Number(v2[i])) {
            return false;
        }
       else if(Number(v1[i]) < Number(v2[i])) {
            return true;
        }           

    }

    return (v1.length >= v2.length);
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号