正整数乘以负值

时间:2015-12-31 11:01:05

标签: c++ integer

我正在学习C ++,阅读Stroustrup"原理与实践使用C ++"。

在有关前置和后置条件的部分中,有以下功能示例:

$('button.btn.btn-success').click(function(event)
{

event.preventDefault();

$.post('getpostcodescript.php', $('form').serialize(), function(data, status, xhr)
    {
        // do something here with response;
        console.info(data);
        console.info(status);
        console.info(xhr);
    })
    .done(function() {
        // do something here if done ;
        alert( "saved" );
    })
    .fail(function() {
        // do something here if there is an error ;
        alert( "error" );
    })
    .always(function() {
        // maybe the good state to close the modal
        alert( "finished" );
        // Set a timeout to hide the element again
        setTimeout(function(){
          $("#myModal").hide();
        }, 3000);
    });
});

让我感到困惑的是关于此代码的任务:

  

找到一对值,使这个版本的前提条件   区域成立,但后置条件没有。

是否存在这样的可能的整数值,前提条件是好的但条件不是吗?

9 个答案:

答案 0 :(得分:33)

  

是否存在这样的可能的整数值,前提条件是好的但条件不是吗?

是的,有许多输入值,可能导致后置条件失败。如果是这样的话。

int a = length*width;

溢出正int范围(std::numeric_limits<int>::max()),编译器实现会为此情况产生负值。

