【问题标题】:Best practice for storing weights in a SQL database?在 SQL 数据库中存储权重的最佳实践?
【发布时间】:2012-10-19 15:00:58
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 应用程序是什么?食谱?人体重量?了解数据的范围以及您将如何处理这些数据会很有帮助。
  • 在我的用例中,重量是产品(商品)和运输包裹的重量。自动生成海关表格和运输成本估算等计算已完成。这不完全是火箭科学,但需要一定程度的准确性。例如,一个 13 盎司的包裹运费为 3.50 美元,而 13.1 盎司的包裹运费为 6 美元。如果将 13.1 盎司四舍五入到 13 盎司,我们的运费将减少 2.50 美元。如果每天发生 100 次,则每月相差 7,500 美元!精度应精确到 1/10 盎司。
  • 顺便说一句,“重量”和“质量”是有区别的。重量衡量力,而质量衡量物质的数量。重量随重力而变化,以Newtons(作为任何力)测量,而质量不随重力变化,以公斤(或磅等)测量。你所描述的是质量。
  • 将质量存储为十进制千克,DECIMAL(9,4) 可能就足够了。千克是质量的 SI 单位。转换为用户的单位以进行演示。不太可能,但如有必要,您还可以根据用户的重力进行调整。小数不是浮点数。
  • @Nick - 抱歉,我知道这是超级旧的,但我目前正在尝试将体重存储在数据库中,但我正在查看您的示例,我有点困惑。你介意解释一下 5 磅 6.2 盎司是如何变成5.33671875 lbs 的吗?当我从 5 lbs 6.2 oz 转换为十进制时,我得到 5.3875 lbs (6.2/16+5=5.3875),我只是想确保我没有遗漏一些非常明显的东西。谢谢。

标签: mysql database-design


【解决方案1】:

TL;DR

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

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

在决定用numeral system 表示数字时(无论是在纸上、在计算机电路中还是在其他地方),需要考虑两个单独的问题:

  1. 它的基础;和

  2. 它的格式

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

受限于有限的空间,不能代表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 实际值落在该区间内——但这肯定与这里的观点有所不同。

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

应用格式…

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

精度取决于一个表示有多少significant figures。我们需要一种能够将我们的值记录到至少尽可能多的有效数字我们认为它们是正确的的存储格式。以 86.20.0000862 表示时我们认为正确的值为例,两个最常见的选项是:

  • 固定点,其中有效数字的数量取决于大小:例如在固定的 5 位小数点表示中,我们的值将存储为 86.200000.00009(因此分别具有 7 位和 1 位有效数字的精度)。在这个例子中,精确度已经在后一个值中丢失了(事实上,我们完全无法表示任何有意义的东西并不需要更多的时间) ;而前一个值存储false precision,这是对我们有限空间的浪费(实际上,这个值不会花费太多时间就可以变得如此之大以至于它溢出存储容量)。

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

    通常通过将一个值视为公分母上的商并将分子存储为整数来实现定点存储。在我们的示例中,公分母可以是 105,因此可以存储整数 86200009 而不是 86.200000.00009,并记住它们必须除以 @ 987654366@.

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

    这种格式可能适用于记录任何现实世界的测量的一个常见示例:测量仪器的精度(都受到systematicrandom 错误的影响)相当无论比例如何,只要有足够的有效数字(通常约为 3 或 4 位),绝对不会丢失精度即使更改基数导致舍入为不同的数字

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

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

    • IEEE754 single precision (binary32) floating point 数字有 24 位,或 log<sub>10</sub>(2<sup>24</sup>)(超过 7 个)位,具有重要意义——即它的容差小于±0.000006%。换句话说,它比说“86.20000”更准确。

    • 一个 IEEE754 double precision (binary64) floating point 数字有 53 位,或 log<sub>10</sub>(2<sup>53</sup>)(几乎 16)位,具有重要意义——即它的容差刚刚超过±0.00000000000001%。换句话说,它比说“86.2000000000000”更准确。

    要意识到的最重要的事情是,这些格式分别比说一万一万亿更精确 “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 领域致力于理解这些影响,但重要的是要意识到任何可用系统(甚至在您的头脑中执行计算)都容易受到这些问题的影响,因为 任何保证终止的计算方法都无法提供无限的精度:例如,考虑如何计算圆的面积——用于 π 的值必然会损失精度,这会传播结果。

结论

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

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

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

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

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

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

在这两者之间,选择哪一个并不重要 - 浮点数以恒定数量的有效位进行存储无论其规模如何

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

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

  1. 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(就像我们应该使用任何存储格式一样)来丢弃可能存在的任何进一步的错误精度,例如那两个尾随零。

  2. 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

【讨论】:

  • +1 以获得详尽的答案。我认为最好的方法是表示盎司的定点字段。以 65 的最大精度和 1 的比例,我可以有 1/10 盎司的精度,同时仍然允许重量远远超过公司可以想象的销售量。关于您最近的编辑,如果盎司部分不是整盎司,例如 13.1 盎司,那么该值不会导致小数磅方法出现问题吗?
  • @Nick: 0.1 oz 等于 0.00625 lbs,因此使用五位小数的定点方法就足够了。请记住,即使您将值作为定点存储在数据库中,它们也很可能会在您的应用程序中转换为浮点(尤其是当您对它们执行比较/算术时)。另一种选择是将值存储为十分之一盎司的整数,这完全避免了分数。
【解决方案2】:

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

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

【讨论】:

  • 我正在构建一个健身应用程序,我将根据用户区域设置显示公斤或磅/盎司。这种方法对您有任何舍入问题吗?
  • 舍入是一个域问题。它们只是数字。向上和/或向下取整、小数点位数等由您决定。
【解决方案3】:

首先,我不知道浮点数是如何不准确的 - 谢天谢地,后者帮助我理解:Floating Point Inaccuracy Examples

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

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

【讨论】:

    【解决方案4】:

    重量列可以使用十进制数据类型。

    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
    

    【讨论】:

      猜你喜欢
      • 2011-05-04
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      • 2011-03-31
      • 2018-07-27
      • 2012-04-28
      • 2012-04-29
      • 2011-08-24
      相关资源
      最近更新 更多