Skip to content

Reference.MaintainPrecision.zh

inf32768 edited this page Oct 24, 2026 · 10 revisions

English | 简体中文

maintainPrecision 方法

注:本文中用到的各标识符均采用 Fabric Yarn 映射名。

maintainPrecision 是 Minecraft Java 版服务端中的一个实用方法,它用于在噪声采样时防止采样坐标过大导致溢出。

实现

maintainPrecision 方法的实现位于 net.minecraft.util.math.noise.OctavePerlinNoiseSampler 类中,其源码如下:

public static double maintainPrecision(double value) {
    return value - MathHelper.lfloor(value / 33554432D + 0.5D) * 33554432D;
}

其中,MathHelper.lfloor 是一个用于向下取整的方法,参数为 double 类型,返回值为 long 类型,遵循 饱和溢出规则(若输入值大于 Long.MAX_VALUE,则返回 Long.MAX_VALUE,最小值同理)。

解析

原理

从此方法的实现可以看出,其实际上就是对输入值进行了一个类似求余数的操作,此方法在数学上等价于 mod(value-2^24, 2^25)-2^24 。这样,在正常情况下,输出值将在 [-2^24, 2^24) 范围内,而不会超过 2^24 这个整数的范围。

maintainPrecision 方法的函数图象,横轴为输入值,纵轴为输出值

溢出

正常情况下(假设无精度损失),此方法永远等价于上述数学公式。但是,由于其用到的 lfloor 方法的返回值类型为 long,若输入值大于 2^63-1,其将永远返回 2^63-1,此时,此方法便出现了“溢出”的情况,不再拥有求余数的效果,其返回值会随着输入值的增大而无限增大,进而导致各种问题。

maintainPrecision 方法溢出时的函数图象,注意图象靠近原点处的那一段“水平线”实际上是由无数个右下角所示图象构成的

作用

在 Minecraft Java 版使用的柏林噪声采样算法中,若任意一轴的坐标超出 int 类型的范围,则会导致噪声生成器的溢出(请不要与此方法的溢出混淆),生成所谓的“边境之地”。因此,在游戏进行柏林噪声采样前,会把最终的采样坐标(乘以噪声的坐标比例并偏移后的结果)输入到 maintainPrecision 方法中,以防止采样坐标过大导致溢出。实际上这就是 1.14+ 版本中修复边境之地的方案。由于这些采样算法本身具有周期性,每 256 格坐标就会重复一次,因此该修复不会对正常地形生成造成影响。

然而,众所周知 1.14+ 版本中的边境之地并未被彻底修复,而是被推远了 2^25 倍,这正是因为上文提到的溢出问题。经简单推算可得,当输入值的绝对值大于 2^88(≈ 3.09485e+26) 时,此方法便会溢出,这就是 1.14+ 版本中噪声采样坐标上限值的来源。而 2^88/171.103 ≈ 1.8088e+24,这正是 1.14+ 版本中边境之墙开始生成的坐标值。

魔改

maintainPrecision 方法作为噪声采样前的重要关卡,自然具有较高的魔改价值。以下是本模组对 maintainPrecision 魔改后的源代码:

public static double maintainPrecision(double value) {
    // config = ConfigManager.getConfig()
    double result = switch (config.worldGen.farLandsControl.farLandsPos) {
        case BETA -> value;
        case RELEASE -> {
            long l = (long) value;
            yield value - l + l % 16777216L;
        }
        case REMOVED -> (value + 1.6777216E7D) % 3.3554432E7D - 1.6777216E7D;
        case DEFAULT ->
                value - (double) MathHelper.lfloor(value / config.worldGen.farLandsControl.customDivisor + (double) 0.5F) * config.worldGen.farLandsControl.customDivisor;
    };
    if (config.worldGen.farLandsControl.limitReturnValue) {
        result = Math.log10(Math.abs(result)) > config.worldGen.farLandsControl.maxLogarithmValue ? Math.pow(10, Math.log10(Math.abs(result)) - Math.floor(Math.log10(Math.abs(result)) - config.worldGen.farLandsControl.maxLogarithmValue)) * Math.signum(result) : result;
    }
    return result;
}

这样,我们便实现了两个功能: 移动边境之地和 限制返回值范围。

移动边境之地

