Округление до произвольного числа значащих цифр
Как можно округлить любое число (не только целые числа> 0) до N значащих цифр?
Например, если я хочу округлить до трех значащих цифр, я ищу формулу, которая может принять:
1 239 451 и возврат 1 240 000
12,1257 и возврат 12,1
.0681 и возвращение.0681
5 и возврат 5
Естественно, алгоритм не должен быть жестко запрограммирован для обработки только N из 3, хотя это было бы началом.
17 ответов
Вот тот же код на Java без ошибки 12.100000000000001, который есть у других ответов
Я также удалил повторный код, изменил power
к целому числу типа, чтобы предотвратить плавающие проблемы, когда n - d
сделано, и сделал длинный промежуточный более понятным
Ошибка была вызвана умножением большого числа на небольшое число. Вместо этого я делю два числа одинакового размера.
РЕДАКТИРОВАТЬ
Исправлено больше ошибок. Добавлена проверка на 0, так как это приведет к NaN. Заставила функцию фактически работать с отрицательными числами (оригинальный код не обрабатывает отрицательные числа, потому что журнал отрицательного числа является комплексным числом)
public static double roundToSignificantFigures(double num, int n) {
if(num == 0) {
return 0;
}
final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
final int power = n - (int) d;
final double magnitude = Math.pow(10, power);
final long shifted = Math.round(num*magnitude);
return shifted/magnitude;
}
Вот короткая и приятная реализация JavaScript:
function sigFigs(n, sig) {
var mult = Math.pow(10, sig - Math.floor(Math.log(n) / Math.LN10) - 1);
return Math.round(n * mult) / mult;
}
alert(sigFigs(1234567, 3)); // Gives 1230000
alert(sigFigs(0.06805, 3)); // Gives 0.0681
alert(sigFigs(5, 3)); // Gives 5
РЕЗЮМЕ:
double roundit(double num, double N)
{
double d = log10(num);
double power;
if (num > 0)
{
d = ceil(d);
power = -(d-N);
}
else
{
d = floor(d);
power = -(d-N);
}
return (int)(num * pow(10.0, power) + 0.5) * pow(10.0, -power);
}
Таким образом, вам нужно найти десятичное место первой ненулевой цифры, затем сохранить следующие N-1 цифры, а затем округлить N-ю цифру на основе остальных.
Мы можем использовать журнал, чтобы сделать первый.
log 1239451 = 6.09
log 12.1257 = 1.08
log 0.0681 = -1.16
Так что для чисел> 0 возьмите потолок журнала. Для чисел < 0, взять слово журнала.
Теперь у нас есть цифра d
: 7 в первом случае, 2 во втором, -2 в третьем.
Мы должны округлить (d-N)
ая цифра Что-то вроде:
double roundedrest = num * pow(10, -(d-N));
pow(1239451, -4) = 123.9451
pow(12.1257, 1) = 121.257
pow(0.0681, 4) = 681
Затем выполните стандартную операцию округления:
roundedrest = (int)(roundedrest + 0.5);
И отменить пау.
roundednum = pow(roundedrest, -(power))
Где мощность - это мощность, рассчитанная выше.
О точности: ответ Pyrolistics действительно ближе к реальному результату. Но обратите внимание, что вы не можете представлять 12,1 точно в любом случае. Если вы печатаете ответы следующим образом:
System.out.println(new BigDecimal(n));
Ответы:
Pyro's: 12.0999999999999996447286321199499070644378662109375
Mine: 12.10000000000000142108547152020037174224853515625
Printing 12.1 directly: 12.0999999999999996447286321199499070644378662109375
Так что используйте ответ Пиро!
Разве это не "короткая и приятная" реализация JavaScript
Number(n).toPrecision(sig)
например
alert(Number(12345).toPrecision(3)
?
Извините, я здесь не шутливый, просто использование функции "roundit" от Claudiu и.toPrecision в JavaScript дает мне другие результаты, но только при округлении последней цифры.
JavaScript:
Number(8.14301).toPrecision(4) == 8.143
.СЕТЬ
roundit(8.14301,4) == 8.144
Пиролистическое (очень хорошее!) Решение все еще имеет проблему. Максимальное двойное значение в Java порядка 10^308, а минимальное значение порядка 10^-324. Таким образом, вы можете столкнуться с проблемами при применении функции roundToSignificantFigures
к чему-то, что в нескольких силах из десяти Double.MIN_VALUE
, Например, когда вы звоните
roundToSignificantFigures(1.234E-310, 3);
тогда переменная power
будет иметь значение 3 - (-309) = 312. Следовательно, переменная magnitude
станет Infinity
и это все фигня с тех пор. К счастью, это не непреодолимая проблема: это только фактор magnitude
это переполнено. Что действительно важно, так это продукт num * magnitude
и это не переполняет. Одним из способов решения этой проблемы является дробление умножения на коэффициент magintude
в два этапа:
public static double roundToNumberOfSignificantDigits(double num, int n) {
final double maxPowerOfTen = Math.floor(Math.log10(Double.MAX_VALUE));
if(num == 0) {
return 0;
}
final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
final int power = n - (int) d;
double firstMagnitudeFactor = 1.0;
double secondMagnitudeFactor = 1.0;
if (power > maxPowerOfTen) {
firstMagnitudeFactor = Math.pow(10.0, maxPowerOfTen);
secondMagnitudeFactor = Math.pow(10.0, (double) power - maxPowerOfTen);
} else {
firstMagnitudeFactor = Math.pow(10.0, (double) power);
}
double toBeRounded = num * firstMagnitudeFactor;
toBeRounded *= secondMagnitudeFactor;
final long shifted = Math.round(toBeRounded);
double rounded = ((double) shifted) / firstMagnitudeFactor;
rounded /= secondMagnitudeFactor;
return rounded;
}
Как насчет этого решения Java:
double roundToSignificantFigure (double num, int precision) { вернуть новый BigDecimal(num) .round(новый MathContext(точность, RoundingMode.HALF_EVEN)) .doubleValue(); }
JavaScript:
Number( my_number.toPrecision(3) );
Number
функция изменит вывод формы "8.143e+5"
в "814300"
,
Вот модифицированная версия JavaScript Ates, которая обрабатывает отрицательные числа.
function sigFigs(n, sig) {
if ( n === 0 )
return 0
var mult = Math.pow(10,
sig - Math.floor(Math.log(n < 0 ? -n: n) / Math.LN10) - 1);
return Math.round(n * mult) / mult;
}
Это произошло на 5 лет позже, но я поделюсь с другими, у которых все еще есть такая же проблема. Мне это нравится, потому что это просто и никаких вычислений на стороне кода. См. Встроенные методы для отображения значимых цифр для получения дополнительной информации.
Это если вы просто хотите распечатать его.
public String toSignificantFiguresString(BigDecimal bd, int significantFigures){
return String.format("%."+significantFigures+"G", bd);
}
Это если вы хотите конвертировать это:
public BigDecimal toSignificantFigures(BigDecimal bd, int significantFigures){
String s = String.format("%."+significantFigures+"G", bd);
BigDecimal result = new BigDecimal(s);
return result;
}
Вот пример этого в действии:
BigDecimal bd = toSignificantFigures(BigDecimal.valueOf(0.0681), 2);
[Исправлено, 2009-10-26]
По существу, для N значащих дробных цифр:
• Умножьте число на 10N
• Добавить 0,5
• усечь дробные цифры (т.е. усечь результат в целое число)
• Разделить на 10Н
Для N значащих целых (не дробных) цифр:
• Разделите число на 10N
• Добавить 0,5
• усечь дробные цифры (т.е. усечь результат в целое число)
• умножить на 10N
Это можно сделать на любом калькуляторе, например, с оператором INT (целочисленное усечение).
Вы пробовали просто написать код так, как вы это делаете вручную?
- Преобразовать число в строку
- Начиная с начала строки, считайте цифры - начальные нули не имеют значения, все остальное имеет значение.
- Когда вы доберетесь до "n-й" цифры, загляните вперед на следующую цифру и, если она 5 или выше, округлите в большую сторону.
- Замените все завершающие цифры нулями.
/**
* Set Significant Digits.
* @param value value
* @param digits digits
* @return
*/
public static BigDecimal setSignificantDigits(BigDecimal value, int digits) {
//# Start with the leftmost non-zero digit (e.g. the "1" in 1200, or the "2" in 0.0256).
//# Keep n digits. Replace the rest with zeros.
//# Round up by one if appropriate.
int p = value.precision();
int s = value.scale();
if (p < digits) {
value = value.setScale(s + digits - p); //, RoundingMode.HALF_UP
}
value = value.movePointRight(s).movePointLeft(p - digits).setScale(0, RoundingMode.HALF_UP)
.movePointRight(p - digits).movePointLeft(s);
s = (s > (p - digits)) ? (s - (p - digits)) : 0;
return value.setScale(s);
}
Вот код Pyrolistics (в настоящее время самый лучший ответ) в Visual Basic.NET, если он кому-нибудь понадобится:
Public Shared Function roundToSignificantDigits(ByVal num As Double, ByVal n As Integer) As Double
If (num = 0) Then
Return 0
End If
Dim d As Double = Math.Ceiling(Math.Log10(If(num < 0, -num, num)))
Dim power As Integer = n - CInt(d)
Dim magnitude As Double = Math.Pow(10, power)
Dim shifted As Double = Math.Round(num * magnitude)
Return shifted / magnitude
End Function
Вы можете избежать выполнения всех этих вычислений со степенью 10 и т. Д., Просто используя FloatToStrF.
FloatToStrF позволяет (среди прочего) выбирать точность (количество значащих цифр) в выводимом значении (которое будет строкой). Конечно, затем вы можете применить к нему StrToFloat, чтобы получить округленное значение в виде числа с плавающей запятой.
Глянь сюда:
Мне нужно было это в Go, что было немного сложно из-за отсутствия стандартной библиотеки Go. math.Round()
(до go1.10). Так что мне тоже пришлось это взбить. Вот мой перевод превосходного ответа Pyrolistics:
// TODO: replace in go1.10 with math.Round()
func round(x float64) float64 {
return float64(int64(x + 0.5))
}
// SignificantDigits rounds a float64 to digits significant digits.
// Translated from Java at https://stackru.com/a/1581007/1068283
func SignificantDigits(x float64, digits int) float64 {
if x == 0 {
return 0
}
power := digits - int(math.Ceil(math.Log10(math.Abs(x))))
magnitude := math.Pow(10, float64(power))
shifted := round(x * magnitude)
return shifted / magnitude
}
Это тот, который я придумал в VB:
Function SF(n As Double, SigFigs As Integer)
Dim l As Integer = n.ToString.Length
n = n / 10 ^ (l - SigFigs)
n = Math.Round(n)
n = n * 10 ^ (l - SigFigs)
Return n
End Function
return new BigDecimal(value, new MathContext(significantFigures, RoundingMode.HALF_UP)).doubleValue();
public static double roundToSignificantDigits(double num, int n) {
return Double.parseDouble(new java.util.Formatter().format("%." + (n - 1) + "e", num).toString());
}
Этот код использует встроенную функцию форматирования, которая превращается в функцию округления