css calc - round down with two decimal cases
Asked Answered
A

6

56

I have the following situation:

div {
    width: calc((100% / 11) - 9.09px);
}

In the context, 100% = 1440px, and 9.09px is generated in mathematics with sass.

The results is: 94.55px, because calc rounds it up, but I need 94.54px (round down).

How can I round down to the nearest hundredths place?

Edit: example.

Algol answered 10/6, 2016 at 17:59 Comment(5)
You shouldn't be getting either of those values. (1440 / 11) = 130.90909px, minus 9.09px = 121.89109px. Can you please create an minimal reproducible example that reproduces your error?Credendum
But by spec, CSS calc() rounds up. So if you want to round down you'll need to use JavaScript.Credendum
@TylerH, example in question body nowAlgol
W3C official discussion - "Add round()/floor()/ceil() functions" - please thump it upCondemn
related: https://mcmap.net/q/540769/-what-is-the-biggest-usable-number-for-use-in-calc-in-css/8620333Fickle
M
226

In general I would say that it's not possible, but there's a hack. However in the web, hacks seem to be the norm, instead of the exception, so I'll just say that it is possible:

div {
    --shf: 4.9406564584124654e-322;
    width: calc(((100% / 11) - 9.09px) * var(--shf) / var(--shf));
}

What this does: it multiplies the value to be rounded by a really small value that underflows the value starting at the third decimal point. Then, it divides the truncated value back, resulting in a rounded version of the value. This assumes that all browsers you support use 64-bit floating point values. If they don't, not only this will be wrong, it might return zero when using smaller floating point data types, completely breaking your page.

Change the exponent to -323 to round at the first decimal point and -324 to round at integer values.

Mcglynn answered 19/11, 2020 at 23:22 Comment(9)
Out of curiosity, how did you come up with that number specifically for --shf?Propman
@DanielEinars It is the value of the minimum subnormal positive double (en.wikipedia.org/wiki/…)Allot
Your upvotes are coming from reddit.com/r/webdev/comments/k0yj3k/…Seize
@PhilippWilhelm party-pooper, let him scratch his head for a while =PItu
unfortunately it does not work with calc(100vw / 3)Leopold
This is a really cool thing to be aware of. Can ceil and floor also be implemented by this kind of stuff?Slightly
This doesn't seem to work in Safari or Firefox though? E.g. gist.github.com/martynchamberlin/…Cleodell
It doesn't work in Firefox. I don't know about Safari but it should work.Mcglynn
@rexblack, technically you can; Floor(x) = Round(x - 0.5) and Ceiling(x) = Round(x + 0.5)Pestalozzi
V
14

Unfortunately, there is not a native way in CSS to round (or ceil/floor) numbers.

However — you mentioned you are using Sass. I found a small Sass library that can round, floor, and ceil numbers to a specified precision.

For example, if you had a had 94.546 you could use decimal-floor(94.546, 2) which would return 94.54.

Unfortunately, this might not help if you have to use calc() to calculate on the fly with CSS. However, if you can pre-calculate the width and floor it with Sass it would fit your needs. A possible solution could be using @media queries as a way to set breakpoints and use those breakpoints in your Sass preprocessing.

Veronicaveronika answered 10/6, 2016 at 19:12 Comment(1)
Unfortunately it does not solve my problem. Thanks a lot for explaining this and your suggestions.Algol
N
12

The round() CSS function

In CSS Values and Units Module Level 4, you can round your number or dimension however you want with the round() CSS function.

round(<rounding-strategy>, valueToRound, roundingInterval)

The optional rounding-strategy can be one of up, down, nearest (the default), and to-zero, which respectively correspond to Math.ceil, Math.floor, Math.round, and Math.trunc in Javascript.

Example

To resize a <canvas> element to almost (“the rounding interval being a CSS pixel, not a device pixel.”) fit its parent element, preserving the canvas pixel aspect ratio of 1:1 (assuming window.devicePixelRatio is a natural number):

canvas {
    display: block;
    width: round(down, 100%, 1px);
    height: round(down, 100%, 1px);
}

Browser support

Currently Chrome, Safari, and Firefox support this.

See also

Nostoc answered 28/11, 2022 at 19:8 Comment(1)
This is awesome! Chrome has been added, version 125+. Just waiting on Edge.Hymenium
S
0

