14

我正在处理的应用程序需要存储格式的权重X pounds, y.y ounces。数据库是 MySQL,但我想这与 DB 无关。

我可以想到三种方法来做到这一点:

  1. 将重量转换为小数磅并存储在单个字段中。(5 磅 6.2 盎司 = 5.33671875 磅)
  2. 将重量转换为十进制盎司并存储在单个字段中。(5 磅 6.2 盎司 = 86.2 盎司)
  3. 在两个字段中将磅部分存储为整数,将盎司部分存储为小数。

我认为#1 不是一个好主意,因为小数磅会产生任意精度的数字,这需要存储为浮点数,这可能导致浮点数固有的不准确性。

是否有令人信服的理由选择 #2 而不是 #3 或反之亦然?

4

4 回答 4

36

TL;博士

选择选项#1 或选项#2——它们之间没有区别。不要使用选项#3,因为使用起来很尴尬。

您声称浮点数存在固有的不准确性。我认为这值得首先探讨一下。

在决定用数字系统来表示数字时(无论是在一张纸上、在计算机电路中还是在其他地方),有两个不同的问题需要考虑:

  1. 它的依据;和

  2. 它的格式

选择一个基地,任何基地......

受限于有限空间,不能代表无限集合的任意成员。 例如:无论你买了多少纸或你的字迹有多小,总有可能找到一个不适合给定空间的整数(你可以一直附加额外的数字,直到纸用完)。因此,对于整数,我们通常将有限空间限制为仅表示落在某个特定区间内的那些——例如,如果我们为正/负号和三位数字留有空间,我们可能会将自己限制在区间内[-999,+999]

每个非空区间都包含一组无限的实数。 换句话说,无论哪个区间接管实数——无论是 、还是[-999,+999]其他任何区间——在该区间内仍然有无限的实数集(只需不断附加(非零)小数位)!因此,任意实数必须始终“四舍五入”为可以在有限空间中表示的东西。[0,1][0.000001,0.000002]

可以在有限空间中表示的实数集取决于所使用的数字系统。 在我们(熟悉的)位置 以 10 为底的系统中,有限空间足以满足二分之一()但不能满足三分之一();相比之下,在(不太熟悉的)位置基 9系统中,情况正好相反(这些相同的数字分别是和)。所有这一切的结果是,一些数字可以仅使用位置基数为 10 的少量空间来表示(因此出现0.5100.33333…100.44444…90.39对我们人类来说非常“圆”),例如十分之一,实际上需要精确存储无限的二进制电路(因此对于我们的数字朋友来说似乎不是很“圆”)!值得注意的是,由于 2 是 10 的因数,因此反过来也不一样:任何可以用有限二进制表示的数字也可以用有限十进制表示。

对于连续数量,我们不能做得更好。最终,这样的数量必须在某个数字系统 中使用有限表示:该系统是否恰好在计算机电路、人类手指、其他东西上或根本不存在是任意的——无论使用哪种系统,该值都必须四舍五入并且因此它总是导致“表示错误”。

换句话说,即使一个人拥有一台完全准确的测量仪器(这在物理上是不可能的),那么它报告的任何测量结果都已经被四舍五入到一个恰好适合其显示的数字(无论它使用什么基数——通常是十进制,原因很明显)。因此,“86.2 oz”实际上并不是“ 86.2 oz ”,而是“ 86.1500000...oz 和 86.2499999...oz ”之间的代表。(实际上,因为实际上该工具是不完美的,所以我们只能说我们对实际值落在该区间内有一定程度的信心——但这肯定与这里的观点有所不同)。

但是对于离散量我们可以做得更好。这些值不是“任意实数”,因此上述任何一项都不适用于它们:它们可以在定义它们的数字系统中精确表示——实际上,应该是(如转换为另一个数字系统并截断为有限长度会导致舍入为不精确的数字)。计算机可以(低效地)通过将数字表示为字符串来处理这种情况:例如,考虑ASCIIBCD编码。

应用格式...

由于它是数字系统(有点随意)基础的属性,因此值是否看起来是“圆形”与其精度无关。这是一个非常重要的观察,与许多人的直觉背道而驰(这也是我在上面花了这么多时间解释数字基础的原因)。

