返回列表 发帖
回复 44# terse




    100-2-28 的下一天是 100-2-29 还是 100-3-1

如果是前者,那就不是现在的历法,背后有什么故事么。。。?

TOP

本帖最后由 plp626 于 2012-4-11 20:59 编辑

回复 40# neorobin
@echo off
:date2i <year> <month> <day> // 显示year-month-day 所对应的索引值;
setlcoal&set/a y=%1,m=%2,d=%3
set/a m+=9
set/a m%%=12
set/a y-=m/10
set/a i=365*y+y/4-y/100+y/400+(m*153+2)/5+d-1
endlocal&echo %i%COPY
@echo off
:i2date <index> // 显示 %1 所对应的日期 %1 取值范围0~2千万
setlocal&set/a i=%1
set/a y=(i*99+145)/36159
set/a t=i-y*365-y/4+y/100-y/400
set/a "y+=t>>9"
set/a t=i-y*365-y/4+y/100-y/400  
set/a m=(t*5+2)/153
set/a d=t-(m*153+2)/5
set/a d=d+1
set/a y+=(m+2)/12
set/a m=(m+2)%%12+1
endlocal&echo %y%-%m%-%d%COPY
在索引号转日期的函数i2date中,(i*99+145)/36159
这个组合保证 [0~21691753)以内的索引;即[0-3-1 ~ 59208-9-2);之间的正确性;

(i*4+999)/1461 这个组合可以保证 [0~12200561 )以内的索引; 即[0-3-1 ~ 33404-3-1)之间的所有日期;

进一步发现
i*33/12053
这个组合可以保证[0~65075232) 以内;即[0-3-1 ~ 177625-3-7) 之间的所有日期;
error: /65075263:177625/ 9/ 9

以上结论只代表在假定函数date2index正确的情况下,没有报错的测试结果;

如果正确,date2index和 index2date两个函数轻松搞定了有关时间日期计算(0-3-1后)的所有问题;

TOP

本帖最后由 plp626 于 2012-4-11 21:02 编辑

C测试,40万年以内测试用时秒杀
//&cls&@type %~fs0|tcc -run -
// 把tcc.exe 和 lib\msvcrt.def 放在当前目录下;双击本脚本解释执行下面C代码
int i2date(int i, int *, int *, int *);
int date2i(int, int, int);
/*
* 把0-3-1作为参考日历{0-0-0}的0号索引,后面依次类推;
* 参考日历月份数为 0,1,2,...,11;
* 11月分闰月和平月;当所在年份+1为闰年时为闰月;
* 参考日历日期数为 0,1,...,30;
* 大月最大偏移30,小月最大偏移29;
* 11月中,闰月最大偏移28;平月最大偏移27;
*/
int main(){
int i,j;
int y,t,m,d;
for (i=0; i<1461*100000; i++){ // 0 ~ 40万年索引号
i2date(i,&y,&m,&d);
j=date2i(y,m,d);
if (i!=j) { // 测试,寻找不对称转换的具体值。。
printf("/%5d:%4d/%2d/%2d\n",i,y,m,d);
getchar();
}
}
return 0;
}
int i2date(int i, int *year, int *month, int *day){ // 索引转日期
int t,y,m,d;
y=(i*4+999)/1461;          // 1/365.2425的前6位小数最佳有理逼近
// y=(i*99+145)/36159;
// y=i*33/12053;            //1/365.2425的前9位小数最佳有理逼近
t=i-y*365-y/4+y/100-y/400;
y+=t>>9; // 获得参考年份;若t为负数,将参考年减1
t=i-y*365-y/4+y/100-y/400;  // 获得参考年00日的偏移数
m=(t*5+2)/153; // 获得参考月份
d=t-(m*153+2)/5; // 获得参考日期
d=d+1; // 获得实际日期
y+=(m+2)/12; // 获得实际年份
m=(m+2)%12+1; // 获得实际月份
*year=y;
*month=m;
*day=d;
return 0;
}
int date2i(int y, int m, int d){// 日期转索引
int i;
m+=9;
m%=12;
y-=m/10;
//上面三句做平移取模,3月作为当年的0月;2月作为上1年的11月;
i=365*y+y/4-y/100+y/400+(m*153+2)/5+d-1;
// 11月初的偏移为337337/11153/5;
// (m*153+2)/5 将 {0,1,2,...,11}映射为{0,31,61,...,337}
return i;
}COPY
可是,这样的对称测试 有意义吗? 即使在date2index正确的条件下,也存在index2date出错;

即使date2index正确,date2index它也只是保证正确的日期生成正确的索引;
错误的日期也有可能生成正确的索引;2002-2-29号是错的,但它在意义上式2002-3-1;生成的索引也是2002-3-1日的索引;
如此以来,还需验证index2date生成正确的日期。。。

TOP

进一步测试发现,i2date函数很是问题;

bug丛生;

问题出在 i2date函数的第一句; 不是随便可以精简的。。。

TOP

回复 48# plp626

这里不考虑 terse 提出的历法变化问题,

