diff --git a/lib/protocol/Writer.js b/lib/protocol/Writer.js index fbf76a6..4594141 100644 --- a/lib/protocol/Writer.js +++ b/lib/protocol/Writer.js @@ -28,9 +28,13 @@ var REGEX = { DATE: /(\d{4})-(\d{2})-(\d{2})/, TIME: /(\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)/, TIMESTAMP: /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2}(?:\.\d+)?)/, - DECIMAL: /^([+-])?(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/ + // DECIMAL will match "" and ".", both of which are invalid, requires an + // additional check + DECIMAL: /^([+-])?(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/ }; +const maxDecimalMantissaLen = 34; + function Writer(types, useCesu8) { this._types = types.map(normalizeType); this.reset(); @@ -589,20 +593,75 @@ Writer.prototype[TypeCode.SECONDDATE] = function writeSecondDate(value) { throw createNotImplementedError(); }; +function setChar(str, i, c) { + if(i >= str.length) return str; + return str.substring(0, i) + c + str.substring(i + 1); +} + +function trimLeadingZeroes(str) { + var i = 0; + while(i < str.length && str[i] === '0') { + ++i; + } + return str.substring(i); +} + +function trimTrailingZeroes(str) { + var i = str.length - 1; + while(i >= 0 && str[i] === '0') { + --i; + } + return str.substring(0, i + 1); +} + function stringToDecimal(str) { /* jshint bitwise:false */ var dec = str.match(REGEX.DECIMAL); - if (!dec) { + // REGEX.DECIMAL will match "." and "" despite these being invalid. + if (!dec || str === "." || str === "") { throw createInputError('DECIMAL'); } var sign = dec[1] === '-' ? -1 : 1; - var mInt = dec[2] || '0'; + var mInt = dec[2] || ''; var mFrac = dec[3] || ''; var exp = ~~dec[4]; + + mFrac = trimTrailingZeroes(mFrac); + var mantissa = trimLeadingZeroes(mInt + mFrac); + if(mantissa.length === 0) mantissa = "0"; + exp -= mFrac.length + + // round to maxDecimalMantissaLen digits and increment exp appropriately + if(mantissa.length > maxDecimalMantissaLen) { + var followDigit = mantissa[maxDecimalMantissaLen]; + exp += (mantissa.length - maxDecimalMantissaLen) + mantissa = mantissa.substring(0, maxDecimalMantissaLen); + if(followDigit > '4') { + // round up + var i = maxDecimalMantissaLen - 1; + while(i >= 0 && mantissa[i] === '9') { + i -= 1; + } + // i = index of first non-9 digit from back + if(i === -1) { + exp += mantissa.length; + mantissa = "1"; + } else { + exp += mantissa.length - 1 - i; + mantissa = mantissa.substring(0, i + 1); + mantissa = setChar(mantissa, i, String.fromCharCode(mantissa.charCodeAt(i) + 1)); + } + } else if(mantissa[maxDecimalMantissaLen - 1] === '0') { + var trimmed = trimTrailingZeroes(mantissa); + exp += (maxDecimalMantissaLen - trimmed.length); + mantissa = trimmed; + } + } + return { s: sign, - m: mInt + mFrac, - e: exp - mFrac.length + m: mantissa, + e: exp }; } diff --git a/lib/util/bignum.js b/lib/util/bignum.js index 2ee8a7d..edbd357 100644 --- a/lib/util/bignum.js +++ b/lib/util/bignum.js @@ -67,6 +67,10 @@ var BIN_10_35_5 = 29383; var BIN_10_35_6 = 16993; var BIN_10_35_7 = 19; +/* max values for UInt */ +var UInt64Max = '18446744073709551615'; +var UInt64MaxLen = 20; + function _readInt64(buffer, offset, unsigned) { var x, y, s, y0, y1, x0, x1, x2; @@ -141,10 +145,10 @@ function isUInt64(value) { if (typeof value === 'number') { return true; } - if (value.length < 20) { + if (value.length < UInt64MaxLen) { return true; } - if (value.length === 20 && value < '18446744073709551616') { + if (value.length === UInt64MaxLen && value <= UInt64Max) { return true; } return false; @@ -775,4 +779,4 @@ exports.writeUInt128LE = writeUInt128; exports.readDec128 = readDec128; exports.writeDec128 = writeDec128; exports.readDecFloat = readDecFloat; -exports.readDecFixed = readDecFixed; \ No newline at end of file +exports.readDecFixed = readDecFixed; diff --git a/test/fixtures/parametersData.js b/test/fixtures/parametersData.js index cecf398..3d3ed9f 100644 --- a/test/fixtures/parametersData.js +++ b/test/fixtures/parametersData.js @@ -237,3 +237,182 @@ exports.EMOJI = { '🍩' ] }; + +exports.DECIMAL = { + part: { + argumentCount: 1, + buffer: new Buffer( + '057b000000000000000000000000004030' + // 123 + '057b0000000000000000000000000040b0' + // -123 + '057b000000000000000000000000004030' + // 123 + '057b0000000000000000000000000040b0' + // -123 + '057b00000000000000000000000000a430' + // 123e50 + '057b00000000000000000000000000dcaf' + // -123e-50 + '057b000000000000000000000000004030' + // 123 + '057b0000000000000000000000000040b0' + // -123 + '057b000000000000000000000000004030' + // 123 + '057b000000000000000000000000003a30' + // 0.123 + '057b000000000000000000000000003ab0' + // -0.123 + '057b000000000000000000000000003a30' + // 0.123 + '057b000000000000000000000000003ab0' + // -0.123 + '0500000000000000000000000000004030' + // 0 + '0500000000000000000000000000004030' + // 0 + '0500000000000000000000000000004030' + // 0 + '0500000000000000000000000000004030' + // 0 + '0540e20100000000000000000000003a30' + // 123.456 + '0540e20100000000000000000000003ab0' + // -123.456 + '0540e20100000000000000000000003a30' + // 123.456 + '0540e20100000000000000000000003ab0' + // -123.456 + '05ffffffffffffffffffffffffffff4030' + // max 112 bit unsigned int + '05ffffffffffffffffffffffffffff3009' + // max112UInt * 10^-5000 + '05ffffffffffffffffffffffffffff40b0' + // -max112UInt + '05ffffffffffffffffffffffffffff50d7' + // -max112UInt * 10^5000 + '05ffffffffffffffffffffffffffff5430' + // max112UInt * 10^10 + '05ffffffffffffffffffffffffffff54b0' + // -max112UInt * 10^10 + '05ffffffffffffffffffffffffffff2c30' + // max112UInt * 10^-10 + '05ffffffffffffffffffffffffffff2cb0' + // -max112UInt * 10^-10 + '0500000000000000000000000000004130' + // max112UInt + 1 + '05000000000000000000000000000041b0' + // -(max112UInt + 1) + '05f3af967ed05c82de3297ff6fde3c6030' + // 1234567890123456789012345678901235 + '05f3af967ed05c82de3297ff6fde3c0230' + // 123.4567890123456789012345678901235 + '05f3af967ed05c82de3297ff6fde3c60b0' + // -1234567890123456789012345678901235 + '05f3af967ed05c82de3297ff6fde3c02b0' + // -123.4567890123456789012345678901235 + '057b00000000000000000000000000d42f' + // 123 * 10^-54 + '057b00000000000000000000000000d4af' + // -123 * 10^-54 + '05fdffffff638e8d37c087adbe09ed4130' + // 9999999999999999999999999999999997 + '05feffffff638e8d37c087adbe09ed4130' + // 9999999999999999999999999999999998 + '05ffffffff638e8d37c087adbe09ed4130' + // 9999999999999999999999999999999999 + '0501000000000000000000000000008430' + // 10000000000000000000000000000000000 + '0501000000000000000000000000008430' + // 10000000000000000000000000000000000 + '0501000000000000000000000000008430' + // 10000000000000000000000000000000000 + '0501000000000000000000000000008430' + // 10000000000000000000000000000000000 + '0501000000000000000000000000008430' + // 10000000000000000000000000000000000 + '05010000000a5bc138938d44c64d314230' + // 10000000000000000000000000000000010 + '05010000000a5bc138938d44c64d314230' + // 10000000000000000000000000000000010 + '05010000000a5bc138938d44c64d314230' + // 10000000000000000000000000000000010 + '05010000000a5bc138938d44c64d314230' + // 10000000000000000000000000000000010 + '05010000000a5bc138938d44c64d314230' + // 10000000000000000000000000000000010 + '0501000000000000000000000000008630' + // 100000000000000000000000000000000000 + '05fe7fc6a47e8d03000000000000006830' + // 99999999999999800000000000000000000 + '05ffffffff638e8d37c087adbe09ed4330' + // 99999999999999999999999999999999990 + '0501000000000000000000000000008630' + // 100000000000000000000000000000000000 + '05fe7fc6a47e8d03000000000000001ab0' + // -0.0000999999999999998 + '05d30a3f4eeee073c3f60fe98e01004e30', 'hex') // 1234567890123456789012345678910000000 + }, + types: [ + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + TypeCode.DECIMAL, + ], + values: [ + 123, + -123, + "123", + "-123", + "123e50", + "-123e-50", + "00000123", + "-00000123", + "123.", + ".123", + "-.123", + "00000.123", + "-00000.123", + "0.", + "00000.", + ".0", + ".00000", + "000123.456", + "-000123.456", + "123.456000", + "-123.456000", + "5192296858534827628530496329220095", // max 112 bit unsigned int + "5192296858534827628530496329220095e-5000", + "-5192296858534827628530496329220095", + "-5192296858534827628530496329220095e5000", + "51922968585348276285304963292200950000000000", + "-51922968585348276285304963292200950000000000", + "519229685853482762853049.6329220095", + "-519229685853482762853049.6329220095", + "5192296858534827628530496329220096", // max 112 bit + 1 + "-5192296858534827628530496329220096", + "12345678901234567890123456789012345678901234567890", + "123.45678901234567890123456789012345678901234567890", + "-12345678901234567890123456789012345678901234567890", + "-123.45678901234567890123456789012345678901234567890", + ".000000000000000000000000000000000000000000000000000123", + "-.000000000000000000000000000000000000000000000000000123", + "9999999999999999999999999999999997", + "9999999999999999999999999999999998", + "9999999999999999999999999999999999", + "10000000000000000000000000000000000", + "10000000000000000000000000000000001", + "10000000000000000000000000000000002", + "10000000000000000000000000000000003", + "10000000000000000000000000000000004", + "10000000000000000000000000000000005", + "10000000000000000000000000000000006", + "10000000000000000000000000000000007", + "10000000000000000000000000000000008", + "10000000000000000000000000000000009", + "99999999999999999999999999999999999", + "99999999999999799999999999999999999", + "99999999999999999999999999999999994", + "99999999999999999999999999999999995", + "-00000000000000000000000000000000000000009999999999999979999999999999999999999999999999.99900000000000e-50", + "1234567890123456789012345678909999999" + ] +}; diff --git a/test/lib.Writer.js b/test/lib.Writer.js index 579b590..310549d 100644 --- a/test/lib.Writer.js +++ b/test/lib.Writer.js @@ -105,6 +105,18 @@ describe('Lib', function () { }); }); + it('should write decimal types', function (done) { + var test = data.DECIMAL; + var writer = Writer.create(test); + writer.getParameters(SIZE, function (err, buffer) { + if (err) { + return done(err); + } + buffer.should.eql(test.part.buffer); + done(); + }); + }); + it('should get WriteLobRequest', function (done) { var writer = new Writer([TypeCode.BLOB]); var stream = new lib.util.stream.Readable(); @@ -383,6 +395,8 @@ describe('Lib', function () { Writer.prototype.setValues.bind(writer, [false]).should.throw(); // Regex does not match Writer.prototype.setValues.bind(writer, ['1^6']).should.throw(); + Writer.prototype.setValues.bind(writer, ['']).should.throw(); + Writer.prototype.setValues.bind(writer, ['.']).should.throw(); }); it('should raise wrong input type error for DATE', function () {