Skip to content

Conversation

@tannergooding
Copy link
Member

No description provided.

@tannergooding
Copy link
Member Author

@MihuBot

@tannergooding
Copy link
Member Author

tannergooding commented Nov 3, 2025

@EgorBot -amd -arm -windows_intel

using System;
using BenchmarkDotNet.Attributes;

public class Benchmarks
{
    public static IEnumerable<object> SingleValues => new object[]
    {
        float.MinValue,
        float.Epsilon,
        float.Pi,
        12345.0f,
        (float)int.MaxValue,
        (float)long.MaxValue,
        (float)ulong.MaxValue,
        float.MaxValue
    };
    
    public static IEnumerable<object> DoubleValues => new object[]
    {
        double.MinValue,
        double.Epsilon,
        double.Pi,
        12345.0,
        (double)int.MaxValue,
        (double)long.MaxValue,
        (double)ulong.MaxValue,
        double.MaxValue
    };
    
    [Benchmark]
    [ArgumentsSource(nameof(SingleValues))]
    [MemoryRandomization]
    public string ToStringSingle(float value) => value.ToString();
    
    [Benchmark]
    [ArgumentsSource(nameof(DoubleValues))]
    [MemoryRandomization]
    public string ToStringDouble(double value) => value.ToString();
}

@tannergooding
Copy link
Member Author

Numbers look fine, within noise or slightly faster.

I expect there's some edge cases (like the subnormal value with 767 significant digits), but those are edges that require explicit user opt-in and are already going to be very expensive; so they aren't a big concern. We can do a separate follow up for minimizing the diffs further and tweaking the code slightly to allow bounds check elision on those remaining cases.

The Unsafe.SkipInit is still necessary due to the cost of zeroing 116x uint being fairly significant in contrast to the other work being done. Locally, I see that being at least a 55% regression (such as 12345 for float changing from 38ns to 59ns).

The Unsafe.As is still necessary because of the Pow10BigNumTable which contains 8 different BigInteger in the space of just 2 and using an RVA static instead of requiring a static constructor and higher overhead each startup.

In both cases this is conceptually similar to List<T> in that the backing buffer may be larger than the exposed item length (tracked by the separate _length field). Zeroing those additional elements is therefore technically unnecessary and over the course of several operations may become non-zero. We have a number of asserts that ensure we are within those bounds and the general Span<uint> for the [InlineArray] now ensures we never do something that would cause an AV. We can likewise insert additional slicing to further restrict it moving forward and which might help improve codegen and bounds check elision further (the separate "follow up" mentioned above)

@tannergooding tannergooding marked this pull request as ready for review November 3, 2025 18:59
Copilot AI review requested due to automatic review settings November 3, 2025 18:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request refactors the BigInteger implementation used in floating-point number formatting by eliminating unsafe code and converting pointer-based operations to safe ref-based alternatives. The changes modernize the codebase to use C# 11+ features like InlineArrays and scoped refs.

  • Replaces the fixed uint _blocks[MaxBlockCount] with an InlineArray-based BlocksBuffer struct to eliminate unsafe code
  • Changes index and bit count types from uint to int throughout for consistency and to avoid unnecessary casts
  • Converts Dragon4's pointer-based margin tracking to safe ref-based approach using scoped ref and Unsafe.AreSame

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
Number.BigInteger.cs Eliminates unsafe code by replacing fixed buffer with InlineArray, changes index/count types to int, adds Unsafe.SkipInit calls, and refactors various methods for safer code
Number.Dragon4.cs Removes unsafe method signature, converts pointer-based scaledMarginHigh tracking to scoped ref with Unsafe.NullRef/AreSame patterns, eliminates unnecessary casts
Number.NumberToFloatingPointBits.cs Changes bit count and precision tracking variables from uint to int for consistency with BigInteger API changes

@tannergooding
Copy link
Member Author

@EgorBot -aws_amd -profiler

using System;
using BenchmarkDotNet.Attributes;

public class Benchmarks
{
    public static IEnumerable<object> SingleValues => new object[]
    {
        float.Epsilon,
    };

    [Benchmark]
    [ArgumentsSource(nameof(SingleValues))]
    [MemoryRandomization]
    public string ToStringSingle(float value) => value.ToString();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants