Подтвердить что ты не робот

Как вы печатаете EXACT значение числа с плавающей запятой?

Прежде всего, это не вопрос новичков с плавающей запятой. Я знаю, что результаты арифметики с плавающей запятой (не говоря уже о трансцендентных функциях) обычно не могут быть представлены точно и что большинство завершающих десятичных знаков не могут быть представлены точно как двоичные числа с плавающей запятой.

Таким образом, каждое возможное значение с плавающей запятой точно соответствует диадическому рациональному (рациональное число p/q, где q - степень 2), которое, в свою очередь, имеет точное десятичное представление.

Мой вопрос: как вы находите это точное десятичное представление эффективно? sprintf и аналогичные функции обычно указываются только с несколькими значащими цифрами, чтобы однозначно определять исходное значение с плавающей запятой; они не обязательно печатают точное десятичное представление. Я знаю один алгоритм, который я использовал, но он очень медленный, O(e^2), где e - показатель экспоненты. Здесь схема:

  • Преобразование мантиссы в десятичное целое. Вы можете сделать это, вытащив биты, чтобы прочитать мантисса напрямую, или вы можете написать беспорядочный цикл с плавающей запятой, который сначала умножает значение на две силы, чтобы поместить его в диапазон 1 <= x < 10, затем вытягивает за вычетом цифры за раз, отбрасывая на int, вычитая и умножая на 10.
  • Применить экспонента путем многократного умножения или деления на 2. Это операция с строкой десятичных цифр, которые вы сгенерировали. Каждые ~ 3 умножения добавят дополнительную цифру влево. Каждое отдельное разделение добавит дополнительную цифру вправо.

Действительно ли это возможно? Я сомневаюсь в этом, но я не эксперт с плавающей запятой, и я не могу найти способ выполнить вычисления base-10 в представлении чисел с плавающей запятой, не запуская возможности неточных результатов (умножения или деления на ничего, кроме мощности 2, является операцией с потерями по номерам с плавающей запятой, если вы не знаете, что у вас есть свободные биты для работы).

4b9b3361

Ответ 1

Этот вопрос имеет бюрократическую часть и алгоритмическую часть. Число с плавающей запятой сохраняется внутри (2 e × m), где e - показатель (сам по себе в двоичном виде), а m - мантисса. Бюрократическая часть вопроса заключается в том, как получить доступ к этим данным, но Р., похоже, больше интересуется алгоритмической частью вопроса, а именно преобразованием (2 e × m) во фракцию (a/b) в десятичной форме. Ответ на бюрократический вопрос на нескольких языках - "frexp" (интересная деталь, о которой я не знал до сегодняшнего дня).

Верно, что на первый взгляд требуется O (e 2) работать только для записи 2 e в десятичной форме и еще больше времени для мантиссы. Но, благодаря волшебству алгоритма быстрого умножения Schonhage-Strassen, вы можете сделать это в течение Õ (e), где тильда означает "до коэффициентов журнала". Если вы рассматриваете Шонхаге-Штрассена как волшебство, тогда не так уж сложно думать о том, что делать. Если e четное, вы можете рекурсивно вычислить 2 e/2 а затем поместить его с помощью быстрого умножения. С другой стороны, если e нечетно, вы можете рекурсивно вычислить 2 e-1 а затем удвоить его. Вы должны быть осторожны, чтобы проверить, есть ли версия Schonhage-Strassen в базе 10. Хотя это не широко документировано, это можно сделать на любой базе.

Преобразование очень длинной мантиссы от двоичного к основанию 10 - это не совсем тот же вопрос, но у него есть аналогичный ответ. Вы можете разделить мантиссу на две половины, m = a 2 k + b. Затем рекурсивно преобразуйте a и b в базу 10, преобразуйте 2 ^ k в базу 10 и выполните еще одно быстрое умножение для вычисления m в базе 10.

Абстрактный результат всего этого состоит в том, что вы можете преобразовывать целые числа из одной базы в другую в течение Õ (N).

