12

入力した年がうるう年かどうかを調べるために、Cでプログラムを作成しました。しかし、残念ながらそれはうまく機能していません。1年は飛躍的であり、前年は飛躍的ではないと書かれています。

#include <stdio.h>
#include <conio.h>

int yearr(int year);

void main(void) {
    int year;
    printf("Enter a year:");
    scanf("%d", &year);
    if (!yearr(year)) {
        printf("It is a leap year.");
    } else {
        printf("It is not a leap year");
    }

    getch();
}

int yearr(int year) {
    if ((year % 4 == 0) && (year / 4 != 0))
        return 1;
    else
        return 0;
}

コメントを読んだ後、コードを次のように編集しました。

#include <stdio.h>
#include <conio.h>

int yearr(int year);

void main(void) {
    int year;
    printf("Enter a year:");
    scanf("%d", &year);
    if (!yearr(year)) {
        printf("It is a leap year.");
    } else {
        printf("It is not a leap year");
    }

    getch();
}

int yearr(int year) {
    if (year % 4 == 0) {
        if (year % 400 == 0)
            return 1;
        if (year % 100 == 0)
            return 0; 
    } else
        return 0;
}
4

17 に答える 17

127

最も効率的なうるう年テスト:

if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0))
{
    /* leap year */
}

このコードは、C、C ++、C#、Java、およびその他の多くのCのような言語で有効です。このコードは、3つの別々のテストで構成される単一のTRUE/FALSE式を利用しています。

  • 4年目のテスト:year & 3
  • 100年目のテスト:year % 25
  • 400年目のテスト:year & 15

このコードがどのように機能するかについての完全な説明を以下に示しますが、最初にウィキペディアのアルゴリズムについての説明が必要です。

ウィキペディアのアルゴリズムは非効率的/信頼性が低い

ウィキペディアは、絶え間ない編集、意見、および破壊行為にさらされてきた擬似コードアルゴリズム(ウィキペディア:うるう年-アルゴリズムを参照)を公開しています。

WIKIPEDIAアルゴリズムを実装しないでください!

最も長く存在する(そして非効率的な)ウィキペディアアルゴリズムの1つは、次のように表示されました。

if year modulo 400 is 0 then
   is_leap_year
else if year modulo 100 is 0 then
   not_leap_year
else if year modulo 4 is 0 then
   is_leap_year
else
   not_leap_year

上記のアルゴリズムは、75%の確率で「4年目のテスト」(モジュロ4テスト)にすぐに失敗する年でも、常に400年目と100年目のテストを実行するため非効率的です。最初に4年目のテストを実行するようにアルゴリズムを並べ替えることで、処理が大幅に高速化されます。

「最も効率的な」擬似コードアルゴリズム

ウィキペディアに次のアルゴリズムを提供しました(複数回)。

if year is not divisible by 4 then not leap year
else if year is not divisible by 100 then leap year
else if year is divisible by 400 then leap year
else not leap year

この「最も効率的な」擬似コードは、テストの順序を変更するだけなので、4による除算が最初に行われ、その後に発生頻度の低いテストが続きます。「年」は75%の時間で4で割られないため、アルゴリズムは4つのケースのうち3つで1つのテストの後に終了します。

注:私はさまざまなウィキペディアの編集者と戦い、そこで公開されているアルゴリズムを改善しました。多くの初心者およびプロのプログラマーが(検索エンジンのトップリストのために)すぐにウィキペディアのページにアクセスし、さらに調査することなくウィキペディアの擬似コードを実装すると主張しました。ウィキペディアの編集者は、公開されたアルゴリズムを改善したり、注釈を付けたり、脚注を付けたりするために私が行ったすべての試みを拒否し、削除しました。どうやら、彼らは効率を見つけることがプログラマーの問題だと感じています。それは本当かもしれませんが、多くのプログラマーは急いでしっかりとした研究を行うことができません!

「最も効率的な」うるう年テストの議論

ビット単位-モジュロの代わりにAND:

ウィキペディアアルゴリズムの2つのモジュロ演算をビットごとのAND演算に置き換えました。なぜそしてどのように?

