(注意:与另一个问题不同,因为OP从未明确指定向0或-Infinity舍入)

JLS 15.17.2表示整数除法向零舍入。 如果我想要正除数的floor()行为(我不在乎负除数的行为),最简单的方法是在数字上对所有输入都正确?

int ifloor(int n, int d)
{
/* returns q such that n = d*q + r where 0 <= r < d
* for all integer n, d where d > 0
*
* d = 0 should have the same behavior as `n/d`
*
* nice-to-have behaviors for d < 0:
*   option (a). same as above:
*     returns q such that n = d*q + r where 0 <= r < -d
*   option (b). rounds towards +infinity:
*     returns q such that n = d*q + r where d < r <= 0
*/
}
long lfloor(long n, long d)
{
/* same behavior as ifloor, except for long integers */
}

(更新:我想同时为int和long算术提供一种解决方案。)

这必须是重复的,但是我找不到它,如果不是重复的,那么我完全感到惊讶的是,经过3年以上的StackOverflow之后,它还没有出现。

在其余的d < 0条件下,我认为您有几个颠倒的符号。 看起来您想要选项(a)为0 <= r < -d,选项(b)为d < r <= 0。

正确:谢谢,我的意思是正数除数。 (它本应向+ infinity舍入)

如果可以使用第三方库,则Guava具有以下内容:IntMath.divide(int, int, RoundingMode.FLOOR)和LongMath.divide(int, int, RoundingMode.FLOOR)。 (公开:我为番石榴做出了贡献。)

如果您不想为此使用第三方库,则仍然可以查看实现。

多数民众赞成在听到-番石榴是一个非常有名的图书馆。

番石榴为什么没有包含双打除法功能?

@voipp:你甚至会怎么做?它与正常的双分割有何不同?

没有。你显然是对的

当n < 0和d > 0时,有一个相当简洁的公式可以使用:取n的按位补码,进行除法,然后取结果的按位补码。

int ifloordiv(int n, int d)
{
if (n >= 0)
return n / d;
else
return ~(~n / d);
}

对于其余部分,进行类似的构造(在满足通常不变式ifloordiv(n, d) * d + ifloormod(n, d) == n的意义上与ifloordiv兼容)得出的结果始终在[0, d)范围内。

int ifloormod(int n, int d)
{
if (n >= 0)
return n % d;
else
return d + ~(~n % d);
}

对于负除数,公式并不是那么整洁。这是ifloordiv和ifloormod的扩展版本,它们遵循负除数的" nice-to-have"行为选项(b)。

int ifloordiv(int n, int d)
{
if (d >= 0)
return n >= 0 ? n / d : ~(~n / d);
else
return n <= 0 ? n / d : (n - 1) / d - 1;
}
int ifloormod(int n, int d)
{
if (d >= 0)
return n >= 0 ? n % d : d + ~(~n % d);
else
return n <= 0 ? n % d : d + 1 + (n - 1) % d;
}

对于d < 0,当d == -1和n为Integer.MIN_VALUE时,存在不可避免的问题,因为这样数学结果就会溢出类型。在这种情况下,就像常规的Java部门一样,上面的公式返回包装的结果。据我所知,这是唯一一个默默地得到"错误"结果的极端情况。

@trutheality:加,我是个白痴!谢谢;将立即解决此问题!

@trutheality:是的,除了结果仍然以MIN_VALUE的形式出现外,这在数学上是错误的(但是对2 ** 取模是正确的,所以这足够好了)。我很讨厌在d < 0中潜伏着其他极端情况,但没有花时间弄清楚它们到底是什么。也许我应该那样做。

呵呵,事实证明我并不知道除法实际上会溢出。

@trutheality:我编辑了最后一段;好点?据我所知,这种情况确实是唯一的极端情况。

是的,现在非常清楚。

(由于int的答案是相同的,所以我正在为long做所有事情,只需将int替换为每个long,将Integer替换为每个long。)

您可以Math.floor双除法结果,否则...

原始答案:

return n/d - ( ( n % d != 0 ) && ( (n<0) ^ (d<0) ) ? 1 : 0 );

优化答案:

public static long lfloordiv( long n, long d ) {
long q = n/d;
if( q*d == n ) return q;
return q - ((n^d) >>> (Long.SIZE-1));
}

(为完整起见,也可以将BigDecimal与ROUND_FLOOR舍入模式一起使用。)

新编辑:现在,我只是想看看它可以优化多远的乐趣。到目前为止,使用Mark的答案是最好的:

public static long lfloordiv2( long n, long d ){
if( d >= 0 ){
n = -n;
d = -d;
}
long tweak = (n >>> (Long.SIZE-1) ) - 1;
return (n + tweak) / d + tweak;
}

(使用比上述方法便宜的操作,但字节码略长(29 vs. 26))。

请不要把我引向Math.floor。如果它恰好对int变量有效(我不确定),则由于缺乏精度,它对long变量无效。

我会在一分钟后走出门,但如果我能在星期一抽查它的正确性,则病+1。谢谢+周末愉快。

真好!一个简单的混淆优化:将(n<0) ^ (d<0)替换为n^d < 0。编译器可能会为您进行优化,但是我对此表示怀疑。

埃德:当心:n * d可能在溢??出时产生不正确的结果。

@JasonS多数民众赞成在XOR(^)而不是*。

第二种解决方案是错误的。如果n是-3,并且d是2,则该函数输出-1(不正确)。

@LouisWasserman不错的收获,那就是我尝试不进行微优化而得到的...

return BigDecimal.valueOf(n).divide(BigDecimal.valueOf(d), RoundingMode.FLOOR).longValue();