Если речь идет о стандартных 64-битных числах с плавающей запятой, то это слишком мало для фантастического алгоритма Шонхаге-Штрассена. В этом диапазоне вы можете вместо этого сохранить работу с помощью различных трюков. Один из подходов состоит в том, чтобы сохранить все 2048 значений 2 e в таблице поиска, а затем работать в мантиссе с асимметричным умножением (между длинным умножением и коротким умножением). Другим трюком является работа на базе 10000 (или более высокая мощность 10, в зависимости от архитектуры) вместо базы 10. Но, как указывает Р. в комментариях, 128-битные числа с плавающей запятой уже позволяют достаточно крупным экспонентам звонить в задайте как таблицы поиска, так и стандартное длинное умножение. В практическом плане длительное умножение является самым быстрым до нескольких цифр, тогда в значительном среднем диапазоне можно использовать умножение Карацубы или Умножение Toom-Cook, а затем после этого вариация Schonhage-Strassen лучше не только в теории, но и на практике.

Фактически, большой целочисленный пакет GMP уже имеет преобразование радиального времени Õ (N), а также хорошие эвристики, для которых выбор алгоритма умножения. Единственное различие между их решением и моей заключается в том, что вместо того, чтобы делать большую арифметику в базе 10, они вычисляют большие мощности 10 в базе 2. В этом решении им также требуется быстрое деление, но это может быть получено из быстрого умножения в любом нескольких способов.

Ответ 2

Я вижу, что вы уже приняли ответ, но вот несколько версий с открытым исходным кодом этого преобразования, которые вы можете посмотреть:

Оба будут печатать столько цифр, сколько вы запрашиваете в типе %f printf (как я писал здесь: http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/ и http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/).

Ответ 3

Было много работы по печати чисел с плавающей запятой. Золотой стандарт заключается в том, чтобы распечатать десятичный эквивалент минимальной длины, так что, когда десятичный эквивалент считывается обратно, вы получаете тот же номер с плавающей запятой, с которого вы начали, независимо от того, какой режим округления во время чтения. Вы можете прочитать об алгоритме в превосходной

Ответ 4

Хотя С# и ваш вопрос отмечен C, у Jon Skeet есть код для преобразования double в его точное представление в виде строки: http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

С быстрым взглядом, кажется, не слишком сложно переносить на C, а еще проще писать на С++.

При дальнейшем отражении оказывается, что алгоритм Jon также O (e ^ 2), поскольку он также пересекает экспоненту. Тем не менее, это означает, что алгоритм O (log (n) ^ 2) (где n - число с плавающей запятой), и я не уверен, что вы можете преобразовать из базы 2 в базовую 10 лучше, чем время в квадрате.

Ответ 5

Хорошо не будучи экспертом с плавающей запятой, я бы предпочел использовать хорошо протестированную библиотеку с открытым исходным кодом.

GNU MPFR является хорошим.

Библиотека MPFR является библиотекой C для многоточечная плавающая точка вычисления с правильным округлением. Основная цель MPFR - предоставить библиотека для многоточности вычисление с плавающей запятой, которое как эффективные, так и четко определенные семантика.

Ответ 6

sprintf и подобные функции обычно указывается только до номера значительных цифр до однозначно определить исходную плавающую точку стоимость; они не обязательно печатают точное десятичное представление.

Вы можете запросить более значимые цифры, чем по умолчанию:

printf("%.100g\n", 0.1);

выводит 0.1000000000000000055511151231257827021181583404541015625.

Ответ 7

Если вы хотите получить более точные результаты, почему бы не использовать математику с фиксированной точкой? Конверсии бывают быстрыми. Ошибка известна и ее можно обойти. Не точный ответ на ваш вопрос, а другая идея для вас.

Ответ 8

Сверху моей головы, почему бы не разбить экспонента вниз на сумму двоичных показателей сначала, тогда все ваши операции будут потеряны.