モジュロ計算を実行するには、除算が必要です。PCをプログラミングするとき、これについてよく考えることはありませんが、小さなデバイスに組み込まれた8ビットマイクロコントローラーをプログラミングするとき、CPUによって除算機能をネイティブに実行できないことがあります。このようなCPUでは、除算は、繰り返しのループ、ビットシフト、および加算/減算操作を含む困難なプロセスであり、非常に低速です。避けることが非常に望ましいです。

2の累乗のモジュロは、ビットごとのAND演算を使用して交互に達成できることがわかります(Wikipedia:モジュロ演算-パフォーマンスの問題を参照)。

x%2 ^ n == x&(2 ^ n-1)

多くの最適化コンパイラは、そのようなモジュロ演算をビット単位のANDに変換しますが、より小さく、あまり人気のないCPU用のあまり高度でないコンパイラはそうではない場合があります。ビット単位-ANDは、すべてのCPUでの単一の命令です。

modulo 4andmodulo 400テストを& 3andに置き換えることで& 15(以下を参照:「数学を減らすための因数分解」)、はるかに遅い除算操作を使用せずに、最速のコード結果を保証できます。

100に等しい2の累乗は存在しません。したがって、100年目のテストではモジュロ演算を引き続き使用する必要がありますが、100は25に置き換えられます(以下を参照)。

数学を単純化するための因数分解:

ビット単位のANDを使用してモジュロ演算を置き換えることに加えて、ウィキペディアのアルゴリズムと最適化された式の間に2つの追加の論争があることに気付くかもしれません。

  • modulo 100に置き換えられますmodulo 25
  • modulo 400に置き換えられます& 15

100年目のテストでは、modulo 25の代わりにを使用しmodulo 100ます。100ファクターが2x2 x 5 x 5になるため、これを行うことができます。4年目のテストではすでに4ファクターがチェックされているため、100からそのファクターを削除して25を残すことができます。この最適化は、ほぼすべてのCPU実装にとっておそらく重要ではありません( 100と25の両方が8ビットに収まるため)。

400年目のテスト& 15では、に相当するものを使用しmodulo 16ます。繰り返しますが、400の因数が2 x 2 x 2 x 2 x 5 x 5になるため、これを行うことができます。100年目のテストでテストされた25の因数を削除して、16を残すことができます。 200の因数であるため、これ以上の因数を削除すると、200年目には望ましくないポジティブが生成されます。

400年目の最適化は、分割を回避するため、最初に8ビットCPUにとって非常に重要です。ただし、さらに重要なのは、値400は9ビットの数値であり、8ビットCPUでは処理がはるかに難しいためです。

論理AND/OR演算子の短絡:

使用される最後の、そして最も重要な最適化は、ほとんどのCに似た言語で実装される、短絡論理AND('&&')およびOR('||')演算子( Wikipedia:短絡評価を参照)です。 。短絡演算子は、左側の式自体が操作の結果を決定する場合、右側の式をわざわざ評価しないため、このように名付けられています。

例:年が2003の場合、year & 3 == 0はfalseです。論理積の右側のテストで結果を真にする方法はないため、他に何も評価されません。

最初に4年目のテストを実行することにより、4年目のテスト(単純なビット単位のAND)のみが4分の3(75パーセント)の時間で評価されます。これにより、特に100年目のテスト(モジュロ25演算)に必要な除算が回避されるため、プログラムの実行が大幅に高速化されます。

括弧の配置に関する注記

あるコメント提供者は、私のコードで括弧が間違っていると感じ、次のように、部分式を論理AND演算子の周りに(論理ORの周りではなく)再グループ化することを提案しました。

if (((year & 3) == 0 && (year % 25) != 0) || (year & 15) == 0) { /* LY */ }

上記は正しくありません。論理AND演算子は、論理ORよりも優先順位が高く、新しい括弧の有無にかかわらず最初に評価されます。論理AND引数の前後の括弧は効果がありません。これにより、サブグループを完全に削除する可能性があります。

if ((year & 3) == 0 && (year % 25) != 0 || (year & 15) == 0) { /* LY */ }