42 楼给出的测试方式是 假定一个 简单易懂易实现的 日期生成 算法 是正确的, 用它从 某个日期 开始依次生成某范围内所有后续的日期.
这样生成的一个日期序列的序号对应着整数序列的一部分,

index = date2index(测试起始日期)
loop (简单算法生成 y.m.d,  直到测试终止日期, (y.m.d)++) {
  y.m.d   ->  i = date2index(y.m.d)   ->   yy.mm.dd = index2date(i)
  上过程中: (i ≠ index)  或者 (yy.mm.dd ≠ y.m.d) 即发现错误
  index++
}

bat 测试确实很慢, 还好没事开着不管它, 写成高级语言的也容易, 下面是已经完成测试过的 bat:
@echo off & setlocal enabledelayedexpansion
REM Index := date2index(1.1.1);
set /a "y=1,m=1,d=1"
set /a "m+=9, m%%=12,y-=m/10, i=365*y + y/4 - y/100 + y/400 + (m*306 + 5)/10 + d - 1"
set /a "Index=i"
for /l %%y in (1 1 20000) do (
  set "y=%%y"
  set /a "leap = (^!(%%y %% 4) & ^!^!(%%y %% 100)) | ^!(%%y %% 400)"
  if "!leap!"=="1" (set "eFeb=29") else set "eFeb=28"
  for %%t in ("1 31" "2 !eFeb!" "3 31" "4 30" "5 31" "6 30" "7 31" "8 31" "9 30" "10 31" "11 30" "12 31") do (
    for /f "tokens=1,2" %%m in ("%%~t") do (
      set "m=%%m"
      for /l %%d in (1 1 %%n) do (
        set "d=%%d"
        REM testing...
        REM if date2index(y.m.d) <> Index (
          REM error
        REM )
        REM if index2date(date2index(y.m.d)) <> y.m.d (
          REM error
        REM )
        REM Index += 1;
        set /a "m+=9, m%%=12,y-=m/10, i=365*y + y/4 - y/100 + y/400 + (m*306 + 5)/10 + d - 1"
        if "!i!" neq "!Index!" ((echo error at %%y.%%m.%%d date2index(y.m.d^)=!i!^<^> !Index!) & pause)
        set /a "y=(i*99+145)/36159,y+=i-365*y-y/4+y/100-y/400>>31,d=i-365*y-y/4+y/100-y/400,m=(5*d+2)/153,d+=1-(153*m+2)/5,y+=m/10,m=(m+2)%%12+1"
        set /a "error=(y-%%y)|(m-%%m)|(d-%%d)"
        if "!error!" neq "0" ((echo error at %%y.%%m.%%d index2date(date2index(y.m.d^)^)=!y!.!m!.!d!) & pause)
        echo %%y.%%m.%%d  Index=!Index!
        set /a "Index+=1"
      )
    )
  )
)
pause
exitCOPY

TOP

本帖最后由 neorobin 于 2012-4-11 23:02 编辑

回复 49# plp626

31 楼, 我做了年份估值误差范围测试, 如果估值函数在给定计算范围内的 估值误差范围 跨过 两个整数(比如 [-0.1,1.3] 跨过了 0 和 1 两个整数), 就不利于后续代码, 后面年份的误差调整就不是简单的只作出 要么不变, 要么加1(或者减1) 这样的方式了, 因为 跨过两个整数 意味着误差的整数绝对值跨度将达到 2.

TOP

回复 46# plp626
100-2-28 的下一天是 100-2-29
这里说的公元100年2月应该有29天
前面我提到 公元1582年10月4号前是每4年一闰  不管年份是否可被100或400整除
1582年10月由格勒哥里十三世改革成为格勒哥里历,取消1582年10月5日至1582年10月14日这10日及取消400年内00年尾的3个闰年,使一年的平均日数变成365.2425日,更接近于准确的回归年365.2422日。
格勒哥里历(也就是现在大多使用的):
每4年置闰年一次,闰年366日,二月29日.凡年份数可被4 整除者,置闰. 如1960,1996等.凡年份数可被100整除者,不置闰, 如1800,1900.凡年份数可被400整除者,置闰, 如2000,2400.

TOP

回复 52# terse


不同年份阶段,要不同的历法,    太复杂了; 很不好计算;

反正matlab里面100-2-28的下一天是100-3-1;
>> datestr(36584,26)
ans =
0100/02/28
>> datestr(36585,26)
ans =
0100/03/01
>> COPY

TOP

本帖最后由 neorobin 于 2012-4-11 23:10 编辑

回复 53# plp626


    用个能算到 公元 3000 年的历法就蛮足够了, 往过去可以算 100 年也就可以了, 我们不考古, 呵呵,  我用过最大的用途就是看下出生日期什么的.


51 楼纠正一下, 应说成 整数误差绝对值 跨度 将达到 2: 例如, 应该得到的 年份是 2012, 但误差范围可能出现 3 种(据算法择其中一种)可能的区间 [2010,2012], [2011,2013], [2012,2014], 尽管这个误差正峰值和负峰值并不会发生在对同一个年份的估值上, 但代码上总是要对这两个峰值作统一处理的.