т.е.

10^2 = 2^6 + 2^5 + 2^2

Тогда сумма:

mantissa<<6 + mantissa<<5 + mantissa<<2

Я думаю, что разбить его на O (n) на число цифр, сдвиг будет O (1), а суммирование - O (n) цифр...

Вы должны иметь целочисленный класс, достаточно большой для хранения результатов, конечно...

Дайте мне знать - мне это интересно, это заставило меня задуматься.: -)

Ответ 9

Есть 3 способа

  • номера печати в bin или hex

    Это самый точный способ. Я предпочитаю hex, потому что он больше похож на базу 10 для чтения/ощущения, например, F.8h = 15.5 без потери точности здесь.

  • печать dec, но только соответствующие цифры

    При этом я имею в виду только цифры, которые могут иметь 1 в вашем номере, представленном как можно ближе.

    num целых цифр являются легкими и точными (без потери точности):

    // n10 - base 10 integer digits
    // n2  - base 2 integer digits
    n10=log10(2^n2)
    n10=log2(2^n2)/log2(10)
    n10=n2/log2(10)
    n10=ceil(n2*0.30102999566398119521373889472449)
    // if fist digit is 0 and n10 > 1 then n10--
    

    num дробных цифр более сложны и эмпирически я нашел это:

    // n10 - base 10 fract. digits
    // n2  - base 2 fract. digits >= 8
    n10=0; if (n02==8) n10=1;
    else if (n02==9) n10=2;
    else if (n02> 9)
        {
        n10=((n02-9)%10);
             if (n10>=6) n10=2;
        else if (n10>=1) n10=1;
        n10+=2+(((n02-9)/10)*3);
        }
    

    если вы создаете таблицу зависимостей n02 <-> n10, то вы видите, что константа 0.30102999566398119521373889472449 по-прежнему присутствует, но при запуске из 8 бит, потому что меньше не может представлять 0.1 с удовлетворительной точностью (0.85 - 1.15). из-за отрицательных показателей базы 2 зависимость не является линейной, вместо этого она образует. Этот код работает для небольшого количества бит (<=52), но при больших подсчетах бит может быть ошибкой, потому что использованный шаблон не соответствует точно log10(2) или 1/log2(10).

    для большего количества бит. Я использую это:

    n10=7.810+(9.6366363636363636363636*((n02>>5)-1.0));
    

    но эта формула выровнена на 32 бита!!! а также ошибка объявления большего количества бит в нем

    P.S. дальнейший анализ двоичного представления декадических чисел

    0.1
    0.01
    0.001
    0.0001
    ...
    

    может выявить точное повторение шаблона, которое приведет к точному количеству соответствующих цифр для любого количества бит.

    для ясности:

    8 bin digits -> 1 dec digits
    9 bin digits -> 2 dec digits
    10 bin digits -> 3 dec digits
    11 bin digits -> 3 dec digits
    12 bin digits -> 3 dec digits
    13 bin digits -> 3 dec digits
    14 bin digits -> 3 dec digits
    15 bin digits -> 4 dec digits
    16 bin digits -> 4 dec digits
    17 bin digits -> 4 dec digits
    18 bin digits -> 4 dec digits
    19 bin digits -> 5 dec digits
    20 bin digits -> 6 dec digits
    21 bin digits -> 6 dec digits
    22 bin digits -> 6 dec digits
    23 bin digits -> 6 dec digits
    24 bin digits -> 6 dec digits
    25 bin digits -> 7 dec digits
    26 bin digits -> 7 dec digits
    27 bin digits -> 7 dec digits
    28 bin digits -> 7 dec digits
    29 bin digits -> 8 dec digits
    30 bin digits -> 9 dec digits
    31 bin digits -> 9 dec digits
    32 bin digits -> 9 dec digits
    33 bin digits -> 9 dec digits
    34 bin digits -> 9 dec digits
    35 bin digits -> 10 dec digits
    36 bin digits -> 10 dec digits
    37 bin digits -> 10 dec digits
    38 bin digits -> 10 dec digits
    39 bin digits -> 11 dec digits
    40 bin digits -> 12 dec digits
    41 bin digits -> 12 dec digits
    42 bin digits -> 12 dec digits
    43 bin digits -> 12 dec digits
    44 bin digits -> 12 dec digits
    45 bin digits -> 13 dec digits
    46 bin digits -> 13 dec digits
    47 bin digits -> 13 dec digits
    48 bin digits -> 13 dec digits
    49 bin digits -> 14 dec digits
    50 bin digits -> 15 dec digits
    51 bin digits -> 15 dec digits
    52 bin digits -> 15 dec digits
    53 bin digits -> 15 dec digits
    54 bin digits -> 15 dec digits
    55 bin digits -> 16 dec digits
    56 bin digits -> 16 dec digits
    57 bin digits -> 16 dec digits
    58 bin digits -> 16 dec digits
    59 bin digits -> 17 dec digits
    60 bin digits -> 18 dec digits
    61 bin digits -> 18 dec digits
    62 bin digits -> 18 dec digits
    63 bin digits -> 18 dec digits
    64 bin digits -> 18 dec digits
    

    И, наконец, не забудьте обрезать цифры! Это означает, что если цифра после последней соответствующей цифры >=5 в делении, чем последняя соответствующая цифра должна быть +1... и если она уже 9, чем вы должны перейти к предыдущей цифре и т.д....

  • напечатать точное значение

    Чтобы напечатать точное значение дробного двоичного числа , просто напечатайте дробные n цифры, где n - количество дробных битов, потому что представленное значение представляет собой сумму этих значений, поэтому число > дробные десятичные знаки не могут быть больше num дробных цифр LSB. Вещественное значение (bullet # 2) имеет значение для хранения десятичного числа до float (или печати только соответствующих десятичных знаков).

    отрицательные степени двух точных значений...

    2^- 1 = 0.5
    2^- 2 = 0.25
    2^- 3 = 0.125
    2^- 4 = 0.0625
    2^- 5 = 0.03125
    2^- 6 = 0.015625
    2^- 7 = 0.0078125
    2^- 8 = 0.00390625
    2^- 9 = 0.001953125
    2^-10 = 0.0009765625
    2^-11 = 0.00048828125
    2^-12 = 0.000244140625
    2^-13 = 0.0001220703125
    2^-14 = 0.00006103515625
    2^-15 = 0.000030517578125
    2^-16 = 0.0000152587890625
    2^-17 = 0.00000762939453125
    2^-18 = 0.000003814697265625
    2^-19 = 0.0000019073486328125
    2^-20 = 0.00000095367431640625
    

    теперь отрицательные мощности 10, напечатанные с помощью точного стиля значения для 64-битного doubles:

    10^+ -1 = 0.1000000000000000055511151231257827021181583404541015625
            = 0.0001100110011001100110011001100110011001100110011001101b
    10^+ -2 = 0.01000000000000000020816681711721685132943093776702880859375
            = 0.00000010100011110101110000101000111101011100001010001111011b
    10^+ -3 = 0.001000000000000000020816681711721685132943093776702880859375
            = 0.000000000100000110001001001101110100101111000110101001111111b
    10^+ -4 = 0.000100000000000000004792173602385929598312941379845142364501953125
            = 0.000000000000011010001101101110001011101011000111000100001100101101b
    10^+ -5 = 0.000010000000000000000818030539140313095458623138256371021270751953125
            = 0.000000000000000010100111110001011010110001000111000110110100011110001b
    10^+ -6 = 0.000000999999999999999954748111825886258685613938723690807819366455078125
            = 0.000000000000000000010000110001101111011110100000101101011110110110001101b
    10^+ -7 = 0.0000000999999999999999954748111825886258685613938723690807819366455078125
            = 0.0000000000000000000000011010110101111111001010011010101111001010111101001b
    10^+ -8 = 0.000000010000000000000000209225608301284726753266340892878361046314239501953125
            = 0.000000000000000000000000001010101111001100011101110001000110000100011000011101b
    10^+ -9 = 0.0000000010000000000000000622815914577798564188970686927859787829220294952392578125
            = 0.0000000000000000000000000000010001001011100000101111101000001001101101011010010101b
    10^+-10 = 0.00000000010000000000000000364321973154977415791655470655996396089904010295867919921875
            = 0.00000000000000000000000000000000011011011111001101111111011001110101111011110110111011b
    10^+-11 = 0.00000000000999999999999999939496969281939810930172340963650867706746794283390045166015625
            = 0.00000000000000000000000000000000000010101111111010111111111100001011110010110010010010101b
    10^+-12 = 0.00000000000099999999999999997988664762925561536725284350612952266601496376097202301025390625
            = 0.00000000000000000000000000000000000000010001100101111001100110000001001011011110101000010001b
    10^+-13 = 0.00000000000010000000000000000303737455634003709136034716842278413651001756079494953155517578125
            = 0.00000000000000000000000000000000000000000001110000100101110000100110100001001001011101101000001b
    10^+-14 = 0.000000000000009999999999999999988193093545598986971343290729163921781719182035885751247406005859375
            = 0.000000000000000000000000000000000000000000000010110100001001001101110000110101000010010101110011011b
    10^+-15 = 0.00000000000000100000000000000007770539987666107923830718560119501514549256171449087560176849365234375
            = 0.00000000000000000000000000000000000000000000000001001000000011101011111001111011100111010101100001011b
    10^+-16 = 0.00000000000000009999999999999999790977867240346035618411149408467364363417573258630000054836273193359375
            = 0.00000000000000000000000000000000000000000000000000000111001101001010110010100101111101100010001001101111b
    10^+-17 = 0.0000000000000000100000000000000007154242405462192450852805618492324772617063644020163337700068950653076171875
            = 0.0000000000000000000000000000000000000000000000000000000010111000011101111010101000110010001101101010010010111b
    10^+-18 = 0.00000000000000000100000000000000007154242405462192450852805618492324772617063644020163337700068950653076171875
            = 0.00000000000000000000000000000000000000000000000000000000000100100111001001011101110100011101001001000011101011b
    10^+-19 = 0.000000000000000000099999999999999997524592683526013185572915905567688179926555402943222361500374972820281982421875
            = 0.000000000000000000000000000000000000000000000000000000000000000111011000001111001001010011111011011011010010101011b
    10^+-20 = 0.00000000000000000000999999999999999945153271454209571651729503702787392447107715776066783064379706047475337982177734375
            = 0.00000000000000000000000000000000000000000000000000000000000000000010111100111001010000100001100100100100100001000100011b
    

    теперь отрицательные степени 10, напечатанные соответствующими десятичными цифрами только стилем (я более привык к этому) для 64-битного doubles:

    10^+ -1 = 0.1
    10^+ -2 = 0.01
    10^+ -3 = 0.001
    10^+ -4 = 0.0001
    10^+ -5 = 0.00001
    10^+ -6 = 0.000001
    10^+ -7 = 0.0000001
    10^+ -8 = 0.00000001
    10^+ -9 = 0.000000001
    10^+-10 = 0.0000000001
    10^+-11 = 0.00000000001
    10^+-12 = 0.000000000001
    10^+-13 = 0.0000000000001
    10^+-14 = 0.00000000000001
    10^+-15 = 0.000000000000001
    10^+-16 = 0.0000000000000001
    10^+-17 = 0.00000000000000001
    10^+-18 = 0.000000000000000001
    10^+-19 = 0.0000000000000000001
    10^+-20 = 0.00000000000000000001
    

    надеюсь, что это поможет:)

Ответ 10

Нет. Самое близкое, что вы можете придумать, это сброс байтов.