用C语言表达描述代数式。

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用C语言表达描述代数式。相关的知识,希望对你有一定的参考价值。

1-- 2ad/cd
2--(x+y)/(x-y)-(z+y)/(z-y)
速度要快。最好现在就有答案啊

换成C的运算式了。返回一个与运算结果。可以用来赋值和输出。
1-- 2*a*d/c*d;
2-- (x+y)/(x-y)-(z+y)/(z-y);
参考技术A 1. 2*a*d/(c*d)
2. (x+y)/(x-y)-(z+y)/(z-y)

连续截断整数除法可以用乘法代替吗?

【中文标题】连续截断整数除法可以用乘法代替吗?【英文标题】:Can consecutive truncating integer divisions be replaced with a multiplication? 【发布时间】:2019-01-20 19:19:55 【问题描述】:

在有理数的小学数学中,表达式(a / b) / c 通过基本代数运算等效于a / (b * c)

/ 在 C 和大多数其他语言中截断整数除法时是否也是如此?也就是所有除数的乘积可以用一个除数代替一系列除数吗?

你可以假设乘法不会溢出(如果溢出,很明显它们不等价)。

【问题讨论】:

编程语言不会改变数学规则。 @old_timer - 我不确定你的意思。数学规则取决于操作的语义。我在学校学到的规则通常适用于实数或有理数,而不适用于“截断除法” - 所以并不完全清楚哪些适用(当然有些不适用,例如a / b * b == a)。因此,如果您愿意,您可以将此问题解释为关于用 C 实现的特定类型的数学。还要考虑大多数“数学”规则根本不适用于浮点数学(我不是在这里问)。 100 / 2 / 5 = 10. 100 / (2*5) = 10;只要你没有分数,这在纸上和编程语言中都是正确的。如果你限制为整数,你在数学上遇到的一个问题是分数,但事实上除法有效,在你插入数字之前它仍然有效 Apparently, it is equivalent ... 但不要将此评论视为真相。请参阅 John Coleman 的回答。 @chqrlie - 关于INT_MIN-1 的好点虽然至少在这种情况下UB 是在“正确的方向” - 也就是说,转换版本是定义的,而原始版本不是'吨。所以转换至少在那个问题上是“安全的”:毕竟,如果原始形式是 UB,你就不能真正期待任何特别是转换后的版本(如果你对反向转换感兴趣,那当然是一个问题) . 【参考方案1】:

答案是“是”(至少对于非负整数)。这来自division algorithm,它指出对于任何正整数a,d,对于唯一的非负整数q,r0 <= q <= d-1,我们都有a = dq+r。在这种情况下,q a/d 在整数除法中。

a/b/c(带整数除法)中,我们可以分两步思考:

a = b*q_1 + r_1  // here q_1 = a/b and 0 <= r_1 <= b-1
q_1 = c*q_2 + r_2 // here q_2 = q_1/c = a/b/c and 0 <= r_2 <= c-1

然后

a = b*q_1 + r_1 = b*(c*q_2 + r_2) + r_1 = (b*c)*q_2 + b*r_2 + r1

注意0 &lt;= b*r_2 + r_1 &lt;= b*(c-1) + b-1 = bc - 1

由此得出q_2a/(b*c)。因此a/b/c = a/(b*c)

【讨论】:

谢谢,这是我正在寻找的证据。对于负数,假设 一致 向 0 舍入或向 -infinity 舍入,类似的情况是否成立?【参考方案2】:

是的,在整数上。有人已经发布(并删除了?)一个示例,说明它可能无法在浮点上运行。 (不过,对于您的应用程序来说,它可能已经足够接近了。)

@JohnColeman 有一个理论论证,但这里有一个实验论证。如果你运行这段代码:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define LIMIT 100
#define NUM_LIMIT 2000

int main() 
    for(int div1 = -LIMIT; div1 < LIMIT; div1++) 
        for(int div2 = -LIMIT; div2 < LIMIT; div2++) 
            if(div1 == 0 || div2 == 0) continue;
            for(int numerator = -NUM_LIMIT; numerator < NUM_LIMIT; numerator++) 
                int a = (numerator / div1) / div2;
                int b = numerator / (div1 * div2);
                if(a != b) 
                    printf("%d %d\n", a, b);
                    exit(1);
                
            
        
    
    return 0;

它尝试两种方式的除法,如果它们不同,则报告错误。按以下方式运行不会报错:

div1、div2 从 -5 到 5,分子从 -INT_MAX/100 到 INT_MAX/100 div1,div2 从 -2000 到 2000,分子从 -2000 到 2000

因此,我敢打赌这适用于任何整数。 (再次假设 div1*div2 没有溢出。)

【讨论】:

