在不允许下划线使用整数常量的语言中,创建10亿个常量的好习惯吗?


39

在不允许在整数文字中使用下划线的语言中,创建一个十亿常量是个好主意吗?例如在C ++中:

size_t ONE_BILLION = 1000000000;

当然,我们不应该为小数(例如100)创建常量。但是对于9个零,可以很容易地忽略零或在代码中添加一个额外的数字,如下所示:

tv_sec = timeInNanosec / 1000000000;
tv_nsec = timeInNanosec % 1000000000;

24
我希望这里的每个人都投票反对。这样,也许某天我的银行将10亿美元转入我的帐户,因为程序员没有使用常数而把零放错了!:)
Reactgular

43
为什么不为小数创建常量?100是什么意思?除非有上下文,否则这是一个神奇的数字。
艾伦

4
@MathewFoscarini通常,错误可能会发生。但是当涉及到您的银行时,错误总是会不利于您。
埃默里

23
考虑编写1e910^9或者1_000_000_000您使用的语言是否支持。
hammar

5
亿还是短亿?
rvalue

Answers:


33

大多数语言都具有某种指数符号。一百万是1e6,(表示10乘以6的幂)。这基本上比这里的大多数命题更好地解决了这个问题。

但是,在许多类似C的语言中,科学计数法定义了浮点类型,如果您确实需要int,则很不幸。但是,您可以轻松地对该常量进行类型转换,以避免在公式器中进行隐式转换。

n / int(1e9) 将除以十亿。

在您的示例中,处理物理量(以纳秒为单位的时间)时,我通常会问自己整数是否是正确的类型。实际上,double在处理可测量的数量时,浮点数可能更适合(尽管在某些情况下,您更希望使用long long)。


6
我认为NANOSECONDS_IN_ONE_SECOND解决方案更清晰,整洁
托马斯博尼尼

1
问题是关于整数自由主义者,我建议使用科学计数法。是就地执行此操作还是通过定义常量来执行,是问题中未要求的结构化代码的问题。定义常量会增加有限的抽象度,我将编写一个转换函数/宏以实现更好的抽象度
wirrbel

1
将很大的double转换为int不会冒浮点数典型的舍入差问题的风险吗?
菲利普

对于普通精度的整数类型,只要您使用双精度浮点数进行转换就不会有问题。使用long long范围值时,您是正确的。
维尔贝尔

145

而是创建一个名为NANOSECONDS_IN_ONE_SECOND的名称。

或者,如果可以想到一个简短的更好的名字。


58
我会说,Nanoseconds_Per_Second但这是正确的答案。
KChaloux

8
@马修我不明白你的意思。每米毫米是没有错的。您可能暗示着它是多余的,在那一纳秒中意味着十亿分之一秒,但是再次说明它没有错。这就像说1 +1 =2。当x和y不相交时,“ x / y”继续变得更有意义,例如“每六打单位”或“纳秒/毫秒”
Mark Canlas

7
@MathewFoscarini实际上,不,不是。如果是这样的话,一个常量名NANOSECONDS是没有意义的,因为您无法确定它应该应用于什么。同样,NANOSECONDS_PER_MICROSECOND也是一个有意义的类似有效常数。
2013年

5
@MathewFoscarini,“毫米/米”是一种删除转换中的单位以获得原始值的方法。1mm/1m = 1000,这正是此处要做的事情。
zzzzBov

11
为什么要打字呢?NS_PER_SEC对于应该处理纳秒级的人来说应该是显而易见的。
Rex Kerr

67

常数用于赋予数字含义。没有任何附加意义ONE_BILLION1000000000。实际上,这使它更加混乱,因为在不同的自然语言中,十亿意味着不同的东西(十亿或一百万)!如果您想写得短一些,那么您的编程语言很有可能允许使用科学记号,即1e9。否则,我同意@JohnB的观点,该数字实际上表示一秒的纳秒数,因此命名为。


9
指出不同语言中的十亿代表不同的零值是很好的提示。
Frozenkoi

