【问题标题】:Working with imperial units使用英制单位
【发布时间】:2010-11-08 10:13:39
【问题描述】:

我正在玩弄一个应用程序,粗略地说,它是一种用于建筑行业的建模器应用程序。将来我希望用户可以同时使用 SI 单位和英制单位。据我了解,在美国建筑业中,在指定尺寸时习惯使用英寸的分数,例如 3 1/2" - 而在国际单位制中,我们会写 3.5,而不是 3 1/2。我正在寻找一种方法在我的软件中使用这些不同的系统 - 存储它们,对它们进行计算等,不仅解析用户输入的内容。它应该能够以他输入的方式向用户显示测量,但能够计算其他测量值 - 例如将 3 厘米添加到 1 1/2 英寸。因此,如果用户绘制 5 英尺的墙壁长度和 3 米的另一面,则总测量值应以用户选择的默认单位系统显示。

我还没有决定我应该为用户输入数据增加多少灵活性;例如如果他输入 1 英尺 14 英寸,下次显示测量值时应该是 2 英尺 2 英寸吗?但是,在我做出这样的决定之前,我正在寻找一种以精确形式存储测量值的方法,这就是我的问题所在。

我正在使用 C++,并且查看了 Boost.Units,但这似乎没有提供处理分数的方法。

简单的选择是将所有内容都转换为毫米,但舍入误差会导致无法返回到用户输入的精确测量值(如果他以英制测量值输入)。所以我需要一些更复杂的东西。

现在我正在使用一个暂定名为“距离”的类,在概念上看起来像这样:

class Distance
{
public:
    Distance(double value);
    // operators +, -, *, /
    Distance operator+(const Distance& that);
    ...etc...

    std::string StringForm(); // Returns a textual form of the value

    Distance operator=(double value);

private:
    <question: what should go here?>
}

