一个聪明的自制模数实现

时间:2013-02-12 20:32:25

标签: algorithm math modulus plc

我正在使用一些传统软件(RSLogix 500,不要问)编程PLC,它本身不支持模数运算,但我需要一个。我没有访问:模数,整数除法,局部变量,截断操作(虽然我可以通过舍入来破解它)。此外,我可用的所有变量都在按数据类型排序的表中列出。最后,它应该适用于浮点小数,例如12345.678 MOD 10000 = 2345.678

如果我们制定方程式:

dividend / divisor = integer quotient, remainder

有两个明显的实现。

实施1: 执行浮点除法:dividend / divisor = decimal quotient。然后将截断操作混在一起,以便找到integer quotient。将其乘以divisor,找出dividendremainder之间的差异,从而产生dividend > divisor

我不喜欢这个,因为它涉及一堆不同类型的变量。我无法将变量“传递”到子程序,因此我只需要分配位于多个不同变量表中的一些全局变量,并且很难遵循。不幸的是,“难以理解”非常重要,因为它需要足够简单,以便维护人员能够搞砸。

实施2: 创建一个循环,以便divisor = dividend - divisor {{1}}。这是非常干净的,但它违反了PLC编程的一个重要规则,即永远不会使用循环,因为如果有人无意中修改了索引计数器,你可能会陷入无限循环,机器会变得疯狂或无法恢复。 Plus循环很难进行维护以进行故障排除。另外,我甚至没有循环指令,我必须使用标签和跳转。 EWW。

所以我想知道是否有人有任何聪明的数学黑客或更聪明的模数实现。我可以访问+ - * /,exponents,sqrt,trig函数,log,abs值和AND / OR / NOT / XOR。

5 个答案:

答案 0 :(得分:6)

你要处理多少位?你可以这样做:

if dividend > 32 * divisor  dividend -= 32 * divisor
if dividend > 16 * divisor  dividend -= 16 * divisor
if dividend > 8 * divisor  dividend -= 8 * divisor
if dividend > 4 * divisor  dividend -= 4 * divisor
if dividend > 2 * divisor  dividend -= 2 * divisor
if dividend > 1 * divisor  dividend -= 1 * divisor
quotient = dividend

只需按dividend中的位数展开多次。确保小心那些溢出的倍数。这就像你的#2,除了它需要log(n)而不是n次迭代,所以完全展开是可行的。

答案 1 :(得分:5)

如果您不介意使事情过于复杂并浪费计算机时间,您可以使用周期性触发函数计算模数:

atan(tan(( 12345.678 -5000)*pi/10000))*10000/pi+5000   =   2345.678

严重的是,减去一次或两次10000(你的“实施2”)会更好。一般浮点模数的常用算法需要许多位级操作,这些操作对您来说可能是不可行的。参见例如http://www.netlib.org/fdlibm/e_fmod.c(算法很简单但代码很复杂,因为特殊情况并且因为它是为IEEE 754双精度数而编写的,假设没有64位整数类型)

答案 2 :(得分:3)

这一切似乎完全过于复杂。您有一个编码器索引,它在10000处滚动,并且对象沿着您在任何给定点跟踪其位置的线滚动。如果您需要沿着线转发项目停止点或动作点,只需添加您需要的许多英寸,如果目标结果大于10000,则立即减去10000.

或者,或者另外,每次PLC扫描都会获得新的编码器值。在当前值和最后一个值之间的差值为负的情况下,您可以激活工作联系人以标记换行事件并对该扫描上的任何计算进行适当的更正。 (**或增加二级计数器如下)

在不了解实际问题的情况下,很难提出更具体的解决方案,但肯定有更好的解决方案。我根本不认为需要MOD。此外,地板上的人会感谢你没有用模糊的向导填充机器。

我引用:

  

最后,它必须适用于浮点小数   12345.678 MOD 10000 = 2345.678

有一个很棒的功能可以做到这一点 - 它是一个减法。为什么它需要比这更复杂?如果您的输送线实际上长于833英尺,则滚动第二个计数器,该计数器在主索引翻转时递增,直到您有足够的距离覆盖您所需的地面。

例如,如果您需要100000英寸的传送带存储器,则可以使用在10处翻转的辅助计数器。如上所述,可以轻松检测到主编码器翻转,并且每次都增加辅助计数器。那么,您的工作编码器位置是计数器值加上当前编码器值的10000倍。仅在扩展单元中工作,并使辅助计数器以您需要的任何值滚动,以免丢失任何部件。然后,问题再次简化为减法(如上所述)。

例如,我将这种技术与行星齿轮传动的旋转零件支架一起使用。我有一个编码器在每次主旋转时翻转一次,而行星齿轮传动卫星部件(它们自身绕定子齿轮旋转)需要43次主旋转才能返回到相同的起始方向。使用一个简单的计数器,在主编码器翻转点处递增(或递减,取决于方向),它为您提供了对部件所在位置的完全绝对测量。在这种情况下,辅助计数器在43处滚动。

