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

Почему рациональные числа не реализованы и не хранятся как фракции с нулевой потерей информации?

Я знаю, что это немного гипотетически, но мне интересно, почему язык, который я знаю, не делает.

Например, вы хотите сохранить 1/3. Дайте программисту возможность указать его как 1/3 и сохранить 1 и 3. Что-то вроде

struct float {
    int numerator;
    int denominator;
};

Арифметика рационального числа становится очень простой и значительно более точной!

Это позволит решить так много проблем, связанных с ограничениями точности и хранения чисел с плавающей запятой, и я также не вижу в нем никаких новых проблем!

Следовательно, мой вопрос: Почему не рациональные числа не реализованы и не хранятся как фракции с нулевой потерей информации?


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

В: Как вы храните pi?

A: Много раз я просто сохраняю 1/3, а не pi. pi можно сохранить старым способом и 1/3 по-новому.

4b9b3361

Ответ 1

Причина, по которой они по умолчанию не сохраняются, заключается в том, что диапазон допустимых значений, которые могут поместиться в фиксированный набор бит, меньше. Ваш класс float может хранить числа между 1/MAXINT и MAXINT (плюс или минус). C/С++ float может представлять числа между 1E + 37 и 1E-37 (плюс или минус). Другими словами, стандарт float может представлять значения на 26 порядков больше и на 26 порядков меньше, чем ваши, несмотря на то, что они занимают половину числа бит. В общем, более удобно представлять очень большие и очень маленькие значения, чем быть совершенно точными. Это особенно верно, поскольку округление имеет тенденцию давать нам правильные ответы с небольшими фракциями, такими как 1/3. В g++ следующее дает 1:

std::cout << ((1.0/3.0) * 3.0) << std::endl; 

Помните, что типы в С++ имеют фиксированный размер в битах. Таким образом, тип данных в 32 битах имеет не более MAX_UINT. Если вы измените способ представления, вы просто изменяете, какие значения могут быть точно представлены, а не увеличивать их. Вы не можете втиснуть больше и, следовательно, не можете быть "более точными". Вы торгуете, имея возможность представлять 1/3 точно за то, что не можете точно представлять другие значения, например, 5.4235E + 25.

Верно, что ваш float может представлять значения более точно между 1E-9 и 1E + 9 (предполагая 32-битные ints), но ценой того, что он не может представлять значения вне этого диапазона. Хуже того, в то время как стандартный float всегда имеет 6 цифр точности, ваш float будет иметь точность, которая варьируется в зависимости от того, насколько близко к нулю значения. (И обратите внимание, что вы используете в два раза больше бит, чем float).

(я предполагаю 32 бит int s. Тот же аргумент применяется для 64-битного int s.)

Изменить. Также обратите внимание, что большинство пользователей данных, использующих float for, не являются точными в любом случае. Если вы считываете данные с датчика, у вас уже есть неточность, поэтому, чтобы "отлично" представлять значение, бессмысленно. Если вы используете float в любом вычислительном контексте, это не имеет значения. Нет смысла прекрасно описывать "1/3", если ваша цель состоит в том, чтобы отобразить немного текста на 1/3 пути по экрану.

Единственными людьми, которые действительно нуждаются в полной точности, являются математики, и у них обычно есть программное обеспечение, которое дает им это. Очень немногие другие нуждаются в точности, кроме того, что дает double.

Ответ 2

Арифметика реального числа становится очень простой и значительно более точной!

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

Мне интересно, почему язык, который я знаю, не делает.

Большинство языков, о которых я могу думать, позволяют делать именно то, что вы описываете. В C вы можете создать структуру, содержащую числитель и знаменатель, и вы можете определить кучу функций, которые работают с такими структурами. С++ облегчает жизнь, позволяя вам определить класс и операции над этим классом - ту же идею, гораздо более хороший синтаксис и т.д. На самом деле разные наборы чисел часто используются в качестве примеров в языках OO: вы можете начать с определения Rational, а затем расширьте это, чтобы включить мнимые числа и т.д.

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

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

Ответ 3

С++ по крайней мере включает в себя компиляционную рациональную арифметическую библиотеку времени. Вот пример:

#include <ratio>
#include <iostream>

int main() {
    using a = std::ratio<3,5>;
    using b = std::ratio<7,6>;

    using c = std::ratio_multiply<a,b>::type;

    std::cout << c::num << '/' << c::den << '\n'; // prints 7/10
}

Здесь мы умножаем 3/5 на 7/6 и получаем 7/10.

Ответ 4

Ремень на шлемах, потому что мы собираемся здесь стать теоретическими.

Любой математический учитель может дать вам объяснение лифта о доказательстве Кантора о неисчислимой мощности действительных чисел. Для более подробного объяснения, перейдите здесь.

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

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

Это может показаться трудным поверить, но думать об общих трансцендентных функциях, например. sin, cos, log. Большинство результатов этих функций не рациональны, и ребята, которые писали IEEE 754 и другие ранние материалы FP, знали это. Они полагали, что с небольшим количеством ошибок в обмен на возможность представления (с некоторым усечением) гораздо большей части поля реальных чисел было хорошим компромиссом проекта.

Ответ 5

Многие ЦП имеют специальную обработку для плавающих точек (см. Википедию), а тип данных float в языке гарантирует, что программы могут использовать FPU в простой форме. С другой стороны, я не знаю ни одного процессора, который мог бы обрабатывать фракции со специальными инструкциями ассемблера, поэтому фракции могут легко и эффективно быть реализованы внутри библиотеки и не должны быть языковой функцией. Если вы хотите использовать фракции в С++, вы можете использовать Boost.Rational.

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

Ответ 7

"Следовательно, мой вопрос: почему не реализованы рациональные числа и хранятся как фракции с нулевой потерей информации?"

В стандартной библиотеке С++ отсутствуют многие практически необходимые типы, предлагаемые другими языками и библиотеками. Библиотека Boost уже предлагает рациональное числовое внедрение. И появляется чтобы он мог в ближайшее время также предложить предлагаемые десятичные типы, например для обработки сумм валют.

В связи с тем, что эти типы отсутствуют, стандартная библиотека С++ обычно предоставляет только общие общие блоки, но не более практичные вещи, которые можно было бы построить с этими блоками. То есть это минималистично. Библиотека Boost является чуть менее минималистичной, а затем, например, библиотека Poco больше похожа на другие языки и rsquo; более функционально богатые стандартные библиотеки.

Ответ 8

Есть несколько причин:
- Нет поддержки от общей архитектуры. Это понятно, так как фракция всегда должна быть упрощена. Это не тривиально, чтобы быть реализованным на аппаратном уровне, или, скорее, это будет инструкция без большого применения.
- Не может обрабатывать очень большие или очень маленькие цифры. Или BigInteger должен включать. И для очень больших или очень маленьких чисел мы обычно не нуждаемся в большей части предоставленной точности.
- Если это тип, поддерживаемый на уровне языка, он должен поддерживать преобразование с другими числовыми типами. Он должен решить, когда возвращать плавающий тип, если внутреннее представление имеет фиксированную точность (в случае умножения).

На языке решение о поддержке чего-то обычно определяется его применением (или обоснованием языка). Если приложение малое, у него меньше шансов на поддержку.