这清楚地表明了我的问题所在。最明显的做法是有一个枚举,说明此距离是存储 SI 单位还是英制单位,并具有存储米、厘米和毫米(如果是 SI 单位)和英尺和英寸(如果这是帝国的。然而,这将使类的实现充满 if(SI) else ...,并且非常浪费内存。另外,例如,我必须存储英尺和英寸的分子和分母才能准确存储 1/3"。

因此,鉴于我的设计要求,我正在寻找有关如何解决这些问题的一般设计建议。当然,如果有一个 C++ 库已经可以做这些事情,或者我可以查看其他语言的库来复制概念,那就太好了。

【问题讨论】:

标签: c++ units-of-measurement


【解决方案1】:

Patterns of Enterprise Application Architecture 看一下Martin Fowler 的Money 模式——它直接适用于这种情况。推荐阅读。 Fowler 还在他的网站上发布了一篇关于 Quantity 模式的简短文章,这是一种更通用的 Money 版本。

【讨论】:

  • 谢谢,这看起来很适用。
【解决方案2】:

我肯定会考虑将 Units 属性添加到距离类中。然后,您可以重载 +、-、*、/(和相关的)运算符,以便仅在单位类型相同时才可以对距离进行算术运算。

就个人而言,我会将所有测量值归一化为您将在每个系统中支持的最低测量单位(例如,SI 为毫米,英制为英寸),但也会存储用户输入的表示形式。以标准化形式执行所有计算,但在呈现给用户时转换回更易读的形式。

您还应该考虑使 Distance 的实例不可变 - 并在执行算术运算时创建一个新的 Distance。

最后,您可以创建帮助方法来在不同单位之间进行转换 - 甚至在对不同单位的距离执行算术时甚至可能在内部调用这些方法。只需将所有内容转换为通用单位,然后执行计算即可。

就个人而言,我不会走在每个系统中为测量创建多种类型的路线 - 我认为您最好整合逻辑并允许您的系统以多态方式处理测量。

【讨论】:

  • ",我会将所有测量值归一化为您在每个系统中支持的最低测量单位(例如,SI 为毫米,英制为英寸),但也存储用户输入的表示。这。您应该只在输入和输出处进行转换,但不要跨系统边界混合单位,除非用户对您这样做。
  • 使其不可变有什么好处?我一直在我的应用程序中使用 CGAL 进行几何计算,它的 3D 点类是不可变的 - 我发现它是一个可以使用的 PITA,当我想要做的只是在 x 轴值上加 1 时必须构造一个新实例, 例如。而不是 doint point.x += 1 我必须用原始点的 y 和 z 值调用构造函数,这更容易输入,更重要的是更难阅读(即,你不容易看到什么正在发生)。只是想知道这种设计的基本原理是什么。
  • @Roel:使测量类不可变的优点是它不可变的。这是一种预期,如果你在一个地方改变它的价值,它不会影响到其他地方。防止这种性质的错误的最简单方法是让它是不可变的。考虑到大多数程序员对测量的心理模型是不可变的(5 英尺始终是 5 英尺,但我的列表可能会添加和删除一些东西),因此额外的痛苦是非常值得的。
【解决方案3】:

您说得对,将所有测量类型转换为一种测量类型会给用户带来恼人的舍入误差。

您应该创建一个声明操作的虚拟基类。

您应该为每个要实现的测量系统创建一个具体的子类:例如公制、美制、古罗马。在内部,这可以以最合适和最准确的格式存储它们 - 例如英制的几分之一英寸。

每个子类(测量系统)都需要一个字符串输出机制。

您应该有一个工厂来将字符串表示形式转换为适当类的实例。

您需要为每个子类实现操作(例如添加),保留类型,因此英制 + 英制构成英制。

您将需要实现跨类型操作,并决定如果您添加毫米和英寸,您希望发生什么。它应该输出公制还是英制?

-亚历克斯

【讨论】:

    【解决方案4】:

    美制测量(有些人称其为“英制”或“英制”单位;我们喜欢将错误归咎于他人)要求您有有理数包来存储英寸。

    对于公制,您有几个具有简单关系的不同单位(cm、m、km 等)。将它们视为具有简单转换因子的独立单元。 m 到 cm 是 *100 的转换。您可以轻松枚举所有可能的公制距离度量中的所有 *100、*1000、*.1、*.01 转换因子。

    对于英语,单位(英寸、英尺、码等)也有简单的关系。他们只是不迷恋小数点。使用 12 和 3 而不是 10。将它们视为具有简单转换因子的独立单元。 ft 到 in 是 *12 转换。同样,您可以轻松枚举所有 *12、*36、*(1/12)、*(1/36) 组合。

    当有人输入 3' 8" 时,您可以将其标准化为英寸并将其正确转换回来。即使他们输入 3' 14",您转换回 4' 2" 也是正确且符合预期的。在某些情况下,它是想要的。

    在某些情况下——即使是英文符号——有一个不是原始输入单元的期望输出单元。例如,某人可能有一长串以英尺和英寸为单位的测量值,但想要以十进制英尺为单位的总和。当您批量购买时,您不在乎它是 25' 6 7/16";25.54' 是一个很好的答案。您购买的是 26' 的木材,这通常意味着 3 个 10' 的木板。

    事实证明,这种单位转换也适用于 m 到 ft 和返回。您可以枚举 in、ft、yd、mm、cm、m、km 转换的每种组合。输入单位被归一化为较短的单位(cm、in),并在输出时转换为所需的单位(m、ft、yd 等)

    您可以存储 1356 英寸。没关系。您可以显示 113 英尺或 37.66 码,或 37 码 2 英尺,具体取决于用户选择的输出单位。

    此方案适用于除温度以外的任何情况。

    唯一的障碍是几分之一英寸。做到这一点需要一个 Rational number 包。你想要的是这样的类层次结构。

    Distance
    |
    +---- Float (no fractions, everything but inches)
    |
    +---- Rational (fractions used for inches)
    

    Rational 测量值采用用户提供的分数表示法。如果您的 Rational 类正确地覆盖了所有运算符,那么它的工作方式与浮点测量相同。如果它提供适当的函数来在 int 和 float 之间进行转换,您应该能够将两者混合并获得合理的答案而无需太多 RTTI。


    这就是奇怪的地方。英寸使用 2 的幂,并具有精确的浮点表示。没有 24.000000001 或其他转换工件。公制使用 10 的幂,因此您会得到各种看起来很傻的 24.00000001 和 23.99999997 的公制计算答案。

    【讨论】:

      【解决方案5】:

      我认为 NASA 曾经以这种方式丢失了火星探测器。

      “因此,如果用户绘制了 5 英尺的墙长度和 3 米的另一堵墙,则总测量值应显示为……”

      很难想象这样做的用户期望发生什么。除非我完全确定我知道用户为什么这样做,以及预期会发生什么,否则我只会拒绝它。如果选择 SI 单位,则不允许使用英制单位,反之亦然。在大多数情况下,我认为这是帮助用户并防止不断扩大的混淆范围的最佳方式。

      使用英制单位时,我会在内部以 1/32 英寸为单位存储距离,并根据输出/输入的要求在英尺、英寸和分数之间进行转换。这将避免舍入错误和从二进制表示的十进制值和分数转换的所有讨厌的问题。

      并不是说在建筑行业中,“2 x 4”实际上并不是 2 x 4 英寸。

      “但是制定一个公制计划并以英制导入 Sketchup 模型并不是牵强附会。我同意混合测量系统是一个坏主意,但我发现在现有软件包中将选择限制为一个非常令人沮丧或其他,尤其是当您在制作新文件/计划时只能选择一个时。”

      是的。导入和转换文件或组件库的单位是程序非常有用的重要且重要的功能。这意味着将所有测量值从一个系统转换到另一个系统。必须决定如何执行此操作(向上舍入到最接近的 1/4" 或 1/8" 等)必须实施、强制执行这些决定,并可能将其纳入用户选项。这很复杂,很难做到正确。因此,复杂性和不稳定性应该被限制在一个单独的模块中,它只做一件事:转换一个文件或一组文件中的单元。程序的其余部分应使用一致的单位集,而不是以零碎的方式从一个转换为另一个。恕我直言

      【讨论】:

      • 好的,在不同的测量系统中有 2 个相邻的墙可能是一个虚构的例子。但是制定公制计划并以英制导入 Sketchup 模型并非遥不可及。我同意混合测量系统是一个坏主意,但我发现在现有的软件包中将选择限制为一个或另一个非常令人沮丧,特别是当您在制作新文件/计划时只能选择一个时。
      【解决方案6】:

      我建议使用Decimal 类型而不是Double 类型。速度较慢,但​​速度差异不会影响您。

      至于四舍五入的问题,只需要显示的最大位数,但在内部存储完整的数字。即使您将 1 英尺存储为 0.305 米(1.00065617 英尺),用户在显示时也不知道您是否将其四舍五入到最接近的 100 分之一。

      回复 Roel: 你可能是低的,也可能是高的(而且,它是 0.006,而不是 0.06)。但是,如果像这样连续有 1000 个相同的测量值,并且 相差 0.06 英尺,那么它将是 100.06 英尺而不是 100 英尺。这可能确实对建筑行业很重要。但是,我使用的有效数字比Decimal 数据类型实际提供的有效数字少得多,因为我演示的是舍入问题而不是精度问题。在实践中,我认为公差是这样的,以至于它们会被尝试建造某些东西所固有的不准确所超越(例如,计算可能会偏离小于 1 nm 的微小数字,但起重机的公差可能比 100 nm 差得多)。

      【讨论】:

      • 除了当你连续有 100 个时,测量值相差 0.06 英尺。
      【解决方案7】:

      实际上在同一张施工图中有两个测量系统是真实的。在这种情况下,我会将 3 米的墙显示为 3 米,将 5 英尺的墙显示为 5 英尺。

      【讨论】:

        猜你喜欢
        • 2020-11-21
        • 2017-02-10
        • 2020-01-30
        • 2020-07-25
        • 2011-10-24
        • 2011-06-21
        • 1970-01-01
        • 2019-03-20
        • 1970-01-01
        相关资源
        最近更新 更多