diff --git a/docs/Real.md b/docs/Real.md index 0e9b484..a0055ee 100644 --- a/docs/Real.md +++ b/docs/Real.md @@ -19,6 +19,8 @@ you can use it's class to ensure type: |-----------------|-------------------------------------------------------------|------| | `.roundToInt()` | round to nearest int like round(x) | Int | | `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int | +| `.isInfinite()` | true when the value is `Infinity` or `-Infinity` | Bool | +| `.isNaN()` | true when the value is `NaN` | Bool | | `.clamp(range)` | clamp value within range boundaries | Real | | | | | | | | | diff --git a/examples/free_fall.lyng b/examples/free_fall.lyng new file mode 100644 index 0000000..d7ad556 --- /dev/null +++ b/examples/free_fall.lyng @@ -0,0 +1,103 @@ + +/* + Рассчитывает глубину провала по времени падения камня и прихода звука. + + @param T измеренное полное время (с) + @param m масса камня (кг) + @param d диаметр камня (м) (предполагается сферическая форма) + @param rho плотность воздуха (кг/м³), по умолчанию 1.2 + @param c скорость звука (м/с), по умолчанию 340.0 + @param g ускорение свободного падения (м/с²), по умолчанию 9.81 + @param Cd коэффициент лобового сопротивления, по умолчанию 0.5 + @param epsilon относительная точность (м), по умолчанию 1e-3 + @param maxIter максимальное число итераций, по умолчанию 20 + @return глубина h (м), или null если расчёт не сошёлся + */ +fun calculateDepth( + T: Real, + m: Real, + d: Real, + rho: Real = 1.2, + c: Real = 340.0, + g: Real = 9.81, + Cd: Real = 0.5, + epsilon: Real = 1e-3, + maxIter: Int = 20 +): Real? { + // Площадь миделя + val r = d / 2.0 + val A = π * r * r + + // Коэффициент сопротивления + val k = 0.5 * Cd * rho * A + + // Предельная скорость + val vTerm = sqrt(m * g / k) + + // Функция времени падения с высоты h + fun tFall(h: Real): Real { + val arg = exp(g * h / (vTerm * vTerm)) + // arcosh(x) = ln(x + sqrt(x^2 - 1)) + val arcosh = ln(arg + sqrt(arg * arg - 1.0)) + return vTerm / g * arcosh + } + + // Производная времени падения по h + fun dtFall_dh(h: Real): Real { + val expArg = exp(2.0 * g * h / (vTerm * vTerm)) + return 1.0 / (vTerm * sqrt(expArg - 1.0)) + } + + // Полное расчётное время T_calc(h) = tFall(h) + h/c + fun Tcalc(h: Real): Real = tFall(h) + h / c + + // Производная T_calc по h + fun dTcalc_dh(h: Real): Real = dtFall_dh(h) + 1.0 / c + + // Начальное приближение (без сопротивления) + val term = 1.0 + g * T / c + val sqrtTerm = sqrt(1.0 + 2.0 * g * T / c) + var h = (c * c / g) * (term - sqrtTerm) + + // Проверка на валидность начального приближения + if (h.isNaN() || h <= 0.0) { + // Если формула дала некорректный результат, используем оценку по свободному падению + h = 0.5 * g * T * T // грубая оценка, всё равно будет уточняться + if (h.isNaN() || h <= 0.0) return null + } + + // Итерации Ньютона + var iter = 0 + while (iter < maxIter) { + val f = Tcalc(h) - T + val df = dTcalc_dh(h) + + // Если производная близка к нулю, выходим + if (abs(df) < 1e-12) return null + + val hNew = h - f / df + + // Проверка сходимости + if (abs(hNew - h) < epsilon) { + return hNew + } + + h = hNew + iter++ + } + + // Не сошлось за maxIter + return null +} + + // Пример использования + val T = 6.0 // секунды + val m = 1.0 // кг + val d = 0.1 // м (10 см) + + val depth = calculateDepth(T, m, d) + if (depth != null) { + println("Глубина: %.2f м".format(depth)) + } else { + println("Расчёт не сошёлся") + } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index 422700f..e2679d5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -155,6 +155,22 @@ data class ObjReal(val value: Double) : Obj(), Numeric { ) { ObjInt.of(thisAs().value.toLong()) } + addFnDoc( + name = "isInfinite", + doc = "Return true if this real number is positive or negative infinity.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { + ObjBool(thisAs().value.isInfinite()) + } + addFnDoc( + name = "isNaN", + doc = "Return true if this real number is NaN (not a number).", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { + ObjBool(thisAs().value.isNaN()) + } } } } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index b4a1b0f..9e735db 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -5271,6 +5271,19 @@ class ScriptTest { ) } + @Test + fun testRealIsInfiniteAndIsNaN() = runTest { + eval( + """ + assertEquals(false, 1.0.isInfinite()) + assertEquals(true, (1.0 / 0.0).isInfinite()) + assertEquals(true, (-1.0 / 0.0).isInfinite()) + assertEquals(false, 1.0.isNaN()) + assertEquals(true, (0.0 / 0.0).isNaN()) + """.trimIndent() + ) + } + @Test fun testEmptySpreadList() = runTest { eval(