我在尝试为太阳系中的其他天体编程可扩展计时器时发现了这条艰难的道路。问题是,每个日历系统都充斥着如此多的规则和异常,以至于日历生成器基本上变成了另一种编程语言。我很清楚Zawinski定律,但我想避免创建另一个Emacs。
常见的计时障碍包括不一致的跳跃间隔、不断变化的时区以及从一种计时系统转换到另一个计时系统。除了这些算法问题,还有设备的局限性,比如振荡器的精度或内存容量。在现代,这些通常被忽视,尤其是自从Y2K漏洞出现以来,内存分配并不是一个严重的问题。
Y2K、Y2038和其他Y2xx错误并不是真正的“错误”,而是保留内存空间的简单溢出。你看,Unix和类Unix计算机系统通过在单个整数变量time_t中增加秒来测量时间。自然,这种计时被命名为Unix时间,其0等于1970年1月1日午夜。
Unix时间的不同实现,对time_t使用不同的数据类型。当数据类型达到其上限时,它将“翻转”,要么是相反的(负)值,要么是0。当前,Linux内核的主要分支,使用带符号的64位整数。此解决方案的展期点为292277026596年。这大约是2920亿,2.77亿,2.4万年后的未来。
但后来呢?
数字溢出,日期将跳回到宇宙大爆炸前2780亿年**?需要说明的是,这需要解决。幸运的是,我们有33个太阳生命周期来解决这个问题,但即使在今天我们也可以提出一些解决方案。显而易见的解决方案是使用动态类型语言。
#!/usr/bin/perl
use strict;
use warnings;
my $time_f = 9223372036854775809; # out of long int range
my $year_s = 31536000; # seconds in year
while (1) {
my $year = int($time_f / $year_s + 1970);
print "unix time: $time_f \tyear: $year\n";
$time_f++;
#sleep(1);
}
问题解决了。括号的忠实粉丝可以使用Lisp。现在,变量将无限增加,唯一的限制是物理内存。time_f(f代表固定值)可以由每1GB内存略多于10亿个数字组成。然而,Linus-Torvalds宁愿使用Debian,也不愿使用C以外的任何程序,所以如果我们想将其放入内核,我们需要获得更多的静态。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmp.h> // GNU Multiple Precision Arithmetic Library
int main() {
mpz_t time_f, year_s, year;
mpz_init(time_f);
mpz_init(year_s);
mpz_init(year);
mpz_set_str(time_f, "9223372036854775809", 10); // out of long int range
mpz_set_str(year_s, "31536000", 10); // seconds in year
while (1) {
mpz_tdiv_q(year, time_f, year_s);
gmp_printf("unix time: %Zd", time_f);
gmp_printf("\t\tyear: %Zd\n", year);
// Increment unix time by 1
mpz_add_ui(time_f, time_f, 1);
}
return 0;
}
在C中,我们可以使用GNU多精度算术库,它将允许我们为变量动态分配内存。不幸的是,gmp.h与内核空间不兼容,所以我们需要从头开始设计这个异端。
对于C中的动态内存分配,我们可以使用数组。然后使用字符串,我们可以读取long-long-int以外的数字,就像使用gmp.h一样。创建一个除法函数将秒转换为合理的时间单位(如年)也很好。
让结构BigInt使用数字数组表示任意精度的大整数。
typedef struct {
int *digits; // Pointer to an array of digits
int size; // Number of digits
} BigInt;
Then we need to initialize the BigInt from the string input.
BigInt initBigInt(const char *str) {
int len = strlen(str); // Get the length of the string
BigInt num; // Declare a BigInt variable
num.size = len; // Set the size of BigInt to the length of the string
num.digits = (int *)malloc(len * sizeof(int)); // Alloc memory for the digits
for (int i = 0; i < len; i++) {
num.digits[i] = str[len - 1 - i] - '0'; // Convert digits to int and store them in reverse
}
return num; // Return the initialized BigInt
}
And of course, we need to free the memory like all good mannered people.
void freeBigInt(BigInt *num) {
free(num->digits);
num->digits = NULL;
num->size = 0;
}
When we have our structures fully defined, we will use them to feed variables with values.
int main() {
BigInt time_f = loadCurrentUnixTime();
BigInt year_s = initBigInt("31536000"); // Seconds in a year
现在我们可以专注于长期划分。这部分对我来说有点问题,原因有两个。C不是我喜欢的语言***,正如我所发现的,在这个星球上,长期的分裂是不同的。我似乎已经学会了日耳曼-欧亚方法,这与英语国家的方法有点不同。(事实证明,数学毕竟不是一门通用的语言……)无论如何,在我的小学笔记和当地图书馆的一本C书的帮助下,我终于吐出了下一个除法函数。
void divideBigInt(BigInt *dividend, BigInt *divisor, BigInt *result) {
// Initialize result size and allocate memory for its digits
result->size = dividend->size;
result->digits = (int *)calloc(result->size, sizeof(int));
// Initialize help BigInt named current
BigInt current;
current.size = 0;
current.digits = (int *)calloc(dividend->size, sizeof(int));
// Fill the "current" helper var
for (int i = dividend->size - 1; i >= 0; i--) {
// Shift digits in the "current" to the left
for (int j = current.size; j > 0; j--) {
current.digits[j] = current.digits[j - 1];
}
// Add the next digit from dividend to the "current"
current.digits[0] = dividend->digits[i];
current.size++;
// Remove leading zeros in the "current"
while (current.size > 1 && current.digits[current.size - 1] == 0) {
current.size--;
}
int count = 0;
// Do division until the "current" is less than the divisor
while (isGreaterOrEqual(¤t, divisor)) {
BigInt tempResult;
// Subtract divisor from the "current"
subtractBigInt(¤t, divisor, &tempResult);
free(current.digits);
current = tempResult;
count++;
}
// Store the quotient in the result
result->digits[i] = count;
}
// Remove leading zeros in the result
while (result->size > 1 && result->digits[result->size - 1] == 0) {
result->size--;
}
free(current.digits);
}
通过添加更多的功能来监控进度,我可以获得当前Unix时间的输出,也可以获得超过400位数字的虚构输出。
下一个合乎逻辑的步骤是修改它以适应Linux内核中的time.c,然而,我对内核空间编程的了解正在收敛到零。此外,我不确定除法函数将如何处理大于long-long-int的素数。
无论如何,fixed.c是在GPLv3下发布的,任何想在内核级别为子孙后代修复Y292b问题的人都可以在这里使用。祝你好运,记住,时间在流逝。
*Zawinski定律是计算机科学中的一个虚构定律,它嘲弄了不可避免的功能蠕变,指出每个程序最终都会尝试读取电子邮件。请注意,该法律是在90年代制定的,因此有了电子邮件功能。我还发现了一个有更多计算机科学法律的好网站。
**在这段视频日志中,泰森先生提到,在宇宙大爆炸之前,时间并不重要。他提出,在这之前,现在所知的时间概念可能根本不存在。对我来说,以这种方式思考时间是非常有趣的。
***尽管我很欣赏代码的速度和直接控制,但我仍然想写更直观的代码。但既然我指的是perl,我想这一切都归结为个人偏好。