ただし、上記のどちらの場合も、論理OR(400年目のテスト)の右側はほぼ毎回評価されます(つまり、4年と100年で割り切れない年)。したがって、有用な最適化が誤って削除されました。

元のコードの括弧は、最も最適化されたソリューションを実装しています。

if ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) { /* LY */ }

ここで、論理ORは、4で割り切れる年についてのみ評価されます(短絡ANDのため)。論理ORの右側は、4と100で割り切れる年についてのみ評価されます(短絡ORのため)。

C /C++プログラマー向けの注意

C / C ++プログラマーは、この式がより最適化されていると感じるかもしれません。

if (!(year & 3) && ((year % 25) || !(year & 15))) { /* LY */ }

これはこれ以上最適化されていません!明示的テスト== 0!= 0テストは削除されますが、暗黙的になり、引き続き実行されます。year & 3さらに悪いことに、コードは、に評価されるC#のような強い型の言語では無効になりますintが、論理AND(&&)、OR(||)、およびNOT(!)演算子にはbool引数が必要です。

于 2012-07-21T21:15:23.690 に答える
17

うるう年を決定する論理が間違っています。これで始められるはずです(ウィキペディアから):

if year modulo 400 is 0
       then is_leap_year
else if year modulo 100 is 0
       then not_leap_year
else if year modulo 4 is 0
       then is_leap_year
else
       not_leap_year

x modulo yxの余りを。で割ったものを意味しyます。たとえば、5を法とする12は2です。

于 2010-07-10T17:33:15.760 に答える
9

多くの回答がパフォーマンスについて語っています。なしは測定値を示しません。gccのドキュメントからの素晴らしい引用はこれを__builtin_expect言います:

プログラマーは、プログラムが実際にどのように実行されるかを予測するのが苦手なことで有名です。

ほとんどの実装では、最適化ツールとしての短絡を使用&&||、「最良の」パフォーマンスの除算性チェックの「正しい」順序を規定します。短絡は必ずしも最適化機能ではないことに注意してください。

同意すると、いくつかのチェックは決定的な答えを与え(例えば、年は4の倍数ではない)、その後のテストを役に立たなくするかもしれません。不必要な計算を続けるのではなく、この時点ですぐに戻ることはすべて合理的であるように思われます。一方、早期返品はブランチを導入し、これによりパフォーマンスが低下する可能性があります。(この伝説的な投稿を参照してください。)ブランチの予測ミスと不要な計算の間のトレードオフを推測するのは非常に困難です。実際、それはハードウェア、入力データ、コンパイラーによって発行された正確なアセンブリ命令(バージョンごとに変わる可能性があります)などに依存します。

続編はquick-bench.comで得られた測定値を示すものとします。std::array<int, 65536>いずれの場合も、に格納されている各値がうるう年であるかどうかを確認するためにかかる時間を測定します。値は疑似ランダムであり、[-400、399]の間隔で均一に分布しています。より正確には、これらは次のコードによって生成されます。

auto const years = [](){
  std::uniform_int_distribution<int> uniform_dist(-400, 399);
  std::mt19937 rng;
  std::array<int, 65536> years;
  for (auto& year : years)
    year = uniform_dist(rng);
  return years;
}();

%可能な場合でも、&(たとえばyear & 3 == 0の代わりに)に置き換えませんyear % 4 == 0。私はコンパイラ(GCC-9.2 at -O3)が私のためにそれをしてくれると信じています。(します。)

4-100-400テスト

うるう年のチェックは、通常、3つの除算性テストで記述されます:year % 4 == 0、、。以下は、これらのチェックが表示される可能性のあるすべての順序をカバーする実装のリストです。各実装には、対応するラベルが接頭辞として付けられます。(一部の注文では2つの異なる実装が許可されています。その場合、2番目の実装には接尾辞が付きます。)それらは次のとおりです。year % 100 != 0year % 400 == 0b

b4_100_400  : year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
b4_100_400b : (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
b4_400_100  : year % 4 == 0 && (year % 400 == 0 || year % 100 != 0)
b100_4_400  : (year % 100 != 0 && year % 4 == 0) || year % 400 == 0
b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0
b400_4_100  : year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)
b400_100_4  : year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)
b400_100_4b : (year % 400 == 0 || year % 100 != 0) && year % 4 == 0

結果を以下に示します。(ライブでご覧ください。)

4-100-400テスト

多くの人がアドバイスしていることに反して、4最初に除算性をチェックすることは最善の方法ではないようです。それどころか、少なくともこれらの測定では、最初の3つのバーは最悪の5つのバーの1つです。最高は

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0

4-25-16テスト

別の提供されたヒント(私はそれが良いものだと思ったことを告白しなければなりません)はに置き換えyear % 100 != 0ていyear % 25 != 0ます。チェックも行うので、これは正確さには影響しませんyear % 4 == 0。(数値がの倍数である場合、4による除算は。100による除算と同等25です。)同様に、による除算チェックが存在するため、year % 400 == 0に置き換えることができます。year % 16 == 025

前のセクションと同様に、4-25-16の除算性チェックを使用した8つの実装があります。

b4_25_16  : year % 4 == 0 && (year % 25 != 0 || year % 16 == 0)
b4_25_16b : (year % 4 == 0 && year % 25 != 0) || year % 16 == 0
b4_16_25  : year % 4 == 0 && (year % 16 == 0 || year % 25 != 0)
b25_4_16  : (year % 25 != 0 && year % 4 == 0) || year % 16 == 0
b25_16_4  : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0
b16_4_25  : year % 16 == 0 || (year % 4 == 0 && year % 25 != 0)
b16_25_4  : year % 16 == 0 || (year % 25 != 0 && year % 4 == 0)
b16_25_4b : (year % 16 == 0 || year % 25 != 0) && year % 4 == 0

結果(ここに住んでいます):

4-25-16テスト

繰り返しますが、4最初に除算性をチェックすることは良い考えではありません。このラウンドでは、速いです

b25_16_4  : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0

4-100-400テスト(分岐なし)

前述のように、分岐によってパフォーマンスが低下する可能性があります。特に、短絡は逆効果になる可能性があります。この場合、古典的なトリックは、論理演算子&&||そのビット単位の対応物&およびを置き換えること|です。実装は次のようになります。

nb4_100_400  : (year % 4 == 0) & ((year % 100 != 0) | (year % 400 == 0))
nb4_100_400b : ((year % 4 == 0) & (year % 100 != 0)) | (year % 400 == 0)
nb4_400_100  : (year % 4 == 0) & ((year % 400 == 0) | (year % 100 != 0))
nb100_4_400  : ((year % 100 != 0) & (year % 4 == 0)) | (year % 400 == 0)
nb100_400_4  : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)
nb400_4_100  : (year % 400 == 0) | ((year % 4 == 0) & (year % 100 != 0))
nb400_100_4  : (year % 400 == 0) | ((year % 100 != 0) & (year % 4 == 0))
nb400_100_4b : ((year % 400 == 0) | (year % 100 != 0)) & (year % 4 == 0)

結果(ここに住んでいます):

4-100-400テスト(分岐なし)

注目すべき特徴は、パフォーマンスの変動が分岐の場合ほど顕著ではなく、勝者を宣言するのが難しいことです。これを選びます:

nb100_400_4  : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)

4-25-16テスト(分岐なし)

演習を完了するために、4-25-16の除算性テストを使用した分岐なしのケースを検討します。

nb4_25_16  : (year % 4 == 0) & ((year % 25 != 0) | (year % 16 == 0))
nb4_25_16b : ((year % 4 == 0) & (year % 25 != 0)) | (year % 16 == 0)
nb4_16_25  : (year % 4 == 0) & ((year % 16 == 0) | (year % 25 != 0))
nb25_4_16  : ((year % 25 != 0) & (year % 4 == 0)) | (year % 16 == 0)
nb25_16_4  : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)
nb16_4_25  : (year % 16 == 0) | ((year % 4 == 0) & (year % 25 != 0))
nb16_25_4  : (year % 16 == 0) | ((year % 25 != 0) & (year % 4 == 0))
nb16_25_4b : ((year % 16 == 0) | (year % 25 != 0)) & (year % 4 == 0)

結果(ここに住んでいます):

4-25-16テスト(分岐なし)

繰り返しになりますが、最良のものを定義することは困難であり、これを選択します。

nb25_16_4  : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)

チャンピオンズリーグ

今度は、前の各セクションの最良のものを選び、それらを比較します。

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0
b25_16_4    : (year % 25 != 0 || year % 16 == 0) && year % 4 == 0
nb100_400_4 : ((year % 100 != 0) | (year % 400 == 0)) & (year % 4 == 0)
nb25_16_4   : ((year % 25 != 0) | (year % 16 == 0)) & (year % 4 == 0)

結果(ここに住んでいます):

チャンピオンズリーグ

このチャートは、短絡が実際に最適化であることを示唆していますが、除算4は最初ではなく最後にチェックする必要があります。パフォーマンスを向上させるには、100最初に除算性を確認する必要があります。これはかなり驚くべきことです!結局のところ、後者のテストは、年が飛躍するかどうかを判断するのに十分ではなく、後続のテスト(by400またはby 4)が常に必要です。

25また、より単純な除数を使用する分岐バージョンの場合、より直感的で16を使用するよりも優れているわけではないことも驚くべきことです。私の「理論」を提供することができます。これは、最初のテストがのテストよりも優れている理由を部分的に説明しています。多くの人が指摘しているように、分割可能性テストは実行を(25%、75%)の部分に分割しますが、のテストはそれを(1%、99%)に分割します。後者のチェックの後、実行は別のテストに進む必要があることは問題ではありません。少なくとも、分岐予測子はどちらに進むべきかを正しく推測する可能性が高いからです。同様に、除算性をチェックする1004001004410025実行を(4%、96%)に分割します。これは、分岐予測子にとって(1%、99%)よりも困難です。早期復帰の確率を最大化するよりも、分布のエントロピーを最小化して分岐予測を支援する方がよいようです。

分岐のないバージョンの場合、単純化された除数はより良いパフォーマンスを提供します。この場合、分岐予測子は何の役割も果たさないため、単純であるほど優れています。一般に、コンパイラーは、より少ない数でより良い最適化を実行できます。

これでしょうか?

壁にぶつかって、それを見つけましたか

b100_400_4  : (year % 100 != 0 || year % 400 == 0) && year % 4 == 0

うるう年で最もパフォーマンスの高いチェックはありますか?絶対にありません。たとえば、分岐演算子が混在している、または分岐演算子がない、などはあり&&ませ||ん。おそらく...上記を見て、他の2つの実装と比較してみましょう。最初は&|

m100_400_4 : (year % 100 != 0 || year % 400 == 0) & (year % 4 == 0)

||(分岐演算子と非分岐演算子の組み合わせに注意してください&。)2つ目は、あいまいな「ハック」です。

bool b;
auto n = 0xc28f5c29 * year;
auto m = n + 0x051eb850;
m = (m << 30 | m >> 2);
if (m <= 0x28f5c28)
  b = n % 16 == 0;
else
  b = n % 4 == 0;
return b

後者は機能しますか?はい、そうです。数学的な証明を与える代わりに、上記で発行されたコードをこのより読みやすいソースのコードと比較することをお勧めします。

bool b;
if (year % 100 == 0)
  b = year % 16 == 0;
else
  b = year % 4 == 0;
return b;

コンパイラエクスプローラで。それらはほとんど同じですが、唯一の違いは、一方がadd命令を使用し、もう一方が。を使用することleaです。これにより、ハックコードが機能することを納得させることができます(他のコードが機能する限り)。

ベンチマーク結果(ここに住んでいます):

最後の

ちょっと待ってください、上の写真のもっと読みやすいコードを使ってみませんか?さて、私は別のレッスンを試し、学びました。このコードがベンチマークループに挿入されると、コンパイラはソース全体を調べ、ソースを分離して見た場合とは異なるコードを出力することを決定しました。パフォーマンスが悪化しました。フィギュアに行こう!

