Skip to content

[API Proposal]: BitOperations add Log10(uint/ulong value) #116043

@Molth

Description

@Molth

Background and motivation

when people need to calculate the Log10 of an integer,
they often use:

(int)Math.Log10((double)value)

        [Intrinsic]
        [MethodImpl(MethodImplOptions.InternalCall)]
        public static extern double Log10(double d);

however, for calculating the Log10 of an integer,
don’t actually need to convert it to a double and then back to an integer.
this approach incurs two overheads of converting between double and int.

in fact, can use BitOperations.Log2 and a small lookup table to speed up Log10 calculation for integers.
// https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
// Find integer log base 10 of an integer
here is a simple benchmark:

using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;

namespace TestLog10
{
    [ShortRunJob]
    [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
    [CategoriesColumn]
    public class BenchmarkLog10
    {
        [Params(1000, 10000, 100000)] public int Count { get; set; }

        public List<uint> Input;

        public int Value1;
        public int Value2;

        [GlobalSetup]
        public void Init()
        {
            Input = new List<uint>(Count);
            for (int i = 0; i < Count; i++)
            {
                Input.Add((uint)Random.Shared.Next());
            }
        }

        // https://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
        // Find integer log base 10 of an integer

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Log10(uint value)
        {
            value |= 1;
            int num1 = BitOperations.Log2(value) + 1;
            int num2 = (num1 * 0x4D1) >> 0xC;
            return value < Unsafe.Add(ref MemoryMarshal.GetReference(PowersOf10), num2) ? num2 - 1 : num2;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int Log10(ulong value)
        {
            value |= 1;
            int num1 = BitOperations.Log2(value) + 1;
            int num2 = (num1 * 0x4D1) >> 0xC;
            return value < Unsafe.Add(ref MemoryMarshal.GetReference(PowersOf10), num2) ? num2 - 1 : num2;
        }

        private static ReadOnlySpan<ulong> PowersOf10 => new ulong[]
        {
            0x1, 0xA, 0x64, 0x3E8, 0x2710, 0x186A0, 0xF4240, 0x989680, 0x5F5E100, 0x3B9ACA00, 0x2540BE400, 0x174876E800,
            0xE8D4A51000, 0x9184E72A000, 0x5AF3107A4000, 0x38D7EA4C68000, 0x2386F26FC10000, 0x16345785D8A0000,
            0xDE0B6B3A7640000, 0x8AC7230489E80000
        };

        [BenchmarkCategory("Log10")]
        [Benchmark]
        public void Log10ByBitOperations()
        {
            foreach (uint i in Input)
            {
                Value1 ^= Log10(i);
            }
        }

        [BenchmarkCategory("Log10")]
        [Benchmark]
        public void Log10ByMath()
        {
            foreach (uint i in Input)
            {
                Value2 ^= (int)Math.Log10((double)i);
            }
        }
    }
}

result:


| Method               | Count  | Mean       | Error      | StdDev    |
|--------------------- |------- |-----------:|-----------:|----------:|
| Log10ByBitOperations | 1000   |   1.237 us |  0.1159 us | 0.0064 us |
| Log10ByMath          | 1000   |   7.003 us |  1.5248 us | 0.0836 us |
| Log10ByBitOperations | 10000  |  12.155 us |  0.6631 us | 0.0363 us |
| Log10ByMath          | 10000  |  70.714 us |  5.7563 us | 0.3155 us |
| Log10ByBitOperations | 100000 | 326.526 us | 61.5916 us | 3.3760 us |
| Log10ByMath          | 100000 | 710.025 us | 66.2781 us | 3.6329 us |

API Proposal

public interface IBinaryInteger<TSelf>
{
    static abstract TSelf Log10(TSelf value);
}

Cover BigInteger and the various primitive types that it will be publicly exposed on as part of defining it for IBinaryInteger. In particular, byte, sbyte, short, ushort, int, uint, long, ulong, Int128, UInt128, CLong, and CULong.

It will also end up being exposed as part of the vector APIs: Vector64, Vector128, Vector256 and Vector512 in the System.Runtime.Intrinsics namespace; as well as Vector2, Vector3, Vector4, and Vector in the System.Numerics namespace; and as part of TensorPrimitives and Tensor in the System.Numerics.Tensors namespace

API Usage

        int value = XX;
        int log10 = BitOperations.Log10(value);

Alternative Designs

No response

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions