数乘以常数得到负数

Posted

技术标签:

【中文标题】数乘以常数得到负数【英文标题】:Number multiplied by constant results in a negative number 【发布时间】:2021-02-17 15:26:49 【问题描述】:

我正在尝试计算一年前相应一周的日期。它现在似乎可以工作了,但有一件事让我很困扰,它最初导致了几个错误,我通过尝试各种方法修复了这些错误。

问题是当我不使用to_number&weekOfYearNow - &weekOfYearThen 并使用此结果计算lastYearFixed 时,结果将是-311 而不是7。为什么会这样?那两个变量(weekOfYearNow & weekOfYearThen)不是已经是数字了吗?

define today = to_date('30/12/20', 'dd/mm/yy');
define lastYear = add_months(&today, -12);

select &today as "Today" from sys.dual;
select &lastYear as "LastYear" from sys.dual;

define weekOfYearNow = to_number(to_char(&today, 'ww'));
define weekOfYearThen = to_number(to_char(&lastYear, 'ww'));
define weekOfYearOffset = to_number(&weekOfYearNow - &weekOfYearThen); -- <-- must use to_number - but why?

define lastYearFixed = &lastYear + (&weekOfYearOffset * 7); -- <-- this equals -311 if I don't call to_number above

define monday = add_months(sysdate - 7 - to_char(sysdate, 'd') + 2, -12);
define sunday = add_months(sysdate - 7 + to_char(sysdate, 'd'), -12);

select &weekOfYearNow as "WeekOfYearNow" from sys.dual;
select &weekOfYearThen as "WeekOfYearThen" from sys.dual;
select &weekOfYearOffset as "WeekOfYearOffset" from sys.dual;
select &lastYearFixed as "LastYearFixed" from sys.dual;

【问题讨论】:

【参考方案1】:

你不需要(也不应该使用)to_number();但您确实需要将第一个表达式括在括号中,可以是:

define weekOfYearOffset = (&weekOfYearNow - &weekOfYearThen);
define lastYearFixed = &lastYear + (&weekOfYearOffset * 7);

define weekOfYearOffset = &weekOfYearNow - &weekOfYearThen;
define lastYearFixed = &lastYear + ((&weekOfYearOffset) * 7);

如果没有其中任何一个,当替换扩展时,您将有效地得到:

define lastYearFixed = &lastYear + (&weekOfYearNow - &weekOfYearThen * 7);

(实际上所有这些术语都被扩展以显示嵌套的to_date()to_number() 等调用,但为简洁起见,这给出了这个想法;关键是它们还不是数字,并且没有转换或计算完成了。你可以set verify on查看完整的荣耀/恐怖。)

operator precedence 具有比(二进制)- 更高的优先级 *,这意味着它相当于:

define lastYearFixed = &lastYear + (&weekOfYearNow - (&weekOfYearThen * 7));

所以你加了weekOfYearNow并减去了7次weekOfYearThen;而不是增加它们之间的 7 倍。

数字:

select 53 - 52 * 7 as a,
       53 - (52 * 7) as b,
       (53 - 52) * 7 as c
from dual;

    A     B     C
----- ----- -----
 -311  -311     7

当您使用to_number() 时,您将覆盖默认优先级,但会不必要地隐式转换为字符串并显式转换回数字。只需括号就足以覆盖默认行为。

【讨论】:

我永远不会想到这些表达式并没有立即解决,而是作为一个整体被替换了。这完全有道理!同时也很可怕。 是的,我想这是与 Gary 的 PL/SQL 版本的主要区别——它们在分配时被评估,但这不是替换变量的工作方式。 (当然,您仍然需要小心precedence!)如果您在会话中使用set verify on,那么您可以在替换前后看到旧/新查询。它们实际上比我展示的要扩展,但您仍然可以看到问题。【参考方案2】:

我想说我打赌这是 SQL/Plus 的事情(Alex 证明了这一点,感谢 Alex),因为当我将它转换为 PL/SQL 匿名块并在 Toad 中运行它时,它按预期工作。我不打算发布这个,因为它不是一个真正的答案,但是比较语言并注意可能会咬你的问题很有趣,所以我会为了学习而留下它。

SET serveroutput ON

DECLARE
  today DATE;
  lastyear DATE;
  weekOfYearNow NUMBER;
  weekOfYearThen NUMBER;
  weekOfYearOffset NUMBER;
  lastYearFixed DATE;
  monday DATE;
  sunday DATE;
BEGIN
 today := TO_DATE('30/12/20', 'dd/mm/yy');
 lastYear := ADD_MONTHS(today, -12);

 DBMS_OUTPUT.PUT_LINE('Today: ' || today);
 DBMS_OUTPUT.PUT_LINE('LastYear: ' || lastYear);

 weekOfYearNow    := TO_NUMBER(TO_CHAR(today, 'ww'));
 weekOfYearThen   := TO_NUMBER(TO_CHAR(lastYear, 'ww'));
 weekOfYearOffset := weekOfYearNow - weekOfYearThen; -- <-- must use to_number - but why?

 lastYearFixed := lastYear + (weekOfYearOffset * 7); -- <-- this equals -311 if I don't call to_number above

 monday := ADD_MONTHS(SYSDATE - 7 - TO_CHAR(SYSDATE, 'd') + 2, -12);
 sunday := ADD_MONTHS(SYSDATE - 7 + TO_CHAR(SYSDATE, 'd'), -12);

 DBMS_OUTPUT.PUT_LINE('WeekOfYearNow: ' || WeekOfYearNow);
 DBMS_OUTPUT.PUT_LINE('WeekOfYearThen: ' || WeekOfYearThen);
 DBMS_OUTPUT.PUT_LINE('WeekOfYearOffset: ' || WeekOfYearOffset);
 DBMS_OUTPUT.PUT_LINE('LastYearFixed: ' || LastYearFixed);

END;

Today: 30-DEC-20
LastYear: 30-DEC-19
WeekOfYearNow: 53
WeekOfYearThen: 52
WeekOfYearOffset: 1
LastYearFixed: 06-JAN-20

【讨论】:

以上是关于数乘以常数得到负数的主要内容,如果未能解决你的问题,请参考以下文章

理解矩阵乘法

(剑指offer)丑数 JavaScript解法

什么是常数项,它能是负数吗

矩阵的乘法运算

python复数

结对作业