そして今?これでしょうか?

知らない!私たちが探求できることはたくさんあります。たとえば、最後のセクションでは、if短絡ではなくステートメントを使用したさらに別のバージョンを示しました。これは、パフォーマンスをさらに向上させる方法になる可能性があります。三項演算子を試すこともでき?ます。

すべての測定と結論はGCC-9.2に基づいていることに注意してください。別のコンパイラやバージョンでは、状況が変わる可能性があります。たとえば、バージョン9.1のGCCでは、除算性チェックのための新しい改良されたアルゴリズムが導入されました。したがって、古いバージョンではパフォーマンスが異なり、不要な計算とブランチの誤予測との間のトレードオフが変更されている可能性があります。

私たちが確実に結論付けることができるポイントは次のとおりです。

  1. 考えすぎないで。理解しにくい最適化よりも明確なコードを優先します(非常に効率的な実装でユーザーに高レベルのAPIを提供するライブラリライターでない限り)。結局のところ、コンパイラをアップグレードする場合、手作りのスニペットは最もパフォーマンスの高いオプションではない可能性があります。ex-nihiloがコメントで述べたように、「パフォーマンスのボトルネックであることがわかっていない限り、このようなコードをいじくりまわすことにはほとんど意味がありません。」
  2. 推測は難しく、測定する方が良いです。
  3. 測定は難しいですが、推測するよりはましです。
  4. 短絡/分岐はパフォーマンスに良い場合がありますが、コードが分岐予測にどの程度役立つかなど、多くのことに依存します。
  5. スニペットに対して単独で出力されるコードは、どこかにインライン化されている場合は異なる場合があります。
于 2020-03-12T02:53:55.107 に答える
6
int isLeapYear(int year)
{
   return (year % 400 == 0) || ( ( year % 100 != 0) && (year % 4 == 0 ));
}
于 2010-07-10T17:36:40.283 に答える
4

最初に400で除算するロジックは非の打ちどころがありませんが、最初に4で除算するほど計算効率は高くありません。あなたはロジックでそれを行うことができます:

#define LEAPYEAR(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))

これはすべての値について4で除算されますが、それらの3/4については、テストはそこで終了します。最初のテストに合格した1/4については、100で除算し、24/25の値を削除します。100のうち残りの1つについては、400で割って、最終的な答えを出します。確かに、これは大きな節約にはなりません。

于 2011-09-29T05:27:43.563 に答える
4

これは正しい解決策かもしれません。ウィキペディアに記載されているアルゴリズムは正しくありません。

-(BOOL)isLeapYear: (int)year{    

    if(year%4==0){
      if(year%100!=0){
        return YES;
     }
     else if(year%400!=0){
        return YES;
     }
     else return NO;
   }

    else return NO;
  }
于 2015-05-21T08:20:59.690 に答える
3

うるう年に関するウィキペディアの記事から:

if (year modulo 4 is 0) and (year modulo 100 is not 0) or (year modulo 400 is 0)
   then is_leap_year
else
   not_leap_year
于 2010-07-10T17:32:40.443 に答える
2

yearrコードの問題は、年がうるう年であると考えた場合にゼロ以外の値を返すことです。!したがって、ifステートメントにinは必要ありません。

于 2010-07-10T17:35:56.403 に答える
1

http://www.wwu.edu/depts/skywise/leapyear.html

うるう年の規則

毎年、うるう年があり、その数は4で完全に割り切れます。ただし、100で割り切れ、400で割り切れない年は除きます。規則の2番目の部分は、世紀の年に影響します。例えば; 1600年と2000年の世紀はうるう年ですが、1700年、1800年、1900年の世紀はうるう年ではありません。これは、400年ごとに3回、うるう年の間に8年があることを意味します。

于 2010-07-10T17:32:50.973 に答える
1
 if(year%400 ==0 || (year%100 != 0 && year%4 == 0))
    {
        printf("Year %d is a leap year",year);
    }
    else
    {
        printf("Year %d is not a leap year",year);
    }

