diff --git a/pkl-core/src/main/java/org/pkl/core/DataSize.java b/pkl-core/src/main/java/org/pkl/core/DataSize.java index 76f3a9f76..8c46de553 100644 --- a/pkl-core/src/main/java/org/pkl/core/DataSize.java +++ b/pkl-core/src/main/java/org/pkl/core/DataSize.java @@ -253,6 +253,8 @@ public final class DataSize implements Value { @Override public String toString() { - return MathUtils.isMathematicalInteger(value) ? (long) value + "." + unit : value + "." + unit; + return MathUtils.isMathematicalIntegerFittingLong(value) + ? (long) value + "." + unit + : value + "." + unit; } } diff --git a/pkl-core/src/main/java/org/pkl/core/runtime/VmDataSize.java b/pkl-core/src/main/java/org/pkl/core/runtime/VmDataSize.java index e23760d31..3d1c89f25 100644 --- a/pkl-core/src/main/java/org/pkl/core/runtime/VmDataSize.java +++ b/pkl-core/src/main/java/org/pkl/core/runtime/VmDataSize.java @@ -157,7 +157,9 @@ public final class VmDataSize extends VmValue implements Comparable @Override public String toPklString() { - return MathUtils.isMathematicalInteger(value) ? (long) value + "." + unit : value + "." + unit; + return MathUtils.isMathematicalIntegerFittingLong(value) + ? (long) value + "." + unit + : value + "." + unit; } private double convertValueTo(DataSizeUnit other) { diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DataSizeNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DataSizeNodes.java index 660874462..454ad5fb8 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DataSizeNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DataSizeNodes.java @@ -29,12 +29,12 @@ public final class DataSizeNodes { @ImportStatic(MathUtils.class) @PklName("value") public abstract static class valueProperty extends ExternalPropertyNode { - @Specialization(guards = "isMathematicalInteger(self.getValue())") + @Specialization(guards = "isMathematicalIntegerFittingLong(self.getValue())") protected long evalInt(VmDataSize self) { return (long) self.getValue(); } - @Specialization(guards = "!isMathematicalInteger(self.getValue())") + @Specialization(guards = "!isMathematicalIntegerFittingLong(self.getValue())") protected double evalFloat(VmDataSize self) { return self.getValue(); } diff --git a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DurationNodes.java b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DurationNodes.java index 60ddd7a31..060afec5f 100644 --- a/pkl-core/src/main/java/org/pkl/core/stdlib/base/DurationNodes.java +++ b/pkl-core/src/main/java/org/pkl/core/stdlib/base/DurationNodes.java @@ -31,12 +31,12 @@ public final class DurationNodes { @ImportStatic(MathUtils.class) @PklName("value") public abstract static class valueProperty extends ExternalPropertyNode { - @Specialization(guards = "isMathematicalInteger(self.getValue())") + @Specialization(guards = "isMathematicalIntegerFittingLong(self.getValue())") protected long evalInt(VmDuration self) { return (long) self.getValue(); } - @Specialization(guards = "!isMathematicalInteger(self.getValue())") + @Specialization(guards = "!isMathematicalIntegerFittingLong(self.getValue())") protected double evalFloat(VmDuration self) { return self.getValue(); } diff --git a/pkl-core/src/main/java/org/pkl/core/util/DurationUtils.java b/pkl-core/src/main/java/org/pkl/core/util/DurationUtils.java index 88b86739e..8b13dc998 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/DurationUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/util/DurationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved. + * Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,9 @@ public final class DurationUtils { private DurationUtils() {} public static String toPklString(double value, DurationUnit unit) { - return MathUtils.isMathematicalInteger(value) ? (long) value + "." + unit : value + "." + unit; + return MathUtils.isMathematicalIntegerFittingLong(value) + ? (long) value + "." + unit + : value + "." + unit; } // see: https://standards.calconnect.org/csd/cc-18011.html#toc32 diff --git a/pkl-core/src/main/java/org/pkl/core/util/MathUtils.java b/pkl-core/src/main/java/org/pkl/core/util/MathUtils.java index c060f0207..6f598c2a3 100644 --- a/pkl-core/src/main/java/org/pkl/core/util/MathUtils.java +++ b/pkl-core/src/main/java/org/pkl/core/util/MathUtils.java @@ -55,6 +55,15 @@ public final class MathUtils { <= StrictMath.getExponent(x)); } + /** + * Returns {@code true} if {@code x} is a mathematical integer that can be represented exactly as + * a {@code long}. + */ + @TruffleBoundary + public static boolean isMathematicalIntegerFittingLong(double x) { + return isMathematicalInteger(x) && MIN_LONG_AS_DOUBLE <= x && x < MAX_LONG_AS_DOUBLE_PLUS_ONE; + } + /** * Returns {@code true} if {@code x} represents a power of two. * diff --git a/pkl-core/src/test/files/LanguageSnippetTests/input/api/durationValueSaturation.pkl b/pkl-core/src/test/files/LanguageSnippetTests/input/api/durationValueSaturation.pkl new file mode 100644 index 000000000..171bd923c --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/input/api/durationValueSaturation.pkl @@ -0,0 +1,19 @@ +amends "../snippetTest.pkl" + +facts { + ["value"] { + 1e300.d.value == 1e300 + 1e300.b.value == 1e300 + 1.0E18.d.value == 1000000000000000000 + 7.d.value == 7 + 2.2.h.value == 2.2 + } +} + +examples { + ["display"] { + 1e300.d + 1e300.b + 7.d + } +} diff --git a/pkl-core/src/test/files/LanguageSnippetTests/output/api/durationValueSaturation.pcf b/pkl-core/src/test/files/LanguageSnippetTests/output/api/durationValueSaturation.pcf new file mode 100644 index 000000000..684d52083 --- /dev/null +++ b/pkl-core/src/test/files/LanguageSnippetTests/output/api/durationValueSaturation.pcf @@ -0,0 +1,16 @@ +facts { + ["value"] { + true + true + true + true + true + } +} +examples { + ["display"] { + 1.0E300.d + 1.0E300.b + 7.d + } +} diff --git a/pkl-core/src/test/kotlin/org/pkl/core/DataSizeTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/DataSizeTest.kt index fdf3fff1a..93e971f53 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/DataSizeTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/DataSizeTest.kt @@ -127,4 +127,18 @@ class DataSizeTest { assertThat(DataSize(0.0, BYTES).hashCode()).isEqualTo(DataSize(-0.0, BYTES).hashCode()) assertThat(DataSize(0.0, BYTES).hashCode()).isEqualTo(DataSize(-0.0, PEBIBYTES).hashCode()) } + + @Test + fun `toString()`() { + assertThat(DataSize(3.0, BYTES).toString()).isEqualTo("3.b") + assertThat(DataSize(1.5, BYTES).toString()).isEqualTo("1.5.b") + assertThat(DataSize(1e18, BYTES).toString()).isEqualTo("1000000000000000000.b") + // -2^63 still fits into a `long` + assertThat(DataSize(-9.223372036854776E18, BYTES).toString()) + .isEqualTo("-9223372036854775808.b") + // values too large to fit into a `long` are printed in their `double` form, + // instead of saturating to 9223372036854775807 + assertThat(DataSize(9.223372036854776E18, BYTES).toString()).isEqualTo("9.223372036854776E18.b") + assertThat(DataSize(1e300, PETABYTES).toString()).isEqualTo("1.0E300.pb") + } } diff --git a/pkl-core/src/test/kotlin/org/pkl/core/DurationTest.kt b/pkl-core/src/test/kotlin/org/pkl/core/DurationTest.kt index 690cf6d1c..955954fa2 100644 --- a/pkl-core/src/test/kotlin/org/pkl/core/DurationTest.kt +++ b/pkl-core/src/test/kotlin/org/pkl/core/DurationTest.kt @@ -291,4 +291,18 @@ class DurationTest { assertThat(Duration(0.0, MILLIS).hashCode()).isEqualTo(Duration(-0.0, MILLIS).hashCode()) assertThat(Duration(0.0, NANOS).hashCode()).isEqualTo(Duration(-0.0, DAYS).hashCode()) } + + @Test + fun `toString()`() { + assertThat(Duration(3.0, DAYS).toString()).isEqualTo("3.d") + assertThat(Duration(1.5, DAYS).toString()).isEqualTo("1.5.d") + assertThat(Duration(1e18, DAYS).toString()).isEqualTo("1000000000000000000.d") + // -2^63 still fits into a `long` + assertThat(Duration(-9.223372036854776E18, DAYS).toString()).isEqualTo("-9223372036854775808.d") + // values too large to fit into a `long` are printed in their `double` form, + // instead of saturating to 9223372036854775807 + assertThat(Duration(9.223372036854776E18, DAYS).toString()).isEqualTo("9.223372036854776E18.d") + assertThat(Duration(1e300, DAYS).toString()).isEqualTo("1.0E300.d") + assertThat(Duration(-1e300, DAYS).toString()).isEqualTo("-1.0E300.d") + } }