From 140a6b51998f7f96e0445f191571dd2db2f7a149 Mon Sep 17 00:00:00 2001 From: Kenta Moriuchi Date: Sat, 11 Sep 2021 00:32:21 +0900 Subject: [PATCH] Add Float16Array#withAt --- src/Float16Array.mjs | 43 +++++++++++++++++++++++++++++++++++++++-- src/helper/is.mjs | 28 +++++++++++++++++++-------- src/helper/spec.mjs | 18 +++++++++++++++++ test/Float16Array.js | 46 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 10 deletions(-) diff --git a/src/Float16Array.mjs b/src/Float16Array.mjs index 84a40a46..654c4d45 100644 --- a/src/Float16Array.mjs +++ b/src/Float16Array.mjs @@ -1,8 +1,8 @@ import { wrapInArrayIterator } from "./helper/arrayIterator.mjs"; import { convertToNumber, roundToFloat16Bits } from "./helper/converter.mjs"; -import { isArrayBuffer, isCanonicalIntegerIndexString, isIterable, isObject, isObjectLike, isOrdinaryArray, isOrdinaryTypedArray, isSharedArrayBuffer, isTypedArray, isUint16Array } from "./helper/is.mjs"; +import { isArrayBuffer, isCanonicalIntegerIndexString, isIntegralNumber, isIterable, isObject, isObjectLike, isOrdinaryArray, isOrdinaryTypedArray, isSharedArrayBuffer, isTypedArray, isUint16Array } from "./helper/is.mjs"; import { createPrivateStorage } from "./helper/private.mjs"; -import { LengthOfArrayLike, SpeciesConstructor, ToIntegerOrInfinity, defaultCompare } from "./helper/spec.mjs"; +import { CanonicalNumericIndexString, LengthOfArrayLike, SpeciesConstructor, ToIntegerOrInfinity, defaultCompare } from "./helper/spec.mjs"; const brand = Symbol.for("__Float16Array__"); @@ -399,6 +399,45 @@ export class Float16Array extends Uint16Array { return convertToNumber(this[k]); } + withAt(index, value) { + assertFloat16BitsArray(this); + + const length = this.length; + + /** @type {number} */ + let numericIndex; + + if (typeof index === "string") { + numericIndex = CanonicalNumericIndexString(index); + if (numericIndex === undefined) { + throw TypeError("Invalid index type"); + } + + } else if (typeof index === "number") { + numericIndex = index; + + } else { + throw TypeError("Invalid index type"); + } + + if (!isIntegralNumber(numericIndex)) { + throw RangeError("Invalid index"); + } + + const actualIndex = numericIndex >= 0 ? numericIndex : length + numericIndex; + if (!isIntegralNumber(actualIndex) || actualIndex < 0 || actualIndex >= length) { + throw RangeError("Invalid index"); + } + + const uint16 = new Uint16Array(this.buffer, this.byteOffset, this.length); + const proxy = new Float16Array(uint16.slice().buffer); + const float16bitsArray = getFloat16BitsArrayFromFloat16Array(proxy); + + float16bitsArray[actualIndex] = roundToFloat16Bits(value); + + return proxy; + } + /** * @see https://tc39.es/ecma262/#sec-%typedarray%.prototype.map */ diff --git a/src/helper/is.mjs b/src/helper/is.mjs index acc6f78c..f6a398f9 100644 --- a/src/helper/is.mjs +++ b/src/helper/is.mjs @@ -116,25 +116,37 @@ export function isOrdinaryTypedArray(value) { /** * @param {unknown} value - * @returns {value is string} + * @returns {value is number} */ -export function isCanonicalIntegerIndexString(value) { - if (typeof value !== "string") { +export function isIntegralNumber(value) { + if (typeof value !== "number") { return false; } - const number = Number(value); - if (value !== number + "") { + if (!Number.isFinite(value)) { return false; } - if (!Number.isFinite(number)) { + if (value !== Math.trunc(value)) { + return false; + } + + return true; +} + +/** + * @param {unknown} value + * @returns {value is string} + */ +export function isCanonicalIntegerIndexString(value) { + if (typeof value !== "string") { return false; } - if (number !== Math.trunc(number)) { + const number = Number(value); + if (value !== number + "") { return false; } - return true; + return isIntegralNumber(number); } diff --git a/src/helper/spec.mjs b/src/helper/spec.mjs index 47c249da..4b35d017 100644 --- a/src/helper/spec.mjs +++ b/src/helper/spec.mjs @@ -41,6 +41,24 @@ function ToLength(target) { return length < Number.MAX_SAFE_INTEGER ? length : Number.MAX_SAFE_INTEGER; } +/** + * @see https://tc39.es/ecma262/#sec-canonicalnumericindexstring + * @param {string} target + * @return {number|undefined} + */ +export function CanonicalNumericIndexString(target) { + if (target === "-0") { + return -0; + } + + const num = Number(target); + if (target !== num + "") { + return undefined; + } + + return num; +} + /** * @see https://tc39.es/ecma262/#sec-lengthofarraylike * @param {object} arrayLike diff --git a/test/Float16Array.js b/test/Float16Array.js index b22472b7..740a8c56 100644 --- a/test/Float16Array.js +++ b/test/Float16Array.js @@ -664,7 +664,53 @@ describe("Float16Array", () => { assert.throws(() => { float16.at(BigInt(0), 0); }, TypeError); } }); + }); + + describe("#withAt()", () => { + + it("property `name` is 'withAt'", () => { + assert( Float16Array.prototype.withAt.name === "withAt" ); + }); + + it("property `length` is 1", () => { + assert( Float16Array.prototype.withAt.length === 2 ); + }); + + it("create Array", () => { + const float16_1 = new Float16Array([1, 2, 3]); + const float16_2 = float16_1.withAt(1, 4); + + assert( float16_1.buffer !== float16_2.buffer ); + assert.equalFloat16ArrayValues( float16_1, [1, 2, 3] ); + assert.equalFloat16ArrayValues( float16_2, [1, 4, 3] ); + + const float16_3 = float16_1.withAt(-1, 4); + assert( float16_1.buffer !== float16_3.buffer ); + assert.equalFloat16ArrayValues( float16_1, [1, 2, 3] ); + assert.equalFloat16ArrayValues( float16_3, [1, 2, 4] ); + }); + + it("throw Error with invalid index", () => { + const float16 = new Float16Array([1, 2, 3]); + + // out of range + assert.throws(() => { float16.withAt(5, 0); }, RangeError); + assert.throws(() => { float16.withAt(-5, 0); }, RangeError); + + // not integer number + assert.throws(() => { float16.withAt(0.5, 0); }, RangeError); + assert.throws(() => { float16.withAt(NaN, 0); }, RangeError); + assert.throws(() => { float16.withAt(Infinity, 0); }, RangeError); + + assert.throws(() => { float16.withAt("", 0); }, TypeError); + assert.throws(() => { float16.withAt(Symbol(), 0); }, TypeError); + + // Safari 13 doesn't have BigInt + if (typeof BigInt !== "undefined") { + assert.throws(() => { float16.withAt(BigInt(0), 0); }, TypeError); + } + }); }); describe("#map()", () => {