TL;DR
选择选项 #1 或选项 #2 — 它们之间没有区别。不要使用选项 #3,因为使用起来很尴尬。
您声称浮点数存在固有的不准确性。我认为这值得先探索一下。
在决定用numeral system 表示数字时(无论是在纸上、在计算机电路中还是在其他地方),需要考虑两个单独的问题:
它的基础;和
它的格式。
选择一个基地,任何基地......
受限于有限的空间,不能代表infinite set的任意成员。例如:无论你买多少纸或你的字迹多小,总有可能找到一个不适合给定空间的整数(您可以继续附加额外的数字,直到纸张用完)。因此,使用 整数,我们通常将有限空间限制为仅表示那些落在某个特定区间内的那些——例如如果我们为正号/负号和三位数字留有空间,我们可能会将自己限制在区间 [-999,+999]。
每个非空的interval包含无限的实数集。换句话说,无论是什么区间,一个实数——无论是[-999,+999]、[0,1]、[0.000001,0.000002] 还是其他任何东西——在那个区间内仍然有无限的实数集(只需要继续附加(非零)小数位)!因此,任意实数必须总是被“四舍五入”到可以在有限空间中表示的东西。
可以在有限空间中表示的实数集取决于所使用的数字系统。在我们(熟悉的)positionalbase-10 系统中,有限空间就足够了- 一半(0.5<sub>10</sub>)但不是三分之一(0.33333…<sub>10</sub>);相比之下,在(不太熟悉的)位置base-9 系统中,情况正好相反(这些相同的数字分别是0.44444…<sub>9</sub> 和0.3<sub>9</sub>)。所有这一切的结果是,一些数字可以仅使用位置基数为 10 的少量空间来表示(因此 对我们人类来说似乎是非常“圆”的),例如十分之一,实际上需要精确存储无限的二进制电路(因此对于我们的数字朋友来说似乎不是很“圆”)!值得注意的是,由于 2 是 10 的因数,因此反过来也不一样:任何可以用有限二进制表示的数字也可以用有限十进制表示。
对于连续量,我们做得再好不过了。 最终,这些量必须在某个数字系统中使用有限表示:该系统是否简单是任意的在计算机电路上、在人的手指上、在其他东西上或根本没有——无论使用哪个系统,值必须四舍五入,因此它总是导致“表示错误”。
换句话说,即使一个人拥有一个完全准确的测量仪器(这在物理上是不可能的),那么它报告的任何测量结果都已经被四舍五入到恰好适合其显示的数字(在它使用的任何基础上——通常是十进制,原因很明显)。因此,“86.2 oz”实际上并不是“86.2 oz”,而是“介于 86.1500000...oz 和 86.2499999...oz之间的东西”的表示。 (实际上,由于该工具实际上并不完美,我们只能说我们有一些degree of confidence 实际值落在该区间内——但这肯定与这里的观点有所不同。
但是对于离散量我们可以做得更好。这些值不是“任意实数”,因此上述任何一项都不适用于它们:它们可以在定义它们的数字系统中准确表示——实际上,应该 (因为转换为另一个数字系统并截断为有限长度会导致舍入为不精确的数字)。计算机可以(低效地)通过将数字表示为字符串来处理这种情况:例如考虑ASCII 或BCD 编码。
应用格式…
由于它是数字系统(有点随意)基础的属性,一个值是否看起来是“圆形”与其精度无关。这是一个非常重要的观察,这与许多人的直觉背道而驰(这也是我在上面花了这么多时间解释数字基础的原因)。
精度取决于一个表示有多少significant figures。我们需要一种能够将我们的值记录到至少尽可能多的有效数字我们认为它们是正确的的存储格式。以 86.2 和 0.0000862 表示时我们认为正确的值为例,两个最常见的选项是:
-
固定点,其中有效数字的数量取决于大小:例如在固定的 5 位小数点表示中,我们的值将存储为 86.20000 和 0.00009(因此分别具有 7 位和 1 位有效数字的精度)。在这个例子中,精确度已经在后一个值中丢失了(事实上,我们完全无法表示任何有意义的东西并不需要更多的时间) ;而前一个值存储false precision,这是对我们有限空间的浪费(实际上,这个值不会花费太多时间就可以变得如此之大以至于它溢出存储容量)。
这种格式适用于会计系统的一个常见示例是:通常必须跟踪货币金额到一美分,无论其大小(因此,对于较小的值,精度要求较低,并且较大的值需要更高的精度)。碰巧,货币通常也被认为是离散的(便士是不可分割的),因此这也是需要特定基础(大多数现代货币为十进制)以避免上面讨论的表示错误的情况的一个很好的例子。
通常通过将一个值视为公分母上的商并将分子存储为整数来实现定点存储。在我们的示例中,公分母可以是 105,因此可以存储整数 8620000 和 9 而不是 86.20000 和 0.00009,并记住它们必须除以 @ 987654366@.
-
浮点数,其中有效数字的数量是恒定的,与大小无关:例如在 5 位有效数字十进制表示中,我们的值将存储为 86.200 和 0.000086200(根据定义,两次都有 5 个有效数字精度)。在这个例子中,两个值都被存储了没有任何精度损失;而且它们都具有相同数量的错误精度,这样浪费更少(因此我们可以使用有限空间来表示更大范围的值——无论大小)。
这种格式可能适用于记录任何现实世界的测量的一个常见示例:测量仪器的精度(都受到systematic 和random 错误的影响)相当无论比例如何,只要有足够的有效数字(通常约为 3 或 4 位),绝对不会丢失精度即使更改基数导致舍入为不同的数字。
通常通过将一个值视为具有整数指数的整数significands来实现浮点存储。在我们的示例中,两个值的有效数字都可以是 86200,因此(以 10 为底的)指数分别是 -4 和 -9。
但是我们的计算机使用的浮点存储格式有多精确?
要意识到的最重要的事情是,这些格式分别比说一万和一万亿倍更精确 “86.2”——即使将二进制精确转换回十进制恰好包含错误的错误精度(我们必须忽略:稍后会详细介绍)!
另请注意, 固定 和 浮点格式都将导致精度损失,如果一个值已知比格式支持的更精确。 这样的rounding errors 可以在算术运算中传播以产生明显错误的结果(这无疑解释了您对浮点数“固有的不准确性”的引用):例如,5 位定点中的<sup>1</sup>⁄<sub>3</sub> × 3000 将产生@987654379 @ 而不是 1000.00000;和<sup>1</sup>⁄<sub>7</sub> − <sup>7</sup>⁄<sub>50</sub> 5 位有效数字浮点将产生0.0028600 而不是0.0028571。
numerical analysis 领域致力于理解这些影响,但重要的是要意识到任何可用系统(甚至在您的头脑中执行计算)都容易受到这些问题的影响,因为 任何保证终止的计算方法都无法提供无限的精度:例如,考虑如何计算圆的面积——用于 π 的值必然会损失精度,这会传播结果。
结论
现实世界的测量应该使用二进制浮点:它快速、紧凑、极其精确,并且不比其他任何东西差(包括您开始使用的十进制版本)。由于MySQL's floating-point datatypes 是 IEEE754,这正是他们提供的。
货币应用程序应使用 denary 定点:虽然它速度慢且浪费内存,但它确保值不会四舍五入到不精确的数量,并且便士不会因大额货币而丢失。由于MySQL's fixed-point datatypes 是 BCD 编码的字符串,这正是他们提供的。
最后,请记住,编程语言通常使用二进制浮点数表示小数值:因此,如果您的数据库以另一种格式存储值,您需要小心它们是如何被带入您的应用程序,否则它们可能会在界面处被转换(以及随之而来的所有问题)。
在这种情况下哪个选项最好?
希望我已经说服您,您的值可以安全地(并且应该)存储在浮点类型中,而不必过多担心任何“不准确”?请记住,它们更比你脆弱的 3 位有效数字十进制表示更精确:你只需要忽略错误的精度(但无论如何必须始终这样做,即使使用定点十进制格式)。
至于您的问题:选择选项 1 或选项 2 而不是选项 3 — 它使比较更容易(例如,要找到最大质量,可以只使用 MAX(mass),而要有效地跨两列进行则需要一些嵌套)。
在这两者之间,选择哪一个并不重要 - 浮点数以恒定数量的有效位进行存储无论其规模如何。
此外,虽然在一般情况下,可能会使用选项 1 将某些值四舍五入为更接近其原始十进制表示的二进制数,同时使用选项将其他值四舍五入为更接近其原始十进制表示的二进制数2,我们很快就会看到这种表示错误只会在应该始终被忽略的错误精度中表现出来。
但是,在 这种 的情况下,因为碰巧有 16 盎司到 1 磅(16 是 2 的幂),原始十进制值和存储的二进制数之间的相对差异使用两种方法相同:
-
5.3875<sub>10</sub>(不是您的问题中所述的5.33671875<sub>10</sub>)将作为101.011000110011001100110<sub>2</sub>(即5.38749980926513671875<sub>10</sub>)存储在二进制32浮点数中:这是来自原始值的0.0000036%(但是,如所讨论的)上面,“原始值”已经是它所代表的物理量的一个非常糟糕的表示)。
知道 binary32 浮点数仅存储 7 位十进制精度,我们的编译器肯定知道从第 8 位开始的所有内容肯定错误精度,因此 必须在每个情况下都被忽略——因此,假设我们的输入值不需要比这更高的精度(并且如果是这样,binary32 显然是错误的格式选择),这保证返回一个十进制值,看起来和我们开始时一样圆:5.387500<sub>10</sub>。但是,此时我们真的应该应用domain knowledge(就像我们应该使用任何存储格式一样)来丢弃可能存在的任何进一步的错误精度,例如那两个尾随零。
86.2<sub>10</sub> 将作为1010110.00110011001100110<sub>2</sub>(即86.1999969482421875<sub>10</sub>)存储在二进制32 浮点数中:这也是原始值的0.0000036%。和以前一样,然后我们忽略错误精度以返回原始输入。
注意数字的二进制表示是相同的,除了 radix point 的位置(相隔四位):
101.0110 00110011001100110
101 0110.00110011001100110
这是因为 5.3875 × 24 = 86.2。
顺便说一句:作为欧洲人(尽管是英国人),我也对英制测量单位有强烈的反感——处理不同尺度的值只是 如此 混乱。我几乎可以肯定将质量存储在SI units(例如千克或克)中,然后在我的应用程序的表示层中根据需要执行到英制单位的转换。再加上严格遵守 SI 单位,有一天可能会让您摆脱losing $125m。