3
建议将常规语言更改为自然语言。常规意味着别的东西
jk。

用语言对“十亿”的不同解释是如此的好!我为什么不能两次对您的答案进行投票!
DSF

3
您不需要其他语言。您甚至不需要其他国家。在英式英语中,“十亿”是指1974年之前和之后的官方通讯(大众媒体和政府)不同,并且两种用法仍然存在。
约尔格W¯¯米塔格

1
ONE_BILLION到10000000000之间没有任何其他含义。我不同意。(提示:我故意给您加错了报价,并添加了另一个零;如果我没有提到它,我会注意到吗?)
Keith Thompson

27

对于一种或两种用法,我将使用以下约定:

tv_sec = timeInNanosec / (1000 * 1000 * 1000);
tv_nsec = timeInNanosec % (1000 * 1000 * 1000);

它是完全自我解释的,被编译成一个常量,很难搞砸。

另外,在诸如以下情况下非常有用:

var Time = 24 * 60 * 60;

很容易看到我们正在谈论几秒钟的一天。


这就是我通常要做的。它还具有的优点是,我不会忘记我昨天定义了NANOSECONDS_IN_ONE_SECOND,而今天定义了NANOSECONDS_PER_SECOND。也许明天是ONE_AMERICAN_BILLION。
Thomas Padron-McCarthy

当然,“ SecondsInOneDay = 24 * 60 * 60”更容易吗?
JBR威尔金森

@JBRWilkinson可以肯定的是,我最初的代码段使用的是课程instance.Time = ...,但是后来我把它
愚蠢

3
在C或C ++中,(1000 * 1000 * 1000)其类型为int,仅需要为16位,因此它可能会溢出。您可以写(1000L * 1000L * 1000L)来避免这种情况。
基思·汤普森

我经常这样做。效果很好。
vy32 2013年

10

值的长度不是定义是否需要常数的因素。

您可以使用常量来避免幻数,而不是避免键入。

例如,这些是完全有效的常数:

public static final int CLOSE_CURSORS_AT_COMMIT = 1;
public static final int CONCUR_READ_ONLY = 2;
public static final int CONCUR_UPDATABLE = 3;
public static final int FETCH_FORWARD = 4;
public static final int FETCH_REVERSE = 5; 
public static final int FETCH_UNKNOWN = 6;
public static final int HOLD_CURSORS_OVER_COMMIT = 7;
public static final int TYPE_FORWARD_ONLY = 8;
public static final int TYPE_SCROLL_INSENSITIVE = 9;
public static final int TYPE_SCROLL_SENSITIVE = 10;

采用:

public static final int NANOSECS_PER_SECOND = 1000000000;

(代码示例使用Java,可翻译成您喜欢的语言)


3
+1命名号码几乎没有用。常数的目的在于赋予这些数字以含义。它们代表什么?他们在计算或限制或正式命名系数是什么?不算什么值。
JustinC

5
看到我们在谈论Java时,我会观察到,从Java 7开始,我们可以在数字上加下划线以提高可读性!
尼克,

2
这些是有效常数的可怕例子。它们应该是枚举,除非它们是在枚举之前创建的。
ChristofferHammarström'13年

@ChristofferHammarström它们确实是在枚举之前创建的,它们是ResultSet类的一部分,位于Java SDK的SQL包中。
TulainsCórdova'13

2
@ChristofferHammarström它们很糟糕,因为现在我们有了枚举,但并不是无意义。创建这些类时,Enum不存在,并且为了区分相互排斥的选项(例如FETCH_FORWARD和FETCH_REVERSE),给它们提供了一个不同的值。价值并不重要,只是它们不同。
图兰斯·科尔多瓦

8

美国还是欧洲的十亿?

(或从技术上讲,短期或长期都为10亿-一个是10亿,另一个是100亿)。

考虑到这种混淆,我想说是-一次定义并保持不变是有意义的,同样适用于您需要同意定义的任何常量-一次定义。


