Skip to content

fix(float-precision): correct float widget rounding #4291

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 6, 2025

Conversation

bigcat88
Copy link
Contributor

@bigcat88 bigcat88 commented Jun 28, 2025

This PR addresses a long-standing floating-point precision issue that causes float values like 0.1 to be saved incorrectly as 0.10000000000000002 in workflows.

Closes:


When a float widget's rounding value was calculated, it was using Math.pow(0.1, precision). Since 0.1 cannot be perfectly represented in binary, this introduced a tiny error that propagated into the final widget value.

The original code tried to mitigate this with a multiplication/division trick, but the error was already present before that trick could be effective.

This PR was not fully tested, I run tests myself only on a few workflows to prove that there is no such error anymore as in the issues.

┆Issue is synchronized with this Notion page by Unito

@bigcat88 bigcat88 requested a review from a team as a code owner June 28, 2025 18:21
Copy link
Contributor

@webfiltered webfiltered left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great at first glance - thanks for tracking that down. I'll give it a bit of a thrashing and see if there's impact elsewhere.

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested the fix and confirmed it resolves the floating-point precision issue. Values like 0.1 and 1.0 now serialize correctly to JSON without the trailing digits (e.g., no more 0.10000000000000002). The mathematical approach using Math.pow(10, -precision) is cleaner and more accurate than the previous implementation. LGTM 👍

christian-byrne

This comment was marked as spam.

christian-byrne

This comment was marked as spam.

christian-byrne

This comment was marked as spam.

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing process:

  1. Basic Float Values in KSampler
    - Added KSampler node, set denoise to 0.1
    - Saveed workflow and checked JSON - was 0.1 not 0.10000000000000002
    - Tested other values: 0.5, 0.25, 0.75, 1.0
  2. Workflow Save/Load Cycle
    - Created workflow with multiple float values
    - Saved to file
    - Reloaded and verified all float values remain exact
  3. StyleModelApply Node Test
    - Added StyleModelApply node
    - Set strength to exactly 1.0
    - Export and verified it saves as 1 or 1.0, not 1.0000000000000002
  4. Different Precision Settings
    - Went to Settings → Comfy → Float Rounding Precision
    - Test with precision 1, 2, 3, 4, 5, 6
    - Verify rounding works correctly at each level (except with 6, serializes incorrectly after using buttons)
  5. Edge Case Values
    - Very small: 0.001, 0.0001
    • fails at precision 6 => e.g. 0.000010 serializes to 0.000009999999999999999
      - Large values: 100.1, 1000.1
      - Values that round badly in binary: 0.1, 0.2, 0.3, 0.7
    • fails on precision 6 => e.g. 0.1 serializes to 0.09999999999999999
  6. Multiple Node Types
    - Tested float widgets in different nodes:
    • LoRA nodes (model_weight, clip_weight)

Found a separate issue while testing: #4350

@christian-byrne
Copy link
Contributor

christian-byrne commented Jul 4, 2025

It seems there's some new issues e.g. the serialization of 0.1 at precision 6 is now

0.09999999999999999

before it was

0.10000000000000003

@bigcat88
Copy link
Contributor Author

bigcat88 commented Jul 5, 2025

It seems there's some new issues e.g. the serialization of 0.1 at precision 6 is now

I tried setting the precision to 6 and exporting the workflow but couldn't reproduce it. Do you have any advice on how I can reproduce it?

Screenshot from 2025-07-05 03-47-57

@christian-byrne
Copy link
Contributor

Can you try after toggling this one?

Selection_1656

Then maybe try with using the buttons on the widget.

@bigcat88

This comment was marked as outdated.

@bigcat88 bigcat88 marked this pull request as draft July 5, 2025 11:06
Signed-off-by: Alexander Piskun <[email protected]>
@bigcat88
Copy link
Contributor Author

bigcat88 commented Jul 5, 2025

Hope that this fix(use toFixed(precision) and convert back to number) is enough for these edge cases.

Found another strange situation during testing: when we set precision=1 and try from UI change the denoise it does not change at all, cause step=0.01. Looks weird. I have local version with demo of the fix. But that is for another PR as this is not related to the current scope of problem.

version with logging for fast check
function onFloatValueChange(this: INumericWidget, v: number) {
  const round = this.options.round
  const min = this.options.min ?? -Infinity
  const max = this.options.max ?? Infinity

  if (round) {
    const precision =
      this.options.precision ?? Math.max(0, -Math.floor(Math.log10(round)))

    const adjusted = v / round
    const rounded = Math.round(adjusted) * round

    const fixedValue = Number(rounded.toFixed(precision))

    const clamped = _.clamp(fixedValue, min, max)

    console.log('[onFloatValueChange debug]')
    console.log('input value =', v)
    console.log('round =', round)
    console.log('precision =', precision)
    console.log('adjusted = v / round =', adjusted)
    console.log(
      'rounded (intermediate, possibly imprecise) = Math.round(adjusted) * round =',
      rounded
    )
    console.log(
      'fixedValue (final corrected value) = Number(rounded.toFixed(precision)) =',
      fixedValue
    )
    console.log('clamped =', clamped)

    this.value = clamped
  } else {
    console.log('[onFloatValueChange debug] no rounding, value =', v)
    this.value = v
  }
}

@bigcat88 bigcat88 marked this pull request as ready for review July 5, 2025 14:40
Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested again and works well. The unwanting floating-point noise is successfuly removed and all edge cases are handled.

LGTM, nice work!

@christian-byrne christian-byrne merged commit c7877db into Comfy-Org:main Jul 6, 2025
10 checks passed
This was referenced Jul 9, 2025
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.

3 participants