diff options
Diffstat (limited to 'node_modules/ctype/ctio.js')
-rw-r--r-- | node_modules/ctype/ctio.js | 1485 |
1 files changed, 1485 insertions, 0 deletions
diff --git a/node_modules/ctype/ctio.js b/node_modules/ctype/ctio.js new file mode 100644 index 000000000..62c5d7b23 --- /dev/null +++ b/node_modules/ctype/ctio.js @@ -0,0 +1,1485 @@ +/* + * rm - Feb 2011 + * ctio.js: + * + * A simple way to read and write simple ctypes. Of course, as you'll find the + * code isn't as simple as it might appear. The following types are currently + * supported in big and little endian formats: + * + * uint8_t int8_t + * uint16_t int16_t + * uint32_t int32_t + * float (single precision IEEE 754) + * double (double precision IEEE 754) + * + * This is designed to work in Node and v8. It may in fact work in other + * Javascript interpreters (that'd be pretty neat), but it hasn't been tested. + * If you find that it does in fact work, that's pretty cool. Try and pass word + * back to the original author. + * + * Note to the reader: If you're tabstop isn't set to 8, parts of this may look + * weird. + */ + +/* + * Numbers in Javascript have a secret: all numbers must be represented with an + * IEEE-754 double. The double has a mantissa with a length of 52 bits with an + * implicit one. Thus the range of integers that can be represented is limited + * to the size of the mantissa, this makes reading and writing 64-bit integers + * difficult, but far from impossible. + * + * Another side effect of this representation is what happens when you use the + * bitwise operators, i.e. shift left, shift right, and, or, etc. In Javascript, + * each operand and the result is cast to a signed 32-bit number. However, in + * the case of >>> the values are cast to an unsigned number. + */ + +/* + * A reminder on endian related issues: + * + * Big Endian: MSB -> First byte + * Little Endian: MSB->Last byte + */ +var mod_assert = require('assert'); + +/* + * An 8 bit unsigned integer involves doing no significant work. + */ +function ruint8(buffer, endian, offset) +{ + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + return (buffer[offset]); +} + +/* + * For 16 bit unsigned numbers we can do all the casting that we want to do. + */ +function rgint16(buffer, endian, offset) +{ + var val = 0; + + if (endian == 'big') { + val = buffer[offset] << 8; + val |= buffer[offset+1]; + } else { + val = buffer[offset]; + val |= buffer[offset+1] << 8; + } + + return (val); + +} + +function ruint16(buffer, endian, offset) +{ + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 1 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + return (rgint16(buffer, endian, offset)); +} + +/* + * Because most bitshifting is done using signed numbers, if we would go into + * the realm where we use that 32nd bit, we'll end up going into the negative + * range. i.e.: + * > 200 << 24 + * -939524096 + * + * Not the value you'd expect. To work around this, we end up having to do some + * abuse of the JavaScript standard. in this case, we know that a >>> shift is + * defined to cast our value to an *unsigned* 32-bit number. Because of that, we + * use that instead to save us some additional math, though it does feel a + * little weird and it isn't obvious as to why you woul dwant to do this at + * first. + */ +function rgint32(buffer, endian, offset) +{ + var val = 0; + + if (endian == 'big') { + val = buffer[offset+1] << 16; + val |= buffer[offset+2] << 8; + val |= buffer[offset+3]; + val = val + (buffer[offset] << 24 >>> 0); + } else { + val = buffer[offset+2] << 16; + val |= buffer[offset+1] << 8; + val |= buffer[offset]; + val = val + (buffer[offset + 3] << 24 >>> 0); + } + + return (val); +} + +function ruint32(buffer, endian, offset) +{ + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + return (rgint32(buffer, endian, offset)); +} + +/* + * Reads a 64-bit unsigned number. The astue observer will note that this + * doesn't quite work. Javascript has chosen to only have numbers that can be + * represented by a double. A double only has 52 bits of mantissa with an + * implicit 1, thus we have up to 53 bits to represent an integer. However, 2^53 + * doesn't quite give us what we want. Isn't 53 bits enough for anyone? What + * could you have possibly wanted to represent that was larger than that? Oh, + * maybe a size? You mean we bypassed the 4 GB limit on file sizes, when did + * that happen? + * + * To get around this egregious language issue, we're going to instead construct + * an array of two 32 bit unsigned integers. Where arr[0] << 32 + arr[1] would + * give the actual number. However, note that the above code probably won't + * produce the desired results because of the way Javascript numbers are + * doubles. + */ +function rgint64(buffer, endian, offset) +{ + var val = new Array(2); + + if (endian == 'big') { + val[0] = ruint32(buffer, endian, offset); + val[1] = ruint32(buffer, endian, offset+4); + } else { + val[0] = ruint32(buffer, endian, offset+4); + val[1] = ruint32(buffer, endian, offset); + } + + return (val); +} + +function ruint64(buffer, endian, offset) +{ + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 7 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + return (rgint64(buffer, endian, offset)); +} + + +/* + * Signed integer types, yay team! A reminder on how two's complement actually + * works. The first bit is the signed bit, i.e. tells us whether or not the + * number should be positive or negative. If the two's complement value is + * positive, then we're done, as it's equivalent to the unsigned representation. + * + * Now if the number is positive, you're pretty much done, you can just leverage + * the unsigned translations and return those. Unfortunately, negative numbers + * aren't quite that straightforward. + * + * At first glance, one might be inclined to use the traditional formula to + * translate binary numbers between the positive and negative values in two's + * complement. (Though it doesn't quite work for the most negative value) + * Mainly: + * - invert all the bits + * - add one to the result + * + * Of course, this doesn't quite work in Javascript. Take for example the value + * of -128. This could be represented in 16 bits (big-endian) as 0xff80. But of + * course, Javascript will do the following: + * + * > ~0xff80 + * -65409 + * + * Whoh there, Javascript, that's not quite right. But wait, according to + * Javascript that's perfectly correct. When Javascript ends up seeing the + * constant 0xff80, it has no notion that it is actually a signed number. It + * assumes that we've input the unsigned value 0xff80. Thus, when it does the + * binary negation, it casts it into a signed value, (positive 0xff80). Then + * when you perform binary negation on that, it turns it into a negative number. + * + * Instead, we're going to have to use the following general formula, that works + * in a rather Javascript friendly way. I'm glad we don't support this kind of + * weird numbering scheme in the kernel. + * + * (BIT-MAX - (unsigned)val + 1) * -1 + * + * The astute observer, may think that this doesn't make sense for 8-bit numbers + * (really it isn't necessary for them). However, when you get 16-bit numbers, + * you do. Let's go back to our prior example and see how this will look: + * + * (0xffff - 0xff80 + 1) * -1 + * (0x007f + 1) * -1 + * (0x0080) * -1 + * + * Doing it this way ends up allowing us to treat it appropriately in + * Javascript. Sigh, that's really quite ugly for what should just be a few bit + * shifts, ~ and &. + */ + +/* + * Endianness doesn't matter for 8-bit signed values. We could in fact optimize + * this case because the more traditional methods work, but for consistency, + * we'll keep doing this the same way. + */ +function rsint8(buffer, endian, offset) +{ + var neg; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + neg = buffer[offset] & 0x80; + if (!neg) + return (buffer[offset]); + + return ((0xff - buffer[offset] + 1) * -1); +} + +/* + * The 16-bit version requires a bit more effort. In this case, we can leverage + * our unsigned code to generate the value we want to return. + */ +function rsint16(buffer, endian, offset) +{ + var neg, val; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 1 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = rgint16(buffer, endian, offset); + neg = val & 0x8000; + if (!neg) + return (val); + + return ((0xffff - val + 1) * -1); +} + +/* + * We really shouldn't leverage our 32-bit code here and instead utilize the + * fact that we know that since these are signed numbers, we can do all the + * shifting and binary anding to generate the 32-bit number. But, for + * consistency we'll do the same. If we want to do otherwise, we should instead + * make the 32 bit unsigned code do the optimization. But as long as there + * aren't floats secretly under the hood for that, we /should/ be okay. + */ +function rsint32(buffer, endian, offset) +{ + var neg, val; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = rgint32(buffer, endian, offset); + neg = val & 0x80000000; + if (!neg) + return (val); + + return ((0xffffffff - val + 1) * -1); +} + +/* + * The signed version of this code suffers from all of the same problems of the + * other 64 bit version. + */ +function rsint64(buffer, endian, offset) +{ + var neg, val; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = rgint64(buffer, endian, offset); + neg = val[0] & 0x80000000; + + if (!neg) + return (val); + + val[0] = (0xffffffff - val[0]) * -1; + val[1] = (0xffffffff - val[1] + 1) * -1; + + /* + * If we had the key 0x8000000000000000, that would leave the lower 32 + * bits as 0xffffffff, however, since we're goint to add one, that would + * actually leave the lower 32-bits as 0x100000000, which would break + * our ability to write back a value that we received. To work around + * this, if we actually get that value, we're going to bump the upper + * portion by 1 and set this to zero. + */ + mod_assert.ok(val[1] <= 0x100000000); + if (val[1] == -0x100000000) { + val[1] = 0; + val[0]--; + } + + return (val); +} + +/* + * We now move onto IEEE 754: The traditional form for floating point numbers + * and what is secretly hiding at the heart of everything in this. I really hope + * that someone is actually using this, as otherwise, this effort is probably + * going to be more wasted. + * + * One might be tempted to use parseFloat here, but that wouldn't work at all + * for several reasons. Mostly due to the way floats actually work, and + * parseFloat only actually works in base 10. I don't see base 10 anywhere near + * this file. + * + * In this case we'll implement the single and double precision versions. The + * quadruple precision, while probably useful, wouldn't really be accepted by + * Javascript, so let's not even waste our time. + * + * So let's review how this format looks like. A single precision value is 32 + * bits and has three parts: + * - Sign bit + * - Exponent (Using bias notation) + * - Mantissa + * + * |s|eeeeeeee|mmmmmmmmmmmmmmmmmmmmmmmmm| + * 31| 30-23 | 22 - 0 | + * + * The exponent is stored in a biased input. The bias in this case 127. + * Therefore, our exponent is equal to the 8-bit value - 127. + * + * By default, a number is normalized in IEEE, that means that the mantissa has + * an implicit one that we don't see. So really the value stored is 1.m. + * However, if the exponent is all zeros, then instead we have to shift + * everything to the right one and there is no more implicit one. + * + * Special values: + * - Positive Infinity: + * Sign: 0 + * Exponent: All 1s + * Mantissa: 0 + * - Negative Infinity: + * Sign: 1 + * Exponent: All 1s + * Mantissa: 0 + * - NaN: + * Sign: * + * Exponent: All 1s + * Mantissa: non-zero + * - Zero: + * Sign: * + * Exponent: All 0s + * Mantissa: 0 + * + * In the case of zero, the sign bit determines whether we get a positive or + * negative zero. However, since Javascript cannot determine the difference + * between the two: i.e. -0 == 0, we just always return 0. + * + */ +function rfloat(buffer, endian, offset) +{ + var bytes = []; + var sign, exponent, mantissa, val; + var bias = 127; + var maxexp = 0xff; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + /* Normalize the bytes to be in endian order */ + if (endian == 'big') { + bytes[0] = buffer[offset]; + bytes[1] = buffer[offset+1]; + bytes[2] = buffer[offset+2]; + bytes[3] = buffer[offset+3]; + } else { + bytes[3] = buffer[offset]; + bytes[2] = buffer[offset+1]; + bytes[1] = buffer[offset+2]; + bytes[0] = buffer[offset+3]; + } + + sign = bytes[0] & 0x80; + exponent = (bytes[0] & 0x7f) << 1; + exponent |= (bytes[1] & 0x80) >>> 7; + mantissa = (bytes[1] & 0x7f) << 16; + mantissa |= bytes[2] << 8; + mantissa |= bytes[3]; + + /* Check for special cases before we do general parsing */ + if (!sign && exponent == maxexp && mantissa === 0) + return (Number.POSITIVE_INFINITY); + + if (sign && exponent == maxexp && mantissa === 0) + return (Number.NEGATIVE_INFINITY); + + if (exponent == maxexp && mantissa !== 0) + return (Number.NaN); + + /* + * Javascript really doesn't have support for positive or negative zero. + * So we're not going to try and give it to you. That would be just + * plain weird. Besides -0 == 0. + */ + if (exponent === 0 && mantissa === 0) + return (0); + + /* + * Now we can deal with the bias and the determine whether the mantissa + * has the implicit one or not. + */ + exponent -= bias; + if (exponent == -bias) { + exponent++; + val = 0; + } else { + val = 1; + } + + val = (val + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); + + if (sign) + val *= -1; + + return (val); +} + +/* + * Doubles in IEEE 754 are like their brothers except for a few changes and + * increases in size: + * - The exponent is now 11 bits + * - The mantissa is now 52 bits + * - The bias is now 1023 + * + * |s|eeeeeeeeeee|mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm| + * 63| 62 - 52 | 51 - 0 | + * 63| 62 - 52 | 51 - 0 | + * + * While the size has increased a fair amount, we're going to end up keeping the + * same general formula for calculating the final value. As a reminder, this + * formula is: + * + * (-1)^s * (n + m) * 2^(e-b) + * + * Where: + * s is the sign bit + * n is (exponent > 0) ? 1 : 0 -- Determines whether we're normalized + * or not + * m is the mantissa + * e is the exponent specified + * b is the bias for the exponent + * + */ +function rdouble(buffer, endian, offset) +{ + var bytes = []; + var sign, exponent, mantissa, val, lowmant; + var bias = 1023; + var maxexp = 0x7ff; + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 7 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + /* Normalize the bytes to be in endian order */ + if (endian == 'big') { + bytes[0] = buffer[offset]; + bytes[1] = buffer[offset+1]; + bytes[2] = buffer[offset+2]; + bytes[3] = buffer[offset+3]; + bytes[4] = buffer[offset+4]; + bytes[5] = buffer[offset+5]; + bytes[6] = buffer[offset+6]; + bytes[7] = buffer[offset+7]; + } else { + bytes[7] = buffer[offset]; + bytes[6] = buffer[offset+1]; + bytes[5] = buffer[offset+2]; + bytes[4] = buffer[offset+3]; + bytes[3] = buffer[offset+4]; + bytes[2] = buffer[offset+5]; + bytes[1] = buffer[offset+6]; + bytes[0] = buffer[offset+7]; + } + + /* + * We can construct the exponent and mantissa the same way as we did in + * the case of a float, just increase the range of the exponent. + */ + sign = bytes[0] & 0x80; + exponent = (bytes[0] & 0x7f) << 4; + exponent |= (bytes[1] & 0xf0) >>> 4; + + /* + * This is going to be ugly but then again, we're dealing with IEEE 754. + * This could probably be done as a node add on in a few lines of C++, + * but oh we'll, we've made it this far so let's be native the rest of + * the way... + * + * What we're going to do is break the mantissa into two parts, the + * lower 24 bits and the upper 28 bits. We'll multiply the upper 28 bits + * by the appropriate power and then add in the lower 24-bits. Not + * really that great. It's pretty much a giant kludge to deal with + * Javascript eccentricities around numbers. + */ + lowmant = bytes[7]; + lowmant |= bytes[6] << 8; + lowmant |= bytes[5] << 16; + mantissa = bytes[4]; + mantissa |= bytes[3] << 8; + mantissa |= bytes[2] << 16; + mantissa |= (bytes[1] & 0x0f) << 24; + mantissa *= Math.pow(2, 24); /* Equivalent to << 24, but JS compat */ + mantissa += lowmant; + + /* Check for special cases before we do general parsing */ + if (!sign && exponent == maxexp && mantissa === 0) + return (Number.POSITIVE_INFINITY); + + if (sign && exponent == maxexp && mantissa === 0) + return (Number.NEGATIVE_INFINITY); + + if (exponent == maxexp && mantissa !== 0) + return (Number.NaN); + + /* + * Javascript really doesn't have support for positive or negative zero. + * So we're not going to try and give it to you. That would be just + * plain weird. Besides -0 == 0. + */ + if (exponent === 0 && mantissa === 0) + return (0); + + /* + * Now we can deal with the bias and the determine whether the mantissa + * has the implicit one or not. + */ + exponent -= bias; + if (exponent == -bias) { + exponent++; + val = 0; + } else { + val = 1; + } + + val = (val + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); + + if (sign) + val *= -1; + + return (val); +} + +/* + * Now that we have gone through the pain of reading the individual types, we're + * probably going to want some way to write these back. None of this is going to + * be good. But since we have Javascript numbers this should certainly be more + * interesting. Though we can constrain this end a little bit more in what is + * valid. For now, let's go back to our friends the unsigned value. + */ + +/* + * Unsigned numbers seem deceptively easy. Here are the general steps and rules + * that we are going to take: + * - If the number is negative, throw an Error + * - Truncate any floating point portion + * - Take the modulus of the number in our base + * - Write it out to the buffer in the endian format requested at the offset + */ + +/* + * We have to make sure that the value is a valid integer. This means that it is + * non-negative. It has no fractional component and that it does not exceed the + * maximum allowed value. + * + * value The number to check for validity + * + * max The maximum value + */ +function prepuint(value, max) +{ + if (typeof (value) != 'number') + throw (new (Error('cannot write a non-number as a number'))); + + if (value < 0) + throw (new Error('specified a negative value for writing an ' + + 'unsigned value')); + + if (value > max) + throw (new Error('value is larger than maximum value for ' + + 'type')); + + if (Math.floor(value) !== value) + throw (new Error('value has a fractional component')); + + return (value); +} + +/* + * 8-bit version, classy. We can ignore endianness which is good. + */ +function wuint8(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepuint(value, 0xff); + buffer[offset] = val; +} + +/* + * Pretty much the same as the 8-bit version, just this time we need to worry + * about endian related issues. + */ +function wgint16(val, endian, buffer, offset) +{ + if (endian == 'big') { + buffer[offset] = (val & 0xff00) >>> 8; + buffer[offset+1] = val & 0x00ff; + } else { + buffer[offset+1] = (val & 0xff00) >>> 8; + buffer[offset] = val & 0x00ff; + } +} + +function wuint16(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 1 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepuint(value, 0xffff); + wgint16(val, endian, buffer, offset); +} + +/* + * The 32-bit version is going to have to be a little different unfortunately. + * We can't quite bitshift to get the largest byte, because that would end up + * getting us caught by the signed values. + * + * And yes, we do want to subtract out the lower part by default. This means + * that when we do the division, it will be treated as a bit shift and we won't + * end up generating a floating point value. If we did generate a floating point + * value we'd have to truncate it intelligently, this saves us that problem and + * may even be somewhat faster under the hood. + */ +function wgint32(val, endian, buffer, offset) +{ + if (endian == 'big') { + buffer[offset] = (val - (val & 0x00ffffff)) / Math.pow(2, 24); + buffer[offset+1] = (val >>> 16) & 0xff; + buffer[offset+2] = (val >>> 8) & 0xff; + buffer[offset+3] = val & 0xff; + } else { + buffer[offset+3] = (val - (val & 0x00ffffff)) / + Math.pow(2, 24); + buffer[offset+2] = (val >>> 16) & 0xff; + buffer[offset+1] = (val >>> 8) & 0xff; + buffer[offset] = val & 0xff; + } +} + +function wuint32(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepuint(value, 0xffffffff); + wgint32(val, endian, buffer, offset); +} + +/* + * Unlike the other versions, we expect the value to be in the form of two + * arrays where value[0] << 32 + value[1] would result in the value that we + * want. + */ +function wgint64(value, endian, buffer, offset) +{ + if (endian == 'big') { + wgint32(value[0], endian, buffer, offset); + wgint32(value[1], endian, buffer, offset+4); + } else { + wgint32(value[0], endian, buffer, offset+4); + wgint32(value[1], endian, buffer, offset); + } +} + +function wuint64(value, endian, buffer, offset) +{ + if (value === undefined) + throw (new Error('missing value')); + + if (!(value instanceof Array)) + throw (new Error('value must be an array')); + + if (value.length != 2) + throw (new Error('value must be an array of length 2')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 7 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + prepuint(value[0], 0xffffffff); + prepuint(value[1], 0xffffffff); + wgint64(value, endian, buffer, offset); +} + +/* + * We now move onto our friends in the signed number category. Unlike unsigned + * numbers, we're going to have to worry a bit more about how we put values into + * arrays. Since we are only worrying about signed 32-bit values, we're in + * slightly better shape. Unfortunately, we really can't do our favorite binary + * & in this system. It really seems to do the wrong thing. For example: + * + * > -32 & 0xff + * 224 + * + * What's happening above is really: 0xe0 & 0xff = 0xe0. However, the results of + * this aren't treated as a signed number. Ultimately a bad thing. + * + * What we're going to want to do is basically create the unsigned equivalent of + * our representation and pass that off to the wuint* functions. To do that + * we're going to do the following: + * + * - if the value is positive + * we can pass it directly off to the equivalent wuint + * - if the value is negative + * we do the following computation: + * mb + val + 1, where + * mb is the maximum unsigned value in that byte size + * val is the Javascript negative integer + * + * + * As a concrete value, take -128. In signed 16 bits this would be 0xff80. If + * you do out the computations: + * + * 0xffff - 128 + 1 + * 0xffff - 127 + * 0xff80 + * + * You can then encode this value as the signed version. This is really rather + * hacky, but it should work and get the job done which is our goal here. + * + * Thus the overall flow is: + * - Truncate the floating point part of the number + * - We don't have to take the modulus, because the unsigned versions will + * take care of that for us. And we don't have to worry about that + * potentially causing bad things to happen because of sign extension + * - Pass it off to the appropriate unsigned version, potentially modifying + * the negative portions as necessary. + */ + +/* + * A series of checks to make sure we actually have a signed 32-bit number + */ +function prepsint(value, max, min) +{ + if (typeof (value) != 'number') + throw (new (Error('cannot write a non-number as a number'))); + + if (value > max) + throw (new Error('value larger than maximum allowed value')); + + if (value < min) + throw (new Error('value smaller than minimum allowed value')); + + if (Math.floor(value) !== value) + throw (new Error('value has a fractional component')); + + return (value); +} + +/* + * The 8-bit version of the signed value. Overall, fairly straightforward. + */ +function wsint8(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepsint(value, 0x7f, -0x80); + if (val >= 0) + wuint8(val, endian, buffer, offset); + else + wuint8(0xff + val + 1, endian, buffer, offset); +} + +/* + * The 16-bit version of the signed value. Also, fairly straightforward. + */ +function wsint16(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 1 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepsint(value, 0x7fff, -0x8000); + if (val >= 0) + wgint16(val, endian, buffer, offset); + else + wgint16(0xffff + val + 1, endian, buffer, offset); + +} + +/* + * We can do this relatively easily by leveraging the code used for 32-bit + * unsigned code. + */ +function wsint32(value, endian, buffer, offset) +{ + var val; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + val = prepsint(value, 0x7fffffff, -0x80000000); + if (val >= 0) + wgint32(val, endian, buffer, offset); + else + wgint32(0xffffffff + val + 1, endian, buffer, offset); +} + +/* + * The signed 64 bit integer should by in the same format as when received. + * Mainly it should ensure that the value is an array of two integers where + * value[0] << 32 + value[1] is the desired number. Furthermore, the two values + * need to be equal. + */ +function wsint64(value, endian, buffer, offset) +{ + var vzpos, vopos; + var vals = new Array(2); + + if (value === undefined) + throw (new Error('missing value')); + + if (!(value instanceof Array)) + throw (new Error('value must be an array')); + + if (value.length != 2) + throw (new Error('value must be an array of length 2')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + if (offset + 7 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + /* + * We need to make sure that we have the same sign on both values. The + * hokiest way to to do this is to multiply the number by +inf. If we do + * this, we'll get either +/-inf depending on the sign of the value. + * Once we have this, we can compare it to +inf to see if the number is + * positive or not. + */ + vzpos = (value[0] * Number.POSITIVE_INFINITY) == + Number.POSITIVE_INFINITY; + vopos = (value[1] * Number.POSITIVE_INFINITY) == + Number.POSITIVE_INFINITY; + + /* + * If either of these is zero, then we don't actually need this check. + */ + if (value[0] != 0 && value[1] != 0 && vzpos != vopos) + throw (new Error('Both entries in the array must have ' + + 'the same sign')); + + /* + * Doing verification for a signed 64-bit integer is actually a big + * trickier than it appears. We can't quite use our standard techniques + * because we need to compare both sets of values. The first value is + * pretty straightforward. If the first value is beond the extremes than + * we error out. However, the valid range of the second value varies + * based on the first one. If the first value is negative, and *not* the + * largest negative value, than it can be any integer within the range [ + * 0, 0xffffffff ]. If it is the largest negative number, it must be + * zero. + * + * If the first number is positive, than it doesn't matter what the + * value is. We just simply have to make sure we have a valid positive + * integer. + */ + if (vzpos) { + prepuint(value[0], 0x7fffffff); + prepuint(value[1], 0xffffffff); + } else { + prepsint(value[0], 0, -0x80000000); + prepsint(value[1], 0, -0xffffffff); + if (value[0] == -0x80000000 && value[1] != 0) + throw (new Error('value smaller than minimum ' + + 'allowed value')); + } + + /* Fix negative numbers */ + if (value[0] < 0 || value[1] < 0) { + vals[0] = 0xffffffff - Math.abs(value[0]); + vals[1] = 0x100000000 - Math.abs(value[1]); + if (vals[1] == 0x100000000) { + vals[1] = 0; + vals[0]++; + } + } else { + vals[0] = value[0]; + vals[1] = value[1]; + } + wgint64(vals, endian, buffer, offset); +} + +/* + * Now we are moving onto the weirder of these, the float and double. For this + * we're going to just have to do something that's pretty weird. First off, we + * have no way to get at the underlying float representation, at least not + * easily. But that doesn't mean we can't figure it out, we just have to use our + * heads. + * + * One might propose to use Number.toString(2). Of course, this is not really + * that good, because the ECMAScript 262 v3 Standard says the following Section + * 15.7.4.2-Number.prototype.toString (radix): + * + * If radix is an integer from 2 to 36, but not 10, the result is a string, the + * choice of which is implementation-dependent. + * + * Well that doesn't really help us one bit now does it? We could use the + * standard base 10 version of the string, but that's just going to create more + * errors as we end up trying to convert it back to a binary value. So, really + * this just means we have to be non-lazy and parse the structure intelligently. + * + * First off, we can do the basic checks: NaN, positive and negative infinity. + * + * Now that those are done we can work backwards to generate the mantissa and + * exponent. + * + * The first thing we need to do is determine the sign bit, easy to do, check + * whether the value is less than 0. And convert the number to its absolute + * value representation. Next, we need to determine if the value is less than + * one or greater than or equal to one and from there determine what power was + * used to get there. What follows is now specific to floats, though the general + * ideas behind this will hold for doubles as well, but the exact numbers + * involved will change. + * + * Once we have that power we can determine the exponent and the mantissa. Call + * the value that has the number of bits to reach the power ebits. In the + * general case they have the following values: + * + * exponent 127 + ebits + * mantissa value * 2^(23 - ebits) & 0x7fffff + * + * In the case where the value of ebits is <= -127 we are now in the case where + * we no longer have normalized numbers. In this case the values take on the + * following values: + * + * exponent 0 + * mantissa value * 2^149 & 0x7fffff + * + * Once we have the values for the sign, mantissa, and exponent. We reconstruct + * the four bytes as follows: + * + * byte0 sign bit and seven most significant bits from the exp + * sign << 7 | (exponent & 0xfe) >>> 1 + * + * byte1 lsb from the exponent and 7 top bits from the mantissa + * (exponent & 0x01) << 7 | (mantissa & 0x7f0000) >>> 16 + * + * byte2 bits 8-15 (zero indexing) from mantissa + * mantissa & 0xff00 >> 8 + * + * byte3 bits 0-7 from mantissa + * mantissa & 0xff + * + * Once we have this we have to assign them into the buffer in proper endian + * order. + */ + +/* + * Compute the log base 2 of the value. Now, someone who remembers basic + * properties of logarithms will point out that we could use the change of base + * formula for logs, and in fact that would be astute, because that's what we'll + * do for now. It feels cleaner, albeit it may be less efficient than just + * iterating and dividing by 2. We may want to come back and revisit that some + * day. + */ +function log2(value) +{ + return (Math.log(value) / Math.log(2)); +} + +/* + * Helper to determine the exponent of the number we're looking at. + */ +function intexp(value) +{ + return (Math.floor(log2(value))); +} + +/* + * Helper to determine the exponent of the fractional part of the value. + */ +function fracexp(value) +{ + return (Math.floor(log2(value))); +} + +function wfloat(value, endian, buffer, offset) +{ + var sign, exponent, mantissa, ebits; + var bytes = []; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + + if (offset + 3 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + if (isNaN(value)) { + sign = 0; + exponent = 0xff; + mantissa = 23; + } else if (value == Number.POSITIVE_INFINITY) { + sign = 0; + exponent = 0xff; + mantissa = 0; + } else if (value == Number.NEGATIVE_INFINITY) { + sign = 1; + exponent = 0xff; + mantissa = 0; + } else { + /* Well we have some work to do */ + + /* Thankfully the sign bit is trivial */ + if (value < 0) { + sign = 1; + value = Math.abs(value); + } else { + sign = 0; + } + + /* Use the correct function to determine number of bits */ + if (value < 1) + ebits = fracexp(value); + else + ebits = intexp(value); + + /* Time to deal with the issues surrounding normalization */ + if (ebits <= -127) { + exponent = 0; + mantissa = (value * Math.pow(2, 149)) & 0x7fffff; + } else { + exponent = 127 + ebits; + mantissa = value * Math.pow(2, 23 - ebits); + mantissa &= 0x7fffff; + } + } + + bytes[0] = sign << 7 | (exponent & 0xfe) >>> 1; + bytes[1] = (exponent & 0x01) << 7 | (mantissa & 0x7f0000) >>> 16; + bytes[2] = (mantissa & 0x00ff00) >>> 8; + bytes[3] = mantissa & 0x0000ff; + + if (endian == 'big') { + buffer[offset] = bytes[0]; + buffer[offset+1] = bytes[1]; + buffer[offset+2] = bytes[2]; + buffer[offset+3] = bytes[3]; + } else { + buffer[offset] = bytes[3]; + buffer[offset+1] = bytes[2]; + buffer[offset+2] = bytes[1]; + buffer[offset+3] = bytes[0]; + } +} + +/* + * Now we move onto doubles. Doubles are similar to floats in pretty much all + * ways except that the processing isn't quite as straightforward because we + * can't always use shifting, i.e. we have > 32 bit values. + * + * We're going to proceed in an identical fashion to floats and utilize the same + * helper functions. All that really is changing are the specific values that we + * use to do the calculations. Thus, to review we have to do the following. + * + * First get the sign bit and convert the value to its absolute value + * representation. Next, we determine the number of bits that we used to get to + * the value, branching whether the value is greater than or less than 1. Once + * we have that value which we will again call ebits, we have to do the + * following in the general case: + * + * exponent 1023 + ebits + * mantissa [value * 2^(52 - ebits)] % 2^52 + * + * In the case where the value of ebits <= -1023 we no longer use normalized + * numbers, thus like with floats we have to do slightly different processing: + * + * exponent 0 + * mantissa [value * 2^1074] % 2^52 + * + * Once we have determined the sign, exponent and mantissa we can construct the + * bytes as follows: + * + * byte0 sign bit and seven most significant bits form the exp + * sign << 7 | (exponent & 0x7f0) >>> 4 + * + * byte1 Remaining 4 bits from the exponent and the four most + * significant bits from the mantissa 48-51 + * (exponent & 0x00f) << 4 | mantissa >>> 48 + * + * byte2 Bits 40-47 from the mantissa + * (mantissa >>> 40) & 0xff + * + * byte3 Bits 32-39 from the mantissa + * (mantissa >>> 32) & 0xff + * + * byte4 Bits 24-31 from the mantissa + * (mantissa >>> 24) & 0xff + * + * byte5 Bits 16-23 from the Mantissa + * (mantissa >>> 16) & 0xff + * + * byte6 Bits 8-15 from the mantissa + * (mantissa >>> 8) & 0xff + * + * byte7 Bits 0-7 from the mantissa + * mantissa & 0xff + * + * Now we can't quite do the right shifting that we want in bytes 1 - 3, because + * we'll have extended too far and we'll lose those values when we try and do + * the shift. Instead we have to use an alternate approach. To try and stay out + * of floating point, what we'll do is say that mantissa -= bytes[4-7] and then + * divide by 2^32. Once we've done that we can use binary arithmetic. Oof, + * that's ugly, but it seems to avoid using floating point (just based on how v8 + * seems to be optimizing for base 2 arithmetic). + */ +function wdouble(value, endian, buffer, offset) +{ + var sign, exponent, mantissa, ebits; + var bytes = []; + + if (value === undefined) + throw (new Error('missing value')); + + if (endian === undefined) + throw (new Error('missing endian')); + + if (buffer === undefined) + throw (new Error('missing buffer')); + + if (offset === undefined) + throw (new Error('missing offset')); + + + if (offset + 7 >= buffer.length) + throw (new Error('Trying to read beyond buffer length')); + + if (isNaN(value)) { + sign = 0; + exponent = 0x7ff; + mantissa = 23; + } else if (value == Number.POSITIVE_INFINITY) { + sign = 0; + exponent = 0x7ff; + mantissa = 0; + } else if (value == Number.NEGATIVE_INFINITY) { + sign = 1; + exponent = 0x7ff; + mantissa = 0; + } else { + /* Well we have some work to do */ + + /* Thankfully the sign bit is trivial */ + if (value < 0) { + sign = 1; + value = Math.abs(value); + } else { + sign = 0; + } + + /* Use the correct function to determine number of bits */ + if (value < 1) + ebits = fracexp(value); + else + ebits = intexp(value); + + /* + * This is a total hack to determine a denormalized value. + * Unfortunately, we sometimes do not get a proper value for + * ebits, i.e. we lose the values that would get rounded off. + * + * + * The astute observer may wonder why we would be + * multiplying by two Math.pows rather than just summing + * them. Well, that's to get around a small bug in the + * way v8 seems to implement the function. On occasion + * doing: + * + * foo * Math.pow(2, 1023 + 51) + * + * Causes us to overflow to infinity, where as doing: + * + * foo * Math.pow(2, 1023) * Math.pow(2, 51) + * + * Does not cause us to overflow. Go figure. + * + */ + if (value <= 2.225073858507201e-308 || ebits <= -1023) { + exponent = 0; + mantissa = value * Math.pow(2, 1023) * Math.pow(2, 51); + mantissa %= Math.pow(2, 52); + } else { + /* + * We might have gotten fucked by our floating point + * logarithm magic. This is rather crappy, but that's + * our luck. If we just had a log base 2 or access to + * the stupid underlying representation this would have + * been much easier and we wouldn't have such stupid + * kludges or hacks. + */ + if (ebits > 1023) + ebits = 1023; + exponent = 1023 + ebits; + mantissa = value * Math.pow(2, -ebits); + mantissa *= Math.pow(2, 52); + mantissa %= Math.pow(2, 52); + } + } + + /* Fill the bytes in backwards to deal with the size issues */ + bytes[7] = mantissa & 0xff; + bytes[6] = (mantissa >>> 8) & 0xff; + bytes[5] = (mantissa >>> 16) & 0xff; + mantissa = (mantissa - (mantissa & 0xffffff)) / Math.pow(2, 24); + bytes[4] = mantissa & 0xff; + bytes[3] = (mantissa >>> 8) & 0xff; + bytes[2] = (mantissa >>> 16) & 0xff; + bytes[1] = (exponent & 0x00f) << 4 | mantissa >>> 24; + bytes[0] = (sign << 7) | (exponent & 0x7f0) >>> 4; + + if (endian == 'big') { + buffer[offset] = bytes[0]; + buffer[offset+1] = bytes[1]; + buffer[offset+2] = bytes[2]; + buffer[offset+3] = bytes[3]; + buffer[offset+4] = bytes[4]; + buffer[offset+5] = bytes[5]; + buffer[offset+6] = bytes[6]; + buffer[offset+7] = bytes[7]; + } else { + buffer[offset+7] = bytes[0]; + buffer[offset+6] = bytes[1]; + buffer[offset+5] = bytes[2]; + buffer[offset+4] = bytes[3]; + buffer[offset+3] = bytes[4]; + buffer[offset+2] = bytes[5]; + buffer[offset+1] = bytes[6]; + buffer[offset] = bytes[7]; + } +} + +/* + * Actually export our work above. One might argue that we shouldn't expose + * these interfaces and just force people to use the higher level abstractions + * around this work. However, unlike say other libraries we've come across, this + * interface has several properties: it makes sense, it's simple, and it's + * useful. + */ +exports.ruint8 = ruint8; +exports.ruint16 = ruint16; +exports.ruint32 = ruint32; +exports.ruint64 = ruint64; +exports.wuint8 = wuint8; +exports.wuint16 = wuint16; +exports.wuint32 = wuint32; +exports.wuint64 = wuint64; + +exports.rsint8 = rsint8; +exports.rsint16 = rsint16; +exports.rsint32 = rsint32; +exports.rsint64 = rsint64; +exports.wsint8 = wsint8; +exports.wsint16 = wsint16; +exports.wsint32 = wsint32; +exports.wsint64 = wsint64; + +exports.rfloat = rfloat; +exports.rdouble = rdouble; +exports.wfloat = wfloat; +exports.wdouble = wdouble; |