相反,精度取决于一个表示有多少有效数字。我们需要一种能够将我们的值记录到至少与我们认为正确的有效数字一样多的存储格式。以我们认为在表示为86.2和时正确的值为例0.0000862,两个最常见的选项是:

  • Fixed point,其中有效数字的数量取决于幅度:例如,在固定的 5 位小数点表示中,我们的值将存储为86.20000and 0.00009(因此分别具有 7 和 1 位有效数字的精度)。在这个例子中,后一个值失去了精度(事实上,我们完全无法表示任何重要的东西并不需要更多的时间);而前一个值存储了错误的精度,这是对我们有限空间的浪费(实际上,值变得如此之大以至于溢出存储容量并不需要太多时间)。

    当这种格式可能适用于会计系统时,一个常见的例子是:货币金额通常必须跟踪到美分,而不管其大小(因此小值需要较少的精度,而大值需要更高的精度)。碰巧,货币通常也被认为是离散的(便士是不可分割的),因此这也是一个很好的例子,说明需要特定基础(大多数现代货币的十进制)以避免上面讨论的表示错误。

    通常通过将一个值视为公分母上的商并将分子存储为整数来实现定点存储。在我们的示例中,公分母可以是 10 5,因此用 1 代替1 将存储整数,86.20000并且记住它们必须除以。0.0000986200009100000

  • 浮点数,其中有效数字的数量是恒定的,与大小无关:例如,在 5 位有效数字十进制表示中,我们的值将存储为86.2000.000086200(并且根据定义,两次都具有 5 个有效数字)。在此示例中,两个值都已存储而没有任何精度损失;并且它们都具有相同数量的错误精度,这样浪费更少(因此我们可以使用我们的有限空间来表示更大范围的值——无论大小)。

    当这种格式可能适用于记录任何现实世界的测量时,一个常见的例子是:测量仪器的精度(都受到系统误差和随机误差的影响)是相当恒定的,与规模无关,因此,给定足够的有效数字(通常在 3 左右)或 4 位),即使更改基数导致舍入到不同的数字,也绝对不会丢失精度。

    通常通过将一个值视为具有整数指数的整数有效数来实现浮点存储。在我们的示例中,有效数字可以是86200两个值,因此(以 10 为底)指数分别是-4-9

    但是我们的计算机使用的浮点存储格式有多精确?

    • IEEE754 单精度( binary32 )浮点数有 24 位或(超过 7)位有效数字,即它的容差小于. 换句话说,它比说“ ”更精确。log10(224)±0.000006%86.20000

    • 一个 IEEE754双精度(binary64)浮点数有 53 位,或(几乎 16)位有意义——即它的容差刚好超过. 换句话说,它比说“ ”更精确。log10(253)±0.00000000000001%86.2000000000000

    要意识到的最重要的事情是,这些格式分别比说“86.2”的精度高一万多万亿倍——即使将二进制精确转换回十进制恰好包含错误的错误精度我们必须忽略:稍后会详细介绍)!

另请注意,当已知值比格式支持的更精确时,定点浮点格式都会导致精度损失。 这种舍入误差可以在算术运算中传播以产生明显错误的结果(这无疑解释了您对浮点数的“固有不准确性”的引用):例如,在 5 位定点中将产生而不是; 并且在 5 位有效数字中,浮点数将产生而不是.13 × 3000999.990001000.00000177500.00286000.0028571

数值分析领域致力于理解这些影响,但重要的是要认识到任何可用的系统(甚至在你的头脑中执行计算)都容易受到这些问题的影响,因为没有一种可以保证终止的计算方法可以提供无限的精度:例如,考虑如何计算圆的面积——用于 π 的值必然会损失精度,这会传播到结果中。

结论

  1. 现实世界的测量应该使用二进制浮点:它快速、紧凑、极其精确并且不比其他任何东西差(包括您开始使用的十进制版本)。由于MySQL 的浮点数据类型是 IEEE754,这正是它们提供的。

  2. 货币应用程序应该使用 denary 定点:虽然它很慢并且浪费内存,但它确保值不会四舍五入到不精确的数量,并且便士不会因大额货币而丢失。由于MySQL 的定点数据类型是 BCD 编码的字符串,这正是它们提供的。

最后,请记住,编程语言通常使用二进制浮点类型表示小数值:因此,如果您的数据库以另一种格式存储值,您需要小心它们是如何被带入您的应用程序的,否则它们可能会被转换(所有随之而来的问题)在接口处。

