使用英制单位
我正在研究一个应用程序,粗略地说,它是一种建筑行业的建模应用程序。 将来我希望用户能够同时使用 SI 单位和英制单位。 据我了解,美国建筑业在指定尺寸时通常使用英寸的分数,例如 3 1/2" - 而在 SI 中我们会写 3.5,而不是 3 1/2。我正在寻找一种方法在我的软件中使用这些不同的系统 - 存储它们,对它们进行计算等,不仅解析用户输入的内容,它应该能够以用户输入的方式向用户显示测量结果,而且能够进行计算。其他测量值 - 例如,将 3 厘米添加到 1 1/2 英寸,因此,如果用户绘制的墙长为 5 英尺,另一墙长为 3 米,则总测量值应以用户选择的默认单位系统显示
。我尚未决定应该为用户输入数据添加多少灵活性;例如,如果他输入 1 英尺 14 英寸,下次显示测量值时是否应该输入 2 英尺 2 英寸?我正在寻找一种以精确形式存储测量值的方法,这就是
我正在使用 C++ 的问题,并且我已经查看了 Boost.Units,但这似乎没有提供一种处理方法。与分数。
简单的选择是将所有内容转换为毫米,但舍入误差将导致无法返回用户输入的精确测量值(如果他以英制测量值输入)。 所以我需要一些更复杂的东西。
现在我正在使用一个暂定名为“Distance”的类,其概念如下:
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++ 库已经可以完成这些事情,或者有一个我可以从中复制概念的其他语言的库,那就太好了。
I'm toying with an application that is, roughly speaking, a sort of modeler application for the building industry. In the future I'd like it to be possible for the user to use both SI units and imperial. From what I understand, it's customary in the US building industry to use fractions of inches when specifying measurements, eg 3 1/2" - whereas in SI we'd write 3.5, not 3 1/2. I'm looking for a way to work with these different systems in my software - storing them, doing calculations on them etc, not only parsing what a users enters. It should be able to show the user a measurement in the way he entered it, yet being able to calculate with other measurements - for example add 3 cm to 1 1/2 inch. So if a user draws a length of wall of 5 feet and another one of 3 meters, the total measurement should be shown in the default unit system the user selected.
I'm undecided yet on how much flexibility I should add for entering data for the user; e.g. if he enters 1 foot 14 inches, should it should 2 feet 2 inches the next time the measurement is shown? However before I decide things like that, I'm looking for a way to store measurements in an exact form, which is what my question is about.
I'm using C++ and I've looked at Boost.Units, but that doesn't seem to offer a way to deal with fractions.
The simple option is to convert everything to millimeters, but rounding errors would make it impossible to go back to the exact measurement a user entered (if he entered it in imperial measurements). So I'll need something more complex.
For now I'm using a class that is tentatively named 'Distance' and looks conceptually like this:
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?>
}
This clearly shows where my problems are. The most obvious thing to do would be to have an enum that says whether this Distance is storing SI or imperial units, and have fields (doubles, presumably) that store the meters, centimeters and millimeters if it's in SI units and feet and inches if it's imperial. However this will make the implementation of the class littered with if(SI) else ..., and is very wasteful in memory. Plus I'd have to store a numerator and denominator for the feet and inches to be able to exactly store 1/3", for example.
So I'm looking for general design advice on how I should solve these problems, given my design requirements. Of course if there's a C++ library out there that already does these things, or a library in another language I could look at to copy concepts from, that would be great.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
查看 Martin Fowler 的 Money 模式,来自 企业应用程序架构模式 - 它直接适用于这种情况。 推荐阅读。 Fowler 还在他的网站上发布了一篇简短的文章 数量模式,货币的更通用版本。
Take a look at Martin Fowler's Money pattern from Patterns of Enterprise Application Architecture - it is directly applicable to this situation. Recommended reading. Fowler has also posted a short writeup on his site of the Quantity pattern, a more generic version of Money.
我肯定会考虑将 Units 属性添加到距离类中。 然后,您可以重载 +、-、*、/(和相关)运算符,以便仅当单位类型相同时才可以对距离进行算术运算。
就我个人而言,我会将所有测量值标准化为每个系统支持的最低测量单位(例如,SI 为毫米,英制为英寸),但也会存储用户输入的表示形式。 以标准化形式执行所有计算,但在呈现给用户时转换回更易读的形式。
您还应该考虑使 Distance 的实例不可变 - 并在执行算术运算时创建新的 Distance。
最后,您可以创建辅助方法来在不同单位之间进行转换 - 甚至可以在对不同单位的距离执行算术时在内部调用这些方法。 只需将所有内容转换为通用单位,然后进行计算即可。
就我个人而言,我不会走在每个系统中创建多种测量类型的路线 - 我认为您最好整合逻辑并允许您的系统以多态方式处理测量。
I would definitely consider adding a Units property to the distance class. You could then overload the +, -, *, / (and related) operators so that arithmetic operations on distances is only possible when the units are the same type.
Personally, I would normalize all measurements into the lowest unit of measurement you will support in each system (eg. millimeters for SI, inches for imperial) but also store the users' entered representation. Perform all calculation in normalized form, but convert back to a more readable form when presenting to users.
You should also consider making instances of Distance immutable - and creating a new Distance whenever an arithmetic operation is performed.
Finally, you can create helper methods to convert between different units - and potentially even call these internally when performing arithmetic on distances with different units. Just convert everything to a common unit and then perform the calculation.
Personally, I would not go the route of creating multiple types for measurements in each system - I think you are better off consolidating the logic and allowing your system to treat measurements polymorphically.
您是对的,将所有内容转换为一种测量类型会给用户带来烦人的舍入误差。
您应该创建一个声明操作的虚拟基类。
对于要实现的每个测量系统,您应该有一个具体的子类:例如公制、美制、古罗马。 在内部,这可以以最合适和最准确的格式存储它们 - 例如英制的英寸的分数。
您将需要为每个子类(测量系统)提供一个字符串输出机制。
您应该有一个工厂来将字符串表示形式转换为适当类的实例。
您需要为每个子类实现操作(例如添加),保留类型,因此一个英制 + 一个英制构成一个英制。
您将需要实现十字类型操作,并决定如果添加毫米和英寸,您希望发生什么。 它应该输出公制还是英制等级?
-亚历克斯
You are right that converting all to one measurement type will give the user annoying rounding errors.
You should make a virtual baseclass that declares the operations.
You should have a concrete child class for each measurement system you want to implement : eg metric, US imperial, ancient roman. Internally this can store them in the most appropriate and accurate format - e.g. fractions of an inch for imperial.
You will need a string output mechanism for each child class (measurement system).
You should have a factory to turn string representations into instances of the appropriate class.
You will need to implement operations (such as add) for each child class, preserving the type, so an imperial + an imperial make an imperial.
You will need to implement cross type operations, and decide what you want to have happen if you add mm and inches. Should it output a metric or imperial class?
-Alex
美国测量单位(有些人称之为“英制”或“英制”单位;我们喜欢将自己的错误归咎于他人)要求您有有理数包来存储英寸。
对于公制,您有几个具有简单关系的不同单位(厘米、米、公里等)。 将它们视为具有简单转换系数的独立单位。 米到厘米是 *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 英尺长的木板。
事实证明,这种单位转换也适用于米和英尺之间的转换。 您可以枚举 in、ft、yd、mm、cm、m、km 转换的每种组合。 输入单位标准化为短单位(厘米、英寸),并在输出时转换为所需单位(米、英尺、码等)。
您可以存储 1356 英寸。 没关系。 您可以显示 113' 或 37.66 码或 37 码 2 英尺,具体取决于用户选择的输出单位。
该方案适用于除温度之外的任何情况。
唯一的障碍是几分之一英寸。 正确执行此操作需要有理数包。 你想要的是这样的类层次结构。
有理测量采用用户提供的小数表示法。 如果您的 Rational 类正确地覆盖了所有运算符,那么它的工作方式与浮点测量相同。 如果它提供了适当的函数来在 int 和 float 之间进行转换,您应该能够将两者混合并获得合理的答案,而无需太多 RTTI。
这就是奇怪的地方。 英寸使用 2 的幂,并具有精确的浮点表示形式。 没有 24.000000001 或其他转换工件。 公制使用 10 的幂,因此对于公制计算,您会得到各种看起来愚蠢的 24.00000001 和 23.99999997 答案。
US measurement (Some folks call it "English" or "Imperial" units; we like to blame others for our mistakes) requires you have Rational number package to store inches.
For metric, you have several distinct units (cm, m, km, etc.) with simple relationships. Think of them as separate units with simple conversion factors. m to cm is a *100 conversion. You can easily enumerate all of the *100, *1000, *.1, *.01 conversion factors among all possible metric distance measures.
For English, the units (inches, feet, yards, etc.) also have simple relationships. They just don't fetishize over decimal. 12's and 3's are used instead of 10's. Think of them as separate units with simple conversion factors. ft to in is a *12 conversion. Again, you can easily enumerate all of the *12, *36, *(1/12), *(1/36) combinations.
When someone enters 3' 8", you can normalize that to inches and convert it back correctly. Even if they enter 3' 14", your conversion back to 4' 2" is correct and expected. In some cases, it's desired.
In some cases -- even in English notation -- there is a desired output unit which not be the original input unit. For example, someone may have a long list of measurements in feet and inches, but want a sum in decimal feet. When you purchase in bulk, you don't care that it's 25' 6 7/16"; 25.54' is a good answer. You're buying 26' of lumber, which often means 3 10' boards.
Turns out that this units conversion works for m to ft and back, also. You can enumerate every combination of in, ft, yd, mm, cm, m, km conversions. The input units are normalized to something short (cm, in) and converted on output to the desired units (m, ft, yd, etc.)
You can store 1356 inches. That's fine. You can display 113' or 37.66 yds, or 37 yds 2 ft. depending on the output units the user selects.
This scheme works for anything except temperature.
The only snag is fractions of an inch. Doing this right requires a Rational number package. What you want is a class hierarchy like this.
The Rational measurements are in user-supplied fractional notation. If your Rational class override all the operators correctly, it works the same as Floating-point measurements. If it provides appropriate functions to convert between int and float, you should be able to intermix the two and get reasonable answers without much RTTI.
Here's what's weird. Inches use powers of 2, and have exact floating point representations. No 24.000000001 or other artifacts of conversions. Metric uses powers of 10, so you get all kinds of silly-looking 24.00000001 and 23.99999997 answers for metric calculations.
我认为美国宇航局曾经以这种方式丢失过火星探测器。
“因此,如果用户绘制了一面 5 英尺长的墙,另一面墙长 3 米,则总测量结果应显示为……”
很难想象这样做的用户期望会发生什么。 除非我完全确定我知道用户为什么这样做,以及预期会发生什么,否则我会简单地禁止它。 如果选择 SI 单位,则不允许使用英制单位,反之亦然。 在大多数情况下,我认为这是帮助用户并防止混乱蔓延的最佳方式。
当使用英制单位时,我会在内部以 1/32 英寸为单位存储距离,根据输出/输入的需要在英尺、英寸和分数之间进行转换。 这将避免舍入错误以及从二进制表示的十进制值和分数转换的所有讨厌的问题。
而且在建筑行业中,“2 x 4”实际上并不是 2 英寸 x 4 英寸。
“但是制定公制计划并导入英制 SketchUp 模型并不是遥不可及。我同意混合测量系统是一个坏主意,但我发现现有软件包将选择限制为其中之一,这非常令人沮丧,特别是当您在制定新文件/计划时只能选择一个时。”
是的。 导入和转换文件或组件库的单位是程序非常有用的一项至关重要的功能。 这意味着将所有测量值从一个系统转换为另一系统。 必须决定如何执行此操作(向上舍入到最接近的 1/4" 或 1/8" 等)。这些决定必须得到实施、强制执行,并可能纳入用户选项。 这很复杂并且很难做到正确。 因此,复杂性和不稳定性应限制在一个单独的模块中,该模块只做一件事:转换一个文件或一组文件中的单位。 程序的其余部分应该使用一致的单位集,而不是以零碎的方式从一个单位转换为另一个单位。 恕我直言
I think NASA once lost a Mars probe this way.
“So if a user draws a length of wall of 5 feet and another one of 3 meters, the total measurement should be shown in … “
It is very hard to imagine what a user who does this expects to happen. Unless I was absolutely sure I knew why the user was doing this, and what then was expected to happen, I would simply disallow it. If SI units are selected, imperial units are disallowed, and visa versa. In most cases, I think this is the best way you can help the user and prevent an ever widening spread of confusion.
When using imperial units I would internally store distances in units of 1/32th of an inch, converting to and from feet, inches and fractions as reuired for output/ input. This will avoid rounding errors and all the nasty problems of converting from binary represented decimal values and fractions.
Not also that in the building trade a “2 by 4” is not actually 2 inches by 4 inches.
"But having a plan in metric and importing a Sketchup model in imperial isn't far-fetched. I agree that mixing measurement system is a bad idea, but I find it quite frustrating in existing packages that limit the choice to one or the other, especially when you can only choose one at the moment you're making a new file/plan."
Yes. Importing and converting the units of a file or library of components is a vital and important feature where a program can be very useful. This means converting all the measurements from one system to the other. Decisions have to be made on how to do this ( round up round down to the nearest 1/4" or 1/8" etc ) These decisions have to be implemented, enforced and possibly made into user options. It is complicated and hard to get right. The complexity and instability should therefore be confined to a separate module that does one thing: convert units in a file or set of files. The rest of the program should use consistent sets of units without converting from one to other in piecemeal fashion. IMHO
我建议使用
Decimal
类型而不是Double
类型。 虽然速度较慢,但速度差异不会影响你。至于舍入问题,只需设置要显示的最大位数,但在内部存储完整的数字。 即使您将 1 英尺存储为 0.305 米(1.00065617 英尺),用户也不会知道显示时是否将其四舍五入到最接近的百分之一。
回复 Roel: 你处于低位的可能性与你处于高位的可能性一样(而且,它是 0.006,而不是 0.06)。 但是,如果您像这样连续有 1000 个相同的测量值,并且相差 0.06 英尺,那么它将是 100.06 英尺,而不是 100 英尺。 这或许对建筑行业来说确实很重要。 但是,我使用的有效数字远少于
Decimal
数据类型实际提供的有效数字,因为我演示的是舍入问题而不是精度问题。 在实践中,我认为容差会被试图构建某些东西所固有的不准确性所超越(例如,计算可能会偏差小于 1 nm 的微小数字,但起重机的容差可能比 100 nm 差得多) )。I would suggest the use of the
Decimal
type rather than theDouble
type. It's slower, but the speed difference won't affect you.As for rounding concerns, just have a maximum number of digits you'll display but store the full number internally. Even if you store 1 foot as 0.305 meters (1.00065617 feet), the user won't know if you round it to the nearest 100ths when displaying.
In reply to Roel: You're just as likely to be low as you are to be high (and also, it's 0.006, not 0.06). But if you had 1000 of the same measurement in a row like this and were 0.06 feet off, it would be 100.06 feet instead of 100 feet. Which perhaps does matter to the building industry. However, I used far fewer significant digits than is actually provided by the
Decimal
datatype, since I was demonstrating the rounding issue rather than demonstrating the precision issue. In practice, I think the tolerances are such that they will be outdone by the innacurracy that is inherent in trying to build something (e.g. the calculation might be off by a tiny number smaller than 1 nm but the crane's tolerances are probably much worse than 100nm).实际上,在同一张施工图中存在两个测量系统是真实存在的。 在本例中,我会将 3 米墙显示为 3 米,将 5 英尺墙显示为 5 英尺。
Actually having two measurement systems in the same construction drawing is real. In this case I would show the 3 meter wall as 3 meters and the 5 foot wall as 5 foot.