@Override
 public ArrayData shiftRight(final int by) {
   final ArrayData newData = ensure(by + length() - 1);
   if (newData != this) {
     newData.shiftRight(by);
     return newData;
   }
   System.arraycopy(array, 0, array, by, array.length - by);
   return this;
 }
  @Override
  public ArrayData set(final int index, final Object value, final boolean strict) {
    if (value instanceof Double || (value != null && canWiden(value.getClass()))) {
      return set(index, ((Number) value).doubleValue(), strict);
    } else if (value == UNDEFINED) {
      return new UndefinedArrayFilter(this).set(index, value, strict);
    }

    final ArrayData newData = convert(value == null ? Object.class : value.getClass());
    return newData.set(index, value, strict);
  }
  @Override
  public ArrayData fastSplice(final int start, final int removed, final int added)
      throws UnsupportedOperationException {
    final long oldLength = length();
    final long newLength = oldLength - removed + added;
    if (newLength > SparseArrayData.MAX_DENSE_LENGTH && newLength > array.length) {
      throw new UnsupportedOperationException();
    }
    final ArrayData returnValue =
        removed == 0
            ? EMPTY_ARRAY
            : new NumberArrayData(Arrays.copyOfRange(array, start, start + removed), removed);

    if (newLength != oldLength) {
      final double[] newArray;

      if (newLength > array.length) {
        newArray = new double[ArrayData.nextSize((int) newLength)];
        System.arraycopy(array, 0, newArray, 0, start);
      } else {
        newArray = array;
      }

      System.arraycopy(
          array, start + removed, newArray, start + added, (int) (oldLength - start - removed));
      array = newArray;
      setLength(newLength);
    }

    return returnValue;
  }
  @Override
  public ContinuousArrayData fastConcat(final ContinuousArrayData otherData) {
    final int otherLength = (int) otherData.length();
    final int thisLength = (int) length();
    assert otherLength > 0 && thisLength > 0;

    final double[] otherArray = ((NumberArrayData) otherData).array;
    final int newLength = otherLength + thisLength;
    final double[] newArray = new double[ArrayData.alignUp(newLength)];

    System.arraycopy(array, 0, newArray, 0, thisLength);
    System.arraycopy(otherArray, 0, newArray, thisLength, otherLength);

    return new NumberArrayData(newArray, newLength);
  }
 @Override
 public ArrayData ensure(final long safeIndex) {
   if (safeIndex >= SparseArrayData.MAX_DENSE_LENGTH) {
     return new SparseArrayData(this, safeIndex + 1);
   }
   final int alen = array.length;
   if (safeIndex >= alen) {
     final int newLength = ArrayData.nextSize((int) safeIndex);
     array = Arrays.copyOf(array, newLength); // todo fill with nan or never accessed?
   }
   if (safeIndex >= length()) {
     setLength(safeIndex + 1);
   }
   return this;
 }