> if you assign to an index that's way past the end of your the elements array, V8 may downgrade the elements to dictionary mode... avoid copying from the back
Sublime I think still has the snippet for "optimized for loop" that runs in reverse. I have never seen that improve performance, and I didn't know that it could actually be harmful if used to initialize an array.
Yesterday I had to store integers as keys in a map. It's good to learn today that they are implicitly converted to strings. Yes I could have used an array, but that would of required me to know the max index I would suppose. Maybe in JavaScript I insert into the array at any index, but that doesn't seem usual to me, coming from a C background I'd allocate the max index + 1.
I'm not a JavaScript expert but I believe most serious JavaScript implementations can represent sparse arrays, or arrays with holes in the range of indices, for this reason.
If performance is important, I believe it's still a fair bit faster to use a plain object as hash, rather than a Map. (If the keys are integers V8 may initially treat the object as an array, but it'll switch over to dictionary mode pretty quickly.)
Both parts have very clear and unsurprising explanations if you understand how IEEE 754 doubles work (same type as C double). For example, section 15.4 of ES5:
> A property name P (in the form of a String value) is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232−1.
.
-1 is the easier case to understand: ToUint32(-1) is 2^32 - 1 and ToString(ToUint32(-1)) is "4294967295", which is very clearly not -1.
Needless to say, the C equivalent is an out-of-bounds dereference.
.
While -0 is technically a different value from 0 (it's called negative zero in IEEE 754), the ToUint32 operation specifically carves out a case:
A) The literal `-0` is interpreted as an integer. The actual value it uses is zero:
char ** foo = (char **)malloc(3 * sizeof(char *));
foo[0] = foo[1] = foo[2] = "foo";
foo[-0]="bar"; /* this sets foo[0] since the compiler treats -0 as the unary negation of the integer 0 and integers have no signed zero */
printf("%d|%s|%s|%s\n", -0, foo[0], foo[1], foo[2]); /* "0|bar|foo|foo" since the -0 is understood by the compiler to be the actual value 0 */
B) Since JS interprets `-0` as an IEEE754 double, the value can be recreated by going through negative infinity:
double ninf = (-1.0/0.0); /* should be -Infinity */
printf("%g\n", ninf); /* "-inf" just to be sure */
double nzero = (1.0 / ninf); /* should be IEEE 754 negative zero */
printf("%g %d %zu\n", nzero, (int)nzero, (size_t)nzero); /* "-0 0 0" -0 behaves like 0 when casted to integer types */
.
The arguably surprising part is that JS doesn't have integer literals, but that isn't surprising when you think about the fact that JS only has one number type which maps to IEEE 754 double.
> The arguably surprising part is that JS doesn't have integer literals, but that isn't surprising when you think about the fact that JS only has one number type which maps to IEEE 754 double.
The fact that people have been able to run cryptography through JavaScript despite it not having integers is pretty amazing. Yes, I know the mantissa is where the bits are stored, but it still amazes me.
The more I read about the internals of Javascript, the more terrified I get. It seems to be constructed haphazardly and held together with lots of duct tape.
Sublime I think still has the snippet for "optimized for loop" that runs in reverse. I have never seen that improve performance, and I didn't know that it could actually be harmful if used to initialize an array.