在这种情况下哪个选项最好?

希望我已经说服您,您的值可以安全地(并且应该)存储在浮点类型中,而不必过多担心任何“不准确”?请记住,它们比您脆弱的 3 位有效数字十进制表示精确:您只需要忽略错误精度(但无论如何都必须始终这样做,即使使用定点十进制格式)。

至于您的问题:选择选项 1 或选项 2 而不是选项 3-它使比较更容易(例如,要找到最大质量,可以只使用MAX(mass),而要跨两列有效地进行则需要一些嵌套)。

在这两者之间,选择哪一个并不重要——浮点数以恒定数量的有效位存储,而与它们的比例无关。

此外,虽然在一般情况下,可能会使用选项 1 将某些值四舍五入为更接近其原始十进制表示的二进制数,而同时使用选项 2 将其他值四舍五入为更接近其原始十进制表示的二进制数,如我们很快就会看到这种表示错误只会在应该始终被忽略的错误精度中表现出来。

但是,在这种情况下,因为碰巧有 16 盎司到 1 磅(16 是 2 的幂),原始十进制值和使用两种方法存储的二进制数之间的相对差异是相同的:

  1. 5.387510(不是您的问题中所述)将存储在 binary32 浮点数中为(即):这是来自原始值(但是,如上所述,“原始值”已经是它的物理量的非常糟糕的表示代表)。5.3367187510101.01100011001100110011025.38749980926513671875100.0000036%

    知道 binary32 浮点数只存储 7 个十进制数字的精度,我们的编译器肯定知道从第 8 位开始的所有内容肯定是错误精度,因此在每种情况下都必须忽略- 因此,只要我们的输入值不需要更多比这更精确(如果确实如此,binary32 显然是错误的格式选择),这保证了返回一个十进制值,看起来和我们开始时一样圆:. 但是,我们应该真正应用领域知识5.38750010此时(就像我们应该使用任何存储格式一样)丢弃可能存在的任何进一步的错误精度,例如那两个尾随零。

  2. 86.210将存储在 binary32 浮点数中(即):这也来自原始值。和以前一样,我们然后忽略错误精度以返回我们的原始输入。1010110.00110011001100110286.1999969482421875100.0000036%

注意数字的二进制表示是如何相同的,除了小数点的位置(相隔四位):

101.0110 00110011001100110
101 0110.00110011001100110

这是因为 5.3875 × 2 4 = 86.2。

顺便说一句:作为欧洲人(尽管是英国人),我对英制测量单位也有强烈的反感——处理不同尺度的值真是麻烦了。我几乎肯定会以SI 单位(例如千克或克)存储质量,然后在我的应用程序的表示层中根据需要执行到英制单位的转换。再加上严格遵守 SI 单位,有一天可能会让您免于损失 1.25 亿美元

于 2012-10-19T15:03:09.227 回答
9

我很想将它存储在公制单位中,因为它们往往是简单的小数,而不是像磅和盎司这样的复杂值。这样,您可以只存储一个值(即 103.25 公斤)而不是磅 - 盎司等值,并且更容易执行转换。

这是我过去处理过的事情。我在职业摔跤和综合格斗 (MMA) 网站上做了很多工作,这些网站需要记录选手的身高和体重。它们倾向于显示为英尺和英寸以及磅和盎司,但我仍然将这些值存储在它们的厘米和千克等值中,然后在网站上显示时进行转换。

于 2012-10-19T15:30:23.907 回答
1

首先,我不知道浮点数是如何不准确的——谢天谢地,后者帮助我理解:浮点数不准确示例

我完全同意@eggyal - 将数据以单一格式保存在单个列中。这允许您将它公开给应用程序并让应用程序处理它的表示 - 无论是磅/盎司,四舍五入的磅,等等。

数据库应保留原始数据,而表示层决定布局。

于 2012-10-19T15:05:47.337 回答
0

您可以对权重列使用十进制数据类型。

decimal('weight', 8, 2);        // precision = 8, scale = 2
Storage size:
Precision 1-9       5 Bytes
Precision 10-19     9 Bytes
Precision 20-28     13 Bytes
Precision 29-38     17 Bytes
于 2021-08-02T00:21:27.010 回答