ideas icon indicating copy to clipboard operation
ideas copied to clipboard

Добавить функцию `ldexp10()`

Open toughengineer opened this issue 8 months ago • 0 comments

Функция ldexp10() должна рождать число с плавающей точкой из (целого) числа помноженного на 10 в степени экспоненты аналогично ldexp(), где в степень возводится 2. Концептуально она должна работать так:

double ldexp10(number auto mantissa, int exponent) {
  return mantissa * pow(10., exponent);
}

В общем случае неточных (inexact) вычислений должно работать корректное округление (аналогичное требованиям для from_chars()).


Есть from_chars(), которая превращает строку в число с плавающей точкой, но оно накладывает ограничение, что всё число должно быть в строке. С этим нет проблем в тривиальных случаях типа

"1234567890.1234567890e123"

но в общем случае длина валидного числа непредсказуема, например:

"10.000000000000000000000000000000"
"1e0000000000000000000000000000001"

обе строки с валидными числами (они содержат по тридцать нулей, но в общем случае количество нулей может быть неограниченным).

Поэтому в случаях, когда распознаваемый текст приходит по кускам, например по сети, применение from_chars() становится мягко говоря затруднительным.

Мы могли бы выполнить часть работы в пользовательском коде, набрав нужное количество значащих цифр и провалидировав строку, и затем распознать мантиссу со значащими цифрами и экспоненту (например, позвав from_chars() для целых чисел или вообще делая это на лету) и позвать ldexp10(). Для примеров выше это было бы эквивалентно таким вызовам:

ldexp10(100000000000000000, -16); // 18 значащих цифр, 16 из которых после точки
ldexp10(1, 1); // 1 значащая цифра, 30 нулей и единица для экспоненты

18 десятичных значащих цифр достаточно для превращения в double.

Эквивалентно, но менее эффективно, мы могли бы переложить значащие цифры в буфер заранее известного размера (таким образом избежав выделения памяти), распознать экспоненту, поправить её с учётом расположения значащих цифр относительно точки, преваратить экспоненту в строку и положить в буфер, и превратить в число с помощью вызова from_chars():

char buffer[25] = {};
// скопировать в buffer значащие цифры (не больше 18)
// при этом запомнить откуда они приехали относительно точки
int decimalExponent = 0;
// распознать экспоненту, скорректировать с учётом расположения значащих цифр
// записать экспопненту в buffer
buffer[significantDigitsCount] = 'e';
auto e = to_chars(buffer + significantDigitsCount + 1, end(buffer), decimalExponent).ptr;
// наконец родить число со всеми гарантиями from_chars()
double d;
from_chars(buffer, e, d);

Также это было бы полезно для случаев использования нестандартных символов, для которых применение from_chars() также затруднительно, например когда для десятичной точки используется , (запятая).


Существующие реализации from_chars() в libstdc++, libc++ и стандартной библиотеке MSVC уже используют подобный код внутри, поэтому добавление ldexp10() не потребует создания абсолютно нового кода, а лишь выставит интерфейс к уже практически существующей функциональности.

Если я правильно распарсил, libstdc++ и libc++ используют вариации алгоритма Дэниела Лемира, готовую реализацию которого можно найти здесь: https://github.com/fastfloat/fast_float Релевантные места в коде: https://github.com/fastfloat/fast_float/blob/c5a3ca37c459050f367a4cb0b23c862c29242d30/include/fast_float/decimal_to_binary.h#L103 https://github.com/fastfloat/fast_float/blob/c5a3ca37c459050f367a4cb0b23c862c29242d30/include/fast_float/digit_comparison.h#L437

toughengineer avatar Apr 25 '25 14:04 toughengineer