与帝国单位合作

时间:2009-07-06 15:26:44

标签: c++ units-of-measurement

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

我尚未确定应该为用户输入数据添加多少灵活性;例如如果他进入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 ++库,或者我可以看到的另一种语言的库来复制概念,那就太好了。

7 个答案:

答案 0 :(得分:5)

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

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

您还应该考虑制作距离不可变的实例 - 并在执行算术运算时创建新的距离。

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

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

答案 1 :(得分:4)

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

答案 2 :(得分:1)

你是对的,将所有转换为一种测量类型会给用户带来恼人的舍入错误。

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

对于您想要实施的每个测量系统,您应该有一个具体的子类:例如公制,美国帝国,古罗马。在内部,这可以以最合适和准确的格式存储它们 - 例如英制的一小部分。

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

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

你需要为每个子类实现操作(例如add),保留类型,所以一个帝国+一个帝国制造一个帝国。

如果添加mm和inch,则需要实现交叉类型操作,并确定要发生的操作。它应该输出公制或英制等级吗?

-Alex

答案 3 :(得分:1)

美国测量(有些人称之为“英语”或“帝国”单位;我们喜欢责怪他人的错误)要求你有理智的数字包来存储英寸。

对于公制,您有几个具有简单关系的不同单位(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数字包。你想要的是像这样的类层次结构。

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度量计算答案。

答案 4 :(得分:1)

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

“因此,如果用户绘制5英尺长的墙壁和3米长的墙壁,则总测量值应显示在......”

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

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

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

“但是在公制方面制定计划并在帝国中导入Sketchup模型并非遥不可及。我同意混合测量系统是一个坏主意,但我发现在现有的包中将选择限制为一个非常令人沮丧或者另一种,特别是当你在制作新文件/计划时只能选择一个。“

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

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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