If your problem is a one-pixel rounding error related visual discrepancy, in most cases, you don't actually need to round. A relatively simple solution is to make one of your items take up all of the remaining space.

In a flex scenario, if you don't need your layout to wrap, you can even pick oen of your center items for the least noticeable effect possible.

.some-block {
    width: calc( ( 100% / 11 ) - 9.09px );
    flex-shrink: 0;
}

.some-block:nth-of-type(5) {
    // Will attempt to take the whole width, but its siblings refuse to shrink, so it'll just take every bit of remaining space.
    width: 100%;
}

Depending on your exact CSS case, the pixel substraction could potentially be avoided altogether by using paddings or margins and maybe an additional nested element. That way, the CSS calc() becomes irrelevant and it all could be done in Sass, which has much more powerful math tools and doesn't make the client work for calculations.

Also in a flex context, if your goal is just to make every element equal, you may not need calculations at all. See this question for details.

In a table, since cell widths are calculated left to right, a similar trick can be employed, but you'll have to give width: 100% to the last cell, since there's no concept of flex-shrink and column widths are decided left to right.

.my-table {
    table-layout: fixed;
}

.table-cell {
    width: calc( ( 100% / 11 ) - 9.09px );
}

.table-cell:last-of-type {
    // Will attempt to take the whole width, but since previous column widths have already been determined, it cannot grow any further than its allocated width plus any stray pixels.
    width: 100%;
}
Sprue answered 1/12, 2020 at 16:6 Comment(0)
C
0

If you dynamically generate your CSS, I created a small class to do simple functions like round(), ceil(), floor(), abs(), mod() and sign() -- it can be used to build complex calc rules which you can pass to each other as arguments. If you use "var(--my-variable)" as a parameter, the CSS variable will be used. Note: it works best with raw numbers and has a utility function toUnit to convert to pixels (etc) at the end.

you can call it like so, jquery as an example to set the style:

 $('#my-element').css('width', `${cssHack.toUnit(cssHack.round(1000/3),'px')}`)

Below is the code for the class (es6, no dependencies):

class cssHack
{
    //primary used for dynamic css variables-- pass in 'var(--dynamic-height)'
    static toUnit(val,unit)
    {
        unit='1'+unit;
        return ` calc(${val} * ${unit}) `;
    }

    static round(val)
    {
        // the magic number below is the lowest possible integer, causing a "underflow" that happily results in a rounding error we can use for purposeful rounding.
        return ` calc(${val} * ${Number.MIN_VALUE} / ${Number.MIN_VALUE}) `;
    }

    static abs(val)
    {
        return ` max(${val}, calc(${val} * -1)) `;
    }

    static floor(val)
    {
        return cssHack.round(` calc(${val} - .5) `);
    }

    static ceil(val)
    {
        return cssHack.round( `calc(${val} + .5) `);
    }

    static sign(val)
    {   
        let n = ` min(${val},0) `; //if val is positive then n =0. otherwise n=val.
        let isNegative= ` min(${cssHack.ceil(cssHack.abs(n))},1) `;
        let p = ` max(${val},0) `; //if val is negative then n=0, otherwise n = val;
        let isPositive= ` min(${cssHack.ceil(cssHack.abs(p))},1) `;
        return ` calc(${isPositive} + calc(${isNegative} * -1)) `;
    }

    static mod(val, base)
    {
        let abs = cssHack.abs(val);
        let div = ` calc(${abs} / ${base})`;
        let dec = ` calc(${div} - ${cssHack.floor(div)})`;
        return cssHack.round(` calc(${dec} * ${base}) `);
    }
}
Cown answered 11/11, 2021 at 18:9 Comment(1)
What may not be obvious here is that you can pass in other CSS variables and such. So what I do is use Observers to watch for size changes on objects and set custom CSS variables. Then, we can use these custom variables and pass them in to these functions ie: $('#ID').css('width',cssHack.round(cssHack.toUnit(cssHack.floor("calc( var(--window-width,'1024') / 3)' ,'px) So my observer or event I write to set --window-width at the body level when it changes. And my calc rule applies on the fly with no javascript at that point.Cown
L
-1

cant you round with sass? since you already use it, try its round, floor and ceil methods: https://sass-lang.com/documentation/modules/math#bounding-functions

(also in older versions of sass, with a different syntax though)

Louiselouisette answered 16/3, 2022 at 13:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.