opentelemetry-js/packages/sdk-metrics/test/aggregator/exponential-histogram/ExponentMapping.test.ts

296 lines
8.3 KiB
TypeScript

/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExponentMapping } from '../../../src/aggregator/exponential-histogram/mapping/ExponentMapping';
import * as ieee754 from '../../../src/aggregator/exponential-histogram/mapping/ieee754';
import * as assert from 'assert';
const MIN_SCALE = -10;
const MAX_SCALE = 0;
describe('ExponentMapping', () => {
it('maps expected values for scale 0', () => {
const mapping = new ExponentMapping(0);
assert.strictEqual(mapping.scale, 0);
const expectedMappings = [
// near +inf
[Number.MAX_VALUE, ieee754.MAX_NORMAL_EXPONENT],
[Number.MAX_VALUE, 1023],
[Math.pow(2, 1023), 1022],
[1.0625 * Math.pow(2, 1023), 1023],
[Math.pow(2, 1022), 1021],
[1.0625 * Math.pow(2, 1023), 1023],
// near 0
[Math.pow(2, -1022), -1023],
[1.0625 * Math.pow(2, -1022), -1022],
[Math.pow(2, -1021), -1022],
[1.0625 * Math.pow(2, -1021), -1021],
[Math.pow(2, -1022), ieee754.MIN_NORMAL_EXPONENT - 1],
[Math.pow(2, -1021), ieee754.MIN_NORMAL_EXPONENT],
[Number.MIN_VALUE, ieee754.MIN_NORMAL_EXPONENT - 1],
// near 1
[4, 1],
[3, 1],
[2, 0],
[1.5, 0],
[1, -1],
[0.75, -1],
[0.51, -1],
[0.5, -2],
[0.26, -2],
[0.25, -3],
[0.126, -3],
[0.125, -4],
];
expectedMappings.forEach(([value, expected]) => {
const result = mapping.mapToIndex(value);
assert.strictEqual(
result,
expected,
`expected: ${value} to map to: ${expected}, got: ${result}`
);
});
});
it('maps expected values for min scale', () => {
const mapping = new ExponentMapping(MIN_SCALE);
assert.strictEqual(mapping.scale, MIN_SCALE);
const expectedMappings = [
[1.000001, 0],
[1, -1],
[Number.MAX_VALUE / 2, 0],
[Number.MAX_VALUE, 0],
[Number.MIN_VALUE, -1],
[0.5, -1],
];
expectedMappings.forEach(([value, expected]) => {
const result = mapping.mapToIndex(value);
assert.strictEqual(
result,
expected,
`expected: ${value} to map to: ${expected}, got: ${result}`
);
});
});
it('maps expected values for scale -1', () => {
const mapping = new ExponentMapping(-1);
assert.strictEqual(mapping.scale, -1);
const expectedMappings = [
[17, 2],
[16, 1],
[15, 1],
[9, 1],
[8, 1],
[5, 1],
[4, 0],
[3, 0],
[2, 0],
[1.5, 0],
[1, -1],
[0.75, -1],
[0.5, -1],
[0.25, -2],
[0.2, -2],
[0.13, -2],
[0.125, -2],
[0.1, -2],
[0.0625, -3],
[0.06, -3],
];
expectedMappings.forEach(([value, expected]) => {
const result = mapping.mapToIndex(value);
assert.strictEqual(
result,
expected,
`expected: ${value} to map to: ${expected}, got: ${result}`
);
});
});
it('maps expected values for scale -4', () => {
const mapping = new ExponentMapping(-4);
assert.strictEqual(mapping.scale, -4);
const expectedMappings = [
[0x1, -1],
[0x10, 0],
[0x100, 0],
[0x1000, 0],
[0x10000, 0], // Base == 2**16
[0x100000, 1],
[0x1000000, 1],
[0x10000000, 1],
[0x100000000, 1], // == 2**32
[0x1000000000, 2],
[0x10000000000, 2],
[0x100000000000, 2],
[0x1000000000000, 2], // 2**48
[0x10000000000000, 3],
[0x1000000000000000, 3],
[0x10000000000000000, 3], // 2**64
[0x100000000000000000, 4],
[0x1000000000000000000, 4],
[0x10000000000000000000, 4],
[0x100000000000000000000, 4], // 2**80
[0x1000000000000000000000, 5],
[1 / 0x1, -1],
[1 / 0x10, -1],
[1 / 0x100, -1],
[1 / 0x1000, -1],
[1 / 0x10000, -2], // 2**-16
[1 / 0x100000, -2],
[1 / 0x1000000, -2],
[1 / 0x10000000, -2],
[1 / 0x100000000, -3], // 2**-32
[1 / 0x1000000000, -3],
[1 / 0x10000000000, -3],
[1 / 0x100000000000, -3],
[1 / 0x1000000000000, -4], // 2**-48
[1 / 0x10000000000000, -4],
[1 / 0x100000000000000, -4],
[1 / 0x1000000000000000, -4],
[1 / 0x10000000000000000, -5], // 2**-64
[1 / 0x100000000000000000, -5],
// Max values
// below is equivalent to [0x1.FFFFFFFFFFFFFp1023, 63],
[
Array.from({ length: 13 }, (_, x) => 0xf * Math.pow(16, -x - 1)).reduce(
(x, y) => x + y,
1
) * Math.pow(2, 1023),
63,
],
[Math.pow(2, 1023), 63],
[Math.pow(2, 1019), 63],
[Math.pow(2, 1009), 63],
[Math.pow(2, 1008), 62],
[Math.pow(2, 1007), 62],
[Math.pow(2, 1000), 62],
[Math.pow(2, 993), 62],
[Math.pow(2, 992), 61],
[Math.pow(2, 991), 61],
// Min and subnormal values
[Math.pow(2, -1074), -64],
[Math.pow(2, -1073), -64],
[Math.pow(2, -1072), -64],
[Math.pow(2, -1057), -64],
[Math.pow(2, -1056), -64],
[Math.pow(2, -1041), -64],
[Math.pow(2, -1040), -64],
[Math.pow(2, -1025), -64],
[Math.pow(2, -1024), -64],
[Math.pow(2, -1023), -64],
[Math.pow(2, -1022), -64],
[Math.pow(2, -1009), -64],
[Math.pow(2, -1008), -64],
[Math.pow(2, -1007), -63],
[Math.pow(2, -993), -63],
[Math.pow(2, -992), -63],
[Math.pow(2, -991), -62],
[Math.pow(2, -977), -62],
[Math.pow(2, -976), -62],
[Math.pow(2, -975), -61],
];
expectedMappings.forEach(([value, expected]) => {
const result = mapping.mapToIndex(value);
assert.strictEqual(
result,
expected,
`expected: ${value} to map to: ${expected}, got: ${result}`
);
});
});
it('handles max index for all scales', () => {
for (let scale = MIN_SCALE; scale <= MAX_SCALE; scale++) {
const mapping = new ExponentMapping(scale);
const index = mapping.mapToIndex(Number.MAX_VALUE);
const maxIndex = ((ieee754.MAX_NORMAL_EXPONENT + 1) >> -scale) - 1;
assert.strictEqual(
index,
maxIndex,
`expected index: ${index} and ${maxIndex} to be equal for scale: ${scale}`
);
const boundary = mapping.lowerBoundary(index);
assert.strictEqual(boundary, roundedBoundary(scale, maxIndex));
assert.throws(() => {
// one larger will overflow
mapping.lowerBoundary(index + 1);
});
}
});
it('handles min index for all scales', () => {
for (let scale = MIN_SCALE; scale <= MAX_SCALE; scale++) {
const mapping = new ExponentMapping(scale);
const minIndex = mapping.mapToIndex(ieee754.MIN_VALUE);
let expectedMinIndex = ieee754.MIN_NORMAL_EXPONENT >> -scale;
if (ieee754.MIN_NORMAL_EXPONENT % (1 << -scale) === 0) {
expectedMinIndex--;
}
assert.strictEqual(
minIndex,
expectedMinIndex,
`expected expectedMinIndex: ${expectedMinIndex} and ${minIndex} to be equal for scale: ${scale}`
);
const boundary = mapping.lowerBoundary(minIndex);
const expectedBoundary = roundedBoundary(scale, expectedMinIndex);
assert.strictEqual(boundary, expectedBoundary);
//one smaller will underflow
assert.throws(() => {
mapping.lowerBoundary(minIndex - 1);
});
// subnormals map to the min index
[
ieee754.MIN_VALUE / 2,
ieee754.MIN_VALUE / 3,
Math.pow(2, -1050),
Math.pow(2, -1073),
1.0625 * Math.pow(2, -1073),
Math.pow(2, -1074),
].forEach(value => {
assert.strictEqual(mapping.mapToIndex(value), expectedMinIndex);
});
}
});
});
function roundedBoundary(scale: number, index: number): number {
let result = Math.pow(2, index);
for (let i = scale; i < 0; i++) {
result = result * result;
}
return result;
}