这对于线性输送机来说同样有效,唯一的区别是线性输送机可以继续无限距离。然后问题只需要受到线路上最坏情况部分所采用的最长线性路径的限制。

随着我从未使用RSLogix的警告,这里是一般的想法(我在这里使用了通用符号,我的语法可能有点不对,但你应该明白这一点)

RSLogix Sample

通过上述内容,您最终获得的值ENC_EXT基本上将您的编码器从10k英寸转换为100k英寸。我不知道您的输送机是否可以反向运行,如果可以,您还需要处理下降计数。如果程序的其余部分仅使用ENC_EXT值,那么您甚至不必担心编码器只能达到10k。它现在变为100k(或任何你想要的),并且可以使用减法而不是模数来处理环绕。

后记:

PLC首先是状态机。 PLC程序的最佳解决方案通常是与这个想法相协调的解决方案。如果您的硬件不足以完全代表机器的状态,那么PLC程序应该尽力用它所拥有的信息填补缺失状态信息的空白。上述解决方案是这样做的 - 它需要不足10000英寸的状态信息并将其扩展以满足过程的要求。

这种方法的好处是,您现在已经保留了绝对状态信息,不仅适用于输送机,还适用于生产线上的任何部件。您可以向前和向后跟踪它们以进行故障排除和调试,并且您可以使用更简单,更清晰的坐标系统来进行将来的扩展。使用模数计算,您将丢弃状态信息并尝试以功能方式解决个别问题 - 这通常不是使用PLC的最佳方式。你必须忘记从其他编程语言中得到的知识并以不同的方式工作。 PLC是一种不同的野兽,它们在处理时效果最佳。

答案 3 :(得分:3)

您可以使用子程序来完成您所说的内容。你可以把这些棘手的代码丢掉,这样维护技术人员就永远不会遇到它。对于您和您的维护人员来说,几乎可以肯定最容易理解。

自从我使用RSLogix500以来已经有一段时间了,所以我可能会遇到一些错误,但你会明白这一点。

为浮点和整数定义一个数据文件,并为它们提供符合MOD_F和MOD_N的符号。如果你让这些令人生畏,那么维护技术人员就会把它们留下来,而你所需要的就是在数学过程中传递参数和工作空间。

如果你真的担心它们弄乱了数据表,有很多方法可以保护它们,但我忘记了它们在SLC / 500上的用途。

接下来,如果可能的话,定义一个子程序,远离现在使用的子程序。将它命名为MODULUS。同样,如果维护人员听起来像编程名称,他们几乎总是不在SBR中。

在JSR指令之前的梯级中,将要处理的变量加载到MOD_N和MOD_F数据文件中。将这些梯级注释为加载MODULUS SBR数据的说明。任何有编程背景的人都可以清楚地看到评论。

只有在您需要时才有条件地调用您的JSR。维护技术人员不会对非执行逻辑进行故障排除,因此如果您的JSR未处于活动状态,他们很少会查看它。

现在你有了自己的小围墙花园,在那里你可以编写循环而无需维护就可以参与其中。只使用那些数据文件,并且不要假设除了那些文件之外的任何状态。换句话说,你不能相信间接寻址。只要在MODULUS JSR中定义索引,索引编址就可以了。不要相信任何传入的索引。使用MOD_N文件中的一个单词,跳转和标签来编写FOR循环非常容易。您的整个实施#2应该少于十个级别左右。我会考虑使用表达式指令或其他东西......让你只需输入一个表达式。该指令可能需要504或505。适用于组合浮点/整数数学。检查结果,以确保舍入不会杀死你。

完成后,尽可能完美地验证您的代码。如果此代码导致数学溢出并使处理器出现故障,您将永远不会听到它的结束。如果你有模拟器,可以在模拟器上运行它,具有奇怪的值(如果它们以某种方式搞乱了函数输入的加载),并确保PLC没有故障。

如果你做到这一切,没有人会意识到你在PLC中使用常规编程技术,你会没事的。尽管它工作正常。

答案 4 :(得分:0)

这是一个基于@Keith Randall答案的循环,但它也保留了减法除法的结果。为了清晰起见,我保留了printf。

#include <stdio.h>
#include <limits.h>
#define NBIT (CHAR_BIT * sizeof (unsigned int))

unsigned modulo(unsigned dividend, unsigned divisor)
{
unsigned quotient, bit;

printf("%u / %u:", dividend, divisor);

for (bit = NBIT, quotient=0; bit-- && dividend >= divisor; ) {
        if (dividend < (1ul << bit) * divisor) continue;
        dividend -= (1ul << bit) * divisor;
        quotient += (1ul << bit);
        }
printf("%u, %u\n", quotient, dividend);
return dividend; // the remainder *is* the modulo
}

int main(void)
{
modulo( 13,5);
modulo( 33,11);
return 0;
}