31

我知道这有点假设,但我想知道为什么我所知道的语言都没有。

例如,您要存储 1/3。给程序员一个选项,将其指定为 1/3,并存储 1 和 3。类似于

struct float {
    int numerator;
    int denominator;
};

有理数算术变得非常简单而且更加准确!

这将解决许多与浮点数的精度和存储限制相关的问题,而且我认为它不会引入任何新问题!

因此我的问题是:为什么没有实现有理数并将其存储为零丢失信息的分数?


正如乔所问的那样,其他人也可能会指出,我并不是说要替换现有系统,而是要补充它。

问:你们是怎么储存的pi

A:很多时候,我只是存储1/3而不是pipi可以以旧方式存储,也可以以1/3新方式存储。

4

8 回答 8

17

默认情况下它们不以这种方式存储的原因是可以适合固定位集的有效值范围更小。您的float班级可以存储 1/MAXINT 和 MAXINT(正负)之间的数字。AC/C++float可以表示 1E+37 和 1E-37(加号或减号)之间的数字。换句话说,一个标准float可以表示比你的值大 26 个数量级和小 26 个数量级的值,尽管它占用了一半的位数。通常,能够表示非常大和非常小的值比非常精确更方便。尤其如此,因为四舍五入往往会给我们提供像 1/3 这样的小分数的正确答案。在 g++ 中,以下给出 1:

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

请记住,C++ 中的类型具有固定的位大小。因此,32 位数据类型最多具有 MAX_UINT 值。如果你改变它的表示方式,你只是改变了可以精确表示的值,而不是增加它们。你不能塞得更多,因此不能“更精确”。你可以用能够精确表示 1/3 来换取不能精确表示其他值,例如 5.4235E+25。

确实,您float可以更精确地表示 1E-9 和 1E+9 之间的值(假设为 32 位整数),但代价是完全无法表示超出此范围的值。更糟糕的是,虽然标准float始终具有 6 位精度,但您的精度float会根据值接近零的程度而有所不同。(请注意,您使用的是两倍的位float。)

(我假设 32 bit ints。同样的论点适用于 64 bit ints。)

编辑:另请注意,人们使用的大多数数据floats 无论如何都不精确。如果您正在从传感器读取数据,那么您已经获得了不精确性,因此即将“完美”地表示该值是没有意义的。如果您float在任何类型的计算环境中使用 a ,那都没关系。如果您的目的是在屏幕的 1/3 处显示一些文本,那么完美地描述“1/3”是没有意义的。

唯一真正需要完美精确度的人是数学家,他们通常有软件可以做到这一点。很少有人需要超出double给定范围的精度。

于 2012-05-27T03:49:05.327 回答
10

实数算术变得非常简单并且更加准确!

不,它没有。您描述的结构仅处理有理数,即可以表示为分数的那些。实数集包括有理数和无理数。大多数现实世界的计算都是使用实数完成的,所以你不能只局限于有理数并期望一切都好。

我想知道为什么没有我知道的语言可以做到这一点。

我能想到的大多数语言都可以完全按照您的描述进行。在 C 中,您可以创建一个包含分子和分母的结构,并且您可以定义一堆对此类结构进行操作的函数。C++ 通过让你定义一个类和对该类的操作使事情变得更容易——同样的想法,更好的语法等等。事实上,不同的数字集经常在 OO 语言中用作示例:你可以从定义一个有理类,然后将其扩展为包括虚数,依此类推。

我猜想没有更多内置支持精确类型的语言的原因可能与处理器不直接支持此类操作的事实有关。现代处理器包含实现浮点类型算术运算的指令,因此很容易将这些指令包含在任何语言中。支持精确类型意味着将数学库构建到语言中,并且在多个级别上将数学库从语言中移除并让需要它的人将其构建到他们的软件中可能会更好。

如果您要不遗余力地产生准确的结果,您可能不想将自己限制在有理数上,因此您作为示例给出的结构不会削减它。如果您在第一次出现无理数时退回到不精确的结果,那么能够对有理数进行精确计算并不是很有帮助。幸运的是,那里有复杂的数学系统。Mathematica是一个著名的例子。

于 2012-05-27T03:28:06.850 回答
8

C++ 至少包含一个编译时有理算术库。这是一个例子:

#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。

于 2012-05-27T05:38:45.787 回答
6

戴上你的头盔,因为我们要在这里开始理论了。

任何数学本科生都可以给你一个关于康托尔证明实数不可数基数的电梯解释。如需更长的解释,请转到此处。

但正如 Caleb 指出的那样,实数域包含有理数和无理数。这意味着实数字段的某些子集将永远无法表示为分子/分母对。这个子集有多大?事实证明,大多数实数都是无理数,因为有理数的集合是可数的。

这是妙语:以这种方式存储数字将非常愚蠢,因为实值函数的大多数输出​​不能存储为分子和分母。

这似乎令人难以置信,但想想常见的超越函数,例如 sin、cos、log。这些函数的大多数输出​​都是不合理的,编写 IEEE 754 和其他早期 FP 东西的人都知道这一点。他们认为,处理少量错误以换取表示(有一些截断)实数字段的更大部分的可能性是一个很好的设计折衷。

于 2012-05-27T03:55:46.870 回答
5

许多 CPU 对浮点有特殊处理(参见 Wikipedia),float语言中的数据类型确保程序可以轻松地利用 FPU。另一方面,我不知道有任何 CPU 可以使用特殊的汇编指令处理分数,因此分数可以轻松有效地在库中实现,而不必是语言功能。如果你想在 C++ 中使用分数,你可以使用Boost.Rational

现代 CPU 实现浮点运算而不是处理分数的原因是浮点可以更容易地实现。要实现基本float操作,您基本上需要能够加减乘除整数并进行一些位移。另一方面,要与分数进行比较,您需要找到两个整数的最大公约数,这在硬件中实现起来要困难得多。

于 2012-05-27T03:38:21.473 回答
4

与您得到的许多答案相反,您的想法适用于多种问题。尤其是像这样的问题,你知道会有很多取消,并且想要一个准确的答案。 出于这个原因,Python 有fractions模块,正如@bames53 指出的那样,C++ 有<ratio>.

于 2012-05-27T07:03:35.933 回答
2

“因此我的问题是:为什么没有实现有理数并将其存储为零丢失信息的分数?”

C++ 标准库缺少其他语言和库提供的许​​多实际必需的类型。Boost 库已经提供了一个有理数实现。而且它似乎很快也会提供建议的小数类型,例如用于处理货币金额。

至于为什么缺少这些类型,C++ 标准库通常只提供基本的通用构建块,而不是使用这些块构建的更实用的东西。即它是简约的。Boost 库只是少了一点简约,然后例如 Poco 库更像其他语言的功能更丰富的标准库。

于 2012-05-27T03:52:21.150 回答
1

有几个原因:
- 没有来自通用架构的支持。这是可以理解的,因为分数必须总是伴随着简化。这在硬件级别实现并非易事,或者更确切地说,它将是一条没有太多应用程序的指令。
- 无法处理非常大或非常小的数字。或者 BigInteger 必须涉及。对于非常大或非常小的数字,我们通常不需要提供的大部分精度。
- 如果这是语言级别支持的类型,它必须支持与其他数字类型的转换。如果内部表示具有固定精度(在乘法的情况下),它必须决定何时返回浮点类型。

在一种语言中,支持某事的决定通常取决于其应用(或语言的基本原理)。如果应用程序较小,则支持的机会较小。

于 2012-05-27T03:52:31.327 回答