正如其他人在他们的回答中所指出的那样,length*width超出]0-std::numeric_limits<int>::max()[范围的情况实际上是未定义的行为,并且后置条件呈现的仅仅是无用的,因为可能需要预期任何值a

解决此问题的关键点在@Deduplicatoranswer中给出,前提条件需要改进。

作为Bjarne Stroustrup给出这个例子的理由的长矛:

我认为他想指出,这种未定义的行为可能会导致后置条件中出现意外的负值,并且会出现令人惊讶的结果,因为在前置条件下检查了一个天真的假设。

答案 1 :(得分:27)

不,在标准C ++定义的行为范围内,没有任何值会违反后置条件。但是,有些值仍然可以使函数表现不正确,即值太大而其产品不适合整数。尝试通过200&000;和15&#39;

由于大多数编译器实现C ++的方式,您可能会看到后置条件被违反,但您实际观察到的是由于整数溢出而导致的未定义行为。

答案 2 :(得分:12)

答案是他的前提条件检查不完整。即使它太严格了。
他未能检查产品是否可以代表而不是产生UB:

int area(int length, int width) {
    // calculate area of a rectangle
    assert(length >= 0 && width >= 0 && (!width
        || std::numeric_limits<int>::max() / width >= length));
    int a = length * width;
    assert(a >= 0); // Not strictly neccessary - the math is easy enough
    return a;
}

答案 3 :(得分:5)

我想到的是签名溢出。这是未定义的行为,但可能产生负值 试试std::numeric_limits<int>::max()2

答案 4 :(得分:4)

是的,如果假设您使用的是16位计算机,那么int = 2B最大值+32767,所以在下面

{
    length = 500, width = 100;
    if (length<=0 || width <=0) error("area() pre-condition");
    int a = length*width;   // a = 500 * 100 = 50000
    if (a<=0) error("area() post-condition");
    return a;
}

现在最终值为a = -17233,因为它会进入-ve值。 所以第二个条件变得虚假。

一切都取决于范围。

答案 5 :(得分:3)

当用于所有符合编译器的长度和宽度时,

INT_MAX将无法满足后置条件。

有人可能会说,由于标准保证INT_MAX&gt; = 32767,因此INT_MAX*INT_MAX将始终大于INT_MAX,因此无法在{{int中表示1}}被定义为能够保持最大值INT_MAX 这是一个很好的论据,实际上最常发生的事情是,大多数编译器都会出现溢出。

但要涵盖所有基础,我们需要注意C++ standard状态:

  

<强> 3.4.3
   1未定义的行为
  行为,使用不可移植或错误的程序结构或错误数据,本国际标准不对此作出任何要求

     

2注意可能的未定义行为包括完全忽略具有不可预测结果的情况,在翻译或程序执行期间以环境特征记录的方式执行行为   (有或没有发出诊断消息),终止翻译或执行(发布诊断消息)。

     

3示例未定义行为的示例是整数溢出的行为。

所以它比没有为该地区获得正确的价值更严重。当INT_MAX用于长度和宽度(或任何其他具有无法表示的结果的组合)时,无法保证编译的程序将执行的操作。任何事情都可能发生;从可能的溢出或崩溃到不太可能的那些,如磁盘格式。

答案 6 :(得分:3)

溢出值类型的位表示的值的乘法是未定义的,因为溢出的位数可能大于1.因此,您可能最终得到正或负符号位,并且丢失位的数量是可变的。 / p>

示例1:INT_MAX * 2:结果是正确的,但由于高位表示符号位,因此不会根据其类型进行更正。

示例2:INT_MAX * 4:溢出丢失1位,符号位不正确,如上例所示。

示例3:(INT_MAX + 1) * 2 = 0:由于所有设置位溢出但符号正确。

我正在使用8位二进制表示来使其更容易阅读,以说明为什么会发生这种情况。

0111 1111              // Max positive signed value
+1
1000 0000              // Sign bit set but binary value is correct
*2
0000 0000              // Upper bit is lost due to overflow

在这种情况下,软溢出,没有丢失信息,但表示不正确。并且在结果中不再出现该位的硬溢出。

溢出的不同之处在于如何检测溢出。通常硬件将检测到硬溢出,并且需要很少的工作来使软件处理。但是,软件溢出可能需要软件明确测试溢出条件,因为硬件通常无法识别整数数学运算中的符号位。

运行时库如何处理溢出取决于库。大多数人会忽略它,因为这样做会更快,而其他人可能会抛出错误。 未定义的行为并不意味着它可能会格式化您的磁盘。除了代码的逻辑指示之外,数学运算的结果不会以任何方式改变代码流。它可以忽略溢出或尝试以某种方式处理它。如果代码或硬件试图处理问题,标准没有规定采用什么方法。

基本上有三种可能发生的事情  1.溢出被忽略,返回值无效  2.运行时库忽略溢出但硬件抛出的错误也被忽略,导致运行代码出现硬故障。在这种情况下,操作系统完全可以确定接下来会发生什么。坚持不懈并破坏数据将导致糟糕的设计决策  3.溢出由运行时库处理,该库必须确定最佳的继续方式。通常,这意味着让代码有机会捕获错误并处理错误,或者通过尽可能优雅地关闭代码。

答案 7 :(得分:3)

从C ++ 11开始,你可以测试一个布尔值:

std::numeric_limits<int>::is_modulo

如果此值为true,则签名算术将以环绕方式运行,并且原始代码中没有未定义的行为。确实可以产生负值,因此原始代码中的测试是有意义的。

有关is_modulo see here

的进一步讨论

答案 8 :(得分:0)

基本上,乘法中的正值...导致正值但这些实际上可能不适合结果类型

您的前提条件未完成,您的后置条件也无效。您不仅可以获得负值,还可以获得仅小于输入值的正值,您只需将足够大的值作为输入,使得环绕超过零,即 long-wrap-around

您可以使用this

bool multiplication_is_safe(uint32_t a, uint32_t b) {
    size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
    return (a_bits+b_bits<=32);
}

防止溢出,但是你会想要对FALSE-Positives采用额外的检查。

或者,如果性能不是很大的问题,您可以使用MPZ库。如果性能是一个问题,并且您想为具有溢出标志的CPU编写程序集,那么您可以这样做。您的编译器也可以为您进行检查,例如在前置条件检查之后,G ++有fno-strict-overflow或者可能被转换为unsigned int

无论如何,大多数这些解决方案实际上并没有解决您的问题,结果将是 foo ,即您可能会获得比实际结果更小的区域。

因此,您唯一安全的选择是只允许安全乘法,如此处所示,这样做会错过一些东西,但不会那么多。