上記のように変更してください。これも読んでください。

于 2010-07-10T17:33:04.663 に答える
1
    #含む
    void main(void)
    {{
        int年;
        printf("うるう年かどうかを確認する年を入力してください\n");
        scanf( "%d"、&year);
        if(year%400 == 0)/*なぜmod400なのか*/
            printf( "%dはうるう年です\ n"、year);
        else if(year%100 == 0)/*  なぜmod100   * /
            printf( "%dはうるう年ではありません\ n"、year);
        else if(year%4 == 0)
            printf( "%dはうるう年です\ n"、year);
        そうしないと
            printf( "%dはうるう年ではありません\ n"、year);

    }

于 2014-11-26T09:51:51.037 に答える
1
I used this code:

#include <stdio.h>

int main()
{
    int yr;
    printf ("Enter a year \n");
    scanf ("%d", &yr);

    if (yr%400 == 0)
        printf("\n LEAP YEAR.");

    else if (yr%4==0 && yr%100!=0)
        printf("\n LEAP YEAR.");
    else
        printf ("\n NOT LEAP YEAR.");
}
于 2017-07-26T09:02:03.610 に答える
1

ここに、quick-bench.comベンチマークで以前のソリューションを上回っているように見える2つのソリューションがあります。

これにはテストがありますが、clangを使用してブランチレスコードにコンパイルされます。

int isleap3(int year) {
    unsigned y = year + 16000;
    return (y % 100) ? !(y % 4) : !(y % 16);
}

これは、単一のモジュロ演算を使用し、テストは行わず、2つの乗算にコンパイルされます。

static unsigned char const leaptest[400] = {
    1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
    0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,
    0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,
    0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,
    0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,
};
int isleap4(int year) {
    unsigned y = year + 16000;
    return leaptest[y % 400];
}

clang 64ビットアセンブリ

isleap3:                                # @isleap3
        add     edi, 16000
        imul    eax, edi, -1030792151
        ror     eax, 2
        cmp     eax, 42949673
        mov     eax, 15
        mov     ecx, 3
        cmovb   ecx, eax
        xor     eax, eax
        test    ecx, edi
        sete    al
        ret
isleap4:                                # @isleap4
        add     edi, 16000
        imul    rax, rdi, 1374389535
        shr     rax, 39
        imul    eax, eax, 400
        sub     edi, eax
        movzx   eax, byte ptr [rdi + leaptest]
        ret
leaptest:
        .asciz  "\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\000\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000\000\001\000\000"

これがベンチマーク結果です:

ここに画像の説明を入力してください

于 2020-03-30T16:40:54.837 に答える
0

他の人も言及しているように、うるう年の条件は正しくありません。そうすべき:

int yearr(int year)  
{  
    if(((year%4 == 0) && (year%100 !=0)) || (year%400==0))  
        return 1;    
    else    
        return 0;    
}  

Cでうるう年を確認する方法をここで読んでください。

于 2015-05-24T14:57:08.043 に答える
0

Kevinの答えは、最適な8操作テスト(定数を使用したXORを使用)を提供しますが、もう少し読みやすいものを探している場合は、この9操作テストを試してください。

year % 4 == 0 && !((year % 100 == 0) ^ (year % 400 == 0))

の真理値表(year % 100 == 0) ^ (year % 400 == 0)

                              (year % 100 == 0) ^ (year % 400 == 0)
100 doesnt divide year     .    F
only 100 divides year      .    T
100 and 400 divides year   .    F

!(year % 100 == 0) ^ (year % 400 == 0)あなたが望むものを与えます。

于 2016-12-07T05:23:33.743 に答える
-1

月の最大/最終日を計算する:1..12、年:1..3999

maxDays = month == 2 ?
  28 + ((year & 3) == 0 && ((year % 25) != 0 || (year & 15) == 0)) :
  30 + ((month & 1) ^ (month > 7));
于 2016-09-28T03:24:31.633 に答える
-6

#define is_leap(A) !((A) & 3)

マイナスの年を入力しないように注意してください:)

于 2016-02-20T09:42:30.377 に答える