Tcl 提供了多种数学函数,例如
round()
、ceil()
和 floor()
:
https://tcl.tk/man/tcl/TclCmd/mathfunc.htm
我最近注意到关于
round()
行为的一个有趣的“异常”,我想知道是否有更深层次的原因:虽然 ceil()
和 floor()
返回浮点类型,round()
返回浮点类型任意精度整数。这可以产生令人惊讶的结果。例如:
% expr {floor(1000.5)/3}
333.3333333333333
% expr {ceil(1000.5)/3}
333.6666666666667
% expr {round(1000.5)/3}
333
非有限值参数的行为也因显而易见的原因而不同:
% expr {floor(+inf)}
Inf
% expr {ceil(+inf)}
Inf
% expr {round(+inf)}
integer value too large to represent
Tcl 与 C 语言有着密切的联系,它提供了
math.h
中定义的类似函数:
float round (float num);
float ceil (float num);
float floor (float num);
但是它们都返回浮点类型,包括
round()
。似乎大多数语言(例如Python)都选择了这条道路。 Tcl 中的 round()
不同有什么原因吗?
PS:Tcl版本是8.6.4
很抱歉发布我自己问题的答案。但由于我同时对这个主题进行了相当深入的研究,我想分享结果,尽管我还没有找到这个问题的明确答案,但我认为这是人们所能得到的:
round()
功能是在Tcl 7.0中添加的,可以追溯到1993年。它的实现在tclExpr.c
中出现为:
static int
ExprRoundFunc(clientData, interp, args, resultPtr)
ClientData clientData;
Tcl_Interp *interp;
Tcl_Value *args;
Tcl_Value *resultPtr;
{
resultPtr->type = TCL_INT;
if (args[0].type == TCL_INT) {
resultPtr->intValue = args[0].intValue;
} else {
if (args[0].doubleValue < 0) {
if (args[0].doubleValue <= (((double) (long) LONG_MIN) - 0.5)) {
tooLarge:
interp->result = "integer value too large to represent";
Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW",
interp->result, (char *) NULL);
return TCL_ERROR;
}
resultPtr->intValue = (args[0].doubleValue - 0.5);
} else {
if (args[0].doubleValue >= (((double) LONG_MAX + 0.5))) {
goto tooLarge;
}
resultPtr->intValue = (args[0].doubleValue + 0.5);
}
}
return TCL_OK;
}
此初始版本返回本机整数类型。后来,通过添加任意精度整数(TIP237),这被更改为任意精度整数:
https://tcl.tk/cgi-bin/tct/tip/237
更改日志没有给出选择返回整数类型的原因。它简单地写着:
167. 4/3/93 Changes to expressions:
...
- Expressions now support transcendental and other functions, e.g. sin,
acos, hypot, ceil, and round. Can add new math functions with
Tcl_CreateMathFunc().
源代码也不包含描述原因的注释。就提交信息而言,这是该语言的发明者 John Ousterhout 本人的决定。
有趣的是,大多数数学函数(包括
ceil()
和 floor()
)只是转发到相应的 C 库实现,而 round()
则没有:
#ifndef TCL_NO_MATH
#include <math.h>
#endif
//...
static BuiltinFunc funcTable[] = {
#ifndef TCL_NO_MATH
{"ceil", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) ceil},
{"floor", 1, {TCL_DOUBLE}, ExprUnaryFunc, (ClientData) floor},
// ...
#endif
{"int", 1, {TCL_EITHER}, ExprIntFunc, 0},
{"round", 1, {TCL_EITHER}, ExprRoundFunc, 0},
// ...
};
有一个宏
TCL_NO_MATH
可以排除 C 库数学函数。然而,这样做并没有禁用浮点支持。为了能够将浮点类型转换为整数,即使排除 C 库数学函数,也需要 round()
的离散实现。
答案的推测部分是这样的:
思考的重点可能是进行转换,而不是模仿 C 原型。 一方面,它可能看起来更方便
set y [expr {round($x)}]
而不是
set y [expr {int(round($x))}]
获取整数值。另一方面,
int()
和round()
在源代码中是邻居。即使使用 TCL_NO_MATH
,它们也可能被实现为“将浮点转换为整数类型的两种不同方法”。