根据 config.worldGen.farLandsControl.farLandsPos 配置项的值,我们可以在不影响正常地形生成的情况下,选择移动边境之地的位置(实际上是更改了使这个方法溢出的参数的取值范围,即下文中的“参数取值上限”)。

  • BETA:类似 Java 版 Infdev 20100327 至 Beta 1.7.3 和基岩版,参数取值上限为 Integer.MAX_VALUE。

    原理:直接返回输入值,一旦输入值超过 Integer.MAX_VALUE,则会导致溢出。

  • RELEASE:类似 Java 版 Beta 1.8 至正式版 1.13.2,参数取值上限为 Long.MAX_VALUE。

    原理:将输入值转换为 long,再由小数部分和低 24 位(l % 16777216)重建返回值,从而复现 Beta 1.8 至 1.13.2 的行为。当输入值超出 long 范围时,转换会饱和,求余效果失效,返回值随输入值增大而无限增大,最终溢出。

  • DEFAULT:维持原有 1.14+ 实现。除数不再硬编码,而是从 config.worldGen.farLandsControl.customDivisor(下文简称 divisor)读取;默认值 33554432 可复现原有行为。旧版的 CUSTOM 选项已被移除,选择 DEFAULT 并修改 divisor 就等同于旧版的 CUSTOM。需要注意,这是一个会改变地形生成 行为的高级参数,并不是精确指定边境之地位置的选项。

    修改后,方法仍具有原有功能,数学上等价于 mod(value-divisor/2, divisor)-divisor/2,但参数取值上限变为 2^63 * divisor + 2^31。此外,divisor 过大、过小或不是 2 的整数倍都会对正常地形生成造成影响,请看下文:

    • divisor 不是 2 的整数倍:在输入值很大但没有触发溢出时,由于精度损失,返回值可能超过 2^31,导致边境之地生成,同时并不是每一个大数值都会触发溢出,因此实际上边境之地和正常地形会交替出现。
    • divisor > 2^32:输入值即使大于 2^31 也有可能不受影响(余数仍小于 divisor/2),导致噪声生成器在常规位置(BETA 的对应位置)溢出,看起来就是边境之地在原位置生成,但不会一直持续生成(因为输入值达到 divisor/2 时就会被影响)。
    • 512 <= divisor <= 2^32:通常不会有问题,可正常移动边境之地。
    • 4 <= divisor < 512:此时方法的周期小于柏林噪声的周期,会影响正常地形生成。
    • 1 <= divisor < 4:此时方法的周期非常小,正常地形出现了肉眼可见的变化,随着数值减小,正常地形会变得越加不规则和破碎。
    • 0.01 <= divisor < 1:与上一阶段的破碎不同,此阶段地形变得缓和并且按照重复的模式生成,值越小,重复越明显,同时地形也越缓和,看起来就像被拉伸了,到 0.01 时,地形几乎变得完全平坦。
    • 1.0e-15 <= divisor < 0.01:地形几乎完全平坦,直到边境之地。
    • divisor <= 1.0e-15:地形的起伏开始慢慢恢复,数值越小,地形越接近正常地形,同时边境之地位置也越接近 BETA 选项的“正常”位置。
    • divisor = 0:此时生成的地形(包括边境之地)与 BETA 选项完全相同(正常地形与未修改的一致),这是 lfloor 方法的返回值乘上 divisor(这里是 0)后总是为 0,随后输入值减去 0 后仍等于输入值,因此实际上与 BETA 选项的处理没有区别。
  • REMOVED:彻底修复此方法的溢出问题,进而使噪声生成器不再溢出。

    原理:将原本会溢出的伪求余替换为真正的求余。

限制返回值范围

在 config.worldGen.farLandsControl.limitReturnValue 为 true 时,我们可以根据 config.worldGen.farLandsControl.maxLogarithmValue(下文简称 maxLog)的值,限制此方法的返回值范围,设计用途是阻止边缘之地的生成。

原理:在输入值经过上述“移动边境之地”的处理后,将结果写成科学记数法并提取指数部分,若指数部分大于 maxLog,则将其设为 maxLog,并与系数相乘后作为返回值。这样处理的好处是返回值的增长状态将与输入值的保持一致(不算突变点)。 不是哥们这是哪个神人写的方法给我看力竭了,明明一个简单的钳制就能解决,而且返回值突变带来的问题可远远大于保持增长状态一致带来的好处,必须尽早替换!

测试发现,maxLog 小于等于 26 将阻止边缘之地生成,小于等于 9 则会阻止噪声溢出(即边境之地不再生成),过小的值同样会导致正常地形出现问题,类似于上文中 divisor 过小的情况。


附录

A1. 标识符映射名对照

类型 官方映射表 Yarn 映射表
类 net.minecraft.world.level.levelgen.synth.PerlinNoise net.minecraft.util.math.noise.OctavePerlinNoiseSampler
方法 PerlinNoise.wrap(double) OctavePerlinNoiseSampler.maintainPrecision(double)
类 net.minecraft.util.Mth net.minecraft.util.math.MathHelper
方法 Mth.lfloor(double) MathHelper.lfloor(double)

Wiki 导航 | Navigation

🏠 首页 | Home

🧭 用户指南 | User Guide

📈 项目治理 | Project Management

📚 参考资料 | References

Clone this wiki locally