我敢打赌这适用于任何整数几乎不能作为证明。 我还使用了“尝试一些数字”的方法,无论是在我的脑海中还是在上面的程序中。问题是我不认为它是确定的,除非你能做到详尽无遗。 也许对于 16 位数字是可行的(如果您可以将每次检查的时间降低到 1 ns,则需要几天时间来搜索 48 位空间),但即使是 32 位数字也不是吨。另外,我猜 C 对除法有 impl-defined 行为,所以你冒着让它在你的机器上工作的风险,但并不总是符合标准。【参考方案3】:

通过实验查看这个有助于了解您如何应用数学。

软件不会改变数学、身份或您使用变量来简化或更改方程的其他操作。软件执行要求的操作。除非您溢出,否则定点可以完美地完成它们。

根据数学而不是软件,我们知道 b 或 c 都不能为零以使其起作用。软件补充的是 b*c 也不能为零。如果溢出则结果是错误的。

a / b / c = (a/b) * (1/c) = (a*1) / (bc) = a / (bc) 从小学和你可以在您的编程语言中实现 a/b/c 或 a/(b*c),这是您的选择。大多数情况下,如果您坚持使用整数,则由于未表示分数,结果是不正确的。如果您使用浮点数,结果相当多的时候是不正确的,同样的原因,没有足够的位来存储无限大或小的数字,就像纯数学的情况一样。那么你在哪里遇到这些限制呢?您可以通过一个简单的实验开始看到这一点。

编写一个程序,遍历 a、b 和 c 的 0 到 15 之间所有可能的数字组合,计算 a/b/c 和 a/(b*c) 并比较它们。由于这些是四位数字,因此如果您想查看编程语言将如何处理,请记住中间值不能超过 4 位。仅打印除以零和不匹配的除数。

您会立即看到,允许任何值为零会导致一个非常无趣的实验,您要么得到很多除以零,要么 0/something_not_zero 不是一个有趣的结果。

 1  2  8 : divide by zero
 1  3 11 :  1  0
 1  4  4 : divide by zero
 1  4  8 : divide by zero
 1  4 12 : divide by zero
 1  5 13 :  1  0
 1  6  8 : divide by zero
 1  7  7 :  1  0
 1  8  2 : divide by zero
 1  8  4 : divide by zero
 1  8  6 : divide by zero
 1  8  8 : divide by zero
 1  8 10 : divide by zero
 1  8 12 : divide by zero
 1  8 14 : divide by zero
 1  9  9 :  1  0
 1 10  8 : divide by zero
 1 11  3 :  1  0
 1 12  4 : divide by zero
 1 12  8 : divide by zero
 1 12 12 : divide by zero
 1 13  5 :  1  0
 1 14  8 : divide by zero
 1 15 15 :  1  0
 2  2  8 : divide by zero
 2  2  9 :  1  0
 2  3  6 :  1  0
 2  3 11 :  2  0

所以到目前为止,答案是匹配的。对于有意义的小 a 或特别是 a=1,结果要么是 0 要么是 1。两条路径都会让你到达那里。

 1  2  8 : divide by zero

至少对于 a=1,b=1。 c=1 为 1,其余为 0。

2*8 = 16 或 0x10 位太多,因此会溢出,结果是 0x0 除以零,因此无论是浮点数还是定点数,您都必须查找。

 1  3 11 :  1  0

第一个有趣的 1 / (3*11) = 1 / 0x21 表示 1/1 = 1; 1 / 3 = 0, 0 / 11 = 0。 所以他们不匹配。 3*11 溢出。

这样下去。

所以将 ra 设为更大的数字可能会使这更有趣?无论如何,一个小的 a 变量大部分时间都会使结果为 0。

15  2  8 : divide by zero
15  2  9 :  7  0
15  2 10 :  3  0
15  2 11 :  2  0
15  2 12 :  1  0
15  2 13 :  1  0
15  2 14 :  1  0
15  2 15 :  1  0
15  3  6 :  7  0
15  3  7 :  3  0
15  3  8 :  1  0
15  3  9 :  1  0
15  3 10 :  1  0
15  3 11 : 15  0
15  3 12 :  3  0
15  3 13 :  2  0
15  3 14 :  1  0
15  3 15 :  1  0
15  4  4 : divide by zero
15  4  5 :  3  0
15  4  6 :  1  0
15  4  7 :  1  0
15  4  8 : divide by zero
15  4  9 :  3  0


15  2  9 :  7  0

15 / (2 * 9) = 15 / 0x12 = 15 / 2 = 7。 15 / 2 = 7; 7 / 9 = 0;

15  3 10 :  1  0
15  3 11 : 15  0

这两种情况都溢出没意思。

所以改变你的程序,只显示结果不匹配的那些,但也没有 b*c 溢出......没有输出。使用 4 位值与 8 位与 128 位执行此操作之间没有任何魔法或区别……它只是让您获得更多可能有效的结果。