TOP

i2date中,
y=(i*99+145)/36159;COPY
通过9999年以内的索引转日期测试;
我用matlab生成标准日期文件69M+;
再用测试代码用C生成了标准日期文件69M+;
两个文件md5值相同均为 a6740e69a45b825334b8c4c4c986fe2f;

标准打印格式:

fid 模式wt;
fprintf(fid,"%7d:%.4d/%.2d/%.2d\n",i,y,m,d);
末行 3652060:9999/03/02

索引范围 0~3652060 [0-3-1 ~ 9999-3-2]

TOP

针对1582 10 4 ----1582 10 15 的测试
@echo off&setlocal enabledelayedexpansion
(FOR /l %%i in (2298883 1 2299603) DO (
    set JD=%%i
    IF !JD! GEQ 2299161 set /a "JD+=1+(JD*100-186721625)/3652425-(JD*100-186721625)/3652425/4"
    set /a "B=JD+1524,Y=(B*100-12210)/36525,D=36525*Y/100"
    set /a "M=(B-D)*100/3061,D=B-D-3061*M/100,M=(M-2)%%12+1,Y-=4715+^!(2/M)"
    set "str=!Y!:!M!:!D!"
    set /a "M=(M+9)%%12+3,Y-=M/13,JD=36525*(Y+4716)/100+3061*(M+1)/100+D-1524"
    REM 1582年10月4日后重新计算闰年,先前每4年一闰.
    set /a "JD+=(2-Y/100+Y/400)*^!(2299161/JD)"
    ECHO !str! !JD!
))>Yjd.txt
start "" "Yjd.txt"
PAUSECOPY
@echo off&setlocal enabledelayedexpansion
for %%i in (31 28 31 30 31 30 31 31 30 31 30 31) do set /a N+=1&set "M_!N!=%%i"
set JDX=2298883
for /l %%i in (1582 1 1583) do (
     IF %%i gtr 1582 (
        set /a "N=^!(%%i%%4)^^^!(1582/%%i)&^!(%%i%%400)^^^!(1582/%%i)&^!(%%i%%100)"
     ) else set /a "N=^!(%%i%%4)"
     set /a M_2=28+N
     for /l %%j in (1 1 12) do (
         set /a "M=(%%j+9)%%12+3,Y=%%i-M/13"
         for /l %%k in (1 1 !M_%%j!) do (
             set /a "JD=36525*(Y+4716)/100+3061*(M+1)/100+%%k-1524,JDX+=1"
             set /a "JD+=(2-Y/100+Y/400)*^!(2299161/JD)"  2>nul
             IF !JDX! NEQ !JD! set ERR=!err!$err:%%i:%%j:%%k !JD! !JDX!$
             REM 1582年10月4日后初始日须减10天
             if "%%i:%%j:%%k" == "1582:10:5" set/a JDX-=10
         )
     )
)
IF DEFINED ERR echo !err:$=^
!
ECHO !JD! !JDX!
pauseCOPY

TOP

回复 51# neorobin


    我相信那个比值 (33*i+x)/12053可代替 (i*99+145)/36159;且范围不会大幅缩减;

找3000年以内的;我考虑 (4*i+x)/1461

TOP

本帖最后由 neorobin 于 2012-4-11 23:32 编辑

回复 57# plp626


    我测试的结果是 [1,3000] 内,  (i * 4 + 93) / 1461 就可行

(i * 4 + 0) / 1461
The pOffsMin is: -0.062971;  the pOffsMax is: 0.000000
The pMin is: 3000;  the pMax is: 4
pOffsMin = -0.062971,  pOffsMax + 365*4 / 1461 = 0.999316

0.062971 * 1461 = 92.000631

(i * 4 + 93) / 1461
The pOffsMin is: 0.000000;  the pOffsMax is: 0.063655
The pMin is: 4;  the pMax is: 4
pOffsMin = 0.000000,  pOffsMax + 365*4 / 1461 = 1.062971

TOP

你测试 (i * 4 + 93) / 1461 兼容的最大范围是?

TOP

回复 59# plp626

测试代码确实有问题, 是记录 误差最小值的一个变量的初始值设小了,  但 93 这个常数还是碰对了

修改后:
   
93
The pOffsMin is: 0.000684;  the pOffsMax is: 0.063655
The pMin is: 3000;  the pMax is: 4
pOffsMin = 0.000684,  pOffsMax + 365*4 / 1461 = 1.062971



0
The pOffsMin is: -0.062971;  the pOffsMax is: 0.000000
The pMin is: 3000;  the pMax is: 4
pOffsMin = -0.062971,  pOffsMax + 365*4 / 1461 = 0.999316

0.062971 * 1461 = 92.000631

第一次出错的发生:
3002.2.28  Index=1096456
error at 3002.3.1 index2date(date2index(y.m.d))=3002.2.29

TOP

返回列表