Skip to content

fix: align std.format edge cases with Python#1078

Open
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/format-negative-zero
Open

fix: align std.format edge cases with Python#1078
He-Pin wants to merge 1 commit into
databricks:masterfrom
He-Pin:fix/format-negative-zero

Conversation

@He-Pin

@He-Pin He-Pin commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Motivation:
The official std.format(str, vals) documentation says string formatting follows the same rules as Python, and that the % operator is shorthand for std.format:
https://jsonnet.org/ref/stdlib.html#std-format

sjsonnet diverged from those Python-style rules in several formatting edge cases:

  • floating-point formats normalized IEEE -0.0 to positive zero for %f, %e, %g, and %G
  • dynamic * width/precision silently accepted fractional Jsonnet numbers through truncation
  • negative dynamic width/precision did not consistently follow Python % behavior
  • %s ignored precision, including dynamic precision from *
  • %g exponent selection around powers of ten was sensitive to floating-point log10 roundoff

Modification:
This change keeps std.format anchored to the official Python-style contract instead of treating any single implementation as the specification.

  • Preserve the IEEE sign bit of -0.0 for floating-point conversions while leaving integer conversions unchanged.
  • Normalize dynamic * width and precision through a shared helper:
    • Jsonnet numbers used for * must be finite whole-number values.
    • Negative dynamic width becomes left adjustment with the absolute width.
    • Negative dynamic precision becomes zero; %g then maps precision zero to one as Python does.
    • Fractional dynamic width/precision now raises a user-facing format error instead of being rounded or truncated.
  • Apply %s precision by Jsonnet codepoint before width padding.
  • Stabilize %g exponent selection around powers of ten by compensating for small log10 floating-point error.
  • Add regression tests for negative zero, %g boundary values, dynamic * width/precision, %s precision, and fractional * errors.

Result:
sjsonnet now matches Python % behavior for the covered std.format edge cases. The table below includes the old sjsonnet behavior from master before this PR.

Expression Python % sjsonnet before this PR sjsonnet after this PR C++ jsonnet / go-jsonnet observed jrsonnet observed
"%+f" % (-0.0) "-0.000000" "+0.000000" "-0.000000" "+0.000000" "+0.000000"
"%f" % (-0.0) "-0.000000" "0.000000" "-0.000000" "0.000000" "0.000000"
"%g" % (-0.0) "-0" "0" "-0" "0" "0e-00"
"%g" % 0.0001 "0.0001" "0.0001" "0.0001" "0.0001" "0.0"
"%*g" % [-4, 1.0] "1 " "1" "1 " "1" "1"
"%.*g" % [-3, 1.23456] "1" Internal Error, caused by / by zero "1" "5001e+00" panics / overflows in the observed CLI build
"%.*s" % [-3, "abcdef"] "" "abcdef" "" "abcdef" "abcdef"
"%.1s" % "😀x" "😀" "😀x" "😀" "😀x" "😀x"
"%*g" % [3.7, 1.0] TypeError: * wants int " 1" format error " 1" " 1"
"%.*g" % [2.7, 1.23456] TypeError: * wants int "1.2" format error "1.11.8812766372727552" "1.2"

The PR is now a single format-only commit and no longer includes unrelated manifestXmlJsonml changes.

References:

@He-Pin He-Pin marked this pull request as draft July 3, 2026 17:08
@He-Pin He-Pin changed the title fix: detect negative zero in format operator and clean up manifestYamlStream fix: format operator precision, negative zero detection, and manifestYamlStream cleanup Jul 3, 2026
@He-Pin He-Pin force-pushed the fix/format-negative-zero branch from 7149c61 to fe86128 Compare July 4, 2026 02:14
@He-Pin He-Pin changed the title fix: format operator precision, negative zero detection, and manifestYamlStream cleanup fix: align std.format numeric edge cases with Python Jul 4, 2026
@He-Pin He-Pin force-pushed the fix/format-negative-zero branch 2 times, most recently from 30b5903 to 1ad170b Compare July 4, 2026 06:08
@He-Pin He-Pin changed the title fix: align std.format numeric edge cases with Python fix: align std.format edge cases with Python Jul 4, 2026
@He-Pin He-Pin marked this pull request as ready for review July 4, 2026 06:11
Motivation:
Jsonnet documents std.format and the % string operator as following Python-style formatting rules. sjsonnet diverged on several edge cases: floating-point formats did not preserve the IEEE -0.0 sign bit, dynamic * width/precision accepted fractional values via truncation, negative dynamic width/precision did not consistently follow Python behavior, %s ignored precision, and %g exponent selection was sensitive to floating-point log10 roundoff.

Modification:
Normalize dynamic * width and precision through a shared Python-style integer conversion path, including negative width and negative precision handling. Preserve -0.0 signs for floating-point conversions, keep integral conversions unchanged, apply %s precision by Jsonnet codepoint, and stabilize %g exponent calculation around powers of ten. Add focused regression coverage for negative zero, dynamic * width/precision, fractional * errors, %s precision, and %g boundary values.

Result:
sjsonnet follows the official std.format Python-style contract for these formatting edge cases. The PR is format-only and no longer includes unrelated manifestXmlJsonml changes.

References:
https://jsonnet.org/ref/stdlib.html#std-format
https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting
@He-Pin He-Pin force-pushed the fix/format-negative-zero branch from 1ad170b to 45eaea8 Compare July 6, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant