diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
new file mode 100644
index 00000000000..ca70f59f441
--- /dev/null
+++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs
@@ -0,0 +1,133 @@
+//
+// 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
+//
+// http://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.
+//
+
+#if NET6_0_OR_GREATER
+
+using System;
+using System.Diagnostics;
+
+using OpenTelemetry.Internal;
+
+namespace OpenTelemetry.Metrics
+{
+ ///
+ /// Represents an exponential bucket histogram with base = 2 ^ (2 ^ (-scale)).
+ /// An exponential bucket histogram has infinite number of buckets, which are
+ /// identified by Bucket[i] = ( base ^ i, base ^ (i + 1) ], where i
+ /// is an integer.
+ ///
+ internal class ExponentialBucketHistogram
+ {
+ private static readonly double Log2E = Math.Log2(Math.E); // 1 / Math.Log(2)
+
+ private int scale;
+ private double scalingFactor; // 2 ^ scale / log(2)
+
+ public ExponentialBucketHistogram(int scale, int maxBuckets = 160)
+ {
+ Guard.ThrowIfOutOfRange(scale, min: -20, max: 20); // TODO: calculate the actual range
+
+ this.Scale = scale;
+ }
+
+ internal int Scale
+ {
+ get
+ {
+ return this.scale;
+ }
+
+ private set
+ {
+ this.scale = value;
+ this.scalingFactor = Math.ScaleB(Log2E, value);
+ }
+ }
+
+ internal long ZeroCount { get; private set; }
+
+ ///
+ public override string ToString()
+ {
+ return nameof(ExponentialBucketHistogram)
+ + "{"
+ + nameof(this.Scale) + "=" + this.Scale
+ + "}";
+ }
+
+ public int MapToIndex(double value)
+ {
+ Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount.");
+
+ // TODO: handle +Inf, -Inf, NaN
+
+ value = Math.Abs(value);
+
+ if (this.Scale > 0)
+ {
+ // TODO: due to precision issue, the values that are close to the bucket
+ // boundaries should be closely examined to avoid off-by-one.
+ return (int)Math.Ceiling(Math.Log(value) * this.scalingFactor) - 1;
+ }
+ else
+ {
+ var bits = BitConverter.DoubleToInt64Bits(value);
+ var exp = (int)((bits & IEEE754Double.EXPONENT_MASK) >> IEEE754Double.FRACTION_BITS);
+ var fraction = bits & IEEE754Double.FRACTION_MASK;
+
+ if (exp == 0)
+ {
+ // TODO: benchmark and see if this should be changed to a lookup table.
+ fraction--;
+
+ for (int i = IEEE754Double.FRACTION_BITS - 1; i >= 0; i--)
+ {
+ if ((fraction >> i) != 0)
+ {
+ break;
+ }
+
+ exp--;
+ }
+ }
+ else if (fraction == 0)
+ {
+ exp--;
+ }
+
+ return (exp - IEEE754Double.EXPONENT_BIAS) >> -this.Scale;
+ }
+ }
+
+ public sealed class IEEE754Double
+ {
+#pragma warning disable SA1310 // Field name should not contain an underscore
+ internal const int EXPONENT_BIAS = 1023;
+ internal const long EXPONENT_MASK = 0x7FF0000000000000L;
+ internal const int FRACTION_BITS = 52;
+ internal const long FRACTION_MASK = 0xFFFFFFFFFFFFFL;
+#pragma warning restore SA1310 // Field name should not contain an underscore
+
+ public static string ToString(double value)
+ {
+ var repr = Convert.ToString(BitConverter.DoubleToInt64Bits(value), 2);
+ return new string('0', 64 - repr.Length) + repr + ":" + "(" + value + ")";
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs
new file mode 100644
index 00000000000..2fcd109f4a6
--- /dev/null
+++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogramConfiguration.cs
@@ -0,0 +1,32 @@
+//
+// 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
+//
+// http://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.
+//
+
+namespace OpenTelemetry.Metrics
+{
+ ///
+ /// Stores configuration for a histogram metric stream with exponential bucket boundaries.
+ ///
+ internal class ExponentialBucketHistogramConfiguration : MetricStreamConfiguration
+ {
+ ///
+ /// Gets or sets the maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket.
+ ///
+ ///
+ /// The default value is 160.
+ ///
+ public int MaxSize { get; set; } = 160;
+ }
+}
diff --git a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
new file mode 100644
index 00000000000..ce13bfdde44
--- /dev/null
+++ b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs
@@ -0,0 +1,114 @@
+//
+// 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
+//
+// http://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.
+//
+
+#if NET6_0_OR_GREATER
+
+using System;
+using Xunit;
+
+namespace OpenTelemetry.Metrics.Tests
+{
+ public class ExponentialBucketHistogramTest
+ {
+ [Fact]
+ public void IndexLookup()
+ {
+ // An exponential bucket histogram with scale = 0.
+ // The base is 2 ^ (2 ^ -0) = 2.
+ // The buckets are:
+ //
+ // ...
+ // bucket[-3]: (1/8, 1/4]
+ // bucket[-2]: (1/4, 1/2]
+ // bucket[-1]: (1/2, 1]
+ // bucket[0]: (1, 2]
+ // bucket[1]: (2, 4]
+ // bucket[2]: (4, 8]
+ // bucket[3]: (8, 16]
+ // ...
+
+ var histogram_scale0 = new ExponentialBucketHistogram(0);
+
+ Assert.Equal(-1075, histogram_scale0.MapToIndex(double.Epsilon));
+
+ Assert.Equal(-1074, histogram_scale0.MapToIndex(double.Epsilon * 2));
+
+ Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 3));
+ Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 4));
+
+ Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 5));
+ Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 6));
+ Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 7));
+ Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 8));
+
+ Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072009E-308));
+ Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072014E-308));
+
+ Assert.Equal(-3, histogram_scale0.MapToIndex(0.25));
+
+ Assert.Equal(-2, histogram_scale0.MapToIndex(0.375));
+ Assert.Equal(-2, histogram_scale0.MapToIndex(0.5));
+
+ Assert.Equal(-1, histogram_scale0.MapToIndex(0.75));
+ Assert.Equal(-1, histogram_scale0.MapToIndex(1));
+
+ Assert.Equal(0, histogram_scale0.MapToIndex(1.5));
+ Assert.Equal(0, histogram_scale0.MapToIndex(2));
+
+ Assert.Equal(1, histogram_scale0.MapToIndex(3));
+ Assert.Equal(1, histogram_scale0.MapToIndex(4));
+
+ Assert.Equal(2, histogram_scale0.MapToIndex(5));
+ Assert.Equal(2, histogram_scale0.MapToIndex(6));
+ Assert.Equal(2, histogram_scale0.MapToIndex(7));
+ Assert.Equal(2, histogram_scale0.MapToIndex(8));
+
+ Assert.Equal(3, histogram_scale0.MapToIndex(9));
+ Assert.Equal(3, histogram_scale0.MapToIndex(16));
+
+ Assert.Equal(4, histogram_scale0.MapToIndex(17));
+ Assert.Equal(4, histogram_scale0.MapToIndex(32));
+
+ // An exponential bucket histogram with scale = 1.
+ // The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237.
+ // The buckets are:
+ //
+ // ...
+ // bucket[-3]: (0.35355339059, 1/2]
+ // bucket[-2]: (1/2, 0.70710678118]
+ // bucket[-1]: (0.70710678118, 1]
+ // bucket[0]: (1, 1.41421356237]
+ // bucket[1]: (1.41421356237, 2]
+ // bucket[2]: (2, 2.82842712474]
+ // bucket[3]: (2.82842712474, 4]
+ // ...
+
+ var histogram_scale1 = new ExponentialBucketHistogram(1);
+
+ Assert.Equal(-3, histogram_scale1.MapToIndex(0.5));
+
+ Assert.Equal(-2, histogram_scale1.MapToIndex(0.6));
+
+ Assert.Equal(-1, histogram_scale1.MapToIndex(1));
+
+ Assert.Equal(1, histogram_scale1.MapToIndex(2));
+
+ Assert.Equal(3, histogram_scale1.MapToIndex(4));
+ }
+ }
+}
+
+#endif