Couchbase view rounds off the long value in emit

i have a view which emits few values from the doc. one of the value is long value.
below is the snippet of value in doc i am trying to extract
“Place”: {
“PlaceId”: {
“moSigBits”: 1523365218128317302,
“leSigBits”: -7092643922943239825
}

this is the view i am using

function(doc, meta) {
if (doc.docType == 'CLUSTER_STATE' && doc.isLatestState) {
    var locality = doc.clusterMetaData.locality
    if (doc.archivePlace) {
        var updDt = new Date(doc.clusterMetaData.lastUpdatedTimestamp).toLocaleDateString("en-US")
            if (doc.archivePlace.state != null) {
                emit([doc.archivePlace.iso3Country + '+' + doc.archivePlace.state, 'VALID', updDt, doc.clusterId, BigInt(doc.Place.PlaceId.leSigBits), BigInt(doc.Place.PlaceId.moSigBits), doc.stateCreatorPlaceId, locality], 1);
            } else {
                emit([doc.archivePlace.iso3Country, 'VALID', updDt, doc.clusterId, BigInt(doc.Place.PlaceId.leSigBits), BigInt(doc.Place.PlaceId.moSigBits), doc.stateCreatorPlaceId, locality], 1);
            }
    } 
}

}

but upon emit i get

[ “abc”, “INVALID”, “8/17/2022”, “016a1fd8-9d27-498b-811c-84c78eba”, -7092643922943240000, 1523365218128317200, “86ac4e2e-6716-4685-9382-88f4f4a671f6”, “abc” ]

highlighted values are leSigbits and moSigbits are getting round off. i have tried with toString(), valueOf() and typecasting as BigINT but still getting same output.

what is it i am doing wrong??

I wonder if this is related to the javascript limits on integers.

1 Like

The MAX_SAFE_INTEGER constant has a value of 9007199254740991 (9,007,199,254,740,991 or ~9 quadrillion). The reasoning behind that number is that JavaScript uses double-precision floating-point format numbers as specified in IEEE 754 and can only safely represent integers between -(2^53 – 1) and 2^53 – 1.

If you currently want to use Couchbase to store these large numbers without rounding and use them in Views, Eventing or UDFs you will want to store your numbers as strings.

"Place": {
    "PlaceId": {
        "moSigBits": "1523365218128317302",
        "leSigBits": "-7092643922943239825"
    }
}

Then you can convert your strings as needed into a BigInt and back as needed.

var string = '8795485488564835575723945398498489398434434';
var bigInt = BigInt(string);
var newString = bigInt.toString()

If you have an older pre 7.X versoin of Couchbase you most likely will not have BigInt support if this is the case and you need to so some math (like adding your string representations) you can hack together you minimal string based math library from any available library I used parts of GitHub - rauschma/strint: String-encoded integers in JavaScript

function leftPadZeros(strint, digitCount) {
    forcePositiveString(strint);
    forceNonNegativeNumber(digitCount);

    return prefixZeros(strint, digitCount - strint.length);
}

function prefixZeros(strint, zeroCount) {
    forcePositiveString(strint);
    forceNonNegativeNumber(zeroCount);

    var result = strint;
    for (var i = 0; i < zeroCount; i++) {
        result = "0" + result;
    }
    return result;
}

function shiftLeft(strint, digitCount) {
    while (digitCount > 0) {
        strint = strint + "0";
        digitCount--;
    }
    return strint;
}

function forceNumber(value) {
    forceType(value, "number");
}

function forceNonNegativeNumber(value) {
    forceType(value, "number");
    if (value < 0) {
        throw new Error("Expected a positive number: " + value);
    }
}

function getDigit(x, digitIndex) {
    forceString(x);
    forceNumber(digitIndex);
    if (digitIndex >= getDigitCount(x)) {
        return "0";
    } else {
        return x.charAt(x.length - digitIndex - 1);
    }
}

function getDigitCount(strint) {
    if (isNegative(strint)) {
        return strint.length - 1;
    } else {
        return strint.length;
    }
}

function forceCondition(value, condition, conditionName) {
    if (!condition.call(null, value)) {
        throw new Error("Condition " + conditionName + " failed for value " + value);
    }
}

function forcePositiveString(value) {
    forceString(value);
    forceCondition(value, isPositive, "isPositive");
}

function forceString(value) {
    forceType(value, "string");
}

function forceType(value, type) {
    if (typeof value !== type) {
        throw new Error("Not a " + type + ": " + value);
    }
}

function isNegative(strint) {
    forceString(strint);
    return (strint.indexOf("-") === 0);
}

// Actually: isNonNegative
function isPositive(strint) {
    return !isNegative(strint);
}


function addPositive(x, y) {
    forcePositiveString(x);
    forcePositiveString(y);

    var maxLength = Math.max(x.length, y.length);
    var result = "";
    var borrow = 0;
    var leadingZeros = 0;
    for (var i = 0; i < maxLength; i++) {
        var lhs = Number(getDigit(x, i));
        var rhs = Number(getDigit(y, i));
        var digit = lhs + rhs + borrow;
        borrow = 0;
        while (digit >= 10) {
            digit -= 10;
            borrow++;
        }
        if (digit === 0) {
            leadingZeros++;
        } else {
            result = String(digit) + prefixZeros(result, leadingZeros);
            leadingZeros = 0;
        }
    }
    if (borrow > 0) {
        result = String(borrow) + result;
    }
    return result;
}

function strintAdd(x, y) {
    forceString(x);
    forceString(y);

    if (isPositive(x) && isPositive(y)) {
        return addPositive(x, y);
    } else if (isNegative(x) && isNegative(y)) {
        return negate(addPositive(abs(x), abs(y)));
    } else {
        if (lt(abs(x), abs(y))) {
            var tmp = x;
            x = y;
            y = tmp;
        }
        // |a| >= |b|
        var absResult = subPositive(abs(x), abs(y));
        if (isPositive(x)) {
            // Example: 5 + -3
            return absResult;
        } else {
            // Example: -5 + 3
            return negate(absResult);
        }
    }
}

Now you can add any string based integers as follows:

var eleven = strintAdd("1","10");
var verybig = strintAdd("1","1000000000000000000000000000000000000");

The resulting string will be

"11"
"1000000000000000000000000000000000001"

The issue you have hit isn’t that BigInt doesn’t work it is that the document read from the Data Service doesn’t translate correctly as the interface to the v8 runner only supports 2^53-1.

In addition be aware that it seems in v8 as if JSON.stringify() doesn’t know how to serialize a BigInt so this “issue” really isn’t specific to Couchbase but a result of the numeric translation being ambigous.

Best

Jon Strabala
Principal Product Manager - Server‌

2 Likes