0xF * 0xF = 0xE1,你可以很容易地看到它在二进制中进行长乘法,最坏的情况是覆盖所有可能的 N 位值,你需要 2*N 位来存储结果而不会溢出。因此,对于除法来说,一个由 N/2 位数分母限制的 N 位分子可以覆盖每个具有 N 位结果的所有定点值。 0xFFFF / 0xFF = 0x101。 0xFFFF / 0x01 = 0xFFFF。

因此,如果您想进行此数学运算并且可以确保没有一个数字超过 N 位,那么如果您使用 N*2 位进行数学运算。您不会有任何乘法溢出,您仍然需要担心除以零。

为了通过实验证明这一点,请尝试从 a、b、c 的 0 到 15 的所有组合,但使用 8 位变量而不是 4 位变量进行数学运算(在每次除法之前检查是否除以零并将这些组合丢弃)并且结果总是匹配。

那么有没有“更好”的?乘法和除法都可以使用大量逻辑在单个时钟中实现,或者使用指数减少的多个时钟实现,尽管您的处理器文档说它是单个时钟它可能仍然是多个时钟,因为有管道并且它们可以隐藏 2 或4个循环进入一些管道并节省大量芯片房地产。或者一些处理器根本不做划分以节省空间。一些来自 arm 的 cortex-m 内核可以编译为单时钟或多时钟除法,仅在有人进行乘法时才会受到伤害(谁在他们的代码中进行乘法???或除法???)。当你做类似的事情时,你会看到

x = x / 5;

取决于目标和编译器以及可以/将要实现为 x = x * (1/5) 的优化设置以及使其工作的其他一些动作。

unsigned int fun ( unsigned int x )

    return(x/5);


0000000000000000 <fun>:
   0:   89 f8                   mov    %edi,%eax
   2:   ba cd cc cc cc          mov    $0xcccccccd,%edx
   7:   f7 e2                   mul    %edx
   9:   89 d0                   mov    %edx,%eax
   b:   c1 e8 02                shr    $0x2,%eax
   e:   c3                      retq   

除法和乘法一样可用,但乘法被认为更好,可能是因为时钟,也可能是其他原因。

所以您可能希望考虑到这一点。

如果执行 a/b/c,则必须检查除以零两次,但如果执行 a / (b+c),则只需检查一次。对于每条 alu 指令的 1 个或接近 1 个时钟数,检查除以零的成本比数学本身更高。因此,乘法在理想情况下表现更好,但也有可能例外。

您可以使用带符号的数字重复所有这些操作。这同样适用。如果它适用于 4 位,它将适用于 8 和 16 以及 32 和 64 和 128 等等......

7 * 7 = 0x31
-8 * -8 = 0x40
7 * -8 = 0xC8

这应该涵盖极端情况,因此如果您使用的位数是最坏情况的两倍,则不会溢出。您仍然必须在每次除法之前检查除以零,因此乘法解决方案仅导致一次检查零。如果将所需位数加倍,则不必检查乘法是否溢出。

这里没有魔法,这一切都是用基础数学解决的。我什么时候溢出,使用铅笔和纸并且没有编程语言(或者像我那样使用计算器以使其更快)你可以看到什么时候。您还可以使用更多的小学数学。 N 位的 b 的 msbit 是 b[n-1] * 2^(n-1) 对吗?使用 4 位数,无符号,msbit 为 0x8,即 1 * 2^(4-1);其余的 b (b[3] * 2^3) + (b[2] * 2^2) + (b[1] * 2^1) + (b[0] * 2^ 0); C 也一样,所以当我们使用小学数学将它们相乘时,如果你坐下来工作,我们会从 (b[3]c[3])(2^(3+3)) 开始最坏的情况不能超过 2^8。也可以这样看:

     abcd
*    1111
=========
     abcd
    abcd
   abcd
+ abcd
=========
  xxxxxxx

7 位加上进位的可能性,使其总共 8 位。所有简单的小学数学,看看潜在的问题是什么。

实验将不会显示失败的位模式(除以零不算数,这对于 a/b/c = a/(b*c) 也不起作用)。 John Colemans 从另一个角度回答可能有助于感觉所有位模式都可以工作。尽管那都是正数。只要您检查所有溢出,这也适用于负数。

【讨论】:

请注意,如果您想添加舍入,那么以 2 为底很容易,您只需将数字设为两倍大小 (((a>1;但是现在您必须再次注意溢出...或者是吗?

以上是关于用C语言表达描述代数式。的主要内容,如果未能解决你的问题,请参考以下文章

将代数式写成C语言表达式

请将代数式2ac+b-3ab改写成C语言的表达式?

用 C 语言开发一门编程语言 — Lispy 编程演示

代数式在C语言中的书写格式

代数式-2ad+b-4ac改写成C语言

c语言根据以下描述编写一个程序使其能完成对稀疏矩阵的压缩与还原,即给定稀疏矩阵可以压缩存储一个三元组