17
“是美国还是欧洲的十亿?” -“什么?我不知道!啊!!!!”
Tesserex

至少在英国,我们早就采用了90亿欧元。
杰克·艾德利

1
@Tesserex-好吧,知道,当你成为国王时,你必须了解这些事情。
gbjbaanb

5

不这样做的原因

首先,这是不写任何下划线或使用任何技巧对其进行模拟的原因:这使常量在代码中更难找到。假设某个程序在其操作的某个位置显示某个参数的硬编码值1500000。我想知道这在程序源代码中实际发生的位置,因此我对进行了grep编码1500000,却一无所获。为什么?可能采用十六进制格式(但是为什么要使用舍入十进制数)。我不知道,常数实际上写为1_500_000。我需要正则表达式1_?500_?000

注释中的引导字符

仅仅因为一种视觉辅助工具不可用,或者我们不希望由于上述原因而使用它,并不意味着我们无法利用文本文件的两个维度来创建替代的视觉辅助工具:

foo = bar / 1000000000;
//           --^--^--^  

有了这个,我们可以轻松地说服自己,三个零的三组。但是,我们仍然可以grep的源代码1000000000并找到它。

语法着色

可以使用带有可编程语法着色的文本编辑器来对数字常量中的数字组颜色进行交替显示,以提高可读性。我们无需在代码中做任何事情。

预处理:C,C ++,目标C

现在,如果我们真的想要数字之间的逗号,在C和C ++中,我们可以使用一些预处理:

/* Four digit base TH-ousand constant macro */
/* Condensed using Horner's rule */
#define TH(A,B,C,D) ((((((A) * 1000) + (B)) * 1000) + (C)) * 1000 + D)

tv_sec = nanoseconds / TH(1,000,000,000)

适用于数字TH(1,234,567,890)

类似于TH的宏也可以使用令牌粘贴而不是算术。在C预处理器中,##可以在宏主体中使用二进制运算符(“令牌粘贴”),以便将两个操作数粘贴到单个令牌中。一个或两个操作数可以是宏参数。不利的一面(给我们带来风险)是,如果最终的分类不是有效的令牌,则行为是不确定的。

#define TOK4(A, B, C, D) A ## B ## C ## D

现在

TOK4(1,000,000,000)       /* produces the single token 1000000000 */
TOK4(1,123,000,000.0E+2)  /* produces the single token 1123000000.0E+2 */
TOK4(pr,in,t,f)           /* produces the token printf */
TOK4(#,*,a,b)             /* undefined behavior, #*ab is not valid token syntax */

粘贴标识符并使​​用结果命名全局变量和函数的C程序已经存在并且无法使用,因为它们无法使用GNU id-utils和ctags之类的工具。


2
+1是我所见过的预处理程序的最佳滥用之一。不过,我还是会选择NSEC_PER_SEC或生产中的东西。
维克多

滥用预处理器非常接近-1 :)
CVn

3

是的,这听起来很合理。一对一的DIGIT错误甚至比臭名昭著的一对一错误更糟。虽然,这可能会使其他人(包括您将来的自己)阅读代码时感到困惑。

像NANOSEC_PER_SEC这样的更具解释性的名称似乎不错,因为这样可以增加时间的清晰性。但是,在时间以外的环境中使用没有任何意义,并且为每种情况单独创建1,000,000,000都是不切实际的。

乍一看来,您真正想做的就是“分秒必争”。这留下了NANO_PER,它不仅与语言无关(在美国和欧洲为10 ^ 9),而且与情况无关(对单位没有限制),并且易于键入和阅读。


这篇文章很难阅读(文字墙)。您介意将其编辑为更好的形状吗?
gna

3

通常,将标量常量用于单位转换是个坏主意,如果发现自己为此类事情制作了常量,那么您正在太多地方进行转换。

当您拥有一个单位的数量(例如10秒),并且想要转换为另一单位(即纳秒)时;现在正是时候使用语言的类型系统来确保单位实际按您的期望进行缩放。

