/* * 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 { LogarithmMapping } from '../../../src/aggregator/exponential-histogram/mapping/LogarithmMapping'; import * as ieee754 from '../../../src/aggregator/exponential-histogram/mapping/ieee754'; import * as assert from 'assert'; import { assertInEpsilon } from './helpers'; const MIN_SCALE = 1; const MAX_SCALE = 20; describe('LogarithmMapping', () => { it('maps values for scale 1', () => { const mapping = new LogarithmMapping(1); assert.strictEqual(mapping.scale, 1); const expectedMappings = [ [15, 7], [9, 6], [7, 5], [5, 4], [3, 3], [2.5, 2], [1.5, 1], [1.2, 0], [1, -1], [0.75, -1], [0.55, -2], [0.45, -3], ]; expectedMappings.forEach(([value, expected]) => { const result = mapping.mapToIndex(value); assert.strictEqual( result, expected, `expected: ${value} to map to: ${expected}, got: ${result}` ); }); }); it('computes boundary', () => { [1, 2, 3, 4, 10, 15].forEach(scale => { const mapping = new LogarithmMapping(scale); [-100, -10, -1, 0, 1, 10, 100].forEach(index => { const boundary = mapping.lowerBoundary(index); const mappedIndex = mapping.mapToIndex(boundary); assert.ok(index - 1 <= mappedIndex); assert.ok(index >= mappedIndex); assertInEpsilon(roundedBoundary(scale, index), boundary, 1e-9); }); }); }); it('handles max index for each scale', () => { for (let scale = MIN_SCALE; scale <= MAX_SCALE; scale++) { const mapping = new LogarithmMapping(scale); const index = mapping.mapToIndex(Number.MAX_VALUE); // the max index is one less than the first index that // overflows Number.MAX_VALUE const maxIndex = ((ieee754.MAX_NORMAL_EXPONENT + 1) << scale) - 1; assert.strictEqual(index, maxIndex); const boundary = mapping.lowerBoundary(index); const base = mapping.lowerBoundary(1); assert.ok( boundary < Number.MAX_VALUE, `expected boundary: ${boundary} to be < max value: ${Number.MAX_VALUE}` ); assertInEpsilon( base - 1, (Number.MAX_VALUE - boundary) / boundary, 10e-6 ); } }); it('handles min index for each scale', () => { for (let scale = MIN_SCALE; scale <= MAX_SCALE; scale++) { const mapping = new LogarithmMapping(scale); const minIndex = mapping.mapToIndex(ieee754.MIN_VALUE); const expectedMinIndex = (ieee754.MIN_NORMAL_EXPONENT << scale) - 1; assert.strictEqual(minIndex, expectedMinIndex); const expectedBoundary = roundedBoundary(scale, expectedMinIndex); assert.ok(expectedBoundary < ieee754.MIN_VALUE); const expectedUpperBoundary = roundedBoundary( scale, expectedMinIndex + 1 ); assert.strictEqual(ieee754.MIN_VALUE, expectedUpperBoundary); const mappedLowerBoundary = mapping.lowerBoundary(minIndex + 1); assertInEpsilon(ieee754.MIN_VALUE, mappedLowerBoundary, 1e-6); // subnormals map to the min index [ ieee754.MIN_VALUE / 2, ieee754.MIN_VALUE / 3, ieee754.MIN_VALUE / 100, Math.pow(2, -1050), Math.pow(2, -1073), 1.0625 * Math.pow(2, -1073), Math.pow(2, -1074), ].forEach(value => { const result = mapping.mapToIndex(value); assert.strictEqual(result, expectedMinIndex); }); const mappedMinLower = mapping.lowerBoundary(minIndex); assertInEpsilon(expectedBoundary, mappedMinLower, 1e-6); // one smaller will underflow assert.throws(() => { mapping.lowerBoundary(minIndex - 1); }); } }); it('maps max float to max index for each scale', () => { for (let scale = MIN_SCALE; scale <= MAX_SCALE; scale++) { const mapping = new LogarithmMapping(scale); const index = mapping.mapToIndex(Number.MAX_VALUE); const maxIndex = ((ieee754.MAX_NORMAL_EXPONENT + 1) << scale) - 1; assert.strictEqual(maxIndex, index); const boundary = mapping.lowerBoundary(index); const base = mapping.lowerBoundary(1); assert.ok(boundary < Number.MAX_VALUE); assertInEpsilon(base - 1, (Number.MAX_VALUE - boundary) / boundary, 1e-6); //one larger will overflow assert.throws(() => { mapping.lowerBoundary(index + 1); }); } }); }); function roundedBoundary(scale: number, index: number): number { while (scale > 0) { if (index < -1022) { index /= 2; scale--; } else { break; } } let result = Math.pow(2, index); for (let i = scale; i > 0; i--) { result = Math.sqrt(result); } return result; }