让你的函数取一个Nanoseconds参数,并提供转换运营商和/或构造在类SecondsMinutes或者什么具备的,你。这是你的const int或者#define1e9在其他的答案看到所属。

这样可以避免在代码中浮动模糊单元的变量;并防止整个错误区域,这些错误区域是从错误的乘/除方法应用到的,或者已经被应用的,或者数量实际上是距离而不是时间,或者...

同样,在此类中,最好是将纯标量构造为私有,并使用静态“ MakeSeconds(int)”或类似的方法,以防止不透明数字的草率使用。

更具体地说,在您的示例中,在C ++中查看Boost.Chrono


1
+至少,通常使用带有缩放或偏移因数的通用类型,这与经常发生混乱的时区非常相似。
JustinC

1

除非需要一个常量,否则我个人认为创建一个常量不是一种好习惯。如果将它放在多个位置并在文件顶部定义以进行修改/或测试将很有用,那么绝对可以。

如果只是因为其输入尴尬?那就不要。

就个人而言,如果我得到别人的代码并定义了常量,那么我通常会认为这是代码的重要方面。例如tcp keep alive计时器,允许的最大连接数。如果我必须调试它,我可能会花很多不必要的精力来试图弄清楚为什么/在哪里使用它。


我开了个玩笑,但是如果银行程序员必须为每个数字都设置一个常数,那么您可以转让的软件将是巨大,难以管理且速度缓慢。我只能想象那会是什么样子,想象被告知要花3个工作日才能汇款到....我的天哪!
Simon McLoughlin

我的银行确实需要3天的时间才能汇款:(
Reactgular

1
@MathewFoscarini银行家使用Excel,他们不需要程序员;)
Mateusz

@Simon取决于语言和编译器,应在代码中优化常量,而不会产生太多开销。我理解您的意思,但是可以在任何使用名称而不是魔术数字的地方使用常量,这有助于提高代码的可读性。
史蒂文

笨拙的阅读比笨拙的打字更多的是一个问题。
Alb

0

当您思考为什么在问题标题中使用“ 10亿”而不是“ 1000000000”时,您会明白为什么答案是肯定的。


0

不要为大字面量创建常量。您需要为每个这样的文字提供一个常量,(在我看来)是一个完整的笑话。如果您迫切需要在不借助语法突出显示等内容的情况下使文字更加清晰,则可以(尽管我不会)创建函数或宏来使您的生活“更轻松”:

#define SPLIT3(x, y, z) x##y##z

int largeNumber1 = SPLIT3(123,456,789);
int largeNumber2 = 123456789;

0

我会这样做:

const int Million = 1000 * 1000;
const int Billion = 1000 * Million;

要么

const int SciMega = 1000 * 1000; const int SciGiga = 1000 * SciMega;

关于每秒的纳秒数:纳秒是千兆字节的“倒数”。

Kilo  Mega  Giga   etc.
10^3  10^6  10^9
Milli Micro Nano   etc.
10^-3 10^-6 10^-9

请注意“科学”-科学中的含义,例如在计算机中,千,兆,千兆等的含义是不同的:1024(2 ^ 10),1024 * 1024(2 ^ 20)等。2兆字节不是2,000,000字节。

UPDATE Commenter指出2的数字指数存在特殊术语:http : //en.wikipedia.org/wiki/Mebibyte


“ 2兆字节不是2,000,000字节。” 询问您想要的任何旋转盘式硬盘制造商。(顺便说一句,不是下降投票者)
CVn

@michaelkjorling这是一个编程问题,而不是商业道德或市场营销问题。我确实同意硬盘驱动器,但这是一个不同的话题。以及关于投票否决权!
TA先生

1
实际上,2 MB是2,000,000字节。2兆字节为2,097,152字节。参见en.wikipedia.org/wiki/Mebibyte
vy32 2013年

@ vy32谢谢,以前从未听说过。将更新我的答案以反映这一点。
先生2013年

@TA先生,没问题!我们正在努力使计算机科学与SI部门保持一致!加入俱乐部。
vy32 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.